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 
SettingsPoolingHandler(@onnull final SetupWizardActivity ownerInstance, final InputMethodManager imm)87         public SettingsPoolingHandler(@Nonnull final SetupWizardActivity ownerInstance,
88                 final InputMethodManager imm) {
89             super(ownerInstance);
90             mImmInHandler = imm;
91         }
92 
93         @Override
handleMessage(final Message msg)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 
startPollingImeSettings()111         public void startPollingImeSettings() {
112             sendMessageDelayed(obtainMessage(MSG_POLLING_IME_SETTINGS),
113                     IME_SETTINGS_POLLING_INTERVAL);
114         }
115 
cancelPollingImeSettings()116         public void cancelPollingImeSettings() {
117             removeMessages(MSG_POLLING_IME_SETTINGS);
118         }
119     }
120 
121     @Override
onCreate(final Bundle savedInstanceState)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
onClick(final View v)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 
invokeSetupWizardOfThisIme()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 
invokeSettingsOfThisIme()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 
invokeLanguageAndInputSettings()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 
invokeInputMethodPicker()281     void invokeInputMethodPicker() {
282         // Invoke input method picker.
283         mImm.showInputMethodPicker();
284         mNeedsToAdjustStepNumberToSystemState = true;
285     }
286 
invokeSubtypeEnablerOfThisIme()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 
determineSetupStepNumberFromLauncher()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 
determineSetupStepNumber()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
onSaveInstanceState(final Bundle outState)326     protected void onSaveInstanceState(final Bundle outState) {
327         super.onSaveInstanceState(outState);
328         outState.putInt(STATE_STEP, mStepNumber);
329     }
330 
331     @Override
onRestoreInstanceState(final Bundle savedInstanceState)332     protected void onRestoreInstanceState(final Bundle savedInstanceState) {
333         super.onRestoreInstanceState(savedInstanceState);
334         mStepNumber = savedInstanceState.getInt(STATE_STEP);
335     }
336 
isInSetupSteps(final int stepNumber)337     private static boolean isInSetupSteps(final int stepNumber) {
338         return stepNumber >= STEP_1 && stepNumber <= STEP_3;
339     }
340 
341     @Override
onRestart()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
onResume()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
onBackPressed()370     public void onBackPressed() {
371         if (mStepNumber == STEP_1) {
372             mStepNumber = STEP_WELCOME;
373             updateSetupStepView();
374             return;
375         }
376         super.onBackPressed();
377     }
378 
hideWelcomeVideoAndShowWelcomeImage()379     void hideWelcomeVideoAndShowWelcomeImage() {
380         mWelcomeVideoView.setVisibility(View.GONE);
381         mWelcomeImageView.setImageResource(R.raw.setup_welcome_image);
382         mWelcomeImageView.setVisibility(View.VISIBLE);
383     }
384 
showAndStartWelcomeVideo()385     private void showAndStartWelcomeVideo() {
386         mWelcomeVideoView.setVisibility(View.VISIBLE);
387         mWelcomeVideoView.setVideoURI(mWelcomeVideoUri);
388         mWelcomeVideoView.start();
389     }
390 
hideAndStopWelcomeVideo()391     private void hideAndStopWelcomeVideo() {
392         mWelcomeVideoView.stopPlayback();
393         mWelcomeVideoView.setVisibility(View.GONE);
394     }
395 
396     @Override
onPause()397     protected void onPause() {
398         hideAndStopWelcomeVideo();
399         super.onPause();
400     }
401 
402     @Override
onWindowFocusChanged(final boolean hasFocus)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 
updateSetupStepView()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