1 /*
2  * Copyright (C) 2020 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 com.android.internal.accessibility.AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME;
20 
21 import android.content.ContentResolver;
22 import android.content.Context;
23 import android.database.ContentObserver;
24 import android.net.Uri;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.os.SystemProperties;
28 import android.os.UserHandle;
29 import android.provider.Settings;
30 import android.text.TextUtils;
31 
32 import androidx.annotation.VisibleForTesting;
33 
34 /**
35  * The Util to query one-handed mode settings config
36  */
37 public class OneHandedSettingsUtils {
38 
39     static final String ONE_HANDED_MODE_TARGET_NAME =
40             ONE_HANDED_COMPONENT_NAME.getShortClassName();
41 
42     static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
43     static final int OFF = 0;
44     static final int ON = 1;
45     static final Uri ONE_HANDED_MODE_ENABLED_URI =
46             Settings.Secure.getUriFor(Settings.Secure.ONE_HANDED_MODE_ENABLED);
47     static final Uri SHOW_NOTIFICATION_ENABLED_URI =
48             Settings.Secure.getUriFor(Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED);
49     static final Uri SOFTWARE_SHORTCUT_ENABLED_URI =
50             Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
51     static final Uri HARDWARE_SHORTCUT_ENABLED_URI =
52             Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE);
53     static final Uri QS_SHORTCUT_ENABLED_URI =
54             Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_QS_TARGETS);
55 
56     public enum OneHandedTimeout {
57         NEVER(0), SHORT(4), MEDIUM(8), LONG(12);
58 
59         private final int mValue;
60 
OneHandedTimeout(int value)61         OneHandedTimeout(int value) {
62             this.mValue = value;
63         }
64 
getValue()65         public int getValue() {
66             return mValue;
67         }
68     }
69 
70     private final Context mContext;
71     private final SettingsObserver mSettingsObserver;
72 
73     private static int sCurrentUserId;
74 
OneHandedSettingsUtils(Context context)75     OneHandedSettingsUtils(Context context) {
76         mContext = context;
77         sCurrentUserId = UserHandle.myUserId();
78         mSettingsObserver = new SettingsObserver(new Handler(Looper.getMainLooper()));
79     }
80 
81     /**
82      * Gets One-Handed mode support flag.
83      */
isSupportOneHandedMode()84     public static boolean isSupportOneHandedMode() {
85         return SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
86     }
87 
88     /**
89      * Gets one-handed mode feature enable or disable flag from Settings provider.
90      *
91      * @param context App context
92      * @return enable or disable one-handed mode flag.
93      */
isOneHandedModeEnabled(Context context)94     public static boolean isOneHandedModeEnabled(Context context) {
95         return Settings.Secure.getIntForUser(context.getContentResolver(),
96                 Settings.Secure.ONE_HANDED_MODE_ENABLED, OFF, sCurrentUserId) == ON;
97     }
98 
99     /**
100      * Sets one-handed mode enable or disable flag to Settings provider.
101      *
102      * @param context App context
103      * @param enable  enable or disable one-handed mode.
104      */
setOneHandedModeEnabled(Context context, boolean enable)105     public static void setOneHandedModeEnabled(Context context, boolean enable) {
106         Settings.Secure.putIntForUser(context.getContentResolver(),
107                 Settings.Secure.ONE_HANDED_MODE_ENABLED, enable ? ON : OFF, sCurrentUserId);
108     }
109 
110     /**
111      * Gets enabling taps app to exit one-handed mode flag from Settings provider.
112      *
113      * @param context App context
114      * @return enable or disable taps app to exit.
115      */
isTapsAppToExitEnabled(Context context)116     public static boolean isTapsAppToExitEnabled(Context context) {
117         return Settings.Secure.getIntForUser(context.getContentResolver(),
118                 Settings.Secure.TAPS_APP_TO_EXIT, OFF, sCurrentUserId) == ON;
119     }
120 
121     /**
122      * Sets enabling taps app to exit one-handed mode flag to Settings provider.
123      *
124      * @param context App context
125      * @param enable  enable or disable when taping app to exit one-handed mode.
126      */
setTapsAppToExitEnabled(Context context, boolean enable)127     public static boolean setTapsAppToExitEnabled(Context context, boolean enable) {
128         return Settings.Secure.putIntForUser(context.getContentResolver(),
129                 Settings.Secure.TAPS_APP_TO_EXIT, enable ? ON : OFF, sCurrentUserId);
130     }
131 
132     /**
133      * Gets one-handed mode timeout value from Settings provider.
134      *
135      * @param context App context
136      * @return timeout value in seconds.
137      */
getTimeoutValue(Context context)138     public static int getTimeoutValue(Context context) {
139         return Settings.Secure.getIntForUser(context.getContentResolver(),
140                 Settings.Secure.ONE_HANDED_MODE_TIMEOUT,
141                 OneHandedTimeout.MEDIUM.getValue() /* default MEDIUM(8) by UX */,
142                 sCurrentUserId);
143     }
144 
145     /**
146      * Gets current user id from OneHandedSettingsUtils
147      *
148      * @return the current user id in OneHandedSettingsUtils
149      */
getUserId()150     public static int getUserId() {
151         return sCurrentUserId;
152     }
153 
154     /**
155      * Sets specific user id for OneHandedSettingsUtils
156      *
157      * @param userId the user id to be updated
158      */
setUserId(int userId)159     public static void setUserId(int userId) {
160         sCurrentUserId = userId;
161     }
162 
163     /**
164      * Sets one-handed mode timeout value to Settings provider.
165      *
166      * @param context App context
167      * @param timeout timeout in seconds for exiting one-handed mode.
168      */
setTimeoutValue(Context context, int timeout)169     public static void setTimeoutValue(Context context, int timeout) {
170         Settings.Secure.putIntForUser(context.getContentResolver(),
171                 Settings.Secure.ONE_HANDED_MODE_TIMEOUT, timeout, sCurrentUserId);
172     }
173 
174     /**
175      * Gets Swipe-down-notification enable or disable flag from Settings provider.
176      *
177      * @param context App context
178      * @return enable or disable Swipe-down-notification flag.
179      */
isSwipeDownNotificationEnabled(Context context)180     public static boolean isSwipeDownNotificationEnabled(Context context) {
181         return Settings.Secure.getIntForUser(context.getContentResolver(),
182                 Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, OFF, sCurrentUserId) == ON;
183     }
184 
185     /**
186      * Sets Swipe-down-notification enable or disable flag to Settings provider.
187      *
188      * @param context App context
189      * @param enable enable or disable Swipe-down-notification.
190      */
setSwipeDownNotificationEnabled(Context context, boolean enable)191     public static void setSwipeDownNotificationEnabled(Context context, boolean enable) {
192         Settings.Secure.putIntForUser(context.getContentResolver(),
193                 Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, enable ? ON : OFF,
194                 sCurrentUserId);
195     }
196 
197     /**
198      * Set NavigationBar mode flag to Settings provider.
199      * @param context App context
200      * @param value Navigation bar mode:
201      *  0 = 3 button
202      *  1 = 2 button
203      *  2 = fully gestural
204      * @return true if the value was set, false on database errors.
205      */
206     @VisibleForTesting
setNavigationBarMode(Context context, String value)207     public boolean setNavigationBarMode(Context context, String value) {
208         return Settings.Secure.putStringForUser(context.getContentResolver(),
209                 Settings.Secure.NAVIGATION_MODE, value, UserHandle.myUserId());
210     }
211 
212     /**
213      * Get NavigationBar mode flag from Settings provider.
214      * @param context App context
215      * @return Navigation bar mode:
216      *  0 = 3 button
217      *  1 = 2 button
218      *  2 = fully gestural
219      */
getNavigationBarMode(Context context)220     public static int getNavigationBarMode(Context context) {
221         return Settings.Secure.getIntForUser(context.getContentResolver(),
222                 Settings.Secure.NAVIGATION_MODE, 2 /* fully gestural */, sCurrentUserId);
223     }
224 
225     /**
226      * Check if One-handed mode settings controllers can enabled or disabled.
227      * @param context App context
228      * @return true if controllers are able to enabled, false otherwise.
229      *
230      * Note: For better UX experience, just disabled controls that let users know to use
231      * this feature, they need to make sure gesture navigation is turned on in system
232      * navigation settings.
233      */
canEnableController(Context context)234     public static boolean canEnableController(Context context) {
235         return ((OneHandedSettingsUtils.isOneHandedModeEnabled(context)
236                 && getNavigationBarMode(context) != 0 /* 3-button */)
237                 || getShortcutEnabled(context));
238     }
239 
240     /**
241      * Queries one-handed mode shortcut enabled in settings or not.
242      *
243      * @return true if user enabled one-handed shortcut in settings, false otherwise.
244      */
getShortcutEnabled(Context context)245     public static boolean getShortcutEnabled(Context context) {
246         // Checks SOFTWARE_SHORTCUT_KEY
247         final String targetsSW = Settings.Secure.getStringForUser(context.getContentResolver(),
248                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, sCurrentUserId);
249         if (!TextUtils.isEmpty(targetsSW) && targetsSW.contains(ONE_HANDED_MODE_TARGET_NAME)) {
250             return true;
251         }
252 
253         // Checks HARDWARE_SHORTCUT_KEY
254         final String targetsHW = Settings.Secure.getStringForUser(context.getContentResolver(),
255                 Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, sCurrentUserId);
256         if (!TextUtils.isEmpty(targetsHW) && targetsHW.contains(ONE_HANDED_MODE_TARGET_NAME)) {
257             return true;
258         }
259 
260         if (android.view.accessibility.Flags.a11yQsShortcut()) {
261             // Checks QS_SHORTCUT_KEY
262             final String targetsQs = Settings.Secure.getStringForUser(context.getContentResolver(),
263                     Settings.Secure.ACCESSIBILITY_QS_TARGETS, sCurrentUserId);
264             if (!TextUtils.isEmpty(targetsQs) && targetsQs.contains(ONE_HANDED_MODE_TARGET_NAME)) {
265                 return true;
266             }
267         }
268 
269         return false;
270     }
271 
272     /**
273      * This is a test only API for set Shortcut enabled or not.
274      */
275     @VisibleForTesting
setShortcutEnabled(Context context, boolean enabled)276     public void setShortcutEnabled(Context context, boolean enabled) {
277         final String targetName = enabled ? ONE_HANDED_MODE_TARGET_NAME : "";
278         Settings.Secure.putStringForUser(context.getContentResolver(),
279                 Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, targetName, sCurrentUserId);
280     }
281 
282     /**
283      * Registers callback for observing Settings.Secure.ONE_HANDED_MODE_ENABLED state.
284      * @param callback for state changes
285      */
registerToggleAwareObserver(TogglesCallback callback)286     public void registerToggleAwareObserver(TogglesCallback callback) {
287         mSettingsObserver.observe();
288         mSettingsObserver.setCallback(callback);
289     }
290 
291     /**
292      * Unregisters callback for observing Settings.Secure.ONE_HANDED_MODE_ENABLED state.
293      */
unregisterToggleAwareObserver()294     public void unregisterToggleAwareObserver() {
295         final ContentResolver resolver = mContext.getContentResolver();
296         resolver.unregisterContentObserver(mSettingsObserver);
297     }
298 
299     private final class SettingsObserver extends ContentObserver {
300         private TogglesCallback mCallback;
301 
SettingsObserver(Handler handler)302         SettingsObserver(Handler handler) {
303             super(handler);
304         }
305 
setCallback(TogglesCallback callback)306         private void setCallback(TogglesCallback callback) {
307             mCallback = callback;
308         }
309 
observe()310         public void observe() {
311             final ContentResolver resolver = mContext.getContentResolver();
312             resolver.registerContentObserver(ONE_HANDED_MODE_ENABLED_URI, true, this);
313             resolver.registerContentObserver(SHOW_NOTIFICATION_ENABLED_URI, true, this);
314             resolver.registerContentObserver(SOFTWARE_SHORTCUT_ENABLED_URI, true, this);
315             resolver.registerContentObserver(HARDWARE_SHORTCUT_ENABLED_URI, true, this);
316             if (android.view.accessibility.Flags.a11yQsShortcut()) {
317                 resolver.registerContentObserver(QS_SHORTCUT_ENABLED_URI, true, this);
318             }
319         }
320 
321         @Override
onChange(boolean selfChange, Uri uri)322         public void onChange(boolean selfChange, Uri uri) {
323             if (mCallback != null) mCallback.onChange(uri);
324         }
325     }
326 
327     /**
328      * An interface for when Settings.Secure key state changes.
329      */
330     public interface TogglesCallback {
331         /**
332          * Callback method for Settings.Secure key state changes.
333          *
334          * @param uri The Uri of the changed content.
335          */
onChange(Uri uri)336         void onChange(Uri uri);
337     }
338 }
339