• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.gestures;
18 
19 import static android.os.UserHandle.USER_CURRENT;
20 import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
21 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
22 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
23 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
24 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
25 
26 import android.app.settings.SettingsEnums;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.SharedPreferences;
30 import android.content.om.IOverlayManager;
31 import android.content.om.OverlayInfo;
32 import android.content.res.Resources;
33 import android.os.Bundle;
34 import android.os.RemoteException;
35 import android.os.ServiceManager;
36 import android.provider.Settings;
37 import android.text.TextUtils;
38 import android.view.accessibility.AccessibilityManager;
39 
40 import androidx.annotation.Nullable;
41 import androidx.annotation.VisibleForTesting;
42 import androidx.preference.PreferenceScreen;
43 
44 import com.android.internal.accessibility.common.ShortcutConstants;
45 import com.android.settings.R;
46 import com.android.settings.accessibility.AccessibilityShortcutsTutorial;
47 import com.android.settings.core.BasePreferenceController;
48 import com.android.settings.core.PreferenceControllerListHelper;
49 import com.android.settings.core.SubSettingLauncher;
50 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
51 import com.android.settings.overlay.FeatureFactory;
52 import com.android.settings.search.BaseSearchIndexProvider;
53 import com.android.settings.support.actionbar.HelpResourceProvider;
54 import com.android.settings.utils.CandidateInfoExtra;
55 import com.android.settings.widget.RadioButtonPickerFragment;
56 import com.android.settingslib.search.SearchIndexable;
57 import com.android.settingslib.search.SearchIndexableRaw;
58 import com.android.settingslib.widget.CandidateInfo;
59 import com.android.settingslib.widget.IllustrationPreference;
60 import com.android.settingslib.widget.SelectorWithWidgetPreference;
61 
62 import java.util.ArrayList;
63 import java.util.List;
64 
65 @SearchIndexable
66 public class SystemNavigationGestureSettings extends RadioButtonPickerFragment implements
67         HelpResourceProvider {
68 
69     @VisibleForTesting
70     static final String KEY_SYSTEM_NAV_3BUTTONS = "system_nav_3buttons";
71     @VisibleForTesting
72     static final String KEY_SYSTEM_NAV_2BUTTONS = "system_nav_2buttons";
73     @VisibleForTesting
74     static final String KEY_SYSTEM_NAV_GESTURAL = "system_nav_gestural";
75 
76     public static final String PREF_KEY_SUGGESTION_COMPLETE =
77             "pref_system_navigation_suggestion_complete";
78 
79     private static final String KEY_SHOW_A11Y_TUTORIAL_DIALOG = "show_a11y_tutorial_dialog_bool";
80 
81     private static final int MIN_LARGESCREEN_WIDTH_DP = 600;
82 
83     private boolean mA11yTutorialDialogShown = false;
84 
85     private IOverlayManager mOverlayManager;
86 
87     private IllustrationPreference mVideoPreference;
88 
89     @Override
onCreate(@ullable Bundle savedInstanceState)90     public void onCreate(@Nullable Bundle savedInstanceState) {
91         super.onCreate(savedInstanceState);
92         if (savedInstanceState != null) {
93             mA11yTutorialDialogShown =
94                     savedInstanceState.getBoolean(KEY_SHOW_A11Y_TUTORIAL_DIALOG, false);
95             if (mA11yTutorialDialogShown) {
96                 AccessibilityShortcutsTutorial.showGestureNavigationTutorialDialog(
97                         getContext(), dialog -> mA11yTutorialDialogShown = false);
98             }
99         }
100     }
101 
102     @Override
onSaveInstanceState(Bundle outState)103     public void onSaveInstanceState(Bundle outState) {
104         outState.putBoolean(KEY_SHOW_A11Y_TUTORIAL_DIALOG, mA11yTutorialDialogShown);
105         super.onSaveInstanceState(outState);
106     }
107 
108     @Override
onAttach(Context context)109     public void onAttach(Context context) {
110         super.onAttach(context);
111 
112         SuggestionFeatureProvider suggestionFeatureProvider =
113                 FeatureFactory.getFeatureFactory().getSuggestionFeatureProvider();
114         SharedPreferences prefs = suggestionFeatureProvider.getSharedPrefs(context);
115         prefs.edit().putBoolean(PREF_KEY_SUGGESTION_COMPLETE, true).apply();
116 
117         mOverlayManager = IOverlayManager.Stub.asInterface(
118                 ServiceManager.getService(Context.OVERLAY_SERVICE));
119 
120         mVideoPreference = new IllustrationPreference(context);
121         Context windowContext = context.createWindowContext(TYPE_APPLICATION_OVERLAY, null);
122         if (windowContext.getResources()
123                 .getConfiguration().smallestScreenWidthDp >= MIN_LARGESCREEN_WIDTH_DP) {
124             mVideoPreference.applyDynamicColor();
125         }
126         setIllustrationVideo(mVideoPreference, getDefaultKey());
127 
128         migrateOverlaySensitivityToSettings(context, mOverlayManager);
129     }
130 
131     @Override
getMetricsCategory()132     public int getMetricsCategory() {
133         return SettingsEnums.SETTINGS_GESTURE_SWIPE_UP;
134     }
135 
136     @Override
updateCandidates()137     public void updateCandidates() {
138         final String defaultKey = getDefaultKey();
139         final String systemDefaultKey = getSystemDefaultKey();
140         final PreferenceScreen screen = getPreferenceScreen();
141         screen.removeAll();
142         screen.addPreference(mVideoPreference);
143 
144         addPreferencesFromResource(getPreferenceScreenResId());
145         final List<BasePreferenceController> preferenceControllers = PreferenceControllerListHelper
146                 .getPreferenceControllersFromXml(getContext(), getPreferenceScreenResId());
147         preferenceControllers.forEach(controller -> {
148             controller.updateState(findPreference(controller.getPreferenceKey()));
149             controller.displayPreference(screen);
150         });
151 
152         final List<? extends CandidateInfo> candidateList = getCandidates();
153         if (candidateList == null) {
154             return;
155         }
156         for (CandidateInfo info : candidateList) {
157             SelectorWithWidgetPreference pref = new SelectorWithWidgetPreference(getPrefContext());
158             bindPreference(pref, info.getKey(), info, defaultKey);
159             bindPreferenceExtra(pref, info.getKey(), info, defaultKey, systemDefaultKey);
160             screen.addPreference(pref);
161         }
162         mayCheckOnlyRadioButton();
163     }
164 
165     @Override
bindPreferenceExtra(SelectorWithWidgetPreference pref, String key, CandidateInfo info, String defaultKey, String systemDefaultKey)166     public void bindPreferenceExtra(SelectorWithWidgetPreference pref,
167             String key, CandidateInfo info, String defaultKey, String systemDefaultKey) {
168         if (!(info instanceof CandidateInfoExtra)) {
169             return;
170         }
171 
172         pref.setSummary(((CandidateInfoExtra) info).loadSummary());
173 
174         if (KEY_SYSTEM_NAV_GESTURAL.equals(info.getKey())) {
175             pref.setExtraWidgetOnClickListener((v) -> startActivity(new Intent(
176                     GestureNavigationSettingsFragment.GESTURE_NAVIGATION_SETTINGS)
177                     .setPackage(getContext().getPackageName())));
178         }
179 
180         if ((KEY_SYSTEM_NAV_2BUTTONS.equals(info.getKey())
181                 || KEY_SYSTEM_NAV_3BUTTONS.equals(info.getKey()))
182                 // Don't add the settings button if that page will be blank.
183                 && !PreferenceControllerListHelper.areAllPreferencesUnavailable(
184                         getContext(), getPreferenceManager(), R.xml.button_navigation_settings)) {
185             pref.setExtraWidgetOnClickListener((v) ->
186                     new SubSettingLauncher(getContext())
187                             .setDestination(ButtonNavigationSettingsFragment.class.getName())
188                             .setSourceMetricsCategory(SettingsEnums.SETTINGS_GESTURE_SWIPE_UP)
189                             .launch());
190         }
191     }
192 
193     @Override
getPreferenceScreenResId()194     protected int getPreferenceScreenResId() {
195         return R.xml.system_navigation_gesture_settings;
196     }
197 
198     @Override
getCandidates()199     protected List<? extends CandidateInfo> getCandidates() {
200         final Context c = getContext();
201         List<CandidateInfoExtra> candidates = new ArrayList<>();
202 
203         if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
204                 NAV_BAR_MODE_GESTURAL_OVERLAY)) {
205             candidates.add(new CandidateInfoExtra(
206                     c.getText(R.string.edge_to_edge_navigation_title),
207                     c.getText(R.string.edge_to_edge_navigation_summary),
208                     KEY_SYSTEM_NAV_GESTURAL, true /* enabled */));
209         }
210         if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
211                 NAV_BAR_MODE_2BUTTON_OVERLAY)) {
212             candidates.add(new CandidateInfoExtra(
213                     c.getText(R.string.swipe_up_to_switch_apps_title),
214                     c.getText(R.string.swipe_up_to_switch_apps_summary),
215                     KEY_SYSTEM_NAV_2BUTTONS, true /* enabled */));
216         }
217         if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
218                 NAV_BAR_MODE_3BUTTON_OVERLAY)) {
219             candidates.add(new CandidateInfoExtra(
220                     c.getText(R.string.legacy_navigation_title),
221                     c.getText(R.string.legacy_navigation_summary),
222                     KEY_SYSTEM_NAV_3BUTTONS, true /* enabled */));
223         }
224 
225         return candidates;
226     }
227 
228     @Override
getDefaultKey()229     protected String getDefaultKey() {
230         return getCurrentSystemNavigationMode(getContext());
231     }
232 
233     @Override
setDefaultKey(String key)234     protected boolean setDefaultKey(String key) {
235         setCurrentSystemNavigationMode(mOverlayManager, key);
236         setIllustrationVideo(mVideoPreference, key);
237         if (!android.provider.Flags.a11yStandaloneGestureEnabled()) {
238             setGestureNavigationTutorialDialog(key);
239         }
240         return true;
241     }
242 
migrateOverlaySensitivityToSettings(Context context, IOverlayManager overlayManager)243     static void migrateOverlaySensitivityToSettings(Context context,
244             IOverlayManager overlayManager) {
245         if (!SystemNavigationPreferenceController.isGestureNavigationEnabled(context)) {
246             return;
247         }
248 
249         OverlayInfo info = null;
250         try {
251             info = overlayManager.getOverlayInfo(NAV_BAR_MODE_GESTURAL_OVERLAY, USER_CURRENT);
252         } catch (RemoteException e) { /* Do nothing */ }
253         if (info != null && !info.isEnabled()) {
254             // Enable the default gesture nav overlay. Back sensitivity for left and right are
255             // stored as separate settings values, and other gesture nav overlays are deprecated.
256             setCurrentSystemNavigationMode(overlayManager, KEY_SYSTEM_NAV_GESTURAL);
257             Settings.Secure.putFloat(context.getContentResolver(),
258                     Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT, 1.0f);
259             Settings.Secure.putFloat(context.getContentResolver(),
260                     Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT, 1.0f);
261         }
262     }
263 
264     @VisibleForTesting
getCurrentSystemNavigationMode(Context context)265     static String getCurrentSystemNavigationMode(Context context) {
266         if (SystemNavigationPreferenceController.isGestureNavigationEnabled(context)) {
267             return KEY_SYSTEM_NAV_GESTURAL;
268         } else if (SystemNavigationPreferenceController.is2ButtonNavigationEnabled(context)) {
269             return KEY_SYSTEM_NAV_2BUTTONS;
270         } else {
271             return KEY_SYSTEM_NAV_3BUTTONS;
272         }
273     }
274 
275     @VisibleForTesting
setCurrentSystemNavigationMode(IOverlayManager overlayManager, String key)276     static void setCurrentSystemNavigationMode(IOverlayManager overlayManager, String key) {
277         String overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
278         switch (key) {
279             case KEY_SYSTEM_NAV_GESTURAL:
280                 overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
281                 break;
282             case KEY_SYSTEM_NAV_2BUTTONS:
283                 overlayPackage = NAV_BAR_MODE_2BUTTON_OVERLAY;
284                 break;
285             case KEY_SYSTEM_NAV_3BUTTONS:
286                 overlayPackage = NAV_BAR_MODE_3BUTTON_OVERLAY;
287                 break;
288         }
289 
290         try {
291             overlayManager.setEnabledExclusiveInCategory(overlayPackage, USER_CURRENT);
292         } catch (RemoteException e) {
293             throw e.rethrowFromSystemServer();
294         }
295     }
296 
setIllustrationVideo(IllustrationPreference videoPref, String systemNavKey)297     private void setIllustrationVideo(IllustrationPreference videoPref,
298             String systemNavKey) {
299         switch (systemNavKey) {
300             case KEY_SYSTEM_NAV_GESTURAL:
301                 videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_fully_gestural);
302                 break;
303             case KEY_SYSTEM_NAV_2BUTTONS:
304                 videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_2_button);
305                 break;
306             case KEY_SYSTEM_NAV_3BUTTONS:
307                 videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_3_button);
308                 break;
309         }
310     }
311 
setGestureNavigationTutorialDialog(String systemNavKey)312     private void setGestureNavigationTutorialDialog(String systemNavKey) {
313         if (TextUtils.equals(KEY_SYSTEM_NAV_GESTURAL, systemNavKey)
314                 && !isAccessibilityFloatingMenuEnabled()
315                 && (isAnyServiceSupportAccessibilityButton() || isNavBarMagnificationEnabled())) {
316             mA11yTutorialDialogShown = true;
317             AccessibilityShortcutsTutorial.showGestureNavigationTutorialDialog(getContext(),
318                     dialog -> mA11yTutorialDialogShown = false);
319         } else {
320             mA11yTutorialDialogShown = false;
321         }
322     }
323 
isAnyServiceSupportAccessibilityButton()324     private boolean isAnyServiceSupportAccessibilityButton() {
325         final AccessibilityManager ams = getContext().getSystemService(AccessibilityManager.class);
326         final List<String> targets = ams.getAccessibilityShortcutTargets(
327                 ShortcutConstants.UserShortcutType.SOFTWARE);
328         return !targets.isEmpty();
329     }
330 
isNavBarMagnificationEnabled()331     private boolean isNavBarMagnificationEnabled() {
332         return Settings.Secure.getInt(getContext().getContentResolver(),
333                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1;
334     }
335 
isAccessibilityFloatingMenuEnabled()336     private boolean isAccessibilityFloatingMenuEnabled() {
337         return Settings.Secure.getInt(getContext().getContentResolver(),
338                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE, /* def= */ -1)
339                 == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
340     }
341 
342     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
343             new BaseSearchIndexProvider(R.xml.system_navigation_gesture_settings) {
344 
345                 @Override
346                 protected boolean isPageSearchEnabled(Context context) {
347                     return SystemNavigationPreferenceController.isGestureAvailable(context);
348                 }
349 
350                 @Override
351                 public List<SearchIndexableRaw> getRawDataToIndex(Context context,
352                         boolean enabled) {
353                     final Resources res = context.getResources();
354                     final List<SearchIndexableRaw> result = new ArrayList<>();
355 
356                     if (SystemNavigationPreferenceController.isOverlayPackageAvailable(context,
357                             NAV_BAR_MODE_GESTURAL_OVERLAY)) {
358                         SearchIndexableRaw data = new SearchIndexableRaw(context);
359                         data.title = res.getString(R.string.edge_to_edge_navigation_title);
360                         data.key = KEY_SYSTEM_NAV_GESTURAL;
361                         result.add(data);
362                     }
363 
364                     if (SystemNavigationPreferenceController.isOverlayPackageAvailable(context,
365                             NAV_BAR_MODE_2BUTTON_OVERLAY)) {
366                         SearchIndexableRaw data = new SearchIndexableRaw(context);
367                         data.title = res.getString(R.string.swipe_up_to_switch_apps_title);
368                         data.key = KEY_SYSTEM_NAV_2BUTTONS;
369                         result.add(data);
370                     }
371 
372                     if (SystemNavigationPreferenceController.isOverlayPackageAvailable(context,
373                             NAV_BAR_MODE_3BUTTON_OVERLAY)) {
374                         SearchIndexableRaw data = new SearchIndexableRaw(context);
375                         data.title = res.getString(R.string.legacy_navigation_title);
376                         data.key = KEY_SYSTEM_NAV_3BUTTONS;
377                         data.keywords = res.getString(R.string.keywords_3_button_navigation);
378                         result.add(data);
379                     }
380 
381                     return result;
382                 }
383             };
384 
385     // From HelpResourceProvider
386     @Override
getHelpResource()387     public int getHelpResource() {
388         // TODO(b/146001201): Replace with system navigation help page when ready.
389         return R.string.help_uri_default;
390     }
391 }
392