/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.settings.slices; import android.annotation.MainThread; import android.annotation.Nullable; import android.content.Context; import android.net.Uri; import android.util.ArrayMap; import android.util.Log; import java.io.Closeable; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * The Slice background worker is used to make Settings Slices be able to work with data that is * changing continuously, e.g. available Wi-Fi networks. * * The background worker will be started at {@link SettingsSliceProvider#onSlicePinned(Uri)}, be * stopped at {@link SettingsSliceProvider#onSliceUnpinned(Uri)}, and be closed at {@link * SettingsSliceProvider#shutdown()}. * * {@link SliceBackgroundWorker} caches the results, uses the cache to compare if there is any data * changed, and then notifies the Slice {@link Uri} to update. * * It also stores all instances of all workers to ensure each worker is a Singleton. */ public abstract class SliceBackgroundWorker implements Closeable { private static final String TAG = "SliceBackgroundWorker"; private static final Map LIVE_WORKERS = new ArrayMap<>(); private final Context mContext; private final Uri mUri; private List mCachedResults; protected SliceBackgroundWorker(Context context, Uri uri) { mContext = context; mUri = uri; } protected Uri getUri() { return mUri; } protected Context getContext() { return mContext; } /** * Returns the singleton instance of {@link SliceBackgroundWorker} for specified {@link Uri} if * exists */ @Nullable @SuppressWarnings("TypeParameterUnusedInFormals") public static T getInstance(Uri uri) { return (T) LIVE_WORKERS.get(uri); } /** * Returns the singleton instance of {@link SliceBackgroundWorker} for specified {@link * CustomSliceable} */ static SliceBackgroundWorker getInstance(Context context, Sliceable sliceable, Uri uri) { SliceBackgroundWorker worker = getInstance(uri); if (worker == null) { final Class workerClass = sliceable.getBackgroundWorkerClass(); worker = createInstance(context.getApplicationContext(), uri, workerClass); LIVE_WORKERS.put(uri, worker); } return worker; } private static SliceBackgroundWorker createInstance(Context context, Uri uri, Class clazz) { Log.d(TAG, "create instance: " + clazz); try { return clazz.getConstructor(Context.class, Uri.class).newInstance(context, uri); } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { throw new IllegalStateException( "Invalid slice background worker: " + clazz, e); } } static void shutdown() { for (SliceBackgroundWorker worker : LIVE_WORKERS.values()) { try { worker.close(); } catch (IOException e) { Log.w(TAG, "Shutting down worker failed", e); } } LIVE_WORKERS.clear(); } /** * Called when the Slice is pinned. This is the place to register callbacks or initialize scan * tasks. */ @MainThread protected abstract void onSlicePinned(); /** * Called when the Slice is unpinned. This is the place to unregister callbacks or perform any * final cleanup. */ @MainThread protected abstract void onSliceUnpinned(); /** * @return a {@link List} of cached results */ public final List getResults() { return mCachedResults == null ? null : new ArrayList<>(mCachedResults); } /** * Update the results when data changes */ protected final void updateResults(List results) { boolean needNotify = false; if (results == null) { if (mCachedResults != null) { needNotify = true; } } else { needNotify = !areListsTheSame(results, mCachedResults); } if (needNotify) { mCachedResults = results; notifySliceChange(); } } protected boolean areListsTheSame(List a, List b) { return a.equals(b); } /** * Notify that data was updated and attempt to sync changes to the Slice. */ protected final void notifySliceChange() { mContext.getContentResolver().notifyChange(mUri, null); } }