• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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