• 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.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
21 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
22 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
23 
24 import static com.android.settings.widget.RadioButtonPreferenceWithExtraWidget.EXTRA_WIDGET_VISIBILITY_GONE;
25 import static com.android.settings.widget.RadioButtonPreferenceWithExtraWidget.EXTRA_WIDGET_VISIBILITY_INFO;
26 import static com.android.settings.widget.RadioButtonPreferenceWithExtraWidget.EXTRA_WIDGET_VISIBILITY_SETTING;
27 
28 import android.accessibilityservice.AccessibilityServiceInfo;
29 import android.app.settings.SettingsEnums;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.SharedPreferences;
33 import android.content.om.IOverlayManager;
34 import android.content.om.OverlayInfo;
35 import android.graphics.drawable.Drawable;
36 import android.os.RemoteException;
37 import android.os.ServiceManager;
38 import android.provider.SearchIndexableResource;
39 import android.provider.Settings;
40 import android.text.TextUtils;
41 import android.view.accessibility.AccessibilityManager;
42 
43 import androidx.annotation.VisibleForTesting;
44 import androidx.preference.PreferenceScreen;
45 
46 import com.android.settings.SettingsTutorialDialogWrapperActivity;
47 import com.android.settings.R;
48 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
49 import com.android.settings.overlay.FeatureFactory;
50 import com.android.settings.search.BaseSearchIndexProvider;
51 import com.android.settings.search.Indexable;
52 import com.android.settings.widget.RadioButtonPickerFragment;
53 import com.android.settings.widget.RadioButtonPreference;
54 import com.android.settings.widget.RadioButtonPreferenceWithExtraWidget;
55 import com.android.settings.widget.VideoPreference;
56 import com.android.settingslib.search.SearchIndexable;
57 import com.android.settingslib.widget.CandidateInfo;
58 
59 import java.util.ArrayList;
60 import java.util.Arrays;
61 import java.util.List;
62 
63 @SearchIndexable
64 public class SystemNavigationGestureSettings extends RadioButtonPickerFragment {
65 
66     private static final String TAG = "SystemNavigationGesture";
67 
68     @VisibleForTesting
69     static final String SHARED_PREFERENCES_NAME = "system_navigation_settings_preferences";
70     @VisibleForTesting
71     static final String PREFS_BACK_SENSITIVITY_KEY = "system_navigation_back_sensitivity";
72 
73     @VisibleForTesting
74     static final String KEY_SYSTEM_NAV_3BUTTONS = "system_nav_3buttons";
75     @VisibleForTesting
76     static final String KEY_SYSTEM_NAV_2BUTTONS = "system_nav_2buttons";
77     @VisibleForTesting
78     static final String KEY_SYSTEM_NAV_GESTURAL = "system_nav_gestural";
79 
80     public static final String PREF_KEY_SUGGESTION_COMPLETE =
81             "pref_system_navigation_suggestion_complete";
82 
83     @VisibleForTesting
84     static final String NAV_BAR_MODE_GESTURAL_OVERLAY_NARROW_BACK
85             = "com.android.internal.systemui.navbar.gestural_narrow_back";
86     @VisibleForTesting
87     static final String NAV_BAR_MODE_GESTURAL_OVERLAY_WIDE_BACK
88             = "com.android.internal.systemui.navbar.gestural_wide_back";
89     @VisibleForTesting
90     static final String NAV_BAR_MODE_GESTURAL_OVERLAY_EXTRA_WIDE_BACK
91             = "com.android.internal.systemui.navbar.gestural_extra_wide_back";
92     @VisibleForTesting
93     static final String[] BACK_GESTURE_INSET_OVERLAYS = {
94             NAV_BAR_MODE_GESTURAL_OVERLAY_NARROW_BACK,
95             NAV_BAR_MODE_GESTURAL_OVERLAY,
96             NAV_BAR_MODE_GESTURAL_OVERLAY_WIDE_BACK,
97             NAV_BAR_MODE_GESTURAL_OVERLAY_EXTRA_WIDE_BACK
98     };
99     @VisibleForTesting
100     static int BACK_GESTURE_INSET_DEFAULT_OVERLAY = 1;
101 
102     private IOverlayManager mOverlayManager;
103 
104     private VideoPreference mVideoPreference;
105 
106     @Override
onAttach(Context context)107     public void onAttach(Context context) {
108         super.onAttach(context);
109         SuggestionFeatureProvider suggestionFeatureProvider = FeatureFactory.getFactory(context)
110                 .getSuggestionFeatureProvider(context);
111         SharedPreferences prefs = suggestionFeatureProvider.getSharedPrefs(context);
112         prefs.edit().putBoolean(PREF_KEY_SUGGESTION_COMPLETE, true).apply();
113 
114         mOverlayManager = IOverlayManager.Stub.asInterface(
115                 ServiceManager.getService(Context.OVERLAY_SERVICE));
116 
117         mVideoPreference = new VideoPreference(context);
118         setIllustrationVideo(mVideoPreference, getDefaultKey());
119         mVideoPreference.setHeight( /* Illustration height in dp */
120                 getResources().getDimension(R.dimen.system_navigation_illustration_height)
121                         / getResources().getDisplayMetrics().density);
122     }
123 
124     @Override
getMetricsCategory()125     public int getMetricsCategory() {
126         return SettingsEnums.SETTINGS_GESTURE_SWIPE_UP;
127     }
128 
129     @Override
updateCandidates()130     public void updateCandidates() {
131         final String defaultKey = getDefaultKey();
132         final String systemDefaultKey = getSystemDefaultKey();
133         final PreferenceScreen screen = getPreferenceScreen();
134         screen.removeAll();
135         screen.addPreference(mVideoPreference);
136 
137         final List<? extends CandidateInfo> candidateList = getCandidates();
138         if (candidateList == null) {
139             return;
140         }
141         for (CandidateInfo info : candidateList) {
142             RadioButtonPreferenceWithExtraWidget pref =
143                     new RadioButtonPreferenceWithExtraWidget(getPrefContext());
144             bindPreference(pref, info.getKey(), info, defaultKey);
145             bindPreferenceExtra(pref, info.getKey(), info, defaultKey, systemDefaultKey);
146             screen.addPreference(pref);
147         }
148         mayCheckOnlyRadioButton();
149     }
150 
151     @Override
bindPreferenceExtra(RadioButtonPreference pref, String key, CandidateInfo info, String defaultKey, String systemDefaultKey)152     public void bindPreferenceExtra(RadioButtonPreference pref,
153             String key, CandidateInfo info, String defaultKey, String systemDefaultKey) {
154         if (!(info instanceof NavModeCandidateInfo)
155                 || !(pref instanceof RadioButtonPreferenceWithExtraWidget)) {
156             return;
157         }
158 
159         pref.setSummary(((NavModeCandidateInfo) info).loadSummary());
160 
161         RadioButtonPreferenceWithExtraWidget p = (RadioButtonPreferenceWithExtraWidget) pref;
162         if (info.getKey() == KEY_SYSTEM_NAV_GESTURAL) {
163             if (SystemNavigationPreferenceController.isGestureNavSupportedByDefaultLauncher(
164                     getContext())) {
165                 p.setExtraWidgetVisibility(EXTRA_WIDGET_VISIBILITY_SETTING);
166                 p.setExtraWidgetOnClickListener((v) -> GestureNavigationBackSensitivityDialog
167                         .show(this, getBackSensitivity(getContext(), mOverlayManager)));
168             } else {
169                 p.setEnabled(false);
170                 p.setExtraWidgetVisibility(EXTRA_WIDGET_VISIBILITY_INFO);
171                 p.setExtraWidgetOnClickListener((v) ->
172                         GestureNavigationNotAvailableDialog.show(this));
173             }
174         } else {
175             p.setExtraWidgetVisibility(EXTRA_WIDGET_VISIBILITY_GONE);
176         }
177     }
178 
179     @Override
getPreferenceScreenResId()180     protected int getPreferenceScreenResId() {
181         return R.xml.system_navigation_gesture_settings;
182     }
183 
184     @Override
getCandidates()185     protected List<? extends CandidateInfo> getCandidates() {
186         final Context c = getContext();
187         List<NavModeCandidateInfo> candidates = new ArrayList<>();
188 
189         if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
190                 NAV_BAR_MODE_GESTURAL_OVERLAY)) {
191             candidates.add(new NavModeCandidateInfo(
192                     c.getText(R.string.edge_to_edge_navigation_title),
193                     c.getText(R.string.edge_to_edge_navigation_summary),
194                     KEY_SYSTEM_NAV_GESTURAL, true /* enabled */));
195         }
196         if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
197                 NAV_BAR_MODE_2BUTTON_OVERLAY)) {
198             candidates.add(new NavModeCandidateInfo(
199                     c.getText(R.string.swipe_up_to_switch_apps_title),
200                     c.getText(R.string.swipe_up_to_switch_apps_summary),
201                     KEY_SYSTEM_NAV_2BUTTONS, true /* enabled */));
202         }
203         if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
204                 NAV_BAR_MODE_3BUTTON_OVERLAY)) {
205             candidates.add(new NavModeCandidateInfo(
206                     c.getText(R.string.legacy_navigation_title),
207                     c.getText(R.string.legacy_navigation_summary),
208                     KEY_SYSTEM_NAV_3BUTTONS, true /* enabled */));
209         }
210 
211         return candidates;
212     }
213 
214     @Override
getDefaultKey()215     protected String getDefaultKey() {
216         return getCurrentSystemNavigationMode(getContext());
217     }
218 
219     @Override
setDefaultKey(String key)220     protected boolean setDefaultKey(String key) {
221         final Context c = getContext();
222         if (key == KEY_SYSTEM_NAV_GESTURAL &&
223                 !SystemNavigationPreferenceController.isGestureNavSupportedByDefaultLauncher(c)) {
224             // This should not happen since the preference is disabled. Return to be safe.
225             return false;
226         }
227 
228         setCurrentSystemNavigationMode(c, mOverlayManager, key);
229         setIllustrationVideo(mVideoPreference, key);
230         if (TextUtils.equals(KEY_SYSTEM_NAV_GESTURAL, key) && (
231                 isAnyServiceSupportAccessibilityButton() || isNavBarMagnificationEnabled())) {
232             Intent intent = new Intent(getActivity(), SettingsTutorialDialogWrapperActivity.class);
233             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
234             startActivity(intent);
235         }
236         return true;
237     }
238 
239     @VisibleForTesting
setBackSensitivity(Context context, IOverlayManager overlayManager, int sensitivity)240     static void setBackSensitivity(Context context, IOverlayManager overlayManager,
241             int sensitivity) {
242         if (sensitivity < 0 || sensitivity >= BACK_GESTURE_INSET_OVERLAYS.length) {
243             throw new IllegalArgumentException("Sensitivity out of range.");
244         }
245 
246         // Store the sensitivity level, to be able to restore when user returns to Gesture Nav mode
247         context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE).edit()
248                 .putInt(PREFS_BACK_SENSITIVITY_KEY, sensitivity).apply();
249         if (getCurrentSystemNavigationMode(context) == KEY_SYSTEM_NAV_GESTURAL) {
250             setNavBarInteractionMode(overlayManager, BACK_GESTURE_INSET_OVERLAYS[sensitivity]);
251         }
252     }
253 
254     @VisibleForTesting
getBackSensitivity(Context context, IOverlayManager overlayManager)255     static int getBackSensitivity(Context context, IOverlayManager overlayManager) {
256         for (int i = 0; i < BACK_GESTURE_INSET_OVERLAYS.length; i++) {
257             OverlayInfo info = null;
258             try {
259                 info = overlayManager.getOverlayInfo(BACK_GESTURE_INSET_OVERLAYS[i], USER_CURRENT);
260             } catch (RemoteException e) { /* Do nothing */ }
261             if (info != null && info.isEnabled()) {
262                 return i;
263             }
264         }
265         // If Gesture nav is not selected, read the value from shared preferences.
266         return context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
267                 .getInt(PREFS_BACK_SENSITIVITY_KEY, BACK_GESTURE_INSET_DEFAULT_OVERLAY);
268     }
269 
270     @VisibleForTesting
getCurrentSystemNavigationMode(Context context)271     static String getCurrentSystemNavigationMode(Context context) {
272         if (SystemNavigationPreferenceController.isEdgeToEdgeEnabled(context)) {
273             return KEY_SYSTEM_NAV_GESTURAL;
274         } else if (SystemNavigationPreferenceController.isSwipeUpEnabled(context)) {
275             return KEY_SYSTEM_NAV_2BUTTONS;
276         } else {
277             return KEY_SYSTEM_NAV_3BUTTONS;
278         }
279     }
280 
281     @VisibleForTesting
setCurrentSystemNavigationMode(Context context, IOverlayManager overlayManager, String key)282     static void setCurrentSystemNavigationMode(Context context, IOverlayManager overlayManager,
283             String key) {
284         switch (key) {
285             case KEY_SYSTEM_NAV_GESTURAL:
286                 int sensitivity = getBackSensitivity(context, overlayManager);
287                 setNavBarInteractionMode(overlayManager, BACK_GESTURE_INSET_OVERLAYS[sensitivity]);
288                 break;
289             case KEY_SYSTEM_NAV_2BUTTONS:
290                 setNavBarInteractionMode(overlayManager, NAV_BAR_MODE_2BUTTON_OVERLAY);
291                 break;
292             case KEY_SYSTEM_NAV_3BUTTONS:
293                 setNavBarInteractionMode(overlayManager, NAV_BAR_MODE_3BUTTON_OVERLAY);
294                 break;
295         }
296     }
297 
setNavBarInteractionMode(IOverlayManager overlayManager, String overlayPackage)298     private static void setNavBarInteractionMode(IOverlayManager overlayManager,
299             String overlayPackage) {
300         try {
301             overlayManager.setEnabledExclusiveInCategory(overlayPackage, USER_CURRENT);
302         } catch (RemoteException e) {
303             throw e.rethrowFromSystemServer();
304         }
305     }
306 
setIllustrationVideo(VideoPreference videoPref, String systemNavKey)307     private static void setIllustrationVideo(VideoPreference videoPref, String systemNavKey) {
308         videoPref.setVideo(0, 0);
309         switch (systemNavKey) {
310             case KEY_SYSTEM_NAV_GESTURAL:
311                 videoPref.setVideo(R.raw.system_nav_fully_gestural,
312                         R.drawable.system_nav_fully_gestural);
313                 break;
314             case KEY_SYSTEM_NAV_2BUTTONS:
315                 videoPref.setVideo(R.raw.system_nav_2_button, R.drawable.system_nav_2_button);
316                 break;
317             case KEY_SYSTEM_NAV_3BUTTONS:
318                 videoPref.setVideo(R.raw.system_nav_3_button, R.drawable.system_nav_3_button);
319                 break;
320         }
321     }
322 
isAnyServiceSupportAccessibilityButton()323     private boolean isAnyServiceSupportAccessibilityButton() {
324         final AccessibilityManager ams = (AccessibilityManager) getContext().getSystemService(
325                 Context.ACCESSIBILITY_SERVICE);
326         final List<AccessibilityServiceInfo> services = ams.getEnabledAccessibilityServiceList(
327                 AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
328 
329         for (AccessibilityServiceInfo info : services) {
330             if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
331                 return true;
332             }
333         }
334 
335         return false;
336     }
337 
isNavBarMagnificationEnabled()338     private boolean isNavBarMagnificationEnabled() {
339         return Settings.Secure.getInt(getContext().getContentResolver(),
340                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1;
341     }
342 
343     static class NavModeCandidateInfo extends CandidateInfo {
344         private final CharSequence mLabel;
345         private final CharSequence mSummary;
346         private final String mKey;
347 
NavModeCandidateInfo(CharSequence label, CharSequence summary, String key, boolean enabled)348         NavModeCandidateInfo(CharSequence label, CharSequence summary, String key,
349                 boolean enabled) {
350             super(enabled);
351             mLabel = label;
352             mSummary = summary;
353             mKey = key;
354         }
355 
356         @Override
loadLabel()357         public CharSequence loadLabel() {
358             return mLabel;
359         }
360 
loadSummary()361         public CharSequence loadSummary() {
362             return mSummary;
363         }
364 
365         @Override
loadIcon()366         public Drawable loadIcon() {
367             return null;
368         }
369 
370         @Override
getKey()371         public String getKey() {
372             return mKey;
373         }
374     }
375 
376     public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
377             new BaseSearchIndexProvider() {
378                 @Override
379                 public List<SearchIndexableResource> getXmlResourcesToIndex(
380                         Context context, boolean enabled) {
381                     final SearchIndexableResource sir = new SearchIndexableResource(context);
382                     sir.xmlResId = R.xml.system_navigation_gesture_settings;
383                     return Arrays.asList(sir);
384                 }
385 
386                 @Override
387                 protected boolean isPageSearchEnabled(Context context) {
388                     return SystemNavigationPreferenceController.isGestureAvailable(context);
389                 }
390             };
391 }
392