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