blob: 31ea38c23916326876c3bc71b53455cc90877252 [file] [log] [blame]
// Copyright 2010-2015, 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));
}
}