AccessibilityShortcutController.java revision 32ea37255dbb9c5d7d40b73e435d12df934a803a
1/* 2 * Copyright (C) 2012 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.android.server.policy; 18 19import android.accessibilityservice.AccessibilityServiceInfo; 20import android.app.ActivityManager; 21import android.app.AlertDialog; 22import android.content.ComponentName; 23import android.content.ContentResolver; 24import android.content.Context; 25import android.content.DialogInterface; 26import android.database.ContentObserver; 27import android.media.AudioAttributes; 28import android.media.Ringtone; 29import android.media.RingtoneManager; 30import android.os.Handler; 31import android.os.UserHandle; 32import android.os.Vibrator; 33import android.provider.Settings; 34import android.text.TextUtils; 35import android.util.Slog; 36import android.view.Window; 37import android.view.WindowManager; 38import android.view.accessibility.AccessibilityManager; 39 40import android.widget.Toast; 41import com.android.internal.R; 42 43import java.util.List; 44 45import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG; 46 47/** 48 * Class to help manage the accessibility shortcut 49 */ 50public class AccessibilityShortcutController { 51 private static final String TAG = "AccessibilityShortcutController"; 52 private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() 53 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 54 .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY) 55 .build(); 56 57 58 private final Context mContext; 59 private AlertDialog mAlertDialog; 60 private boolean mIsShortcutEnabled; 61 // Visible for testing 62 public FrameworkObjectProvider mFrameworkObjectProvider = new FrameworkObjectProvider(); 63 64 public static String getTargetServiceComponentNameString( 65 Context context, int userId) { 66 final String currentShortcutServiceId = Settings.Secure.getStringForUser( 67 context.getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, 68 userId); 69 if (currentShortcutServiceId != null) { 70 return currentShortcutServiceId; 71 } 72 return context.getString(R.string.config_defaultAccessibilityService); 73 } 74 75 public AccessibilityShortcutController(Context context, Handler handler) { 76 mContext = context; 77 78 // Keep track of state of shortcut 79 mContext.getContentResolver().registerContentObserver( 80 Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE), 81 false, 82 new ContentObserver(handler) { 83 @Override 84 public void onChange(boolean selfChange) { 85 onSettingsChanged(); 86 } 87 }, 88 UserHandle.USER_ALL); 89 updateShortcutEnabled(); 90 } 91 92 public boolean isAccessibilityShortcutAvailable() { 93 return mIsShortcutEnabled; 94 } 95 96 public void onSettingsChanged() { 97 updateShortcutEnabled(); 98 } 99 100 /** 101 * Called when the accessibility shortcut is activated 102 */ 103 public void performAccessibilityShortcut() { 104 Slog.d(TAG, "Accessibility shortcut activated"); 105 final ContentResolver cr = mContext.getContentResolver(); 106 final int userId = ActivityManager.getCurrentUser(); 107 final int dialogAlreadyShown = Settings.Secure.getIntForUser( 108 cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId); 109 110 // Play a notification tone 111 final Ringtone tone = 112 RingtoneManager.getRingtone(mContext, Settings.System.DEFAULT_NOTIFICATION_URI); 113 if (tone != null) { 114 tone.setAudioAttributes(new AudioAttributes.Builder() 115 .setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT) 116 .build()); 117 tone.play(); 118 } 119 120 // Play a notification vibration 121 Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); 122 if ((vibrator != null) && vibrator.hasVibrator()) { 123 // Don't check if haptics are disabled, as we need to alert the user that their 124 // way of interacting with the phone may change if they activate the shortcut 125 long[] vibePattern = PhoneWindowManager.getLongIntArray(mContext.getResources(), 126 R.array.config_safeModeDisabledVibePattern); 127 vibrator.vibrate(vibePattern, -1, VIBRATION_ATTRIBUTES); 128 } 129 130 131 if (dialogAlreadyShown == 0) { 132 // The first time, we show a warning rather than toggle the service to give the user a 133 // chance to turn off this feature before stuff gets enabled. 134 mAlertDialog = createShortcutWarningDialog(userId); 135 if (mAlertDialog == null) { 136 return; 137 } 138 Window w = mAlertDialog.getWindow(); 139 WindowManager.LayoutParams attr = w.getAttributes(); 140 attr.type = TYPE_KEYGUARD_DIALOG; 141 w.setAttributes(attr); 142 mAlertDialog.show(); 143 Settings.Secure.putIntForUser( 144 cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1, userId); 145 } else { 146 if (mAlertDialog != null) { 147 mAlertDialog.dismiss(); 148 mAlertDialog = null; 149 } 150 151 // Show a toast alerting the user to what's happening 152 final AccessibilityServiceInfo serviceInfo = getInfoForTargetService(); 153 if (serviceInfo == null) { 154 Slog.e(TAG, "Accessibility shortcut set to invalid service"); 155 return; 156 } 157 String toastMessageFormatString = mContext.getString(isServiceEnabled(serviceInfo) 158 ? R.string.accessibility_shortcut_disabling_service 159 : R.string.accessibility_shortcut_enabling_service); 160 String toastMessage = String.format(toastMessageFormatString, 161 serviceInfo.getResolveInfo() 162 .loadLabel(mContext.getPackageManager()).toString()); 163 Toast warningToast = mFrameworkObjectProvider.makeToastFromText( 164 mContext, toastMessage, Toast.LENGTH_LONG); 165 warningToast.getWindowParams().privateFlags |= 166 WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; 167 warningToast.show(); 168 169 mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext) 170 .performAccessibilityShortcut(); 171 } 172 } 173 174 private void updateShortcutEnabled() { 175 mIsShortcutEnabled = !TextUtils.isEmpty(getTargetServiceComponentNameString( 176 mContext, UserHandle.myUserId())); 177 } 178 179 private AlertDialog createShortcutWarningDialog(int userId) { 180 final AccessibilityServiceInfo serviceInfo = getInfoForTargetService(); 181 182 if (serviceInfo == null) { 183 return null; 184 } 185 186 final String warningMessage = String.format( 187 mContext.getString(R.string.accessibility_shortcut_toogle_warning), 188 serviceInfo.getResolveInfo().loadLabel(mContext.getPackageManager()).toString()); 189 final AlertDialog alertDialog = mFrameworkObjectProvider.getAlertDialogBuilder(mContext) 190 .setTitle(R.string.accessibility_shortcut_warning_dialog_title) 191 .setMessage(warningMessage) 192 .setCancelable(false) 193 .setPositiveButton(R.string.leave_accessibility_shortcut_on, null) 194 .setNegativeButton(R.string.disable_accessibility_shortcut, 195 (DialogInterface d, int which) -> { 196 Settings.Secure.putStringForUser(mContext.getContentResolver(), 197 Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "", 198 userId); 199 }) 200 .setOnCancelListener((DialogInterface d) -> { 201 // If canceled, treat as if the dialog has never been shown 202 Settings.Secure.putIntForUser(mContext.getContentResolver(), 203 Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId); 204 }) 205 .create(); 206 return alertDialog; 207 } 208 209 private AccessibilityServiceInfo getInfoForTargetService() { 210 final String currentShortcutServiceString = getTargetServiceComponentNameString( 211 mContext, UserHandle.myUserId()); 212 if (currentShortcutServiceString == null) { 213 return null; 214 } 215 AccessibilityManager accessibilityManager = 216 mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext); 217 return accessibilityManager.getInstalledServiceInfoWithComponentName( 218 ComponentName.unflattenFromString(currentShortcutServiceString)); 219 } 220 221 private boolean isServiceEnabled(AccessibilityServiceInfo serviceInfo) { 222 AccessibilityManager accessibilityManager = 223 mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext); 224 return accessibilityManager.getEnabledAccessibilityServiceList( 225 AccessibilityServiceInfo.FEEDBACK_ALL_MASK).contains(serviceInfo); 226 } 227 228 // Class to allow mocking of static framework calls 229 public static class FrameworkObjectProvider { 230 public AccessibilityManager getAccessibilityManagerInstance(Context context) { 231 return AccessibilityManager.getInstance(context); 232 } 233 234 public AlertDialog.Builder getAlertDialogBuilder(Context context) { 235 return new AlertDialog.Builder(context); 236 } 237 238 public Toast makeToastFromText(Context context, CharSequence charSequence, int duration) { 239 return Toast.makeText(context, charSequence, duration); 240 } 241 } 242} 243