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