• 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.settings.R;
45 import com.android.settings.accessibility.AccessibilityGestureNavigationTutorial;
46 import com.android.settings.core.SubSettingLauncher;
47 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
48 import com.android.settings.overlay.FeatureFactory;
49 import com.android.settings.search.BaseSearchIndexProvider;
50 import com.android.settings.support.actionbar.HelpResourceProvider;
51 import com.android.settings.utils.CandidateInfoExtra;
52 import com.android.settings.widget.RadioButtonPickerFragment;
53 import com.android.settingslib.search.SearchIndexable;
54 import com.android.settingslib.search.SearchIndexableRaw;
55 import com.android.settingslib.widget.CandidateInfo;
56 import com.android.settingslib.widget.IllustrationPreference;
57 import com.android.settingslib.widget.SelectorWithWidgetPreference;
58 
59 import java.util.ArrayList;
60 import java.util.List;
61 
62 @SearchIndexable
63 public class SystemNavigationGestureSettings extends RadioButtonPickerFragment implements
64         HelpResourceProvider {
65 
66     @VisibleForTesting
67     static final String KEY_SYSTEM_NAV_3BUTTONS = "system_nav_3buttons";
68     @VisibleForTesting
69     static final String KEY_SYSTEM_NAV_2BUTTONS = "system_nav_2buttons";
70     @VisibleForTesting
71     static final String KEY_SYSTEM_NAV_GESTURAL = "system_nav_gestural";
72 
73     public static final String PREF_KEY_SUGGESTION_COMPLETE =
74             "pref_system_navigation_suggestion_complete";
75 
76     private static final String KEY_SHOW_A11Y_TUTORIAL_DIALOG = "show_a11y_tutorial_dialog_bool";
77 
78     static final String LAUNCHER_PACKAGE_NAME = "com.google.android.apps.nexuslauncher";
79 
80     static final String ACTION_GESTURE_SANDBOX = "com.android.quickstep.action.GESTURE_SANDBOX";
81 
82     final Intent mLaunchSandboxIntent = new Intent(ACTION_GESTURE_SANDBOX)
83             .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
84             .putExtra("use_tutorial_menu", true)
85             .setPackage(LAUNCHER_PACKAGE_NAME);
86 
87     private static final int MIN_LARGESCREEN_WIDTH_DP = 600;
88 
89     private boolean mA11yTutorialDialogShown = false;
90 
91     private IOverlayManager mOverlayManager;
92 
93     private IllustrationPreference mVideoPreference;
94 
95     @Override
onCreate(@ullable Bundle savedInstanceState)96     public void onCreate(@Nullable Bundle savedInstanceState) {
97         super.onCreate(savedInstanceState);
98         if (savedInstanceState != null) {
99             mA11yTutorialDialogShown =
100                     savedInstanceState.getBoolean(KEY_SHOW_A11Y_TUTORIAL_DIALOG, false);
101             if (mA11yTutorialDialogShown) {
102                 AccessibilityGestureNavigationTutorial.showGestureNavigationTutorialDialog(
103                         getContext(), dialog -> mA11yTutorialDialogShown = false);
104             }
105         }
106     }
107 
108     @Override
onSaveInstanceState(Bundle outState)109     public void onSaveInstanceState(Bundle outState) {
110         outState.putBoolean(KEY_SHOW_A11Y_TUTORIAL_DIALOG, mA11yTutorialDialogShown);
111         super.onSaveInstanceState(outState);
112     }
113 
114     @Override
onAttach(Context context)115     public void onAttach(Context context) {
116         super.onAttach(context);
117 
118         SuggestionFeatureProvider suggestionFeatureProvider =
119                 FeatureFactory.getFactory(context).getSuggestionFeatureProvider();
120         SharedPreferences prefs = suggestionFeatureProvider.getSharedPrefs(context);
121         prefs.edit().putBoolean(PREF_KEY_SUGGESTION_COMPLETE, true).apply();
122 
123         mOverlayManager = IOverlayManager.Stub.asInterface(
124                 ServiceManager.getService(Context.OVERLAY_SERVICE));
125 
126         mVideoPreference = new IllustrationPreference(context);
127         Context windowContext = context.createWindowContext(TYPE_APPLICATION_OVERLAY, null);
128         if (windowContext.getResources()
129                 .getConfiguration().smallestScreenWidthDp >= MIN_LARGESCREEN_WIDTH_DP) {
130             mVideoPreference.applyDynamicColor();
131         }
132         setIllustrationVideo(mVideoPreference, getDefaultKey());
133         setIllustrationClickListener(mVideoPreference, getDefaultKey());
134 
135         migrateOverlaySensitivityToSettings(context, mOverlayManager);
136     }
137 
138     @Override
getMetricsCategory()139     public int getMetricsCategory() {
140         return SettingsEnums.SETTINGS_GESTURE_SWIPE_UP;
141     }
142 
143     @Override
updateCandidates()144     public void updateCandidates() {
145         final String defaultKey = getDefaultKey();
146         final String systemDefaultKey = getSystemDefaultKey();
147         final PreferenceScreen screen = getPreferenceScreen();
148         screen.removeAll();
149         screen.addPreference(mVideoPreference);
150 
151         final List<? extends CandidateInfo> candidateList = getCandidates();
152         if (candidateList == null) {
153             return;
154         }
155         for (CandidateInfo info : candidateList) {
156             SelectorWithWidgetPreference pref =
157                     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         }
178 
179         if (KEY_SYSTEM_NAV_2BUTTONS.equals(info.getKey()) || KEY_SYSTEM_NAV_3BUTTONS.equals(
180                 info.getKey())) {
181             pref.setExtraWidgetOnClickListener((v) ->
182                     new SubSettingLauncher(getContext())
183                             .setDestination(ButtonNavigationSettingsFragment.class.getName())
184                             .setSourceMetricsCategory(SettingsEnums.SETTINGS_GESTURE_SWIPE_UP)
185                             .launch());
186         }
187     }
188 
189     @Override
getPreferenceScreenResId()190     protected int getPreferenceScreenResId() {
191         return R.xml.system_navigation_gesture_settings;
192     }
193 
194     @Override
getCandidates()195     protected List<? extends CandidateInfo> getCandidates() {
196         final Context c = getContext();
197         List<CandidateInfoExtra> candidates = new ArrayList<>();
198 
199         if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
200                 NAV_BAR_MODE_GESTURAL_OVERLAY)) {
201             candidates.add(new CandidateInfoExtra(
202                     c.getText(R.string.edge_to_edge_navigation_title),
203                     c.getText(R.string.edge_to_edge_navigation_summary),
204                     KEY_SYSTEM_NAV_GESTURAL, true /* enabled */));
205         }
206         if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
207                 NAV_BAR_MODE_2BUTTON_OVERLAY)) {
208             candidates.add(new CandidateInfoExtra(
209                     c.getText(R.string.swipe_up_to_switch_apps_title),
210                     c.getText(R.string.swipe_up_to_switch_apps_summary),
211                     KEY_SYSTEM_NAV_2BUTTONS, true /* enabled */));
212         }
213         if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
214                 NAV_BAR_MODE_3BUTTON_OVERLAY)) {
215             candidates.add(new CandidateInfoExtra(
216                     c.getText(R.string.legacy_navigation_title),
217                     c.getText(R.string.legacy_navigation_summary),
218                     KEY_SYSTEM_NAV_3BUTTONS, true /* enabled */));
219         }
220 
221         return candidates;
222     }
223 
224     @Override
getDefaultKey()225     protected String getDefaultKey() {
226         return getCurrentSystemNavigationMode(getContext());
227     }
228 
229     @Override
setDefaultKey(String key)230     protected boolean setDefaultKey(String key) {
231         setCurrentSystemNavigationMode(mOverlayManager, key);
232         setIllustrationVideo(mVideoPreference, key);
233         setGestureNavigationTutorialDialog(key);
234         setIllustrationClickListener(mVideoPreference, key);
235         return true;
236     }
237 
isGestureTutorialAvailable()238     private boolean isGestureTutorialAvailable() {
239         Context context = getContext();
240         return context != null
241                 && mLaunchSandboxIntent.resolveActivity(context.getPackageManager()) != null;
242     }
243 
setIllustrationClickListener(IllustrationPreference videoPref, String systemNavKey)244     private void setIllustrationClickListener(IllustrationPreference videoPref,
245             String systemNavKey) {
246 
247         switch (systemNavKey) {
248             case KEY_SYSTEM_NAV_GESTURAL:
249                 if (isGestureTutorialAvailable()){
250                     videoPref.setOnPreferenceClickListener(preference -> {
251                         startActivity(mLaunchSandboxIntent);
252                         return true;
253                     });
254                 } else {
255                     videoPref.setOnPreferenceClickListener(null);
256                 }
257 
258                 break;
259             case KEY_SYSTEM_NAV_2BUTTONS:
260             case KEY_SYSTEM_NAV_3BUTTONS:
261             default:
262                 videoPref.setOnPreferenceClickListener(null);
263                 break;
264         }
265     }
266 
migrateOverlaySensitivityToSettings(Context context, IOverlayManager overlayManager)267     static void migrateOverlaySensitivityToSettings(Context context,
268             IOverlayManager overlayManager) {
269         if (!SystemNavigationPreferenceController.isGestureNavigationEnabled(context)) {
270             return;
271         }
272 
273         OverlayInfo info = null;
274         try {
275             info = overlayManager.getOverlayInfo(NAV_BAR_MODE_GESTURAL_OVERLAY, USER_CURRENT);
276         } catch (RemoteException e) { /* Do nothing */ }
277         if (info != null && !info.isEnabled()) {
278             // Enable the default gesture nav overlay. Back sensitivity for left and right are
279             // stored as separate settings values, and other gesture nav overlays are deprecated.
280             setCurrentSystemNavigationMode(overlayManager, KEY_SYSTEM_NAV_GESTURAL);
281             Settings.Secure.putFloat(context.getContentResolver(),
282                     Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT, 1.0f);
283             Settings.Secure.putFloat(context.getContentResolver(),
284                     Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT, 1.0f);
285         }
286     }
287 
288     @VisibleForTesting
getCurrentSystemNavigationMode(Context context)289     static String getCurrentSystemNavigationMode(Context context) {
290         if (SystemNavigationPreferenceController.isGestureNavigationEnabled(context)) {
291             return KEY_SYSTEM_NAV_GESTURAL;
292         } else if (SystemNavigationPreferenceController.is2ButtonNavigationEnabled(context)) {
293             return KEY_SYSTEM_NAV_2BUTTONS;
294         } else {
295             return KEY_SYSTEM_NAV_3BUTTONS;
296         }
297     }
298 
299     @VisibleForTesting
setCurrentSystemNavigationMode(IOverlayManager overlayManager, String key)300     static void setCurrentSystemNavigationMode(IOverlayManager overlayManager, String key) {
301         String overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
302         switch (key) {
303             case KEY_SYSTEM_NAV_GESTURAL:
304                 overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
305                 break;
306             case KEY_SYSTEM_NAV_2BUTTONS:
307                 overlayPackage = NAV_BAR_MODE_2BUTTON_OVERLAY;
308                 break;
309             case KEY_SYSTEM_NAV_3BUTTONS:
310                 overlayPackage = NAV_BAR_MODE_3BUTTON_OVERLAY;
311                 break;
312         }
313 
314         try {
315             overlayManager.setEnabledExclusiveInCategory(overlayPackage, USER_CURRENT);
316         } catch (RemoteException e) {
317             throw e.rethrowFromSystemServer();
318         }
319     }
320 
setIllustrationVideo(IllustrationPreference videoPref, String systemNavKey)321     private void setIllustrationVideo(IllustrationPreference videoPref,
322             String systemNavKey) {
323         switch (systemNavKey) {
324             case KEY_SYSTEM_NAV_GESTURAL:
325                 if (isGestureTutorialAvailable()) {
326                     videoPref.setLottieAnimationResId(
327                             R.raw.lottie_system_nav_fully_gestural_with_nav);
328                 } else {
329                     videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_fully_gestural);
330                 }
331                 break;
332             case KEY_SYSTEM_NAV_2BUTTONS:
333                 videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_2_button);
334                 break;
335             case KEY_SYSTEM_NAV_3BUTTONS:
336                 videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_3_button);
337                 break;
338         }
339     }
340 
setGestureNavigationTutorialDialog(String systemNavKey)341     private void setGestureNavigationTutorialDialog(String systemNavKey) {
342         if (TextUtils.equals(KEY_SYSTEM_NAV_GESTURAL, systemNavKey)
343                 && !isAccessibilityFloatingMenuEnabled()
344                 && (isAnyServiceSupportAccessibilityButton() || isNavBarMagnificationEnabled())) {
345             mA11yTutorialDialogShown = true;
346             AccessibilityGestureNavigationTutorial.showGestureNavigationTutorialDialog(getContext(),
347                     dialog -> mA11yTutorialDialogShown = false);
348         } else {
349             mA11yTutorialDialogShown = false;
350         }
351     }
352 
isAnyServiceSupportAccessibilityButton()353     private boolean isAnyServiceSupportAccessibilityButton() {
354         final AccessibilityManager ams = getContext().getSystemService(AccessibilityManager.class);
355         final List<String> targets = ams.getAccessibilityShortcutTargets(
356                 AccessibilityManager.ACCESSIBILITY_BUTTON);
357         return !targets.isEmpty();
358     }
359 
isNavBarMagnificationEnabled()360     private boolean isNavBarMagnificationEnabled() {
361         return Settings.Secure.getInt(getContext().getContentResolver(),
362                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1;
363     }
364 
isAccessibilityFloatingMenuEnabled()365     private boolean isAccessibilityFloatingMenuEnabled() {
366         return Settings.Secure.getInt(getContext().getContentResolver(),
367                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE, /* def= */ -1)
368                 == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
369     }
370 
371     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
372             new BaseSearchIndexProvider(R.xml.system_navigation_gesture_settings) {
373 
374                 @Override
375                 protected boolean isPageSearchEnabled(Context context) {
376                     return SystemNavigationPreferenceController.isGestureAvailable(context);
377                 }
378 
379                 @Override
380                 public List<SearchIndexableRaw> getRawDataToIndex(Context context,
381                         boolean enabled) {
382                     final Resources res = context.getResources();
383                     final List<SearchIndexableRaw> result = new ArrayList<>();
384 
385                     if (SystemNavigationPreferenceController.isOverlayPackageAvailable(context,
386                             NAV_BAR_MODE_GESTURAL_OVERLAY)) {
387                         SearchIndexableRaw data = new SearchIndexableRaw(context);
388                         data.title = res.getString(R.string.edge_to_edge_navigation_title);
389                         data.key = KEY_SYSTEM_NAV_GESTURAL;
390                         result.add(data);
391                     }
392 
393                     if (SystemNavigationPreferenceController.isOverlayPackageAvailable(context,
394                             NAV_BAR_MODE_2BUTTON_OVERLAY)) {
395                         SearchIndexableRaw data = new SearchIndexableRaw(context);
396                         data.title = res.getString(R.string.swipe_up_to_switch_apps_title);
397                         data.key = KEY_SYSTEM_NAV_2BUTTONS;
398                         result.add(data);
399                     }
400 
401                     if (SystemNavigationPreferenceController.isOverlayPackageAvailable(context,
402                             NAV_BAR_MODE_3BUTTON_OVERLAY)) {
403                         SearchIndexableRaw data = new SearchIndexableRaw(context);
404                         data.title = res.getString(R.string.legacy_navigation_title);
405                         data.key = KEY_SYSTEM_NAV_3BUTTONS;
406                         result.add(data);
407                     }
408 
409                     return result;
410                 }
411             };
412 
413     // From HelpResourceProvider
414     @Override
getHelpResource()415     public int getHelpResource() {
416         // TODO(b/146001201): Replace with system navigation help page when ready.
417         return R.string.help_uri_default;
418     }
419 }
420