/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.android.talkback.tutorial;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.TextView;
import com.android.talkback.R;
import com.android.talkback.SpeechController;
import com.android.talkback.contextmenu.ListMenuManager;
import com.android.utils.AccessibilityEventListener;
import com.android.utils.LogUtils;
import com.android.utils.SharedPreferencesUtils;
import com.google.android.marvin.talkback.TalkBackService;
public class AccessibilityTutorialActivity extends Activity implements TutorialNavigationCallback,
AccessibilityEventListener {
//timeout is 10s to auto-navigate from tutorial home page to lesson 1.
private static final int AUTO_NAVIGATION_TIMEOUT = 10000;
private static final String MAIN_FRAGMENT_NAME = "MAIN_FRAGMENT_NAME";
private static AccessibilityTutorialActivity sActiveTutorial;
private final Handler mHandler = new Handler();
private Runnable mRunnable;
public static boolean isTutorialActive() {
return sActiveTutorial != null;
}
public static void stopActiveTutorial() {
if (sActiveTutorial != null) {
sActiveTutorial.finish();
}
}
private TutorialController mTutorialController;
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
if (!isTablet()) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
setContentView(R.layout.tutorial_activity);
try {
mTutorialController = new TutorialController(this);
} catch (Exception e) {
LogUtils.log(Log.ERROR, "failed to create tutorial");
finish();
return;
}
getFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
@Override
public void onBackStackChanged() {
if (getFragmentManager().getBackStackEntryCount() == 0) {
finish();
}
}
});
setTitle(R.string.tutorial_title);
TutorialMainFragment mainFragment = new TutorialMainFragment();
mainFragment.setOnLessonSelectedCallback(this);
mainFragment.setTutorialController(mTutorialController);
switchFragment(mainFragment, MAIN_FRAGMENT_NAME);
}
public boolean isTablet() {
return getResources().getBoolean(R.bool.is_tablet);
}
@Override
public void onStart() {
super.onStart();
sActiveTutorial = this;
/*
* Handle the cases where the tutorial was started with TalkBack in an
* invalid state (inactive, suspended, or without Explore by Touch
* enabled).
*/
final int serviceState = TalkBackService.getServiceState();
final AccessibilityManager accessibilityManager =
(AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE);
/*
* Check for suspended state first because touch exploration reports it
* is disabled when TalkBack is suspended.
*/
if (serviceState == TalkBackService.SERVICE_STATE_SUSPENDED) {
showAlertDialogAndFinish(R.string.tutorial_service_suspended_title,
R.string.tutorial_service_suspended_message);
return;
} else if ((serviceState == TalkBackService.SERVICE_STATE_INACTIVE)) {
showAlertDialogAndFinish(R.string.tutorial_service_inactive_title,
R.string.tutorial_service_inactive_message);
return;
} else if (!accessibilityManager.isTouchExplorationEnabled()) {
showAlertDialogAndFinish(R.string.tutorial_no_touch_explore_title,
R.string.tutorial_no_touch_explore_message);
return;
}
TalkBackService service = TalkBackService.getInstance();
if (service != null) {
service.setMenuManager(new ListMenuManager(service));
}
SharedPreferences preferences = SharedPreferencesUtils.getSharedPreferences(
getApplicationContext());
if (preferences.getBoolean(TalkBackService.PREF_FIRST_TIME_USER, true)) {
final SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean(TalkBackService.PREF_FIRST_TIME_USER, false);
editor.apply();
onFirstTimeLaunch();
}
}
@Override
public void onStop() {
super.onStop();
sActiveTutorial = null;
TalkBackService service = TalkBackService.getInstance();
if (service != null) {
service.updateMenuManagerFromPreferences();
}
}
private void onFirstTimeLaunch() {
final TalkBackService service = TalkBackService.getInstance();
if (service == null) {
return;
}
mRunnable = new Runnable() {
@Override
public void run() {
service.postRemoveEventListener(AccessibilityTutorialActivity.this);
service.getSpeechController().speak(
service.getString(R.string.notification_tutorial_navigate_to_lession_1),
// After navigating to lesson 1, the announcement of the lesson title and
// subtitle will interrupt the content in speech queue. Thus we should
// make this notification uninterruptible.
SpeechController.QUEUE_MODE_UNINTERRUPTIBLE,
0,
null);
showLesson(mTutorialController.getTutorial().getLesson(0), 0);
}
};
SpeechController.UtteranceCompleteRunnable utteranceCompleteRunnable = new SpeechController
.UtteranceCompleteRunnable() {
@Override
public void run(int status) {
switch (status) {
case SpeechController.STATUS_SPOKEN:
service.addEventListener(AccessibilityTutorialActivity.this);
mHandler.postDelayed(mRunnable, AUTO_NAVIGATION_TIMEOUT);
break;
default:
break;
}
}
};
TextView description = (TextView) findViewById(R.id.description);
service.getSpeechController().speak(
description.getText(), /* text */
null, /* earcons */
null, /* haptics */
SpeechController.QUEUE_MODE_QUEUE, /* queueMode */
0, /* flags */
SpeechController.UTTERANCE_GROUP_DEFAULT, /* utteranceGroup */
null, /* speechParams */
null, /* nonSpeechParams */
utteranceCompleteRunnable /* completeAction */);
}
private void switchFragment(Fragment fragment, String name) {
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_holder, fragment);
transaction.addToBackStack(name);
transaction.commit();
}
@Override
public void onLessonSelected(TutorialLesson lesson) {
showLesson(lesson, 0);
}
@Override
public void onLessonPracticeSelected(TutorialLesson lesson) {
showLesson(lesson, lesson.getPracticePage());
}
@Override
public void onNextPageClicked(TutorialLesson lesson, int currentPageIndex) {
int nextPage = currentPageIndex + 1;
if (lesson.getPagesCount() > nextPage) {
showLesson(lesson, nextPage);
} else {
TutorialLesson nextLesson = mTutorialController.getNextLesson(lesson);
if (nextLesson != null) {
onLessonSelected(nextLesson);
} else {
returnToMainFragment();
}
}
}
@Override
public void onPreviousPageClicked(TutorialLesson lesson, int currentPageIndex) {
int nextPage = currentPageIndex - 1;
if (nextPage < 0) {
returnToMainFragment();
} else {
showLesson(lesson, nextPage);
}
}
@Override
public void onNavigateUpClicked() {
returnToMainFragment();
}
private void showLesson(TutorialLesson lesson, int pageNumber) {
TutorialLessonFragment lessonFragment = new TutorialLessonFragment();
lessonFragment.setLesson(lesson, pageNumber);
lessonFragment.setTutorialController(mTutorialController);
lessonFragment.setTutorialNavigationCallback(this);
switchFragment(lessonFragment, null);
}
private void returnToMainFragment() {
getFragmentManager().popBackStack(MAIN_FRAGMENT_NAME, 0);
getWindow().getDecorView().announceForAccessibility(getTitle());
}
private final OnCancelListener mFinishActivityOnCancelListener = new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
finish();
}
};
private final OnClickListener mFinishActivityOnClickListener = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
finish();
}
};
private void showAlertDialogAndFinish(int titleId, int messageId) {
showAlertDialogAndFinish(getString(titleId), getString(messageId));
}
private void showAlertDialogAndFinish(String title, String message) {
returnToMainFragment();
new AlertDialog.Builder(AccessibilityTutorialActivity.this)
.setTitle(title)
.setMessage(message)
.setCancelable(true)
.setOnCancelListener(mFinishActivityOnCancelListener)
.setPositiveButton(R.string.tutorial_alert_dialog_exit,
mFinishActivityOnClickListener)
.create()
.show();
}
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
mHandler.removeCallbacks(mRunnable);
TalkBackService service = TalkBackService.getInstance();
if (service != null) {
service.postRemoveEventListener(this);
}
}
}
}