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.slices; 18 19 import android.annotation.MainThread; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.net.Uri; 23 import android.util.ArrayMap; 24 import android.util.Log; 25 26 import java.io.Closeable; 27 import java.io.IOException; 28 import java.lang.reflect.InvocationTargetException; 29 import java.util.ArrayList; 30 import java.util.List; 31 import java.util.Map; 32 33 /** 34 * The Slice background worker is used to make Settings Slices be able to work with data that is 35 * changing continuously, e.g. available Wi-Fi networks. 36 * 37 * The background worker will be started at {@link SettingsSliceProvider#onSlicePinned(Uri)}, be 38 * stopped at {@link SettingsSliceProvider#onSliceUnpinned(Uri)}, and be closed at {@link 39 * SettingsSliceProvider#shutdown()}. 40 * 41 * {@link SliceBackgroundWorker} caches the results, uses the cache to compare if there is any data 42 * changed, and then notifies the Slice {@link Uri} to update. 43 * 44 * It also stores all instances of all workers to ensure each worker is a Singleton. 45 */ 46 public abstract class SliceBackgroundWorker<E> implements Closeable { 47 48 private static final String TAG = "SliceBackgroundWorker"; 49 50 private static final Map<Uri, SliceBackgroundWorker> LIVE_WORKERS = new ArrayMap<>(); 51 52 private final Context mContext; 53 private final Uri mUri; 54 55 private List<E> mCachedResults; 56 SliceBackgroundWorker(Context context, Uri uri)57 protected SliceBackgroundWorker(Context context, Uri uri) { 58 mContext = context; 59 mUri = uri; 60 } 61 getUri()62 protected Uri getUri() { 63 return mUri; 64 } 65 getContext()66 protected Context getContext() { 67 return mContext; 68 } 69 70 /** 71 * Returns the singleton instance of {@link SliceBackgroundWorker} for specified {@link Uri} if 72 * exists 73 */ 74 @Nullable 75 @SuppressWarnings("TypeParameterUnusedInFormals") getInstance(Uri uri)76 public static <T extends SliceBackgroundWorker> T getInstance(Uri uri) { 77 return (T) LIVE_WORKERS.get(uri); 78 } 79 80 /** 81 * Returns the singleton instance of {@link SliceBackgroundWorker} for specified {@link 82 * CustomSliceable} 83 */ getInstance(Context context, Sliceable sliceable, Uri uri)84 static SliceBackgroundWorker getInstance(Context context, Sliceable sliceable, Uri uri) { 85 SliceBackgroundWorker worker = getInstance(uri); 86 if (worker == null) { 87 final Class<? extends SliceBackgroundWorker> workerClass = 88 sliceable.getBackgroundWorkerClass(); 89 worker = createInstance(context.getApplicationContext(), uri, workerClass); 90 LIVE_WORKERS.put(uri, worker); 91 } 92 return worker; 93 } 94 createInstance(Context context, Uri uri, Class<? extends SliceBackgroundWorker> clazz)95 private static SliceBackgroundWorker createInstance(Context context, Uri uri, 96 Class<? extends SliceBackgroundWorker> clazz) { 97 Log.d(TAG, "create instance: " + clazz); 98 try { 99 return clazz.getConstructor(Context.class, Uri.class).newInstance(context, uri); 100 } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | 101 InvocationTargetException e) { 102 throw new IllegalStateException( 103 "Invalid slice background worker: " + clazz, e); 104 } 105 } 106 shutdown()107 static void shutdown() { 108 for (SliceBackgroundWorker worker : LIVE_WORKERS.values()) { 109 try { 110 worker.close(); 111 } catch (IOException e) { 112 Log.w(TAG, "Shutting down worker failed", e); 113 } 114 } 115 LIVE_WORKERS.clear(); 116 } 117 118 /** 119 * Called when the Slice is pinned. This is the place to register callbacks or initialize scan 120 * tasks. 121 */ 122 @MainThread onSlicePinned()123 protected abstract void onSlicePinned(); 124 125 /** 126 * Called when the Slice is unpinned. This is the place to unregister callbacks or perform any 127 * final cleanup. 128 */ 129 @MainThread onSliceUnpinned()130 protected abstract void onSliceUnpinned(); 131 132 /** 133 * @return a {@link List} of cached results 134 */ getResults()135 public final List<E> getResults() { 136 return mCachedResults == null ? null : new ArrayList<>(mCachedResults); 137 } 138 139 /** 140 * Update the results when data changes 141 */ updateResults(List<E> results)142 protected final void updateResults(List<E> results) { 143 boolean needNotify = false; 144 145 if (results == null) { 146 if (mCachedResults != null) { 147 needNotify = true; 148 } 149 } else { 150 needNotify = !areListsTheSame(results, mCachedResults); 151 } 152 153 if (needNotify) { 154 mCachedResults = results; 155 notifySliceChange(); 156 } 157 } 158 areListsTheSame(List<E> a, List<E> b)159 protected boolean areListsTheSame(List<E> a, List<E> b) { 160 return a.equals(b); 161 } 162 163 /** 164 * Notify that data was updated and attempt to sync changes to the Slice. 165 */ notifySliceChange()166 protected final void notifySliceChange() { 167 mContext.getContentResolver().notifyChange(mUri, null); 168 } 169 } 170