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