Home | History | Annotate | Download | only in setup
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.inputmethod.latin.setup;
     18 
     19 import android.app.Activity;
     20 import android.content.ContentResolver;
     21 import android.content.Intent;
     22 import android.content.res.Resources;
     23 import android.media.MediaPlayer;
     24 import android.net.Uri;
     25 import android.os.Bundle;
     26 import android.os.Message;
     27 import android.provider.Settings;
     28 import android.util.Log;
     29 import android.view.View;
     30 import android.view.inputmethod.InputMethodInfo;
     31 import android.view.inputmethod.InputMethodManager;
     32 import android.widget.ImageView;
     33 import android.widget.TextView;
     34 import android.widget.VideoView;
     35 
     36 import com.android.inputmethod.compat.TextViewCompatUtils;
     37 import com.android.inputmethod.compat.ViewCompatUtils;
     38 import com.android.inputmethod.latin.R;
     39 import com.android.inputmethod.latin.settings.SettingsActivity;
     40 import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
     41 import com.android.inputmethod.latin.utils.UncachedInputMethodManagerUtils;
     42 
     43 import java.util.ArrayList;
     44 
     45 import javax.annotation.Nonnull;
     46 
     47 // TODO: Use Fragment to implement welcome screen and setup steps.
     48 public final class SetupWizardActivity extends Activity implements View.OnClickListener {
     49     static final String TAG = SetupWizardActivity.class.getSimpleName();
     50 
     51     // For debugging purpose.
     52     private static final boolean FORCE_TO_SHOW_WELCOME_SCREEN = false;
     53     private static final boolean ENABLE_WELCOME_VIDEO = true;
     54 
     55     private InputMethodManager mImm;
     56 
     57     private View mSetupWizard;
     58     private View mWelcomeScreen;
     59     private View mSetupScreen;
     60     private Uri mWelcomeVideoUri;
     61     private VideoView mWelcomeVideoView;
     62     private ImageView mWelcomeImageView;
     63     private View mActionStart;
     64     private View mActionNext;
     65     private TextView mStep1Bullet;
     66     private TextView mActionFinish;
     67     private SetupStepGroup mSetupStepGroup;
     68     private static final String STATE_STEP = "step";
     69     private int mStepNumber;
     70     private boolean mNeedsToAdjustStepNumberToSystemState;
     71     private static final int STEP_WELCOME = 0;
     72     private static final int STEP_1 = 1;
     73     private static final int STEP_2 = 2;
     74     private static final int STEP_3 = 3;
     75     private static final int STEP_LAUNCHING_IME_SETTINGS = 4;
     76     private static final int STEP_BACK_FROM_IME_SETTINGS = 5;
     77 
     78     private SettingsPoolingHandler mHandler;
     79 
     80     private static final class SettingsPoolingHandler
     81             extends LeakGuardHandlerWrapper<SetupWizardActivity> {
     82         private static final int MSG_POLLING_IME_SETTINGS = 0;
     83         private static final long IME_SETTINGS_POLLING_INTERVAL = 200;
     84 
     85         private final InputMethodManager mImmInHandler;
     86 
     87         public SettingsPoolingHandler(@Nonnull final SetupWizardActivity ownerInstance,
     88                 final InputMethodManager imm) {
     89             super(ownerInstance);
     90             mImmInHandler = imm;
     91         }
     92 
     93         @Override
     94         public void handleMessage(final Message msg) {
     95             final SetupWizardActivity setupWizardActivity = getOwnerInstance();
     96             if (setupWizardActivity == null) {
     97                 return;
     98             }
     99             switch (msg.what) {
    100             case MSG_POLLING_IME_SETTINGS:
    101                 if (UncachedInputMethodManagerUtils.isThisImeEnabled(setupWizardActivity,
    102                         mImmInHandler)) {
    103                     setupWizardActivity.invokeSetupWizardOfThisIme();
    104                     return;
    105                 }
    106                 startPollingImeSettings();
    107                 break;
    108             }
    109         }
    110 
    111         public void startPollingImeSettings() {
    112             sendMessageDelayed(obtainMessage(MSG_POLLING_IME_SETTINGS),
    113                     IME_SETTINGS_POLLING_INTERVAL);
    114         }
    115 
    116         public void cancelPollingImeSettings() {
    117             removeMessages(MSG_POLLING_IME_SETTINGS);
    118         }
    119     }
    120 
    121     @Override
    122     protected void onCreate(final Bundle savedInstanceState) {
    123         setTheme(android.R.style.Theme_Translucent_NoTitleBar);
    124         super.onCreate(savedInstanceState);
    125 
    126         mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
    127         mHandler = new SettingsPoolingHandler(this, mImm);
    128 
    129         setContentView(R.layout.setup_wizard);
    130         mSetupWizard = findViewById(R.id.setup_wizard);
    131 
    132         if (savedInstanceState == null) {
    133             mStepNumber = determineSetupStepNumberFromLauncher();
    134         } else {
    135             mStepNumber = savedInstanceState.getInt(STATE_STEP);
    136         }
    137 
    138         final String applicationName = getResources().getString(getApplicationInfo().labelRes);
    139         mWelcomeScreen = findViewById(R.id.setup_welcome_screen);
    140         final TextView welcomeTitle = (TextView)findViewById(R.id.setup_welcome_title);
    141         welcomeTitle.setText(getString(R.string.setup_welcome_title, applicationName));
    142 
    143         mSetupScreen = findViewById(R.id.setup_steps_screen);
    144         final TextView stepsTitle = (TextView)findViewById(R.id.setup_title);
    145         stepsTitle.setText(getString(R.string.setup_steps_title, applicationName));
    146 
    147         final SetupStepIndicatorView indicatorView =
    148                 (SetupStepIndicatorView)findViewById(R.id.setup_step_indicator);
    149         mSetupStepGroup = new SetupStepGroup(indicatorView);
    150 
    151         mStep1Bullet = (TextView)findViewById(R.id.setup_step1_bullet);
    152         mStep1Bullet.setOnClickListener(this);
    153         final SetupStep step1 = new SetupStep(STEP_1, applicationName,
    154                 mStep1Bullet, findViewById(R.id.setup_step1),
    155                 R.string.setup_step1_title, R.string.setup_step1_instruction,
    156                 R.string.setup_step1_finished_instruction, R.drawable.ic_setup_step1,
    157                 R.string.setup_step1_action);
    158         final SettingsPoolingHandler handler = mHandler;
    159         step1.setAction(new Runnable() {
    160             @Override
    161             public void run() {
    162                 invokeLanguageAndInputSettings();
    163                 handler.startPollingImeSettings();
    164             }
    165         });
    166         mSetupStepGroup.addStep(step1);
    167 
    168         final SetupStep step2 = new SetupStep(STEP_2, applicationName,
    169                 (TextView)findViewById(R.id.setup_step2_bullet), findViewById(R.id.setup_step2),
    170                 R.string.setup_step2_title, R.string.setup_step2_instruction,
    171                 0 /* finishedInstruction */, R.drawable.ic_setup_step2,
    172                 R.string.setup_step2_action);
    173         step2.setAction(new Runnable() {
    174             @Override
    175             public void run() {
    176                 invokeInputMethodPicker();
    177             }
    178         });
    179         mSetupStepGroup.addStep(step2);
    180 
    181         final SetupStep step3 = new SetupStep(STEP_3, applicationName,
    182                 (TextView)findViewById(R.id.setup_step3_bullet), findViewById(R.id.setup_step3),
    183                 R.string.setup_step3_title, R.string.setup_step3_instruction,
    184                 0 /* finishedInstruction */, R.drawable.ic_setup_step3,
    185                 R.string.setup_step3_action);
    186         step3.setAction(new Runnable() {
    187             @Override
    188             public void run() {
    189                 invokeSubtypeEnablerOfThisIme();
    190             }
    191         });
    192         mSetupStepGroup.addStep(step3);
    193 
    194         mWelcomeVideoUri = new Uri.Builder()
    195                 .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
    196                 .authority(getPackageName())
    197                 .path(Integer.toString(R.raw.setup_welcome_video))
    198                 .build();
    199         final VideoView welcomeVideoView = (VideoView)findViewById(R.id.setup_welcome_video);
    200         welcomeVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
    201             @Override
    202             public void onPrepared(final MediaPlayer mp) {
    203                 // Now VideoView has been laid-out and ready to play, remove background of it to
    204                 // reveal the video.
    205                 welcomeVideoView.setBackgroundResource(0);
    206                 mp.setLooping(true);
    207             }
    208         });
    209         welcomeVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
    210             @Override
    211             public boolean onError(final MediaPlayer mp, final int what, final int extra) {
    212                 Log.e(TAG, "Playing welcome video causes error: what=" + what + " extra=" + extra);
    213                 hideWelcomeVideoAndShowWelcomeImage();
    214                 return true;
    215             }
    216         });
    217         mWelcomeVideoView = welcomeVideoView;
    218         mWelcomeImageView = (ImageView)findViewById(R.id.setup_welcome_image);
    219 
    220         mActionStart = findViewById(R.id.setup_start_label);
    221         mActionStart.setOnClickListener(this);
    222         mActionNext = findViewById(R.id.setup_next);
    223         mActionNext.setOnClickListener(this);
    224         mActionFinish = (TextView)findViewById(R.id.setup_finish);
    225         TextViewCompatUtils.setCompoundDrawablesRelativeWithIntrinsicBounds(mActionFinish,
    226                 getResources().getDrawable(R.drawable.ic_setup_finish), null, null, null);
    227         mActionFinish.setOnClickListener(this);
    228     }
    229 
    230     @Override
    231     public void onClick(final View v) {
    232         if (v == mActionFinish) {
    233             finish();
    234             return;
    235         }
    236         final int currentStep = determineSetupStepNumber();
    237         final int nextStep;
    238         if (v == mActionStart) {
    239             nextStep = STEP_1;
    240         } else if (v == mActionNext) {
    241             nextStep = mStepNumber + 1;
    242         } else if (v == mStep1Bullet && currentStep == STEP_2) {
    243             nextStep = STEP_1;
    244         } else {
    245             nextStep = mStepNumber;
    246         }
    247         if (mStepNumber != nextStep) {
    248             mStepNumber = nextStep;
    249             updateSetupStepView();
    250         }
    251     }
    252 
    253     void invokeSetupWizardOfThisIme() {
    254         final Intent intent = new Intent();
    255         intent.setClass(this, SetupWizardActivity.class);
    256         intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
    257                 | Intent.FLAG_ACTIVITY_SINGLE_TOP
    258                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
    259         startActivity(intent);
    260         mNeedsToAdjustStepNumberToSystemState = true;
    261     }
    262 
    263     private void invokeSettingsOfThisIme() {
    264         final Intent intent = new Intent();
    265         intent.setClass(this, SettingsActivity.class);
    266         intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
    267                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
    268         intent.putExtra(SettingsActivity.EXTRA_ENTRY_KEY,
    269                 SettingsActivity.EXTRA_ENTRY_VALUE_APP_ICON);
    270         startActivity(intent);
    271     }
    272 
    273     void invokeLanguageAndInputSettings() {
    274         final Intent intent = new Intent();
    275         intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS);
    276         intent.addCategory(Intent.CATEGORY_DEFAULT);
    277         startActivity(intent);
    278         mNeedsToAdjustStepNumberToSystemState = true;
    279     }
    280 
    281     void invokeInputMethodPicker() {
    282         // Invoke input method picker.
    283         mImm.showInputMethodPicker();
    284         mNeedsToAdjustStepNumberToSystemState = true;
    285     }
    286 
    287     void invokeSubtypeEnablerOfThisIme() {
    288         final InputMethodInfo imi =
    289                 UncachedInputMethodManagerUtils.getInputMethodInfoOf(getPackageName(), mImm);
    290         if (imi == null) {
    291             return;
    292         }
    293         final Intent intent = new Intent();
    294         intent.setAction(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
    295         intent.addCategory(Intent.CATEGORY_DEFAULT);
    296         intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, imi.getId());
    297         startActivity(intent);
    298     }
    299 
    300     private int determineSetupStepNumberFromLauncher() {
    301         final int stepNumber = determineSetupStepNumber();
    302         if (stepNumber == STEP_1) {
    303             return STEP_WELCOME;
    304         }
    305         if (stepNumber == STEP_3) {
    306             return STEP_LAUNCHING_IME_SETTINGS;
    307         }
    308         return stepNumber;
    309     }
    310 
    311     private int determineSetupStepNumber() {
    312         mHandler.cancelPollingImeSettings();
    313         if (FORCE_TO_SHOW_WELCOME_SCREEN) {
    314             return STEP_1;
    315         }
    316         if (!UncachedInputMethodManagerUtils.isThisImeEnabled(this, mImm)) {
    317             return STEP_1;
    318         }
    319         if (!UncachedInputMethodManagerUtils.isThisImeCurrent(this, mImm)) {
    320             return STEP_2;
    321         }
    322         return STEP_3;
    323     }
    324 
    325     @Override
    326     protected void onSaveInstanceState(final Bundle outState) {
    327         super.onSaveInstanceState(outState);
    328         outState.putInt(STATE_STEP, mStepNumber);
    329     }
    330 
    331     @Override
    332     protected void onRestoreInstanceState(final Bundle savedInstanceState) {
    333         super.onRestoreInstanceState(savedInstanceState);
    334         mStepNumber = savedInstanceState.getInt(STATE_STEP);
    335     }
    336 
    337     private static boolean isInSetupSteps(final int stepNumber) {
    338         return stepNumber >= STEP_1 && stepNumber <= STEP_3;
    339     }
    340 
    341     @Override
    342     protected void onRestart() {
    343         super.onRestart();
    344         // Probably the setup wizard has been invoked from "Recent" menu. The setup step number
    345         // needs to be adjusted to system state, because the state (IME is enabled and/or current)
    346         // may have been changed.
    347         if (isInSetupSteps(mStepNumber)) {
    348             mStepNumber = determineSetupStepNumber();
    349         }
    350     }
    351 
    352     @Override
    353     protected void onResume() {
    354         super.onResume();
    355         if (mStepNumber == STEP_LAUNCHING_IME_SETTINGS) {
    356             // Prevent white screen flashing while launching settings activity.
    357             mSetupWizard.setVisibility(View.INVISIBLE);
    358             invokeSettingsOfThisIme();
    359             mStepNumber = STEP_BACK_FROM_IME_SETTINGS;
    360             return;
    361         }
    362         if (mStepNumber == STEP_BACK_FROM_IME_SETTINGS) {
    363             finish();
    364             return;
    365         }
    366         updateSetupStepView();
    367     }
    368 
    369     @Override
    370     public void onBackPressed() {
    371         if (mStepNumber == STEP_1) {
    372             mStepNumber = STEP_WELCOME;
    373             updateSetupStepView();
    374             return;
    375         }
    376         super.onBackPressed();
    377     }
    378 
    379     void hideWelcomeVideoAndShowWelcomeImage() {
    380         mWelcomeVideoView.setVisibility(View.GONE);
    381         mWelcomeImageView.setImageResource(R.raw.setup_welcome_image);
    382         mWelcomeImageView.setVisibility(View.VISIBLE);
    383     }
    384 
    385     private void showAndStartWelcomeVideo() {
    386         mWelcomeVideoView.setVisibility(View.VISIBLE);
    387         mWelcomeVideoView.setVideoURI(mWelcomeVideoUri);
    388         mWelcomeVideoView.start();
    389     }
    390 
    391     private void hideAndStopWelcomeVideo() {
    392         mWelcomeVideoView.stopPlayback();
    393         mWelcomeVideoView.setVisibility(View.GONE);
    394     }
    395 
    396     @Override
    397     protected void onPause() {
    398         hideAndStopWelcomeVideo();
    399         super.onPause();
    400     }
    401 
    402     @Override
    403     public void onWindowFocusChanged(final boolean hasFocus) {
    404         super.onWindowFocusChanged(hasFocus);
    405         if (hasFocus && mNeedsToAdjustStepNumberToSystemState) {
    406             mNeedsToAdjustStepNumberToSystemState = false;
    407             mStepNumber = determineSetupStepNumber();
    408             updateSetupStepView();
    409         }
    410     }
    411 
    412     private void updateSetupStepView() {
    413         mSetupWizard.setVisibility(View.VISIBLE);
    414         final boolean welcomeScreen = (mStepNumber == STEP_WELCOME);
    415         mWelcomeScreen.setVisibility(welcomeScreen ? View.VISIBLE : View.GONE);
    416         mSetupScreen.setVisibility(welcomeScreen ? View.GONE : View.VISIBLE);
    417         if (welcomeScreen) {
    418             if (ENABLE_WELCOME_VIDEO) {
    419                 showAndStartWelcomeVideo();
    420             } else {
    421                 hideWelcomeVideoAndShowWelcomeImage();
    422             }
    423             return;
    424         }
    425         hideAndStopWelcomeVideo();
    426         final boolean isStepActionAlreadyDone = mStepNumber < determineSetupStepNumber();
    427         mSetupStepGroup.enableStep(mStepNumber, isStepActionAlreadyDone);
    428         mActionNext.setVisibility(isStepActionAlreadyDone ? View.VISIBLE : View.GONE);
    429         mActionFinish.setVisibility((mStepNumber == STEP_3) ? View.VISIBLE : View.GONE);
    430     }
    431 
    432     static final class SetupStep implements View.OnClickListener {
    433         public final int mStepNo;
    434         private final View mStepView;
    435         private final TextView mBulletView;
    436         private final int mActivatedColor;
    437         private final int mDeactivatedColor;
    438         private final String mInstruction;
    439         private final String mFinishedInstruction;
    440         private final TextView mActionLabel;
    441         private Runnable mAction;
    442 
    443         public SetupStep(final int stepNo, final String applicationName, final TextView bulletView,
    444                 final View stepView, final int title, final int instruction,
    445                 final int finishedInstruction, final int actionIcon, final int actionLabel) {
    446             mStepNo = stepNo;
    447             mStepView = stepView;
    448             mBulletView = bulletView;
    449             final Resources res = stepView.getResources();
    450             mActivatedColor = res.getColor(R.color.setup_text_action);
    451             mDeactivatedColor = res.getColor(R.color.setup_text_dark);
    452 
    453             final TextView titleView = (TextView)mStepView.findViewById(R.id.setup_step_title);
    454             titleView.setText(res.getString(title, applicationName));
    455             mInstruction = (instruction == 0) ? null
    456                     : res.getString(instruction, applicationName);
    457             mFinishedInstruction = (finishedInstruction == 0) ? null
    458                     : res.getString(finishedInstruction, applicationName);
    459 
    460             mActionLabel = (TextView)mStepView.findViewById(R.id.setup_step_action_label);
    461             mActionLabel.setText(res.getString(actionLabel));
    462             if (actionIcon == 0) {
    463                 final int paddingEnd = ViewCompatUtils.getPaddingEnd(mActionLabel);
    464                 ViewCompatUtils.setPaddingRelative(mActionLabel, paddingEnd, 0, paddingEnd, 0);
    465             } else {
    466                 TextViewCompatUtils.setCompoundDrawablesRelativeWithIntrinsicBounds(
    467                         mActionLabel, res.getDrawable(actionIcon), null, null, null);
    468             }
    469         }
    470 
    471         public void setEnabled(final boolean enabled, final boolean isStepActionAlreadyDone) {
    472             mStepView.setVisibility(enabled ? View.VISIBLE : View.GONE);
    473             mBulletView.setTextColor(enabled ? mActivatedColor : mDeactivatedColor);
    474             final TextView instructionView = (TextView)mStepView.findViewById(
    475                     R.id.setup_step_instruction);
    476             instructionView.setText(isStepActionAlreadyDone ? mFinishedInstruction : mInstruction);
    477             mActionLabel.setVisibility(isStepActionAlreadyDone ? View.GONE : View.VISIBLE);
    478         }
    479 
    480         public void setAction(final Runnable action) {
    481             mActionLabel.setOnClickListener(this);
    482             mAction = action;
    483         }
    484 
    485         @Override
    486         public void onClick(final View v) {
    487             if (v == mActionLabel && mAction != null) {
    488                 mAction.run();
    489                 return;
    490             }
    491         }
    492     }
    493 
    494     static final class SetupStepGroup {
    495         private final SetupStepIndicatorView mIndicatorView;
    496         private final ArrayList<SetupStep> mGroup = new ArrayList<>();
    497 
    498         public SetupStepGroup(final SetupStepIndicatorView indicatorView) {
    499             mIndicatorView = indicatorView;
    500         }
    501 
    502         public void addStep(final SetupStep step) {
    503             mGroup.add(step);
    504         }
    505 
    506         public void enableStep(final int enableStepNo, final boolean isStepActionAlreadyDone) {
    507             for (final SetupStep step : mGroup) {
    508                 step.setEnabled(step.mStepNo == enableStepNo, isStepActionAlreadyDone);
    509             }
    510             mIndicatorView.setIndicatorPosition(enableStepNo - STEP_1, mGroup.size());
    511         }
    512     }
    513 }
    514