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