1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.internal.accessibility.dialog; 17 18 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON; 19 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY; 20 import static android.view.accessibility.AccessibilityManager.ShortcutType; 21 22 import static com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode; 23 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.createEnableDialogContentView; 24 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getInstalledTargets; 25 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets; 26 import static com.android.internal.accessibility.util.AccessibilityUtils.isUserSetupCompleted; 27 28 import android.annotation.Nullable; 29 import android.app.Activity; 30 import android.app.AlertDialog; 31 import android.content.Context; 32 import android.content.DialogInterface; 33 import android.content.res.TypedArray; 34 import android.os.Bundle; 35 import android.view.View; 36 import android.view.Window; 37 import android.widget.AdapterView; 38 39 import com.android.internal.R; 40 41 import java.util.ArrayList; 42 import java.util.List; 43 44 /** 45 * Activity used to display various targets related to accessibility service, accessibility 46 * activity or allowlisting feature for volume key shortcut. 47 */ 48 public class AccessibilityShortcutChooserActivity extends Activity { 49 @ShortcutType 50 private final int mShortcutType = ACCESSIBILITY_SHORTCUT_KEY; 51 private static final String KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE = 52 "accessibility_shortcut_menu_mode"; 53 private final List<AccessibilityTarget> mTargets = new ArrayList<>(); 54 private AlertDialog mMenuDialog; 55 private AlertDialog mPermissionDialog; 56 private ShortcutTargetAdapter mTargetAdapter; 57 58 @Override onCreate(@ullable Bundle savedInstanceState)59 protected void onCreate(@Nullable Bundle savedInstanceState) { 60 super.onCreate(savedInstanceState); 61 62 final TypedArray theme = getTheme().obtainStyledAttributes(android.R.styleable.Theme); 63 if (!theme.getBoolean(android.R.styleable.Theme_windowNoTitle, /* defValue= */ false)) { 64 requestWindowFeature(Window.FEATURE_NO_TITLE); 65 } 66 67 mTargets.addAll(getTargets(this, mShortcutType)); 68 mTargetAdapter = new ShortcutTargetAdapter(mTargets); 69 mMenuDialog = createMenuDialog(); 70 mMenuDialog.setOnShowListener(dialog -> updateDialogListeners()); 71 mMenuDialog.show(); 72 73 if (savedInstanceState != null) { 74 final int restoreShortcutMenuMode = 75 savedInstanceState.getInt(KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE, 76 ShortcutMenuMode.LAUNCH); 77 if (restoreShortcutMenuMode == ShortcutMenuMode.EDIT) { 78 onEditButtonClicked(); 79 } 80 } 81 } 82 83 @Override onDestroy()84 protected void onDestroy() { 85 mMenuDialog.setOnDismissListener(null); 86 mMenuDialog.dismiss(); 87 super.onDestroy(); 88 } 89 90 @Override onSaveInstanceState(Bundle outState)91 protected void onSaveInstanceState(Bundle outState) { 92 super.onSaveInstanceState(outState); 93 outState.putInt(KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE, mTargetAdapter.getShortcutMenuMode()); 94 } 95 onTargetSelected(AdapterView<?> parent, View view, int position, long id)96 private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) { 97 final AccessibilityTarget target = mTargets.get(position); 98 if (target instanceof AccessibilityServiceTarget 99 || target instanceof AccessibilityActivityTarget) { 100 if (sendRestrictedDialogIntentIfNeeded(target)) { 101 return; 102 } 103 } 104 105 target.onSelected(); 106 mMenuDialog.dismiss(); 107 } 108 onTargetChecked(AdapterView<?> parent, View view, int position, long id)109 private void onTargetChecked(AdapterView<?> parent, View view, int position, long id) { 110 final AccessibilityTarget target = mTargets.get(position); 111 112 if (!target.isShortcutEnabled()) { 113 if (target instanceof AccessibilityServiceTarget 114 || target instanceof AccessibilityActivityTarget) { 115 if (sendRestrictedDialogIntentIfNeeded(target)) { 116 return; 117 } 118 } 119 120 if (target instanceof AccessibilityServiceTarget) { 121 showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target, 122 mTargetAdapter); 123 return; 124 } 125 } 126 127 target.onCheckedChanged(!target.isShortcutEnabled()); 128 mTargetAdapter.notifyDataSetChanged(); 129 } 130 131 /** 132 * Sends restricted dialog intent if the accessibility target is disallowed. 133 * 134 * @return true if sends restricted dialog intent, otherwise false. 135 */ sendRestrictedDialogIntentIfNeeded(AccessibilityTarget target)136 private boolean sendRestrictedDialogIntentIfNeeded(AccessibilityTarget target) { 137 if (AccessibilityTargetHelper.isAccessibilityTargetAllowed(this, 138 target.getComponentName().getPackageName(), target.getUid())) { 139 return false; 140 } 141 142 AccessibilityTargetHelper.sendRestrictedDialogIntent(this, 143 target.getComponentName().getPackageName(), target.getUid()); 144 return true; 145 } 146 showPermissionDialogIfNeeded(Context context, AccessibilityServiceTarget serviceTarget, ShortcutTargetAdapter targetAdapter)147 private void showPermissionDialogIfNeeded(Context context, 148 AccessibilityServiceTarget serviceTarget, ShortcutTargetAdapter targetAdapter) { 149 if (mPermissionDialog != null) { 150 return; 151 } 152 153 mPermissionDialog = new AlertDialog.Builder(context) 154 .setView(createEnableDialogContentView(context, serviceTarget, 155 v -> { 156 mPermissionDialog.dismiss(); 157 targetAdapter.notifyDataSetChanged(); 158 }, 159 v -> mPermissionDialog.dismiss())) 160 .setOnDismissListener(dialog -> mPermissionDialog = null) 161 .create(); 162 mPermissionDialog.show(); 163 } 164 onDoneButtonClicked()165 private void onDoneButtonClicked() { 166 mTargets.clear(); 167 mTargets.addAll(getTargets(this, mShortcutType)); 168 if (mTargets.isEmpty()) { 169 mMenuDialog.dismiss(); 170 return; 171 } 172 173 mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH); 174 mTargetAdapter.notifyDataSetChanged(); 175 176 mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText( 177 getString(R.string.edit_accessibility_shortcut_menu_button)); 178 179 updateDialogListeners(); 180 } 181 onEditButtonClicked()182 private void onEditButtonClicked() { 183 mTargets.clear(); 184 mTargets.addAll(getInstalledTargets(this, mShortcutType)); 185 mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.EDIT); 186 mTargetAdapter.notifyDataSetChanged(); 187 188 mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText( 189 getString(R.string.done_accessibility_shortcut_menu_button)); 190 191 updateDialogListeners(); 192 } 193 updateDialogListeners()194 private void updateDialogListeners() { 195 final boolean isEditMenuMode = 196 mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT; 197 final int selectDialogTitleId = R.string.accessibility_select_shortcut_menu_title; 198 final int editDialogTitleId = 199 mShortcutType == ACCESSIBILITY_BUTTON 200 ? R.string.accessibility_edit_shortcut_menu_button_title 201 : R.string.accessibility_edit_shortcut_menu_volume_title; 202 203 mMenuDialog.setTitle(getString(isEditMenuMode ? editDialogTitleId : selectDialogTitleId)); 204 mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener( 205 isEditMenuMode ? view -> onDoneButtonClicked() : view -> onEditButtonClicked()); 206 mMenuDialog.getListView().setOnItemClickListener( 207 isEditMenuMode ? this::onTargetChecked : this::onTargetSelected); 208 } 209 createMenuDialog()210 private AlertDialog createMenuDialog() { 211 final String dialogTitle = 212 getString(R.string.accessibility_select_shortcut_menu_title); 213 214 final AlertDialog.Builder builder = new AlertDialog.Builder(this) 215 .setTitle(dialogTitle) 216 .setAdapter(mTargetAdapter, /* listener= */ null) 217 .setOnDismissListener(dialog -> finish()); 218 219 if (isUserSetupCompleted(this)) { 220 final String positiveButtonText = 221 getString(R.string.edit_accessibility_shortcut_menu_button); 222 builder.setPositiveButton(positiveButtonText, /* listener= */ null); 223 } 224 225 return builder.create(); 226 } 227 } 228