1 /* 2 * Copyright 2019 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.car.settings.applications.specialaccess; 18 19 import android.app.Application; 20 import android.content.Context; 21 import android.os.Handler; 22 import android.os.Looper; 23 import android.os.Message; 24 25 import androidx.annotation.Nullable; 26 27 import com.android.settingslib.applications.ApplicationsState; 28 29 import java.lang.ref.WeakReference; 30 import java.util.ArrayList; 31 import java.util.Collections; 32 import java.util.List; 33 34 /** 35 * Manages a list of {@link ApplicationsState.AppEntry} instances by syncing in the background and 36 * providing updates via a {@link Callback}. Clients may provide an {@link ExtraInfoBridge} to 37 * populate the {@link ApplicationsState.AppEntry#extraInfo} field with use case sepecific data. 38 * Clients may also provide an {@link ApplicationsState.AppFilter} via an {@link AppFilterProvider} 39 * to determine which entries will appear in the list updates. 40 * 41 * <p>Clients should call {@link #init(ExtraInfoBridge, AppFilterProvider, Callback)} to specify 42 * behavior and then {@link #start()} to begin loading. {@link #stop()} will cancel loading, and 43 * {@link #destroy()} will clean up resources when this class will no longer be used. 44 */ 45 public class AppEntryListManager { 46 47 /** Callback for receiving events from {@link AppEntryListManager}. */ 48 public interface Callback { 49 /** 50 * Called when the list of {@link ApplicationsState.AppEntry} instances or the {@link 51 * ApplicationsState.AppEntry#extraInfo} fields have changed. 52 */ onAppEntryListChanged(List<ApplicationsState.AppEntry> entries)53 void onAppEntryListChanged(List<ApplicationsState.AppEntry> entries); 54 } 55 56 /** 57 * Provides an {@link ApplicationsState.AppFilter} to tailor the entries in the list updates. 58 */ 59 public interface AppFilterProvider { 60 /** 61 * Returns the filter that should be used to trim the entries list before callback delivery. 62 */ getAppFilter()63 ApplicationsState.AppFilter getAppFilter(); 64 } 65 66 /** Bridges extra information to {@link ApplicationsState.AppEntry#extraInfo}. */ 67 public interface ExtraInfoBridge { 68 /** 69 * Populates the {@link ApplicationsState.AppEntry#extraInfo} field on the {@code enrties} 70 * with the relevant data for the implementation. 71 */ loadExtraInfo(List<ApplicationsState.AppEntry> entries)72 void loadExtraInfo(List<ApplicationsState.AppEntry> entries); 73 } 74 75 private final ApplicationsState.Callbacks mSessionCallbacks = 76 new ApplicationsState.Callbacks() { 77 @Override 78 public void onRunningStateChanged(boolean running) { 79 // No op. 80 } 81 82 @Override 83 public void onPackageListChanged() { 84 forceUpdate(); 85 } 86 87 @Override 88 public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) { 89 if (mCallback != null) { 90 mCallback.onAppEntryListChanged(apps); 91 } 92 } 93 94 @Override 95 public void onPackageIconChanged() { 96 // No op. 97 } 98 99 @Override 100 public void onPackageSizeChanged(String packageName) { 101 // No op. 102 } 103 104 @Override 105 public void onAllSizesComputed() { 106 // No op. 107 } 108 109 @Override 110 public void onLauncherInfoChanged() { 111 // No op. 112 } 113 114 @Override 115 public void onLoadEntriesCompleted() { 116 mHasReceivedLoadEntries = true; 117 forceUpdate(); 118 } 119 }; 120 121 private final ApplicationsState mApplicationsState; 122 private final BackgroundHandler mBackgroundHandler; 123 private final MainHandler mMainHandler; 124 125 private ExtraInfoBridge mExtraInfoBridge; 126 private AppFilterProvider mFilterProvider; 127 private Callback mCallback; 128 private ApplicationsState.Session mSession; 129 130 private boolean mHasReceivedLoadEntries; 131 private boolean mHasReceivedExtraInfo; 132 AppEntryListManager(Context context)133 public AppEntryListManager(Context context) { 134 mApplicationsState = ApplicationsState.getInstance( 135 (Application) context.getApplicationContext()); 136 // Run on the same background thread as the ApplicationsState to make sure updates don't 137 // conflict. 138 mBackgroundHandler = new BackgroundHandler(new WeakReference<>(this), 139 mApplicationsState.getBackgroundLooper()); 140 mMainHandler = new MainHandler(new WeakReference<>(this)); 141 } 142 143 /** 144 * Specifies the behavior of this manager. 145 * 146 * @param extraInfoBridge an optional bridge to load information into the entries. 147 * @param filterProvider provides a filter to tailor the contents of the list updates. 148 * @param callback callback to which updated lists are delivered. 149 */ init(@ullable ExtraInfoBridge extraInfoBridge, @Nullable AppFilterProvider filterProvider, Callback callback)150 public void init(@Nullable ExtraInfoBridge extraInfoBridge, 151 @Nullable AppFilterProvider filterProvider, 152 Callback callback) { 153 if (mSession != null) { 154 destroy(); 155 } 156 mExtraInfoBridge = extraInfoBridge; 157 mFilterProvider = filterProvider; 158 mCallback = callback; 159 mSession = mApplicationsState.newSession(mSessionCallbacks); 160 } 161 162 /** 163 * Starts loading the information in the background. When loading is finished, the {@link 164 * Callback} will be notified on the main thread. 165 */ start()166 public void start() { 167 mSession.onResume(); 168 } 169 170 /** 171 * Stops any pending loading. 172 */ stop()173 public void stop() { 174 mSession.onPause(); 175 clearHandlers(); 176 } 177 178 /** 179 * Cleans up internal state when this will no longer be used. 180 */ destroy()181 public void destroy() { 182 mSession.onDestroy(); 183 clearHandlers(); 184 mExtraInfoBridge = null; 185 mFilterProvider = null; 186 mCallback = null; 187 } 188 189 /** 190 * Schedules updates for all {@link ApplicationsState.AppEntry} instances. When loading is 191 * finished, the {@link Callback} will be notified on the main thread. 192 */ forceUpdate()193 public void forceUpdate() { 194 mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ALL); 195 } 196 197 /** 198 * Schedules an update for the given {@code entry}. When loading is finished, the {@link 199 * Callback} will be notified on the main thread. 200 */ forceUpdate(ApplicationsState.AppEntry entry)201 public void forceUpdate(ApplicationsState.AppEntry entry) { 202 mBackgroundHandler.obtainMessage(BackgroundHandler.MSG_LOAD_PKG, 203 entry).sendToTarget(); 204 } 205 rebuild()206 private void rebuild() { 207 if (!mHasReceivedLoadEntries || !mHasReceivedExtraInfo) { 208 // Don't rebuild the list until all the app entries are loaded. 209 return; 210 } 211 mSession.rebuild((mFilterProvider != null) ? mFilterProvider.getAppFilter() 212 : ApplicationsState.FILTER_EVERYTHING, 213 ApplicationsState.ALPHA_COMPARATOR, /* foreground= */ false); 214 } 215 clearHandlers()216 private void clearHandlers() { 217 mBackgroundHandler.removeMessages(BackgroundHandler.MSG_LOAD_ALL); 218 mBackgroundHandler.removeMessages(BackgroundHandler.MSG_LOAD_PKG); 219 mMainHandler.removeMessages(MainHandler.MSG_INFO_UPDATED); 220 } 221 loadInfo(List<ApplicationsState.AppEntry> entries)222 private void loadInfo(List<ApplicationsState.AppEntry> entries) { 223 if (mExtraInfoBridge != null) { 224 mExtraInfoBridge.loadExtraInfo(entries); 225 } 226 for (ApplicationsState.AppEntry entry : entries) { 227 mApplicationsState.ensureIcon(entry); 228 } 229 } 230 231 private static class BackgroundHandler extends Handler { 232 private static final int MSG_LOAD_ALL = 1; 233 private static final int MSG_LOAD_PKG = 2; 234 235 private final WeakReference<AppEntryListManager> mOuter; 236 BackgroundHandler(WeakReference<AppEntryListManager> outer, Looper looper)237 BackgroundHandler(WeakReference<AppEntryListManager> outer, Looper looper) { 238 super(looper); 239 mOuter = outer; 240 } 241 242 @Override handleMessage(Message msg)243 public void handleMessage(Message msg) { 244 AppEntryListManager outer = mOuter.get(); 245 if (outer == null) { 246 return; 247 } 248 switch (msg.what) { 249 case MSG_LOAD_ALL: 250 outer.loadInfo(outer.mSession.getAllApps()); 251 outer.mMainHandler.sendEmptyMessage(MainHandler.MSG_INFO_UPDATED); 252 break; 253 case MSG_LOAD_PKG: 254 ApplicationsState.AppEntry entry = (ApplicationsState.AppEntry) msg.obj; 255 outer.loadInfo(Collections.singletonList(entry)); 256 outer.mMainHandler.sendEmptyMessage(MainHandler.MSG_INFO_UPDATED); 257 break; 258 } 259 } 260 } 261 262 private static class MainHandler extends Handler { 263 private static final int MSG_INFO_UPDATED = 1; 264 265 private final WeakReference<AppEntryListManager> mOuter; 266 MainHandler(WeakReference<AppEntryListManager> outer)267 MainHandler(WeakReference<AppEntryListManager> outer) { 268 mOuter = outer; 269 } 270 271 @Override handleMessage(Message msg)272 public void handleMessage(Message msg) { 273 AppEntryListManager outer = mOuter.get(); 274 if (outer == null) { 275 return; 276 } 277 switch (msg.what) { 278 case MSG_INFO_UPDATED: 279 outer.mHasReceivedExtraInfo = true; 280 outer.rebuild(); 281 break; 282 } 283 } 284 } 285 } 286