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.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
22 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
23 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
24 
25 import android.app.settings.SettingsEnums;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.SharedPreferences;
29 import android.content.om.IOverlayManager;
30 import android.content.om.OverlayInfo;
31 import android.os.Bundle;
32 import android.os.RemoteException;
33 import android.os.ServiceManager;
34 import android.provider.Settings;
35 import android.text.TextUtils;
36 import android.view.accessibility.AccessibilityManager;
37 
38 import androidx.annotation.Nullable;
39 import androidx.annotation.VisibleForTesting;
40 import androidx.preference.PreferenceScreen;
41 
42 import com.android.settings.R;
43 import com.android.settings.accessibility.AccessibilityGestureNavigationTutorial;
44 import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
45 import com.android.settings.overlay.FeatureFactory;
46 import com.android.settings.search.BaseSearchIndexProvider;
47 import com.android.settings.support.actionbar.HelpResourceProvider;
48 import com.android.settings.utils.CandidateInfoExtra;
49 import com.android.settings.widget.RadioButtonPickerFragment;
50 import com.android.settingslib.search.SearchIndexable;
51 import com.android.settingslib.widget.CandidateInfo;
52 import com.android.settingslib.widget.IllustrationPreference;
53 import com.android.settingslib.widget.RadioButtonPreference;
54 
55 import java.util.ArrayList;
56 import java.util.List;
57 
58 @SearchIndexable
59 public class SystemNavigationGestureSettings extends RadioButtonPickerFragment implements
60         HelpResourceProvider {
61 
62     @VisibleForTesting
63     static final String KEY_SYSTEM_NAV_3BUTTONS = "system_nav_3buttons";
64     @VisibleForTesting
65     static final String KEY_SYSTEM_NAV_2BUTTONS = "system_nav_2buttons";
66     @VisibleForTesting
67     static final String KEY_SYSTEM_NAV_GESTURAL = "system_nav_gestural";
68 
69     public static final String PREF_KEY_SUGGESTION_COMPLETE =
70             "pref_system_navigation_suggestion_complete";
71 
72     private static final String KEY_SHOW_A11Y_TUTORIAL_DIALOG = "show_a11y_tutorial_dialog_bool";
73 
74     private boolean mA11yTutorialDialogShown = false;
75 
76     private IOverlayManager mOverlayManager;
77 
78     private IllustrationPreference mVideoPreference;
79 
80     @Override
onCreate(@ullable Bundle savedInstanceState)81     public void onCreate(@Nullable Bundle savedInstanceState) {
82         super.onCreate(savedInstanceState);
83         if (savedInstanceState != null) {
84             mA11yTutorialDialogShown =
85                     savedInstanceState.getBoolean(KEY_SHOW_A11Y_TUTORIAL_DIALOG, false);
86             if (mA11yTutorialDialogShown) {
87                 AccessibilityGestureNavigationTutorial.showGestureNavigationTutorialDialog(
88                         getContext(), dialog -> mA11yTutorialDialogShown = false);
89             }
90         }
91     }
92 
93     @Override
onSaveInstanceState(Bundle outState)94     public void onSaveInstanceState(Bundle outState) {
95         outState.putBoolean(KEY_SHOW_A11Y_TUTORIAL_DIALOG, mA11yTutorialDialogShown);
96         super.onSaveInstanceState(outState);
97     }
98 
99     @Override
onAttach(Context context)100     public void onAttach(Context context) {
101         super.onAttach(context);
102 
103         SuggestionFeatureProvider suggestionFeatureProvider = FeatureFactory.getFactory(context)
104                 .getSuggestionFeatureProvider(context);
105         SharedPreferences prefs = suggestionFeatureProvider.getSharedPrefs(context);
106         prefs.edit().putBoolean(PREF_KEY_SUGGESTION_COMPLETE, true).apply();
107 
108         mOverlayManager = IOverlayManager.Stub.asInterface(
109                 ServiceManager.getService(Context.OVERLAY_SERVICE));
110 
111         mVideoPreference = new IllustrationPreference(context);
112         setIllustrationVideo(mVideoPreference, getDefaultKey());
113 
114         migrateOverlaySensitivityToSettings(context, mOverlayManager);
115     }
116 
117     @Override
getMetricsCategory()118     public int getMetricsCategory() {
119         return SettingsEnums.SETTINGS_GESTURE_SWIPE_UP;
120     }
121 
122     @Override
updateCandidates()123     public void updateCandidates() {
124         final String defaultKey = getDefaultKey();
125         final String systemDefaultKey = getSystemDefaultKey();
126         final PreferenceScreen screen = getPreferenceScreen();
127         screen.removeAll();
128         screen.addPreference(mVideoPreference);
129 
130         final List<? extends CandidateInfo> candidateList = getCandidates();
131         if (candidateList == null) {
132             return;
133         }
134         for (CandidateInfo info : candidateList) {
135             RadioButtonPreference pref =
136                     new RadioButtonPreference(getPrefContext());
137             bindPreference(pref, info.getKey(), info, defaultKey);
138             bindPreferenceExtra(pref, info.getKey(), info, defaultKey, systemDefaultKey);
139             screen.addPreference(pref);
140         }
141         mayCheckOnlyRadioButton();
142     }
143 
144     @Override
bindPreferenceExtra(RadioButtonPreference pref, String key, CandidateInfo info, String defaultKey, String systemDefaultKey)145     public void bindPreferenceExtra(RadioButtonPreference pref,
146             String key, CandidateInfo info, String defaultKey, String systemDefaultKey) {
147         if (!(info instanceof CandidateInfoExtra)) {
148             return;
149         }
150 
151         pref.setSummary(((CandidateInfoExtra) info).loadSummary());
152 
153         if (info.getKey() == KEY_SYSTEM_NAV_GESTURAL) {
154             pref.setExtraWidgetOnClickListener((v) -> startActivity(new Intent(
155                     GestureNavigationSettingsFragment.GESTURE_NAVIGATION_SETTINGS)));
156         }
157     }
158 
159     @Override
getPreferenceScreenResId()160     protected int getPreferenceScreenResId() {
161         return R.xml.system_navigation_gesture_settings;
162     }
163 
164     @Override
getCandidates()165     protected List<? extends CandidateInfo> getCandidates() {
166         final Context c = getContext();
167         List<CandidateInfoExtra> candidates = new ArrayList<>();
168 
169         if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
170                 NAV_BAR_MODE_GESTURAL_OVERLAY)) {
171             candidates.add(new CandidateInfoExtra(
172                     c.getText(R.string.edge_to_edge_navigation_title),
173                     c.getText(R.string.edge_to_edge_navigation_summary),
174                     KEY_SYSTEM_NAV_GESTURAL, true /* enabled */));
175         }
176         if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
177                 NAV_BAR_MODE_2BUTTON_OVERLAY)) {
178             candidates.add(new CandidateInfoExtra(
179                     c.getText(R.string.swipe_up_to_switch_apps_title),
180                     c.getText(R.string.swipe_up_to_switch_apps_summary),
181                     KEY_SYSTEM_NAV_2BUTTONS, true /* enabled */));
182         }
183         if (SystemNavigationPreferenceController.isOverlayPackageAvailable(c,
184                 NAV_BAR_MODE_3BUTTON_OVERLAY)) {
185             candidates.add(new CandidateInfoExtra(
186                     c.getText(R.string.legacy_navigation_title),
187                     c.getText(R.string.legacy_navigation_summary),
188                     KEY_SYSTEM_NAV_3BUTTONS, true /* enabled */));
189         }
190 
191         return candidates;
192     }
193 
194     @Override
getDefaultKey()195     protected String getDefaultKey() {
196         return getCurrentSystemNavigationMode(getContext());
197     }
198 
199     @Override
setDefaultKey(String key)200     protected boolean setDefaultKey(String key) {
201         setCurrentSystemNavigationMode(mOverlayManager, key);
202         setIllustrationVideo(mVideoPreference, key);
203         setGestureNavigationTutorialDialog(key);
204         return true;
205     }
206 
migrateOverlaySensitivityToSettings(Context context, IOverlayManager overlayManager)207     static void migrateOverlaySensitivityToSettings(Context context,
208             IOverlayManager overlayManager) {
209         if (!SystemNavigationPreferenceController.isGestureNavigationEnabled(context)) {
210             return;
211         }
212 
213         OverlayInfo info = null;
214         try {
215             info = overlayManager.getOverlayInfo(NAV_BAR_MODE_GESTURAL_OVERLAY, USER_CURRENT);
216         } catch (RemoteException e) { /* Do nothing */ }
217         if (info != null && !info.isEnabled()) {
218             // Enable the default gesture nav overlay. Back sensitivity for left and right are
219             // stored as separate settings values, and other gesture nav overlays are deprecated.
220             setCurrentSystemNavigationMode(overlayManager, KEY_SYSTEM_NAV_GESTURAL);
221             Settings.Secure.putFloat(context.getContentResolver(),
222                     Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT, 1.0f);
223             Settings.Secure.putFloat(context.getContentResolver(),
224                     Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT, 1.0f);
225         }
226     }
227 
228     @VisibleForTesting
getCurrentSystemNavigationMode(Context context)229     static String getCurrentSystemNavigationMode(Context context) {
230         if (SystemNavigationPreferenceController.isGestureNavigationEnabled(context)) {
231             return KEY_SYSTEM_NAV_GESTURAL;
232         } else if (SystemNavigationPreferenceController.is2ButtonNavigationEnabled(context)) {
233             return KEY_SYSTEM_NAV_2BUTTONS;
234         } else {
235             return KEY_SYSTEM_NAV_3BUTTONS;
236         }
237     }
238 
239     @VisibleForTesting
setCurrentSystemNavigationMode(IOverlayManager overlayManager, String key)240     static void setCurrentSystemNavigationMode(IOverlayManager overlayManager, String key) {
241         String overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
242         switch (key) {
243             case KEY_SYSTEM_NAV_GESTURAL:
244                 overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
245                 break;
246             case KEY_SYSTEM_NAV_2BUTTONS:
247                 overlayPackage = NAV_BAR_MODE_2BUTTON_OVERLAY;
248                 break;
249             case KEY_SYSTEM_NAV_3BUTTONS:
250                 overlayPackage = NAV_BAR_MODE_3BUTTON_OVERLAY;
251                 break;
252         }
253 
254         try {
255             overlayManager.setEnabledExclusiveInCategory(overlayPackage, USER_CURRENT);
256         } catch (RemoteException e) {
257             throw e.rethrowFromSystemServer();
258         }
259     }
260 
setIllustrationVideo(IllustrationPreference videoPref, String systemNavKey)261     private static void setIllustrationVideo(IllustrationPreference videoPref,
262             String systemNavKey) {
263         switch (systemNavKey) {
264             case KEY_SYSTEM_NAV_GESTURAL:
265                 videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_fully_gestural);
266                 break;
267             case KEY_SYSTEM_NAV_2BUTTONS:
268                 videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_2_button);
269                 break;
270             case KEY_SYSTEM_NAV_3BUTTONS:
271                 videoPref.setLottieAnimationResId(R.raw.lottie_system_nav_3_button);
272                 break;
273         }
274     }
275 
setGestureNavigationTutorialDialog(String systemNavKey)276     private void setGestureNavigationTutorialDialog(String systemNavKey) {
277         if (TextUtils.equals(KEY_SYSTEM_NAV_GESTURAL, systemNavKey)
278                 && !isAccessibilityFloatingMenuEnabled()
279                 && (isAnyServiceSupportAccessibilityButton() || isNavBarMagnificationEnabled())) {
280             mA11yTutorialDialogShown = true;
281             AccessibilityGestureNavigationTutorial.showGestureNavigationTutorialDialog(getContext(),
282                     dialog -> mA11yTutorialDialogShown = false);
283         } else {
284             mA11yTutorialDialogShown = false;
285         }
286     }
287 
isAnyServiceSupportAccessibilityButton()288     private boolean isAnyServiceSupportAccessibilityButton() {
289         final AccessibilityManager ams = getContext().getSystemService(AccessibilityManager.class);
290         final List<String> targets = ams.getAccessibilityShortcutTargets(
291                 AccessibilityManager.ACCESSIBILITY_BUTTON);
292         return !targets.isEmpty();
293     }
294 
isNavBarMagnificationEnabled()295     private boolean isNavBarMagnificationEnabled() {
296         return Settings.Secure.getInt(getContext().getContentResolver(),
297                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1;
298     }
299 
isAccessibilityFloatingMenuEnabled()300     private boolean isAccessibilityFloatingMenuEnabled() {
301         return Settings.Secure.getInt(getContext().getContentResolver(),
302                 Settings.Secure.ACCESSIBILITY_BUTTON_MODE, /* def= */ -1)
303                 == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
304     }
305 
306     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
307             new BaseSearchIndexProvider(R.xml.system_navigation_gesture_settings) {
308 
309                 @Override
310                 protected boolean isPageSearchEnabled(Context context) {
311                     return SystemNavigationPreferenceController.isGestureAvailable(context);
312                 }
313             };
314 
315     // From HelpResourceProvider
316     @Override
getHelpResource()317     public int getHelpResource() {
318         // TODO(b/146001201): Replace with system navigation help page when ready.
319         return R.string.help_uri_default;
320     }
321 }
322