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