AccessibilityShortcutController.java revision 106fe732050f3d75a08c3bc48fdbcf84cac20b41
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.provider.Settings; 33import android.text.TextUtils; 34import android.util.Slog; 35import android.view.Window; 36import android.view.WindowManager; 37import android.view.accessibility.AccessibilityManager; 38 39import android.widget.Toast; 40import com.android.internal.R; 41 42import java.util.List; 43 44import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG; 45 46/** 47 * Class to help manage the accessibility shortcut 48 */ 49public class AccessibilityShortcutController { 50 private static final String TAG = "AccessibilityShortcutController"; 51 52 private final Context mContext; 53 private AlertDialog mAlertDialog; 54 private boolean mIsShortcutEnabled; 55 // Visible for testing 56 public FrameworkObjectProvider mFrameworkObjectProvider = new FrameworkObjectProvider(); 57 58 public static String getTargetServiceComponentNameString( 59 Context context, int userId) { 60 final String currentShortcutServiceId = Settings.Secure.getStringForUser( 61 context.getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, 62 userId); 63 if (currentShortcutServiceId != null) { 64 return currentShortcutServiceId; 65 } 66 return context.getString(R.string.config_defaultAccessibilityService); 67 } 68 69 public AccessibilityShortcutController(Context context, Handler handler) { 70 mContext = context; 71 72 // Keep track of state of shortcut 73 mContext.getContentResolver().registerContentObserver( 74 Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE), 75 false, 76 new ContentObserver(handler) { 77 @Override 78 public void onChange(boolean selfChange) { 79 onSettingsChanged(); 80 } 81 }, 82 UserHandle.USER_ALL); 83 updateShortcutEnabled(); 84 } 85 86 public boolean isAccessibilityShortcutAvailable() { 87 return mIsShortcutEnabled; 88 } 89 90 public void onSettingsChanged() { 91 updateShortcutEnabled(); 92 } 93 94 /** 95 * Called when the accessibility shortcut is activated 96 */ 97 public void performAccessibilityShortcut() { 98 Slog.d(TAG, "Accessibility shortcut activated"); 99 final ContentResolver cr = mContext.getContentResolver(); 100 final int userId = ActivityManager.getCurrentUser(); 101 final int dialogAlreadyShown = Settings.Secure.getIntForUser( 102 cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId); 103 final Ringtone tone = 104 RingtoneManager.getRingtone(mContext, Settings.System.DEFAULT_NOTIFICATION_URI); 105 if (tone != null) { 106 tone.setAudioAttributes(new AudioAttributes.Builder() 107 .setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT) 108 .build()); 109 tone.play(); 110 } 111 if (dialogAlreadyShown == 0) { 112 // The first time, we show a warning rather than toggle the service to give the user a 113 // chance to turn off this feature before stuff gets enabled. 114 mAlertDialog = createShortcutWarningDialog(userId); 115 if (mAlertDialog == null) { 116 return; 117 } 118 Window w = mAlertDialog.getWindow(); 119 WindowManager.LayoutParams attr = w.getAttributes(); 120 attr.type = TYPE_KEYGUARD_DIALOG; 121 w.setAttributes(attr); 122 mAlertDialog.show(); 123 Settings.Secure.putIntForUser( 124 cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1, userId); 125 } else { 126 if (mAlertDialog != null) { 127 mAlertDialog.dismiss(); 128 mAlertDialog = null; 129 } 130 131 // Show a toast alerting the user to what's happening 132 final AccessibilityServiceInfo serviceInfo = getInfoForTargetService(); 133 if (serviceInfo == null) { 134 Slog.e(TAG, "Accessibility shortcut set to invalid service"); 135 return; 136 } 137 String toastMessageFormatString = mContext.getString(isServiceEnabled(serviceInfo) 138 ? R.string.accessibility_shortcut_disabling_service 139 : R.string.accessibility_shortcut_enabling_service); 140 String toastMessage = String.format(toastMessageFormatString, 141 serviceInfo.getResolveInfo() 142 .loadLabel(mContext.getPackageManager()).toString()); 143 mFrameworkObjectProvider.makeToastFromText(mContext, toastMessage, Toast.LENGTH_LONG) 144 .show(); 145 146 mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext) 147 .performAccessibilityShortcut(); 148 } 149 } 150 151 private void updateShortcutEnabled() { 152 mIsShortcutEnabled = !TextUtils.isEmpty(getTargetServiceComponentNameString( 153 mContext, UserHandle.myUserId())); 154 } 155 156 private AlertDialog createShortcutWarningDialog(int userId) { 157 final AccessibilityServiceInfo serviceInfo = getInfoForTargetService(); 158 159 if (serviceInfo == null) { 160 return null; 161 } 162 163 final String warningMessage = String.format( 164 mContext.getString(R.string.accessibility_shortcut_toogle_warning), 165 serviceInfo.getResolveInfo().loadLabel(mContext.getPackageManager()).toString()); 166 final AlertDialog alertDialog = mFrameworkObjectProvider.getAlertDialogBuilder(mContext) 167 .setTitle(R.string.accessibility_shortcut_warning_dialog_title) 168 .setMessage(warningMessage) 169 .setCancelable(false) 170 .setPositiveButton(R.string.leave_accessibility_shortcut_on, null) 171 .setNegativeButton(R.string.disable_accessibility_shortcut, 172 (DialogInterface d, int which) -> { 173 Settings.Secure.putStringForUser(mContext.getContentResolver(), 174 Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "", 175 userId); 176 }) 177 .setOnCancelListener((DialogInterface d) -> { 178 // If canceled, treat as if the dialog has never been shown 179 Settings.Secure.putIntForUser(mContext.getContentResolver(), 180 Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId); 181 }) 182 .create(); 183 return alertDialog; 184 } 185 186 private AccessibilityServiceInfo getInfoForTargetService() { 187 final String currentShortcutServiceString = getTargetServiceComponentNameString( 188 mContext, UserHandle.myUserId()); 189 if (currentShortcutServiceString == null) { 190 return null; 191 } 192 AccessibilityManager accessibilityManager = 193 mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext); 194 return accessibilityManager.getInstalledServiceInfoWithComponentName( 195 ComponentName.unflattenFromString(currentShortcutServiceString)); 196 } 197 198 private boolean isServiceEnabled(AccessibilityServiceInfo serviceInfo) { 199 AccessibilityManager accessibilityManager = 200 mFrameworkObjectProvider.getAccessibilityManagerInstance(mContext); 201 return accessibilityManager.getEnabledAccessibilityServiceList( 202 AccessibilityServiceInfo.FEEDBACK_ALL_MASK).contains(serviceInfo); 203 } 204 205 // Class to allow mocking of static framework calls 206 public static class FrameworkObjectProvider { 207 public AccessibilityManager getAccessibilityManagerInstance(Context context) { 208 return AccessibilityManager.getInstance(context); 209 } 210 211 public AlertDialog.Builder getAlertDialogBuilder(Context context) { 212 return new AlertDialog.Builder(context); 213 } 214 215 public Toast makeToastFromText(Context context, CharSequence charSequence, int duration) { 216 return Toast.makeText(context, charSequence, duration); 217 } 218 } 219} 220