• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode;
19 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
20 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE;
21 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getInstalledTargets;
22 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
23 import static com.android.internal.accessibility.util.AccessibilityUtils.isUserSetupCompleted;
24 
25 import android.annotation.Nullable;
26 import android.app.Activity;
27 import android.app.AlertDialog;
28 import android.app.Dialog;
29 import android.app.KeyguardManager;
30 import android.content.Context;
31 import android.content.DialogInterface;
32 import android.content.res.TypedArray;
33 import android.os.Bundle;
34 import android.view.View;
35 import android.view.Window;
36 import android.view.WindowManager;
37 import android.view.accessibility.AccessibilityManager;
38 import android.widget.AdapterView;
39 
40 import com.android.internal.R;
41 import com.android.internal.annotations.VisibleForTesting;
42 
43 import java.util.ArrayList;
44 import java.util.List;
45 
46 /**
47  * Activity used to display various targets related to accessibility service, accessibility
48  * activity or allowlisting feature for volume key shortcut.
49  */
50 public class AccessibilityShortcutChooserActivity extends Activity {
51     @UserShortcutType
52     private final int mShortcutType = HARDWARE;
53     private static final String KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE =
54             "accessibility_shortcut_menu_mode";
55     private final List<AccessibilityTarget> mTargets = new ArrayList<>();
56     private AlertDialog mMenuDialog;
57     private Dialog mPermissionDialog;
58     private ShortcutTargetAdapter mTargetAdapter;
59 
60     @Override
onCreate(@ullable Bundle savedInstanceState)61     protected void onCreate(@Nullable Bundle savedInstanceState) {
62         super.onCreate(savedInstanceState);
63 
64         final TypedArray theme = getTheme().obtainStyledAttributes(android.R.styleable.Theme);
65         if (!theme.getBoolean(android.R.styleable.Theme_windowNoTitle, /* defValue= */ false)) {
66             requestWindowFeature(Window.FEATURE_NO_TITLE);
67         }
68 
69         mTargets.addAll(getTargets(this, mShortcutType));
70         mTargetAdapter = new ShortcutTargetAdapter(mTargets);
71         mMenuDialog = createMenuDialog();
72         mMenuDialog.setOnShowListener(dialog -> updateDialogListeners());
73         mMenuDialog.show();
74 
75         if (savedInstanceState != null) {
76             final int restoreShortcutMenuMode =
77                     savedInstanceState.getInt(KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE,
78                             ShortcutMenuMode.LAUNCH);
79             if (restoreShortcutMenuMode == ShortcutMenuMode.EDIT) {
80                 onEditButtonClicked();
81             }
82         }
83     }
84 
85     @Override
onDestroy()86     protected void onDestroy() {
87         mMenuDialog.setOnDismissListener(null);
88         mMenuDialog.dismiss();
89         super.onDestroy();
90     }
91 
92     @Override
onSaveInstanceState(Bundle outState)93     protected void onSaveInstanceState(Bundle outState) {
94         super.onSaveInstanceState(outState);
95         outState.putInt(KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE, mTargetAdapter.getShortcutMenuMode());
96     }
97 
onTargetSelected(AdapterView<?> parent, View view, int position, long id)98     private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) {
99         final AccessibilityTarget target = mTargets.get(position);
100         if (target instanceof AccessibilityServiceTarget
101                 || target instanceof AccessibilityActivityTarget) {
102             if (sendRestrictedDialogIntentIfNeeded(target)) {
103                 return;
104             }
105         }
106 
107         target.onSelected();
108         mMenuDialog.dismiss();
109     }
110 
onTargetChecked(AdapterView<?> parent, View view, int position, long id)111     private void onTargetChecked(AdapterView<?> parent, View view, int position, long id) {
112         final AccessibilityTarget target = mTargets.get(position);
113 
114         if (target instanceof AccessibilityServiceTarget serviceTarget) {
115             if (sendRestrictedDialogIntentIfNeeded(target)) {
116                 return;
117             }
118             final AccessibilityManager am = getSystemService(AccessibilityManager.class);
119             if (am.isAccessibilityServiceWarningRequired(
120                     serviceTarget.getAccessibilityServiceInfo())) {
121                 showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target,
122                         position, mTargetAdapter);
123                 return;
124             }
125         }
126         if (target instanceof AccessibilityActivityTarget activityTarget) {
127             if (!activityTarget.isShortcutEnabled()
128                     && sendRestrictedDialogIntentIfNeeded(activityTarget)) {
129                 return;
130             }
131         }
132 
133         target.onCheckedChanged(!target.isShortcutEnabled());
134         mTargetAdapter.notifyDataSetChanged();
135     }
136 
137     /**
138      * Sends restricted dialog intent if the accessibility target is disallowed.
139      *
140      * @return true if sends restricted dialog intent, otherwise false.
141      */
sendRestrictedDialogIntentIfNeeded(AccessibilityTarget target)142     private boolean sendRestrictedDialogIntentIfNeeded(AccessibilityTarget target) {
143         if (AccessibilityTargetHelper.isAccessibilityTargetAllowed(this,
144                 target.getComponentName().getPackageName(), target.getUid())) {
145             return false;
146         }
147 
148         AccessibilityTargetHelper.sendRestrictedDialogIntent(this,
149                 target.getComponentName().getPackageName(), target.getUid());
150         return true;
151     }
152 
showPermissionDialogIfNeeded(Context context, AccessibilityServiceTarget serviceTarget, int position, ShortcutTargetAdapter targetAdapter)153     private void showPermissionDialogIfNeeded(Context context,
154             AccessibilityServiceTarget serviceTarget, int position,
155             ShortcutTargetAdapter targetAdapter) {
156         if (mPermissionDialog != null) {
157             return;
158         }
159 
160         mPermissionDialog = AccessibilityServiceWarning
161                 .createAccessibilityServiceWarningDialog(context,
162                         serviceTarget.getAccessibilityServiceInfo(),
163                         v -> {
164                             serviceTarget.onCheckedChanged(true);
165                             targetAdapter.notifyDataSetChanged();
166                             mPermissionDialog.dismiss();
167                         }, v -> {
168                             serviceTarget.onCheckedChanged(false);
169                             mPermissionDialog.dismiss();
170                         },
171                         v -> {
172                             mTargets.remove(position);
173                             context.getPackageManager().getPackageInstaller().uninstall(
174                                     serviceTarget.getComponentName().getPackageName(), null);
175                             targetAdapter.notifyDataSetChanged();
176                             mPermissionDialog.dismiss();
177                         });
178         mPermissionDialog.setOnDismissListener(dialog -> mPermissionDialog = null);
179         mPermissionDialog.show();
180     }
181 
onDoneButtonClicked()182     private void onDoneButtonClicked() {
183         mTargets.clear();
184         mTargets.addAll(getTargets(this, mShortcutType));
185         if (mTargets.isEmpty()) {
186             mMenuDialog.dismiss();
187             return;
188         }
189 
190         mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH);
191         mTargetAdapter.notifyDataSetChanged();
192 
193         mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
194                 getString(R.string.edit_accessibility_shortcut_menu_button));
195 
196         updateDialogListeners();
197     }
198 
onEditButtonClicked()199     private void onEditButtonClicked() {
200         mTargets.clear();
201         mTargets.addAll(getInstalledTargets(this, mShortcutType));
202         mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.EDIT);
203         mTargetAdapter.notifyDataSetChanged();
204 
205         mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
206                 getString(R.string.done_accessibility_shortcut_menu_button));
207 
208         updateDialogListeners();
209     }
210 
updateDialogListeners()211     private void updateDialogListeners() {
212         final boolean isEditMenuMode =
213                 mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT;
214         final int selectDialogTitleId = R.string.accessibility_select_shortcut_menu_title;
215         final int editDialogTitleId = R.string.accessibility_edit_shortcut_menu_volume_title;
216 
217         mMenuDialog.setTitle(getString(isEditMenuMode ? editDialogTitleId : selectDialogTitleId));
218         mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(
219                 isEditMenuMode ? view -> onDoneButtonClicked() : view -> onEditButtonClicked());
220         mMenuDialog.getListView().setOnItemClickListener(
221                 isEditMenuMode ? this::onTargetChecked : this::onTargetSelected);
222     }
223 
224     @VisibleForTesting
getMenuDialog()225     public AlertDialog getMenuDialog() {
226         return mMenuDialog;
227     }
228 
229     @VisibleForTesting
getPermissionDialog()230     public Dialog getPermissionDialog() {
231         return mPermissionDialog;
232     }
233 
createMenuDialog()234     private AlertDialog createMenuDialog() {
235         final String dialogTitle =
236                 getString(R.string.accessibility_select_shortcut_menu_title);
237 
238         final AlertDialog.Builder builder = new AlertDialog.Builder(this)
239                 .setTitle(dialogTitle)
240                 .setAdapter(mTargetAdapter, /* listener= */ null)
241                 .setOnDismissListener(dialog -> finish());
242 
243         boolean allowEditing = isUserSetupCompleted(this);
244         boolean showWhenLocked = false;
245         final KeyguardManager keyguardManager = getSystemService(KeyguardManager.class);
246         if (keyguardManager != null && keyguardManager.isKeyguardLocked()) {
247             allowEditing = false;
248             showWhenLocked = true;
249         }
250         if (allowEditing) {
251             final String positiveButtonText =
252                     getString(R.string.edit_accessibility_shortcut_menu_button);
253             builder.setPositiveButton(positiveButtonText, /* listener= */ null);
254         }
255 
256         final AlertDialog dialog = builder.create();
257         if (showWhenLocked) {
258             dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
259         }
260         return dialog;
261     }
262 }
263