diff options
| -rw-r--r-- | api/current.txt | 6 | ||||
| -rw-r--r-- | core/java/android/app/SharedPreferencesImpl.java | 66 | ||||
| -rw-r--r-- | core/java/android/content/SharedPreferences.java | 58 |
3 files changed, 125 insertions, 5 deletions
diff --git a/api/current.txt b/api/current.txt index 0a8db4611043..a7b38a5d5b10 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10834,7 +10834,9 @@ package android.content { method @Nullable public String getString(String, @Nullable String); method @Nullable public java.util.Set<java.lang.String> getStringSet(String, @Nullable java.util.Set<java.lang.String>); method public void registerOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener); + method public default void registerOnSharedPreferencesClearListener(@NonNull android.content.SharedPreferences.OnSharedPreferencesClearListener); method public void unregisterOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener); + method public default void unregisterOnSharedPreferencesClearListener(@NonNull android.content.SharedPreferences.OnSharedPreferencesClearListener); } public static interface SharedPreferences.Editor { @@ -10854,6 +10856,10 @@ package android.content { method public void onSharedPreferenceChanged(android.content.SharedPreferences, String); } + public static interface SharedPreferences.OnSharedPreferencesClearListener { + method public void onSharedPreferencesClear(@NonNull android.content.SharedPreferences, @NonNull java.util.Set<java.lang.String>); + } + public class SyncAdapterType implements android.os.Parcelable { ctor public SyncAdapterType(String, String, boolean, boolean); ctor public SyncAdapterType(android.os.Parcel); diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java index 0f8976fe924a..9162626e1b37 100644 --- a/core/java/android/app/SharedPreferencesImpl.java +++ b/core/java/android/app/SharedPreferencesImpl.java @@ -16,6 +16,7 @@ package android.app; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UnsupportedAppUsage; import android.content.SharedPreferences; @@ -25,6 +26,7 @@ import android.system.ErrnoException; import android.system.Os; import android.system.StructStat; import android.system.StructTimespec; +import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -92,6 +94,10 @@ final class SharedPreferencesImpl implements SharedPreferences { private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners = new WeakHashMap<OnSharedPreferenceChangeListener, Object>(); + @GuardedBy("mLock") + private final WeakHashMap<OnSharedPreferencesClearListener, Object> mClearListeners = + new WeakHashMap<>(); + /** Current memory state (always increasing) */ @GuardedBy("this") private long mCurrentMemoryStateGeneration; @@ -252,6 +258,28 @@ final class SharedPreferencesImpl implements SharedPreferences { } } + @Override + public void registerOnSharedPreferencesClearListener( + @NonNull OnSharedPreferencesClearListener listener) { + if (listener == null) { + throw new IllegalArgumentException("listener cannot be null."); + } + synchronized (mLock) { + mClearListeners.put(listener, CONTENT); + } + } + + @Override + public void unregisterOnSharedPreferencesClearListener( + @NonNull OnSharedPreferencesClearListener listener) { + if (listener == null) { + throw new IllegalArgumentException("listener cannot be null."); + } + synchronized (mLock) { + mClearListeners.remove(listener); + } + } + @GuardedBy("mLock") private void awaitLoadedLocked() { if (!mLoaded) { @@ -361,7 +389,9 @@ final class SharedPreferencesImpl implements SharedPreferences { private static class MemoryCommitResult { final long memoryStateGeneration; @Nullable final List<String> keysModified; + @Nullable final Set<String> keysCleared; @Nullable final Set<OnSharedPreferenceChangeListener> listeners; + @Nullable final Set<OnSharedPreferencesClearListener> clearListeners; final Map<String, Object> mapToWriteToDisk; final CountDownLatch writtenToDiskLatch = new CountDownLatch(1); @@ -371,10 +401,14 @@ final class SharedPreferencesImpl implements SharedPreferences { private MemoryCommitResult(long memoryStateGeneration, @Nullable List<String> keysModified, @Nullable Set<OnSharedPreferenceChangeListener> listeners, + @Nullable Set<String> keysCleared, + @Nullable Set<OnSharedPreferencesClearListener> clearListeners, Map<String, Object> mapToWriteToDisk) { this.memoryStateGeneration = memoryStateGeneration; this.keysModified = keysModified; this.listeners = listeners; + this.keysCleared = keysCleared; + this.clearListeners = clearListeners; this.mapToWriteToDisk = mapToWriteToDisk; } @@ -492,13 +526,16 @@ final class SharedPreferencesImpl implements SharedPreferences { // SharedPreferences instance back, which has the // changes reflected in memory. notifyListeners(mcr); + notifyClearListeners(mcr); } // Returns true if any changes were made private MemoryCommitResult commitToMemory() { long memoryStateGeneration; List<String> keysModified = null; + Set<String> keysCleared = null; Set<OnSharedPreferenceChangeListener> listeners = null; + Set<OnSharedPreferencesClearListener> clearListeners = null; Map<String, Object> mapToWriteToDisk; synchronized (SharedPreferencesImpl.this.mLock) { @@ -520,12 +557,20 @@ final class SharedPreferencesImpl implements SharedPreferences { keysModified = new ArrayList<String>(); listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet()); } + boolean hasClearListeners = !mClearListeners.isEmpty(); + if (hasClearListeners) { + keysCleared = new ArraySet<>(); + clearListeners = new HashSet<>(mClearListeners.keySet()); + } synchronized (mEditorLock) { boolean changesMade = false; if (mClear) { if (!mapToWriteToDisk.isEmpty()) { + if (hasClearListeners) { + keysCleared.addAll(mapToWriteToDisk.keySet()); + } changesMade = true; mapToWriteToDisk.clear(); } @@ -569,7 +614,7 @@ final class SharedPreferencesImpl implements SharedPreferences { } } return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners, - mapToWriteToDisk); + keysCleared, clearListeners, mapToWriteToDisk); } @Override @@ -596,6 +641,7 @@ final class SharedPreferencesImpl implements SharedPreferences { } } notifyListeners(mcr); + notifyClearListeners(mcr); return mcr.writeToDiskResult; } @@ -618,6 +664,24 @@ final class SharedPreferencesImpl implements SharedPreferences { ActivityThread.sMainThreadHandler.post(() -> notifyListeners(mcr)); } } + + private void notifyClearListeners(final MemoryCommitResult mcr) { + if (mcr.clearListeners == null || mcr.keysCleared == null + || mcr.keysCleared.isEmpty()) { + return; + } + if (Looper.myLooper() == Looper.getMainLooper()) { + for (OnSharedPreferencesClearListener listener : mcr.clearListeners) { + if (listener != null) { + listener.onSharedPreferencesClear(SharedPreferencesImpl.this, + mcr.keysCleared); + } + } + } else { + // Run this function on the main thread. + ActivityThread.sMainThreadHandler.post(() -> notifyClearListeners(mcr)); + } + } } /** diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java index 877dfee138da..9d87e2550c95 100644 --- a/core/java/android/content/SharedPreferences.java +++ b/core/java/android/content/SharedPreferences.java @@ -16,6 +16,7 @@ package android.content; +import android.annotation.NonNull; import android.annotation.Nullable; import java.util.Map; @@ -58,7 +59,9 @@ public interface SharedPreferences { * <p>This callback will be run on your main thread. * * <p><em>Note: This callback will not be triggered when preferences are cleared via - * {@link Editor#clear()}.</em> + * {@link Editor#clear()}. However, from {@link android.os.Build.VERSION_CODES#R Android R} + * onwards, you can use {@link OnSharedPreferencesClearListener} to register for + * {@link Editor#clear()} callbacks.</em> * * @param sharedPreferences The {@link SharedPreferences} that received * the change. @@ -67,7 +70,23 @@ public interface SharedPreferences { */ void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key); } - + + /** + * Interface definition for a callback to be invoked when shared preferences are cleared. + */ + public interface OnSharedPreferencesClearListener { + /** + * Called when shared preferences are cleared via {@link Editor#clear()}. + * + * <p>This callback will be run on your main thread. + * + * @param sharedPreferences The {@link SharedPreferences} that received the change. + * @param keys The set of keys that were cleared. + */ + void onSharedPreferencesClear(@NonNull SharedPreferences sharedPreferences, + @NonNull Set<String> keys); + } + /** * Interface used for modifying values in a {@link SharedPreferences} * object. All changes you make in an editor are batched, and not copied @@ -378,12 +397,43 @@ public interface SharedPreferences { * @see #unregisterOnSharedPreferenceChangeListener */ void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener); - + /** * Unregisters a previous callback. - * + * * @param listener The callback that should be unregistered. * @see #registerOnSharedPreferenceChangeListener */ void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener); + + /** + * Registers a callback to be invoked when preferences are cleared via {@link Editor#clear()}. + * + * <p class="caution"><strong>Caution:</strong> The preference manager does + * not currently store a strong reference to the listener. You must store a + * strong reference to the listener, or it will be susceptible to garbage + * collection. We recommend you keep a reference to the listener in the + * instance data of an object that will exist as long as you need the + * listener.</p> + * + * @param listener The callback that will run. + * @see #unregisterOnSharedPreferencesClearListener + */ + default void registerOnSharedPreferencesClearListener( + @NonNull OnSharedPreferencesClearListener listener) { + throw new UnsupportedOperationException( + "registerOnSharedPreferencesClearListener not implemented"); + } + + /** + * Unregisters a previous callback for {@link Editor#clear()}. + * + * @param listener The callback that should be unregistered. + * @see #registerOnSharedPreferencesClearListener + */ + default void unregisterOnSharedPreferencesClearListener( + @NonNull OnSharedPreferencesClearListener listener) { + throw new UnsupportedOperationException( + "unregisterOnSharedPreferencesClearListener not implemented"); + } } |