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
17package com.android.inputmethod.latin.setup;
18
19import android.app.Activity;
20import android.content.ContentResolver;
21import android.content.Intent;
22import android.content.res.Resources;
23import android.media.MediaPlayer;
24import android.net.Uri;
25import android.os.Bundle;
26import android.os.Message;
27import android.provider.Settings;
28import android.util.Log;
29import android.view.View;
30import android.view.inputmethod.InputMethodInfo;
31import android.view.inputmethod.InputMethodManager;
32import android.widget.ImageView;
33import android.widget.TextView;
34import android.widget.VideoView;
35
36import com.android.inputmethod.compat.TextViewCompatUtils;
37import com.android.inputmethod.compat.ViewCompatUtils;
38import com.android.inputmethod.latin.R;
39import com.android.inputmethod.latin.settings.SettingsActivity;
40import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
41import com.android.inputmethod.latin.utils.UncachedInputMethodManagerUtils;
42
43import java.util.ArrayList;
44
45import javax.annotation.Nonnull;
46
47// TODO: Use Fragment to implement welcome screen and setup steps.
48public 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