• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.launcher3.util;
18 
19 import static android.provider.Settings.System.ACCELEROMETER_ROTATION;
20 
21 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
22 
23 import android.content.ContentResolver;
24 import android.content.Context;
25 import android.database.ContentObserver;
26 import android.net.Uri;
27 import android.os.Handler;
28 import android.os.Looper;
29 import android.provider.Settings;
30 
31 import androidx.annotation.UiThread;
32 
33 import com.android.launcher3.dagger.ApplicationContext;
34 import com.android.launcher3.dagger.LauncherAppSingleton;
35 import com.android.launcher3.dagger.LauncherBaseAppComponent;
36 
37 import java.util.List;
38 import java.util.Map;
39 import java.util.concurrent.ConcurrentHashMap;
40 import java.util.concurrent.CopyOnWriteArrayList;
41 import java.util.function.Function;
42 
43 import javax.inject.Inject;
44 
45 /**
46  * ContentObserver over Settings keys that also has a caching layer.
47  * Consumers can register for callbacks via {@link #register(Uri, OnChangeListener)} and
48  * {@link #unregister(Uri, OnChangeListener)} methods.
49  *
50  * This can be used as a normal cache without any listeners as well via the
51  * {@link #getValue(Uri, int)} and {@link #onChange)} to update (and subsequently call
52  * get)
53  *
54  * The cache will be invalidated/updated through the normal
55  * {@link ContentObserver#onChange(boolean)} calls
56  *
57  * Cache will also be updated if a key queried is missing (even if it has no listeners registered).
58  */
59 @LauncherAppSingleton
60 public class SettingsCache extends ContentObserver {
61 
62     /** Hidden field Settings.Secure.NOTIFICATION_BADGING */
63     public static final Uri NOTIFICATION_BADGING_URI =
64             Settings.Secure.getUriFor("notification_badging");
65     /** Hidden field Settings.Secure.ONE_HANDED_MODE_ENABLED */
66     public static final String ONE_HANDED_ENABLED = "one_handed_mode_enabled";
67     /** Hidden field Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED */
68     public static final String ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED =
69             "swipe_bottom_to_notification_enabled";
70     /** Hidden field Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT */
71     public static final Uri PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI =
72             Settings.Secure.getUriFor("hide_privatespace_entry_point");
73     public static final Uri ROTATION_SETTING_URI =
74             Settings.System.getUriFor(ACCELEROMETER_ROTATION);
75     /** Hidden field {@link Settings.System#TOUCHPAD_NATURAL_SCROLLING}. */
76     public static final Uri TOUCHPAD_NATURAL_SCROLLING = Settings.System.getUriFor(
77             "touchpad_natural_scrolling");
78 
79     private static final String SYSTEM_URI_PREFIX = Settings.System.CONTENT_URI.toString();
80     private static final String GLOBAL_URI_PREFIX = Settings.Global.CONTENT_URI.toString();
81 
82     private final Function<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMapper = uri -> {
83         registerUriAsync(uri);
84         return new CopyOnWriteArrayList<>();
85     };
86 
87     /**
88      * Caches the last seen value for registered keys.
89      */
90     private final Map<Uri, Boolean> mKeyCache = new ConcurrentHashMap<>();
91     private final Map<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMap =
92             new ConcurrentHashMap<>();
93     protected final ContentResolver mResolver;
94 
95     /**
96      * Singleton instance
97      */
98     public static final DaggerSingletonObject<SettingsCache> INSTANCE =
99             new DaggerSingletonObject<>(LauncherBaseAppComponent::getSettingsCache);
100 
101     @Inject
SettingsCache(@pplicationContext Context context, DaggerSingletonTracker tracker)102     SettingsCache(@ApplicationContext Context context, DaggerSingletonTracker tracker) {
103         super(new Handler(Looper.getMainLooper()));
104         mResolver = context.getContentResolver();
105         tracker.addCloseable(() ->
106                 UI_HELPER_EXECUTOR.execute(() -> mResolver.unregisterContentObserver(this)));
107     }
108 
109     @Override
onChange(boolean selfChange, Uri uri)110     public void onChange(boolean selfChange, Uri uri) {
111         // We use default of 1, but if we're getting an onChange call, can assume a non-default
112         // value will exist
113         boolean newVal = updateValue(uri, 1 /* Effectively Unused */);
114         List<OnChangeListener> listeners = mListenerMap.get(uri);
115         if (listeners == null) {
116             return;
117         }
118 
119         for (OnChangeListener listener : listeners) {
120             listener.onSettingsChanged(newVal);
121         }
122     }
123 
124     /**
125      * Returns the value for this classes key from the cache. If not in cache, will call
126      * {@link #updateValue(Uri, int)} to fetch.
127      */
getValue(Uri keySetting)128     public boolean getValue(Uri keySetting) {
129         return getValue(keySetting, 1);
130     }
131 
132     /**
133      * Returns the value for this classes key from the cache. If not in cache, will call
134      * {@link #updateValue(Uri, int)} to fetch.
135      */
getValue(Uri keySetting, int defaultValue)136     public boolean getValue(Uri keySetting, int defaultValue) {
137         if (mKeyCache.containsKey(keySetting)) {
138             return mKeyCache.get(keySetting);
139         } else {
140             return updateValue(keySetting, defaultValue);
141         }
142     }
143 
registerUriAsync(Uri uri)144     private void registerUriAsync(Uri uri) {
145         UI_HELPER_EXECUTOR.execute(() -> mResolver.registerContentObserver(uri, false, this));
146     }
147 
148     /**
149      * Does not de-dupe if you add same listeners for the same key multiple times.
150      * Unregister once complete using {@link #unregister(Uri, OnChangeListener)}
151      */
152     @UiThread
register(Uri uri, OnChangeListener changeListener)153     public void register(Uri uri, OnChangeListener changeListener) {
154         mListenerMap.computeIfAbsent(uri, mListenerMapper).add(changeListener);
155     }
156 
updateValue(Uri keyUri, int defaultValue)157     private boolean updateValue(Uri keyUri, int defaultValue) {
158         String key = keyUri.getLastPathSegment();
159         boolean newVal;
160         if (keyUri.toString().startsWith(SYSTEM_URI_PREFIX)) {
161             newVal = Settings.System.getInt(mResolver, key, defaultValue) == 1;
162         } else if (keyUri.toString().startsWith(GLOBAL_URI_PREFIX)) {
163             newVal = Settings.Global.getInt(mResolver, key, defaultValue) == 1;
164         } else { // SETTING_SECURE
165             newVal = Settings.Secure.getInt(mResolver, key, defaultValue) == 1;
166         }
167 
168         mKeyCache.put(keyUri, newVal);
169         return newVal;
170     }
171 
172     /**
173      * Call to stop receiving updates on the given {@param listener}.
174      * This Uri/Listener pair must correspond to the same pair called with for
175      * {@link #register(Uri, OnChangeListener)}
176      */
unregister(Uri uri, OnChangeListener listener)177     public void unregister(Uri uri, OnChangeListener listener) {
178         List<OnChangeListener> listenersToRemoveFrom = mListenerMap.get(uri);
179         if (listenersToRemoveFrom != null) {
180             listenersToRemoveFrom.remove(listener);
181         }
182     }
183 
184     public interface OnChangeListener {
onSettingsChanged(boolean isEnabled)185         void onSettingsChanged(boolean isEnabled);
186     }
187 }
188