am 7befb7de: Global gesture to toggle Accessibility system-wide.
* commit '7befb7deb2ac15134b3bb190520cba19165d16dd': Global gesture to toggle Accessibility system-wide.
This commit is contained in:
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user