1 /*
2  * Copyright (C) 2024 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 
17 package com.android.settings.accessibility.shortcuts;
18 
19 import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE;
20 
21 import android.accessibilityservice.AccessibilityServiceInfo;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.os.UserHandle;
25 import android.service.quicksettings.TileService;
26 import android.util.ArraySet;
27 import android.view.accessibility.AccessibilityManager;
28 import android.view.accessibility.Flags;
29 
30 import androidx.annotation.NonNull;
31 import androidx.preference.Preference;
32 import androidx.preference.PreferenceScreen;
33 
34 import com.android.internal.accessibility.common.ShortcutConstants;
35 import com.android.internal.accessibility.util.AccessibilityUtils;
36 import com.android.settings.R;
37 import com.android.settings.accessibility.AccessibilityUtil;
38 import com.android.settingslib.utils.StringUtil;
39 
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Set;
43 
44 /**
45  * A controller handles displaying the quick settings shortcut option preference and
46  * configuring the shortcut.
47  */
48 public class QuickSettingsShortcutOptionController extends ShortcutOptionPreferenceController {
QuickSettingsShortcutOptionController( @onNull Context context, @NonNull String preferenceKey)49     public QuickSettingsShortcutOptionController(
50             @NonNull Context context, @NonNull String preferenceKey) {
51         super(context, preferenceKey);
52     }
53 
54     @ShortcutConstants.UserShortcutType
55     @Override
getShortcutType()56     protected int getShortcutType() {
57         return ShortcutConstants.UserShortcutType.QUICK_SETTINGS;
58     }
59 
60     @Override
displayPreference(@onNull PreferenceScreen screen)61     public void displayPreference(@NonNull PreferenceScreen screen) {
62         super.displayPreference(screen);
63         final Preference preference = screen.findPreference(getPreferenceKey());
64         if (preference instanceof ShortcutOptionPreference shortcutOptionPreference) {
65             shortcutOptionPreference.setTitle(
66                     R.string.accessibility_shortcut_edit_dialog_title_quick_settings);
67             shortcutOptionPreference.setIntroImageResId(
68                     R.drawable.accessibility_shortcut_type_quick_settings);
69         }
70     }
71 
72     @Override
getSummary()73     public CharSequence getSummary() {
74         int numFingers = AccessibilityUtil.isTouchExploreEnabled(mContext) ? 2 : 1;
75         return StringUtil.getIcuPluralsString(
76                 mContext,
77                 numFingers,
78                 isInSetupWizard()
79                         ? R.string.accessibility_shortcut_edit_dialog_summary_quick_settings_suw
80                         : R.string.accessibility_shortcut_edit_dialog_summary_quick_settings);
81     }
82 
83     @Override
isShortcutAvailable()84     protected boolean isShortcutAvailable() {
85         return Flags.a11yQsShortcut()
86                 && TileService.isQuickSettingsSupported()
87                 && allTargetsHasQsTile()
88                 && allTargetsHasValidQsTileUseCase();
89     }
90 
allTargetsHasQsTile()91     private boolean allTargetsHasQsTile() {
92         AccessibilityManager accessibilityManager = mContext.getSystemService(
93                 AccessibilityManager.class);
94         if (accessibilityManager == null) {
95             return false;
96         }
97 
98         Map<ComponentName, ComponentName> a11yFeatureToTileMap =
99                 accessibilityManager.getA11yFeatureToTileMap(UserHandle.myUserId());
100         if (a11yFeatureToTileMap.isEmpty()) {
101             return false;
102         }
103         for (String target : getShortcutTargets()) {
104             ComponentName targetComponentName = ComponentName.unflattenFromString(target);
105             if (targetComponentName == null
106                     || !a11yFeatureToTileMap.containsKey(targetComponentName)) {
107                 return false;
108             }
109         }
110 
111         return true;
112     }
113 
114     /**
115      * Returns true if all targets have valid QS Tile shortcut use case.
116      *
117      * <p>
118      * Note: We don't want to promote the qs option in the edit shortcuts screen for
119      * a standard AccessibilityService, because the Tile is provided by the owner of the
120      * AccessibilityService, and they don't have control to enable the A11yService themselves
121      * which makes the TileService not acting as the other a11y shortcut like FAB where the user
122      * can turn on/off the feature by toggling the shortcut.
123      *
124      * A standard AccessibilityService normally won't create a TileService because the
125      * above mentioned reason. In any case where the standard AccessibilityService provides a tile,
126      * we'll hide it from the Setting's UI.
127      * </p>
128      */
allTargetsHasValidQsTileUseCase()129     private boolean allTargetsHasValidQsTileUseCase() {
130         AccessibilityManager accessibilityManager = mContext.getSystemService(
131                 AccessibilityManager.class);
132         if (accessibilityManager == null) {
133             return false;
134         }
135 
136         List<AccessibilityServiceInfo> installedServices =
137                 accessibilityManager.getInstalledAccessibilityServiceList();
138         final Set<String> standardA11yServices = new ArraySet<>();
139         for (AccessibilityServiceInfo serviceInfo : installedServices) {
140             if (AccessibilityUtils.getAccessibilityServiceFragmentType(serviceInfo)
141                     != INVISIBLE_TOGGLE) {
142                 standardA11yServices.add(serviceInfo.getComponentName().flattenToString());
143             }
144         }
145 
146         for (String target : getShortcutTargets()) {
147             if (standardA11yServices.contains(target)) {
148                 return false;
149             }
150         }
151 
152         return true;
153     }
154 }
155