/*
* Copyright 2015 Google Inc.
*
* 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.controller;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.CheckBox;
import android.widget.ScrollView;
import com.android.talkback.DimmingOverlayView;
import com.android.talkback.OrientationMonitor;
import com.android.talkback.R;
import com.google.android.marvin.talkback.TalkBackService;
import com.android.utils.SharedPreferencesUtils;
import java.util.concurrent.TimeUnit;
public class DimScreenControllerApp implements DimScreenController,
OrientationMonitor.OnOrientationChangedListener {
public static final int MIN_API_LEVEL = Build.VERSION_CODES.JELLY_BEAN_MR2;
public static final boolean IS_SUPPORTED_PLATFORM = Build.VERSION.SDK_INT >= MIN_API_LEVEL &&
!TalkBackService.isInArc();
private static final float MAX_DIM_AMOUNT = 0.9f;
private static final float MIN_BRIGHTNESS = 0.1f;
private static final float MAX_BRIGHTNESS = 1.0f;
private static final int START_DIMMING_MESSAGE = 1;
private static final int UPDATE_TIMER_MESSAGE = 2;
private static final int INSTRUCTION_VISIBLE_SECONDS = 180;
private SharedPreferences mPrefs;
private Context mContext;
private boolean mIsDimmed;
private WindowManager mWindowManager;
private LayoutParams mViewParams;
private DimmingOverlayView mView;
private Dialog mDimDialog;
private int mCurrentInstructionVisibleTime;
private boolean mIsInstructionDisplayed;
private Handler mDimmingHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case START_DIMMING_MESSAGE:
mCurrentInstructionVisibleTime = INSTRUCTION_VISIBLE_SECONDS;
mIsInstructionDisplayed = true;
sendEmptyMessage(UPDATE_TIMER_MESSAGE);
break;
case UPDATE_TIMER_MESSAGE:
mCurrentInstructionVisibleTime--;
if (mCurrentInstructionVisibleTime > 0) {
updateText(mCurrentInstructionVisibleTime);
sendEmptyMessageDelayed(UPDATE_TIMER_MESSAGE, TimeUnit.SECONDS.toMillis(1));
} else {
hideInstructionAndTurnOnDimming();
}
break;
}
}
};
public DimScreenControllerApp(Context context) {
mContext = context;
mPrefs = SharedPreferencesUtils.getSharedPreferences(context);
}
@Override
public boolean isDimmingEnabled() {
return SharedPreferencesUtils.getBooleanPref(
mPrefs, mContext.getResources(),
R.string.pref_dim_when_talkback_enabled_key,
R.bool.pref_dim_when_talkback_enabled_default);
}
/**
* Turn on screen dimming without setting the shared preference.
*/
private void makeScreenDim() {
if (mIsDimmed) {
return;
}
mIsDimmed = !mIsDimmed;
initView();
addExitInstructionView();
startDimmingCount();
announceScreenDimChanged(R.string.screen_dimmed);
}
private void initView() {
if (mViewParams == null || mView == null) {
mWindowManager = (WindowManager) mContext.getSystemService(
Context.WINDOW_SERVICE);
mViewParams = new LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
mViewParams.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
} else {
mViewParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
}
mViewParams.flags |= LayoutParams.FLAG_DIM_BEHIND;
mViewParams.flags |= LayoutParams.FLAG_NOT_FOCUSABLE;
mViewParams.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
mViewParams.flags |= LayoutParams.FLAG_FULLSCREEN;
mViewParams.flags &= ~LayoutParams.FLAG_TURN_SCREEN_ON;
mViewParams.flags &= ~LayoutParams.FLAG_KEEP_SCREEN_ON;
mViewParams.format = PixelFormat.OPAQUE;
mView = new DimmingOverlayView(mContext);
mView.setTimerLimit(INSTRUCTION_VISIBLE_SECONDS);
}
initCurtainSize();
}
private void initCurtainSize() {
Point point = new Point();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
mWindowManager.getDefaultDisplay().getRealSize(point);
} else {
mWindowManager.getDefaultDisplay().getSize(point);
}
mViewParams.width = point.x;
mViewParams.height = point.y;
}
private void addExitInstructionView() {
mViewParams.dimAmount = MAX_DIM_AMOUNT;
mViewParams.screenBrightness = getDeviceBrightness();
mViewParams.buttonBrightness = MIN_BRIGHTNESS;
mWindowManager.addView(mView, mViewParams);
mView.showText();
}
private float getDeviceBrightness() {
try {
return android.provider.Settings.System.getInt(
mContext.getContentResolver(),
android.provider.Settings.System.SCREEN_BRIGHTNESS);
} catch (Settings.SettingNotFoundException e) {
return MAX_BRIGHTNESS;
}
}
private void startDimmingCount() {
mDimmingHandler.sendEmptyMessage(START_DIMMING_MESSAGE);
}
private void updateText(int secondsLeft) {
mView.updateSecondsText(secondsLeft);
}
private void hideInstructionAndTurnOnDimming() {
mViewParams.dimAmount = MAX_DIM_AMOUNT;
mViewParams.screenBrightness = MIN_BRIGHTNESS;
mViewParams.buttonBrightness = mViewParams.screenBrightness;
mIsInstructionDisplayed = false;
mView.hideText();
updateView();
}
@Override
public boolean isInstructionDisplayed() {
return mIsInstructionDisplayed;
}
private void updateView() {
if (mIsDimmed) {
mWindowManager.removeViewImmediate(mView);
mWindowManager.addView(mView, mViewParams);
}
}
@Override
public void resume() {
if (isDimmingEnabled()) {
makeScreenDim();
}
}
@Override
public void suspend() {
makeScreenBright();
if (mDimDialog != null && mDimDialog.isShowing()) {
mDimDialog.cancel();
}
}
@Override
public void shutdown() {
suspend();
mViewParams = null;
mView = null;
}
/**
* Turns off screen dimming without setting the shared preference.
*/
private void makeScreenBright() {
if (!mIsDimmed) {
return;
}
mIsDimmed = !mIsDimmed;
mIsInstructionDisplayed = false;
mWindowManager.removeViewImmediate(mView);
announceScreenDimChanged(R.string.screen_brightness_restored);
mDimmingHandler.removeMessages(START_DIMMING_MESSAGE);
mDimmingHandler.removeMessages(UPDATE_TIMER_MESSAGE);
}
@Override
public void disableDimming() {
makeScreenBright();
SharedPreferencesUtils.putBooleanPref(mPrefs, mContext.getResources(),
R.string.pref_dim_when_talkback_enabled_key, false);
}
@Override
public void showDimScreenDialog() {
// Only show one dim screen dialog at a time.
if (mDimDialog != null && mDimDialog.isShowing()) {
return;
}
boolean showConfirmDialog = mPrefs.getBoolean(
mContext.getString(R.string.pref_show_dim_screen_confirmation_dialog), true);
if (!showConfirmDialog) {
makeScreenDim();
SharedPreferencesUtils.putBooleanPref(mPrefs, mContext.getResources(),
R.string.pref_dim_when_talkback_enabled_key, true);
return;
}
LayoutInflater inflater = LayoutInflater.from(mContext);
@SuppressLint("InflateParams") final ScrollView root = (ScrollView) inflater.inflate(
R.layout.dim_screen_confirmation_dialog, null);
final CheckBox confirmCheckBox = (CheckBox) root.findViewById(R.id.show_warning_checkbox);
final DialogInterface.OnClickListener okayClick = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
if (!confirmCheckBox.isChecked()) {
SharedPreferencesUtils.putBooleanPref(mPrefs, mContext.getResources(),
R.string.pref_show_dim_screen_confirmation_dialog, false);
}
// TalkBack should be active here, but let's check just in case.
if (TalkBackService.isServiceActive()) {
makeScreenDim();
SharedPreferencesUtils.putBooleanPref(mPrefs, mContext.getResources(),
R.string.pref_dim_when_talkback_enabled_key, true);
}
mDimDialog = null;
}
}
};
mDimDialog = new AlertDialog.Builder(TalkBackService.getInstance())
.setTitle(R.string.dialog_title_dim_screen)
.setView(root)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, okayClick)
.create();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
mDimDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY);
} else {
mDimDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
}
mDimDialog.show();
}
private void announceScreenDimChanged(int announcementTextResId) {
AccessibilityManager manager = (AccessibilityManager) mContext
.getSystemService(Context.ACCESSIBILITY_SERVICE);
if (manager.isEnabled()) {
AccessibilityEvent event = AccessibilityEvent.obtain();
event.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
event.setClassName(getClass().getName());
event.setPackageName(mContext.getPackageName());
event.getText().add(mContext.getString(announcementTextResId));
manager.sendAccessibilityEvent(event);
}
}
@Override
public void onOrientationChanged(int newOrientation) {
if (mIsDimmed) {
initCurtainSize();
mWindowManager.removeViewImmediate(mView);
mView = new DimmingOverlayView(mContext);
mView.setTimerLimit(INSTRUCTION_VISIBLE_SECONDS);
if (mIsInstructionDisplayed) {
mView.showText();
} else {
mView.hideText();
}
mWindowManager.addView(mView, mViewParams);
}
}
}