am 7befb7de: Global gesture to toggle Accessibility system-wide.

* commit '7befb7deb2ac15134b3bb190520cba19165d16dd':
  Global gesture to toggle Accessibility system-wide.
This commit is contained in:
Svetoslav Ganov
2012-09-27 20:36:31 -07:00
committed by Android Git Automerger
10 changed files with 621 additions and 23 deletions

View File

@@ -3300,7 +3300,7 @@ public final class Settings {
"enabled_accessibility_services";
/**
* List of the accessibility services to which the user has graned
* List of the accessibility services to which the user has granted
* permission to put the device into touch exploration mode.
*
* @hide
@@ -3319,7 +3319,7 @@ public final class Settings {
* <p>
* Note: The JavaScript based screen-reader is served by the
* Google infrastructure and enable users with disabilities to
* efficiantly navigate in and explore web content.
* efficiently navigate in and explore web content.
* </p>
* <p>
* This property represents a boolean value.
@@ -3331,7 +3331,7 @@ public final class Settings {
/**
* The URL for the injected JavaScript based screen-reader used
* for providing accessiblity of content in WebView.
* for providing accessibility of content in WebView.
* <p>
* Note: The JavaScript based screen-reader is served by the
* Google infrastructure and enable users with disabilities to
@@ -4109,6 +4109,15 @@ public final class Settings {
*/
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/global");
/**
* Setting whether the global gesture for enabling accessibility is enabled.
* If this gesture is enabled the user will be able to perfrom it to enable
* the accessibility state without visiting the settings app.
* @hide
*/
public static final String ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED =
"enable_accessibility_global_gesture_enabled";
/**
* Whether Airplane Mode is on.
*/

View File

@@ -20,6 +20,7 @@ package android.view.accessibility;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.content.ComponentName;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -53,4 +54,7 @@ interface IAccessibilityManager {
in AccessibilityServiceInfo info);
void unregisterUiTestAutomationService(IAccessibilityServiceClient client);
void temporaryEnableAccessibilityStateUntilKeyguardRemoved(in ComponentName service,
boolean touchExplorationEnabled);
}

View File

@@ -1578,6 +1578,12 @@
android:description="@string/permdesc_retrieve_window_info"
android:protectionLevel="signature" />
<!-- @hide Allows an application to temporary enable accessibility on the device. -->
<permission android:name="android.permission.TEMPORARY_ENABLE_ACCESSIBILITY"
android:label="@string/permlab_temporary_enable_accessibility"
android:description="@string/permdesc_temporary_enable_accessibility"
android:protectionLevel="signature" />
<!-- @hide Allows an application to magnify the content of a display. -->
<permission android:name="android.permission.MAGNIFY_DISPLAY"
android:label="@string/permlab_magnify_display"

View File

@@ -285,4 +285,7 @@
<!-- Space reserved at the bottom of secure views (pin/pattern/password/SIM pin/SIM puk) -->
<dimen name="kg_secure_padding_height">46dp</dimen>
<!-- Touch slop for the global toggle accessibility gesture -->
<dimen name="accessibility_touch_slop">80dip</dimen>
</resources>

View File

@@ -743,6 +743,13 @@
the content of the active window. Malicious apps may retrieve
the entire window content and examine all its text except passwords.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_temporary_enable_accessibility">temporary enable accessibility</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_temporary_enable_accessibility">Allows an application to temporarily
enable accessibility on the device. Malicious apps may enable accessibility without
user consent.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_retrieve_window_info">retrieve window info</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -3903,4 +3910,13 @@
</string>
<!-- Text spoken when the user is performing a gesture that will enable accessibility. [CHAR LIMIT=none] -->
<string name="continue_to_enable_accessibility">Continue touching the screen to enable accessibility.</string>
<!-- Text spoken when the user enabled accessibility. [CHAR LIMIT=none] -->
<string name="accessibility_enabled">Accessibility enabled.</string>
<!-- Text spoken when the user stops preforming a gesture that would enable accessibility. [CHAR LIMIT=none] -->
<string name="enable_accessibility_canceled">Enable accessibility canceled.</string>
<!-- Text spoken when the current user is switched if accessibility is enabled. [CHAR LIMIT=none] -->
<string name="user_switched">Switched to user <xliff:g id="name" example="Bob">%1$s</xliff:g>.</string>
</resources>

View File

@@ -290,6 +290,7 @@
<java-symbol type="color" name="tab_indicator_text_v4" />
<java-symbol type="dimen" name="accessibility_touch_slop" />
<java-symbol type="dimen" name="config_prefDialogWidth" />
<java-symbol type="dimen" name="config_viewConfigurationTouchSlop" />
<java-symbol type="dimen" name="default_app_widget_padding_bottom" />
@@ -357,6 +358,7 @@
<java-symbol type="string" name="abbrev_month_day_year" />
<java-symbol type="string" name="abbrev_month_year" />
<java-symbol type="string" name="accept" />
<java-symbol type="string" name="accessibility_enabled" />
<java-symbol type="string" name="activity_chooser_view_see_all" />
<java-symbol type="string" name="activitychooserview_choose_application" />
<java-symbol type="string" name="alternate_eri_file" />
@@ -437,6 +439,7 @@
<java-symbol type="string" name="contentServiceSync" />
<java-symbol type="string" name="contentServiceSyncNotificationTitle" />
<java-symbol type="string" name="contentServiceTooManyDeletesNotificationDesc" />
<java-symbol type="string" name="continue_to_enable_accessibility" />
<java-symbol type="string" name="date1_date2" />
<java-symbol type="string" name="date1_time1_date2_time2" />
<java-symbol type="string" name="date_and_time" />
@@ -470,6 +473,7 @@
<java-symbol type="string" name="emailTypeWork" />
<java-symbol type="string" name="emergency_call_dialog_number_for_display" />
<java-symbol type="string" name="emergency_calls_only" />
<java-symbol type="string" name="enable_accessibility_canceled" />
<java-symbol type="string" name="eventTypeAnniversary" />
<java-symbol type="string" name="eventTypeBirthday" />
<java-symbol type="string" name="eventTypeCustom" />
@@ -778,6 +782,7 @@
<java-symbol type="string" name="twelve_hour_time_format" />
<java-symbol type="string" name="twenty_four_hour_time_format" />
<java-symbol type="string" name="upload_file" />
<java-symbol type="string" name="user_switched" />
<java-symbol type="string" name="volume_alarm" />
<java-symbol type="string" name="volume_icon_description_bluetooth" />
<java-symbol type="string" name="volume_icon_description_incall" />

View File

@@ -0,0 +1,277 @@
/*
* Copyright (C) 2012 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.internal.policy.impl;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ServiceInfo;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserManager;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
import android.util.MathUtils;
import android.view.IWindowManager;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
import com.android.internal.R;
import java.util.Iterator;
import java.util.List;
public class EnableAccessibilityController {
private static final int SPEAK_WARNING_DELAY_MILLIS = 2000;
private static final int ENABLE_ACCESSIBILITY_DELAY_MILLIS = 6000;
public static final int MESSAGE_SPEAK_WARNING = 1;
public static final int MESSAGE_SPEAK_ENABLE_CANCELED = 2;
public static final int MESSAGE_ENABLE_ACCESSIBILITY = 3;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MESSAGE_SPEAK_WARNING: {
String text = mContext.getString(R.string.continue_to_enable_accessibility);
mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
} break;
case MESSAGE_SPEAK_ENABLE_CANCELED: {
String text = mContext.getString(R.string.enable_accessibility_canceled);
mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
} break;
case MESSAGE_ENABLE_ACCESSIBILITY: {
enableAccessibility();
mTone.play();
mTts.speak(mContext.getString(R.string.accessibility_enabled),
TextToSpeech.QUEUE_FLUSH, null);
} break;
}
}
};
private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager
.Stub.asInterface(ServiceManager.getService("accessibility"));
private final Context mContext;
private final UserManager mUserManager;
private final TextToSpeech mTts;
private final Ringtone mTone;
private final float mTouchSlop;
private boolean mDestroyed;
private boolean mCanceled;
private float mFirstPointerDownX;
private float mFirstPointerDownY;
private float mSecondPointerDownX;
private float mSecondPointerDownY;
public EnableAccessibilityController(Context context) {
mContext = context;
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mTts = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
@Override
public void onInit(int status) {
if (mDestroyed) {
mTts.shutdown();
}
}
});
mTone = RingtoneManager.getRingtone(context, Settings.System.DEFAULT_NOTIFICATION_URI);
mTone.setStreamType(AudioManager.STREAM_MUSIC);
mTouchSlop = context.getResources().getDimensionPixelSize(
R.dimen.accessibility_touch_slop);
}
public static boolean canEnableAccessibilityViaGesture(Context context) {
AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context);
// Accessibility is enabled and there is an enabled speaking
// accessibility service, then we have nothing to do.
if (accessibilityManager.isEnabled()
&& !accessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty()) {
return false;
}
// If the global gesture is enabled and there is a speaking service
// installed we are good to go, otherwise there is nothing to do.
return Settings.Global.getInt(context.getContentResolver(),
Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1
&& !getInstalledSpeakingAccessibilityServices(context).isEmpty();
}
private static List<AccessibilityServiceInfo> getInstalledSpeakingAccessibilityServices(
Context context) {
List<AccessibilityServiceInfo> services = AccessibilityManager.getInstance(
context).getInstalledAccessibilityServiceList();
Iterator<AccessibilityServiceInfo> iterator = services.iterator();
while (iterator.hasNext()) {
AccessibilityServiceInfo service = iterator.next();
if ((service.feedbackType & AccessibilityServiceInfo.FEEDBACK_SPOKEN) == 0) {
iterator.remove();
}
}
return services;
}
public void onDestroy() {
mDestroyed = true;
}
public boolean onInterceptTouchEvent(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
&& event.getPointerCount() == 2) {
mFirstPointerDownX = event.getX(0);
mFirstPointerDownY = event.getY(0);
mSecondPointerDownX = event.getX(1);
mSecondPointerDownY = event.getY(1);
mHandler.sendEmptyMessageDelayed(MESSAGE_SPEAK_WARNING,
SPEAK_WARNING_DELAY_MILLIS);
mHandler.sendEmptyMessageDelayed(MESSAGE_ENABLE_ACCESSIBILITY,
ENABLE_ACCESSIBILITY_DELAY_MILLIS);
return true;
}
return false;
}
public boolean onTouchEvent(MotionEvent event) {
final int pointerCount = event.getPointerCount();
final int action = event.getActionMasked();
if (mCanceled) {
if (action == MotionEvent.ACTION_UP) {
mCanceled = false;
}
return true;
}
switch (action) {
case MotionEvent.ACTION_POINTER_DOWN: {
if (pointerCount > 2) {
cancel();
}
} break;
case MotionEvent.ACTION_MOVE: {
final float firstPointerMove = MathUtils.dist(event.getX(0),
event.getY(0), mFirstPointerDownX, mFirstPointerDownY);
if (Math.abs(firstPointerMove) > mTouchSlop) {
cancel();
}
final float secondPointerMove = MathUtils.dist(event.getX(1),
event.getY(1), mSecondPointerDownX, mSecondPointerDownY);
if (Math.abs(secondPointerMove) > mTouchSlop) {
cancel();
}
} break;
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL: {
cancel();
} break;
}
return true;
}
private void cancel() {
mCanceled = true;
if (mHandler.hasMessages(MESSAGE_SPEAK_WARNING)) {
mHandler.removeMessages(MESSAGE_SPEAK_WARNING);
} else if (mHandler.hasMessages(MESSAGE_ENABLE_ACCESSIBILITY)) {
mHandler.sendEmptyMessage(MESSAGE_SPEAK_ENABLE_CANCELED);
}
mHandler.removeMessages(MESSAGE_ENABLE_ACCESSIBILITY);
}
private void enableAccessibility() {
List<AccessibilityServiceInfo> services = getInstalledSpeakingAccessibilityServices(
mContext);
if (services.isEmpty()) {
return;
}
boolean keyguardLocked = false;
try {
keyguardLocked = mWindowManager.isKeyguardLocked();
} catch (RemoteException re) {
/* ignore */
}
final boolean hasMoreThanOneUser = mUserManager.getUsers().size() > 1;
AccessibilityServiceInfo service = services.get(0);
boolean enableTouchExploration = (service.flags
& AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
// Try to find a service supporting explore by touch.
if (!enableTouchExploration) {
final int serviceCount = services.size();
for (int i = 1; i < serviceCount; i++) {
AccessibilityServiceInfo candidate = services.get(i);
if ((candidate.flags & AccessibilityServiceInfo
.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0) {
enableTouchExploration = true;
service = candidate;
break;
}
}
}
ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
ComponentName componentName = new ComponentName(serviceInfo.packageName, serviceInfo.name);
if (!keyguardLocked || !hasMoreThanOneUser) {
final int userId = ActivityManager.getCurrentUser();
String enabledServiceString = componentName.flattenToString();
ContentResolver resolver = mContext.getContentResolver();
// Enable one speaking accessibility service.
Settings.Secure.putStringForUser(resolver,
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
enabledServiceString, userId);
// Allow the services we just enabled to toggle touch exploration.
Settings.Secure.putStringForUser(resolver,
Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
enabledServiceString, userId);
// Enable touch exploration.
if (enableTouchExploration) {
Settings.Secure.putIntForUser(resolver, Settings.Secure.TOUCH_EXPLORATION_ENABLED,
1, userId);
}
// Enable accessibility script injection (AndroidVox) for web content.
Settings.Secure.putIntForUser(resolver, Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION,
1, userId);
// Turn on accessibility mode last.
Settings.Secure.putIntForUser(resolver, Settings.Secure.ACCESSIBILITY_ENABLED,
1, userId);
} else if (keyguardLocked) {
try {
mAccessibilityManager.temporaryEnableAccessibilityStateUntilKeyguardRemoved(
componentName, enableTouchExploration);
} catch (RemoteException re) {
/* ignore */
}
}
}
}

View File

@@ -16,12 +16,15 @@
package com.android.internal.policy.impl;
import com.android.internal.app.AlertController;
import com.android.internal.app.AlertController.AlertParams;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.R;
import android.app.ActivityManagerNative;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
@@ -32,11 +35,11 @@ import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -46,17 +49,21 @@ import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.IWindowManager;
import android.util.TypedValue;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicy.WindowManagerFuncs;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;
@@ -78,7 +85,7 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
private final AudioManager mAudioManager;
private ArrayList<Action> mItems;
private AlertDialog mDialog;
private GlobalActionsDialog mDialog;
private Action mSilentModeAction;
private ToggleAction mAirplaneModeOn;
@@ -150,7 +157,7 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
* Create the global actions dialog.
* @return A new dialog.
*/
private AlertDialog createDialog() {
private GlobalActionsDialog createDialog() {
// Simple toggle style if there's no vibrator, otherwise use a tri-state
if (!mHasVibrator) {
mSilentModeAction = new SilentModeToggleAction();
@@ -319,12 +326,14 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
mAdapter = new MyAdapter();
final AlertDialog.Builder ab = new AlertDialog.Builder(mContext);
AlertParams params = new AlertParams(mContext);
params.mAdapter = mAdapter;
params.mOnClickListener = this;
params.mForceInverseBackground = true;
ab.setAdapter(mAdapter, this)
.setInverseBackgroundForced(true);
GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params);
dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
final AlertDialog dialog = ab.create();
dialog.getListView().setItemsCanFocus(true);
dialog.getListView().setLongClickable(true);
dialog.getListView().setOnItemLongClickListener(
@@ -872,4 +881,121 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac
mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off;
}
}
private static final class GlobalActionsDialog extends Dialog implements DialogInterface {
private final Context mContext;
private final int mWindowTouchSlop;
private final AlertController mAlert;
private EnableAccessibilityController mEnableAccessibilityController;
private boolean mIntercepted;
private boolean mCancelOnUp;
public GlobalActionsDialog(Context context, AlertParams params) {
super(context, getDialogTheme(context));
mContext = context;
mAlert = new AlertController(mContext, this, getWindow());
mWindowTouchSlop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
params.apply(mAlert);
}
private static int getDialogTheme(Context context) {
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
outValue, true);
return outValue.resourceId;
}
@Override
protected void onStart() {
// If global accessibility gesture can be performed, we will take care
// of dismissing the dialog on touch outside. This is because the dialog
// is dismissed on the first down while the global gesture is a long press
// with two fingers anywhere on the screen.
if (EnableAccessibilityController.canEnableAccessibilityViaGesture(mContext)) {
mEnableAccessibilityController = new EnableAccessibilityController(mContext);
super.setCanceledOnTouchOutside(false);
} else {
mEnableAccessibilityController = null;
super.setCanceledOnTouchOutside(true);
}
super.onStart();
}
@Override
protected void onStop() {
if (mEnableAccessibilityController != null) {
mEnableAccessibilityController.onDestroy();
}
super.onStop();
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (mEnableAccessibilityController != null) {
final int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
View decor = getWindow().getDecorView();
final int eventX = (int) event.getX();
final int eventY = (int) event.getY();
if (eventX < -mWindowTouchSlop
|| eventY < -mWindowTouchSlop
|| eventX >= decor.getWidth() + mWindowTouchSlop
|| eventY >= decor.getHeight() + mWindowTouchSlop) {
mCancelOnUp = true;
}
}
try {
if (!mIntercepted) {
mIntercepted = mEnableAccessibilityController.onInterceptTouchEvent(event);
if (mIntercepted) {
final long now = SystemClock.uptimeMillis();
event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
mCancelOnUp = true;
}
} else {
return mEnableAccessibilityController.onTouchEvent(event);
}
} finally {
if (action == MotionEvent.ACTION_UP) {
if (mCancelOnUp) {
cancel();
}
mCancelOnUp = false;
mIntercepted = false;
}
}
}
return super.dispatchTouchEvent(event);
}
public ListView getListView() {
return mAlert.getListView();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAlert.installContent();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (mAlert.onKeyDown(keyCode, event)) {
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (mAlert.onKeyUp(keyCode, event)) {
return true;
}
return super.onKeyUp(keyCode, event);
}
}
}

View File

@@ -40,6 +40,8 @@ import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.media.AudioManager;
import android.media.IAudioService;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.os.Bundle;
import android.os.FactoryTest;
import android.os.Handler;
@@ -747,7 +749,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
break;
case LONG_PRESS_POWER_GLOBAL_ACTIONS:
mPowerKeyHandled = true;
performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) {
performAuditoryFeedbackForAccessibilityIfNeed();
}
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
showGlobalActionsDialog();
break;
@@ -4250,6 +4254,25 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
private void performAuditoryFeedbackForAccessibilityIfNeed() {
if (!isGlobalAccessibilityGestureEnabled()) {
return;
}
AudioManager audioManager = (AudioManager) mContext.getSystemService(
Context.AUDIO_SERVICE);
if (audioManager.isSilentMode()) {
return;
}
Ringtone ringTone = RingtoneManager.getRingtone(mContext,
Settings.System.DEFAULT_NOTIFICATION_URI);
ringTone.setStreamType(AudioManager.STREAM_MUSIC);
ringTone.play();
}
private boolean isGlobalAccessibilityGestureEnabled() {
return Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1;
}
public boolean performHapticFeedbackLw(WindowState win, int effectId, boolean always) {
final boolean hapticsDisabled = Settings.System.getIntForUser(mContext.getContentResolver(),
Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0;

View File

@@ -56,6 +56,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
@@ -108,9 +109,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private static final String LOG_TAG = "AccessibilityManagerService";
// TODO: This is arbitrary. When there is time implement this by watching
// when that accessibility services are bound.
private static final int WAIT_FOR_USER_STATE_FULLY_INITIALIZED_MILLIS = 5000;
private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE =
"registerUiTestAutomationService";
private static final String TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED =
"temporaryEnableAccessibilityStateUntilKeyguardRemoved";
private static final char COMPONENT_NAME_SEPARATOR = ':';
private static final int OWN_PROCESS_ID = android.os.Process.myPid();
@@ -157,6 +165,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private final SparseArray<UserState> mUserStates = new SparseArray<UserState>();
private final TempUserStateChangeMemento mTempStateChangeForCurrentUserMemento =
new TempUserStateChangeMemento();
private int mCurrentUserId = UserHandle.USER_OWNER;
private UserState getCurrentUserStateLocked() {
@@ -268,12 +279,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// package changes
monitor.register(mContext, null, UserHandle.ALL, true);
// user change
IntentFilter userFilter = new IntentFilter();
userFilter.addAction(Intent.ACTION_USER_SWITCHED);
userFilter.addAction(Intent.ACTION_USER_REMOVED);
// user change and unlock
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
intentFilter.addAction(Intent.ACTION_USER_REMOVED);
intentFilter.addAction(Intent.ACTION_USER_PRESENT);
mContext.registerReceiver(new BroadcastReceiver() {
mContext.registerReceiverAsUser(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
@@ -281,9 +293,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
restoreStateFromMementoIfNeeded();
}
}
}, userFilter);
}, UserHandle.ALL, intentFilter, null, null);
}
public int addClient(IAccessibilityManagerClient client, int userId) {
@@ -510,6 +524,37 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
public void temporaryEnableAccessibilityStateUntilKeyguardRemoved(
ComponentName service, boolean touchExplorationEnabled) {
mSecurityPolicy.enforceCallingPermission(
Manifest.permission.TEMPORARY_ENABLE_ACCESSIBILITY,
TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED);
try {
if (!mWindowManagerService.isKeyguardLocked()) {
return;
}
} catch (RemoteException re) {
return;
}
synchronized (mLock) {
UserState userState = getCurrentUserStateLocked();
// Stash the old state so we can restore it when the keyguard is gone.
mTempStateChangeForCurrentUserMemento.initialize(mCurrentUserId, getCurrentUserStateLocked());
// Set the temporary state.
userState.mIsAccessibilityEnabled = true;
userState.mIsTouchExplorationEnabled= touchExplorationEnabled;
userState.mIsDisplayMagnificationEnabled = false;
userState.mEnabledServices.clear();
userState.mEnabledServices.add(service);
userState.mTouchExplorationGrantedServices.clear();
userState.mTouchExplorationGrantedServices.add(service);
// Update the internal state.
performServiceManagementLocked(userState);
updateInputFilterLocked(userState);
scheduleSendStateToClientsLocked(userState);
}
}
public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) {
synchronized (mLock) {
// Automation service is not bound, so pretend it died to perform clean up.
@@ -600,9 +645,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private void switchUser(int userId) {
synchronized (mLock) {
if (userId == mCurrentUserId) {
return;
}
// The user switched so we do not need to restore the current user
// state since we will fully rebuild it when he becomes current again.
mTempStateChangeForCurrentUserMemento.clear();
// Disconnect from services for the old user.
UserState oldUserState = getUserStateLocked(mCurrentUserId);
@@ -620,6 +665,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// Recreate the internal state for the new user.
mMainHandler.obtainMessage(MainHandler.MSG_SEND_RECREATE_INTERNAL_STATE,
mCurrentUserId, 0).sendToTarget();
// Schedule announcement of the current user if needed.
mMainHandler.sendEmptyMessageDelayed(MainHandler.MSG_ANNOUNCE_NEW_USER_IF_NEEDED,
WAIT_FOR_USER_STATE_FULLY_INITIALIZED_MILLIS);
}
}
@@ -629,6 +678,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
private void restoreStateFromMementoIfNeeded() {
synchronized (mLock) {
if (mTempStateChangeForCurrentUserMemento.mUserId != UserHandle.USER_NULL) {
UserState userState = getCurrentUserStateLocked();
// Restore the state from the memento.
mTempStateChangeForCurrentUserMemento.applyTo(userState);
mTempStateChangeForCurrentUserMemento.clear();
// Update the internal state.
performServiceManagementLocked(userState);
updateInputFilterLocked(userState);
scheduleSendStateToClientsLocked(userState);
}
}
}
private Service getQueryBridge() {
if (mQueryBridge == null) {
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
@@ -1076,6 +1140,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
handleDisplayMagnificationEnabledSettingChangedLocked(userState);
handleAccessibilityEnabledSettingChangedLocked(userState);
performServiceManagementLocked(userState);
updateInputFilterLocked(userState);
scheduleSendStateToClientsLocked(userState);
}
@@ -1084,6 +1149,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
userState.mIsAccessibilityEnabled = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED, 0, userState.mUserId) == 1;
}
private void performServiceManagementLocked(UserState userState) {
if (userState.mIsAccessibilityEnabled ) {
manageServicesLocked(userState);
} else {
@@ -1186,6 +1254,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
public static final int MSG_SEND_CLEARED_STATE_TO_CLIENTS_FOR_USER = 3;
public static final int MSG_SEND_RECREATE_INTERNAL_STATE = 4;
public static final int MSG_UPDATE_ACTIVE_WINDOW = 5;
public static final int MSG_ANNOUNCE_NEW_USER_IF_NEEDED = 6;
public MainHandler(Looper looper) {
super(looper);
@@ -1226,6 +1295,25 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
final int eventType = msg.arg2;
mSecurityPolicy.updateActiveWindow(windowId, eventType);
} break;
case MSG_ANNOUNCE_NEW_USER_IF_NEEDED: {
announceNewUserIfNeeded();
} break;
}
}
private void announceNewUserIfNeeded() {
synchronized (mLock) {
UserState userState = getCurrentUserStateLocked();
if (userState.mIsAccessibilityEnabled) {
UserManager userManager = (UserManager) mContext.getSystemService(
Context.USER_SERVICE);
String message = mContext.getString(R.string.user_switched,
userManager.getUserInfo(mCurrentUserId).name);
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_ANNOUNCEMENT);
event.getText().add(message);
sendAccessibilityEvent(event, mCurrentUserId);
}
}
}
@@ -2229,6 +2317,46 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
private class TempUserStateChangeMemento {
public int mUserId = UserHandle.USER_NULL;
public boolean mIsAccessibilityEnabled;
public boolean mIsTouchExplorationEnabled;
public boolean mIsDisplayMagnificationEnabled;
public final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>();
public final Set<ComponentName> mTouchExplorationGrantedServices =
new HashSet<ComponentName>();
public void initialize(int userId, UserState userState) {
mUserId = userId;
mIsAccessibilityEnabled = userState.mIsAccessibilityEnabled;
mIsTouchExplorationEnabled = userState.mIsTouchExplorationEnabled;
mIsDisplayMagnificationEnabled = userState.mIsDisplayMagnificationEnabled;
mEnabledServices.clear();
mEnabledServices.addAll(userState.mEnabledServices);
mTouchExplorationGrantedServices.clear();
mTouchExplorationGrantedServices.addAll(userState.mTouchExplorationGrantedServices);
}
public void applyTo(UserState userState) {
userState.mIsAccessibilityEnabled = mIsAccessibilityEnabled;
userState.mIsTouchExplorationEnabled = mIsTouchExplorationEnabled;
userState.mIsDisplayMagnificationEnabled = mIsDisplayMagnificationEnabled;
userState.mEnabledServices.clear();
userState.mEnabledServices.addAll(mEnabledServices);
userState.mTouchExplorationGrantedServices.clear();
userState.mTouchExplorationGrantedServices.addAll(mTouchExplorationGrantedServices);
}
public void clear() {
mUserId = UserHandle.USER_NULL;
mIsAccessibilityEnabled = false;
mIsTouchExplorationEnabled = false;
mIsDisplayMagnificationEnabled = false;
mEnabledServices.clear();
mTouchExplorationGrantedServices.clear();
}
}
private final class AccessibilityContentObserver extends ContentObserver {
private final Uri mAccessibilityEnabledUri = Settings.Secure.getUriFor(
@@ -2272,6 +2400,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
if (mUiAutomationService == null) {
UserState userState = getCurrentUserStateLocked();
handleAccessibilityEnabledSettingChangedLocked(userState);
performServiceManagementLocked(userState);
updateInputFilterLocked(userState);
scheduleSendStateToClientsLocked(userState);
}