| // Copyright 2010-2014, Google Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| package org.mozc.android.inputmethod.japanese; |
| |
| import org.mozc.android.inputmethod.japanese.MozcUtil.TelephonyManagerInterface; |
| import org.mozc.android.inputmethod.japanese.emoji.EmojiProviderType; |
| import org.mozc.android.inputmethod.japanese.preference.PreferenceUtil; |
| import org.mozc.android.inputmethod.japanese.preference.PreferenceUtil.PreferenceManagerStaticInterface; |
| import org.mozc.android.inputmethod.japanese.resources.R; |
| import org.mozc.android.inputmethod.japanese.util.LauncherIconManagerFactory.LauncherIconManager; |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Optional; |
| import com.google.common.base.Preconditions; |
| |
| import android.app.NotificationManager; |
| import android.app.PendingIntent; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.SharedPreferences; |
| import android.content.res.Configuration; |
| import android.content.res.Resources; |
| import android.os.Build; |
| import android.preference.PreferenceManager; |
| import android.support.v4.app.NotificationCompat; |
| import android.util.DisplayMetrics; |
| |
| import java.io.File; |
| |
| /** |
| * Factory class of application initializer. |
| * |
| * <p>The initializer should be invoked once when the application starts. |
| */ |
| public class ApplicationInitializerFactory { |
| |
| /** |
| * Represents initialization status. |
| * |
| * <p>Typically they are calculated by using {@link SharedPreferences}. |
| * The interface is prepared mainly for testing. |
| */ |
| @VisibleForTesting |
| public interface ApplicationInitializationStatus { |
| /** |
| * @deprecated should use {@link #getLastLaunchAbiIndependentVersionCode()} and |
| * {@link #isWelcomeActivityShownAtLeastOnce()} |
| * @return true if the application has launched at least once. False means nothing (remember, |
| * this is a deprecated flag). |
| */ |
| @Deprecated |
| boolean isLaunchedAtLeastOnce(); |
| |
| /** |
| * @return the latest ABI independent version code of the IME which has launched last time. |
| * Absent if this is the first time (or just migrated from {@link #isLaunchedAtLeastOnce()}) |
| */ |
| Optional<Integer> getLastLaunchAbiIndependentVersionCode(); |
| |
| /** |
| * @return true if the "Welcome" activity has been shown at least once. False means nothing. |
| */ |
| boolean isWelcomeActivityShownAtLeastOnce(); |
| } |
| |
| @VisibleForTesting |
| static final String PREF_LAUNCHED_AT_LEAST_ONCE = "pref_launched_at_least_once"; |
| @VisibleForTesting |
| static final String PREF_WELCOME_ACTIVITY_SHOWN = "pref_welcome_activity_shown"; |
| |
| private static class ApplicationInitializerImpl implements ApplicationInitializationStatus { |
| |
| private final SharedPreferences sharedPreferences; |
| |
| private ApplicationInitializerImpl(SharedPreferences sharedPreferences) { |
| Preconditions.checkNotNull(sharedPreferences); |
| |
| this.sharedPreferences = sharedPreferences; |
| } |
| |
| @Deprecated |
| @Override |
| public boolean isLaunchedAtLeastOnce() { |
| return this.sharedPreferences.getBoolean(PREF_LAUNCHED_AT_LEAST_ONCE, false); |
| } |
| |
| @Override |
| public Optional<Integer> getLastLaunchAbiIndependentVersionCode() { |
| if (!this.sharedPreferences.contains( |
| PreferenceUtil.PREF_LAST_LAUNCH_ABI_INDEPENDENT_VERSION_CODE)) { |
| return Optional.absent(); |
| } |
| return Optional.of( |
| this.sharedPreferences.getInt( |
| PreferenceUtil.PREF_LAST_LAUNCH_ABI_INDEPENDENT_VERSION_CODE, 0)); |
| } |
| |
| @Override |
| public boolean isWelcomeActivityShownAtLeastOnce() { |
| return this.sharedPreferences.getBoolean(PREF_WELCOME_ACTIVITY_SHOWN, false); |
| } |
| } |
| |
| /** |
| * The entry point of the application. |
| */ |
| public static class ApplicationInitializer { |
| |
| static final int LAUNCHED_AT_LEAST_ONCE_DEPRECATED_VERSION_CODE = 1429; |
| |
| final ApplicationInitializationStatus initializationStatus; |
| final Context context; |
| final SharedPreferences sharedPreferences; |
| final TelephonyManagerInterface telephonyManager; |
| |
| private ApplicationInitializer( |
| ApplicationInitializationStatus initializationStatus, |
| Context context, |
| SharedPreferences sharedPreferences, |
| TelephonyManagerInterface telephonyManager) { |
| this.initializationStatus = Preconditions.checkNotNull(initializationStatus); |
| this.context = Preconditions.checkNotNull(context); |
| this.sharedPreferences = Preconditions.checkNotNull(sharedPreferences); |
| this.telephonyManager = Preconditions.checkNotNull(telephonyManager); |
| } |
| |
| /** |
| * Initializes the application. |
| * |
| * <p>Updates some preferences. |
| * Here we use three preference items. |
| * <ul> |
| * <li>pref_welcome_activity_shown: True if the "Welcome" activity has shown at least once. |
| * <li>pref_last_launch_abi_independent_version_code: The latest version number which |
| * has launched at least once. |
| * <li>pref_launched_at_least_once: Deprecated. True if the the IME has launched at least once. |
| * </ul> |
| * Some preferences should be set at the first time launch. |
| * If the IME is a system application (preinstalled), it shouldn't show "Welcome" activity. |
| * If an update is performed (meaning that the IME becomes non-system app), |
| * the activity should be shown at the first time launch. |
| * |
| * We have to do migration process. |
| * If pref_launched_at_least_once exists, pref_welcome_activity_shown is recognized as |
| * true and pref_last_launch_abi_independent_version_code is recognized as |
| * LAUNCHED_AT_LEAST_ONCE_DEPRECATED_VERSION_CODE. And then pref_launched_at_least_once is |
| * removed. |
| * |
| * @param isSystemApplication true if the app is a system application (== preinstall) |
| * @param isDevChannel true if the app is built for dev channel |
| * @param isWelcomeActivityPreferred true if the configuration prefers to shown welcome activity |
| * if it's not been shown yet. |
| * @param abiIndependentVersionCode ABI independent version code, typically obtained |
| * from {@link MozcUtil#getAbiIndependentVersionCode(Context)} |
| * |
| * @return if forwarding is needed Intent is returned. The caller side should invoke the Intent. |
| */ |
| public Optional<Intent> initialize(boolean isSystemApplication, |
| boolean isDevChannel, |
| boolean isWelcomeActivityPreferred, |
| int abiIndependentVersionCode, |
| LauncherIconManager launcherIconManager, |
| PreferenceManagerStaticInterface preferenceManager) { |
| Preconditions.checkNotNull(launcherIconManager); |
| Preconditions.checkNotNull(preferenceManager); |
| SharedPreferences.Editor editor = sharedPreferences.edit(); |
| Resources resources = context.getResources(); |
| try { |
| File tempDirectory = MozcUtil.getUserDictionaryExportTempDirectory(context); |
| if (tempDirectory.isDirectory()) { |
| MozcUtil.deleteDirectoryContents(tempDirectory); |
| } |
| |
| Optional<Integer> lastVersionCode; |
| boolean isActivityShown; |
| if (initializationStatus.isLaunchedAtLeastOnce()) { |
| // Migration scenario |
| lastVersionCode = Optional.of(LAUNCHED_AT_LEAST_ONCE_DEPRECATED_VERSION_CODE); |
| isActivityShown = true; |
| } else { |
| lastVersionCode = initializationStatus.getLastLaunchAbiIndependentVersionCode(); |
| isActivityShown = initializationStatus.isWelcomeActivityShownAtLeastOnce(); |
| } |
| |
| // Preferences: Update if this is the first launch |
| if (!lastVersionCode.isPresent()) { |
| // Store full-screen relating preferences. |
| DisplayMetrics portraitMetrics = getPortraitDisplayMetrics( |
| resources.getDisplayMetrics(), resources.getConfiguration().orientation); |
| storeDefaultFullscreenMode( |
| sharedPreferences, portraitMetrics.heightPixels, portraitMetrics.widthPixels, |
| (int) Math.ceil(getDimensionForOrientation( |
| resources, R.dimen.input_frame_height, Configuration.ORIENTATION_PORTRAIT)), |
| (int) Math.ceil(getDimensionForOrientation( |
| resources, R.dimen.input_frame_height, Configuration.ORIENTATION_LANDSCAPE)), |
| resources.getDimensionPixelOffset(R.dimen.fullscreen_threshold)); |
| |
| // Run emoji provider type detection, so that the detected provider will be |
| // used as the default values of the preference activity. |
| EmojiProviderType.maybeSetDetectedEmojiProviderType( |
| sharedPreferences, telephonyManager); |
| } |
| // Update launcher icon visibility and relating preference. |
| launcherIconManager.updateLauncherIconVisibility(context); |
| // Save default preference to the storage. |
| // NOTE: This method must NOT be called before updateLauncherIconVisibility() above. |
| // Above method requires PREF_LAUNCHER_ICON_VISIBILITY_KEY is not filled with |
| // the default value. |
| // If PREF_LAUNCHER_ICON_VISIBILITY_KEY is filled prior to |
| // updateLauncherIconVisibility(), the launcher icon will be unexpectedly shown |
| // when 2.16.1955.3 (preinstall version) is overwritten by PlayStore version. |
| PreferenceUtil.setDefaultValues( |
| preferenceManager, context, MozcUtil.isDebug(context), |
| resources.getBoolean(R.bool.sending_information_features_enabled)); |
| |
| if (isDevChannel) { |
| // Usage Stats: Make pref_other_usage_stats_key enabled when dev channel. |
| editor.putBoolean(PreferenceUtil.PREF_OTHER_USAGE_STATS_KEY, true); |
| maybeShowNotificationForDevChannel(abiIndependentVersionCode, |
| lastVersionCode); |
| } |
| |
| // Welcome Activity |
| if (!isActivityShown && !isSystemApplication && isWelcomeActivityPreferred) { |
| editor.putBoolean(PREF_WELCOME_ACTIVITY_SHOWN, true); |
| Intent intent = new Intent(context, FirstTimeLaunchActivity.class); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| return Optional.of(intent); |
| } |
| return Optional.absent(); |
| } finally { |
| editor.remove(PREF_LAUNCHED_AT_LEAST_ONCE); |
| editor.putInt(PreferenceUtil.PREF_LAST_LAUNCH_ABI_INDEPENDENT_VERSION_CODE, |
| abiIndependentVersionCode); |
| editor.commit(); |
| } |
| } |
| |
| private void maybeShowNotificationForDevChannel( |
| int abiIndependentVersionCode, Optional<Integer> lastVersionCode) { |
| } |
| |
| /** |
| * Returns a modified {@code DisplayMetrics} which equals to portrait modes's one. |
| * |
| * If current orientation is PORTRAIT, given {@code currentMetrics} is returned. |
| * Otherwise {@code currentMetrics}'s {@code heightPixels} and {@code widthPixels} are swapped. |
| */ |
| @VisibleForTesting |
| static DisplayMetrics getPortraitDisplayMetrics(DisplayMetrics currentMetrics, |
| int currnetOrientation) { |
| Preconditions.checkNotNull(currentMetrics); |
| |
| DisplayMetrics result = new DisplayMetrics(); |
| result.setTo(currentMetrics); |
| if (currnetOrientation == Configuration.ORIENTATION_LANDSCAPE) { |
| result.heightPixels = currentMetrics.widthPixels; |
| result.widthPixels = currentMetrics.heightPixels; |
| } |
| return result; |
| } |
| |
| /** |
| * Get a dimension for the specified orientation. |
| * This method may be heavy since it updates the {@code resources} twice. |
| */ |
| @VisibleForTesting |
| static float getDimensionForOrientation(Resources resources, int id, int orientation) { |
| Configuration configuration = resources.getConfiguration(); |
| if (configuration.orientation == orientation) { |
| return resources.getDimension(id); |
| } |
| |
| Configuration originalConfiguration = new Configuration(resources.getConfiguration()); |
| try { |
| configuration.orientation = orientation; |
| resources.updateConfiguration(configuration, null); |
| return resources.getDimension(id); |
| } finally { |
| resources.updateConfiguration(originalConfiguration, null); |
| } |
| } |
| |
| /** |
| * Stores the default value of "fullscreen mode" to the shared preference. |
| */ |
| @VisibleForTesting |
| static void storeDefaultFullscreenMode( |
| SharedPreferences sharedPreferences, |
| int portraitDisplayHeight, int landscapeDisplayHeight, |
| int portraitInputFrameHeight, int landscapeInputFrameHeight, int fullscreenThreshold) { |
| Preconditions.checkNotNull(sharedPreferences); |
| |
| SharedPreferences.Editor editor = sharedPreferences.edit(); |
| editor.putBoolean( |
| "pref_portrait_fullscreen_key", |
| portraitDisplayHeight - portraitInputFrameHeight < fullscreenThreshold); |
| editor.putBoolean( |
| "pref_landscape_fullscreen_key", |
| landscapeDisplayHeight - landscapeInputFrameHeight < fullscreenThreshold); |
| editor.commit(); |
| } |
| } |
| |
| public static ApplicationInitializer createInstance(Context context) { |
| Preconditions.checkNotNull(context); |
| |
| SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); |
| return createInstance(new ApplicationInitializerImpl(sharedPreferences), |
| context, sharedPreferences, MozcUtil.getTelephonyManager(context)); |
| } |
| |
| @VisibleForTesting |
| public static ApplicationInitializer createInstance( |
| ApplicationInitializationStatus initializationStatus, |
| Context context, SharedPreferences sharedPreferences, |
| TelephonyManagerInterface telephonyManager) { |
| return new ApplicationInitializer( |
| Preconditions.checkNotNull(initializationStatus), |
| Preconditions.checkNotNull(context), |
| Preconditions.checkNotNull(sharedPreferences), |
| Preconditions.checkNotNull(telephonyManager)); |
| } |
| } |