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