• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.launcher3.model;
18 
19 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
20 
21 import static java.lang.annotation.RetentionPolicy.SOURCE;
22 
23 import android.content.Context;
24 import android.content.pm.LauncherActivityInfo;
25 import android.content.pm.LauncherApps;
26 import android.os.Process;
27 import android.util.ArrayMap;
28 import android.util.Log;
29 
30 import androidx.annotation.IntDef;
31 import androidx.annotation.Nullable;
32 import androidx.annotation.WorkerThread;
33 import androidx.room.Room;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 import com.android.launcher3.model.AppShareabilityDatabase.ShareabilityDao;
37 import com.android.launcher3.util.MainThreadInitializedObject;
38 
39 import java.lang.annotation.Retention;
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.function.Consumer;
44 
45 /**
46  * This class maintains the shareability status of installed apps.
47  * Each app's status is retrieved from the Play Store's API. Statuses are cached in order
48  * to limit extraneous calls to that API (which can be time-consuming).
49  */
50 public class AppShareabilityManager {
51     @Retention(SOURCE)
52     @IntDef({
53         ShareabilityStatus.UNKNOWN,
54         ShareabilityStatus.NOT_SHAREABLE,
55         ShareabilityStatus.SHAREABLE
56     })
57     public @interface ShareabilityStatus {
58         int UNKNOWN = 0;
59         int NOT_SHAREABLE = 1;
60         int SHAREABLE = 2;
61     }
62 
63     private static final String TAG = "AppShareabilityManager";
64     private static final String DB_NAME = "shareabilityDatabase";
65     public static MainThreadInitializedObject<AppShareabilityManager> INSTANCE =
66             new MainThreadInitializedObject<>(AppShareabilityManager::new);
67 
68     private final Context mContext;
69     // Local map to store the data in memory for quick access
70     private final Map<String, Integer> mDataMap;
71     // Database to persist the data across reboots
72     private AppShareabilityDatabase mDatabase;
73     // Data Access Object for the database
74     private ShareabilityDao mDao;
75     // Class to perform shareability checks
76     private AppShareabilityChecker mShareChecker;
77 
AppShareabilityManager(Context context)78     private AppShareabilityManager(Context context) {
79         mContext = context;
80         mDataMap = new ArrayMap<>();
81         mDatabase = Room.databaseBuilder(mContext, AppShareabilityDatabase.class, DB_NAME).build();
82         mDao = mDatabase.shareabilityDao();
83         MODEL_EXECUTOR.post(this::readFromDB);
84     }
85 
86     /**
87      * Set the shareability checker. The checker determines whether given apps are shareable.
88      * This must be set before the manager can update its data.
89      * @param checker Implementation of AppShareabilityChecker to perform the checks
90      */
setShareabilityChecker(AppShareabilityChecker checker)91     public void setShareabilityChecker(AppShareabilityChecker checker) {
92         mShareChecker = checker;
93     }
94 
95     /**
96      * Retrieve the ShareabilityStatus of an app from the local map
97      * This does not interact with the saved database
98      * @param packageName The app's package name
99      * @return The status as a ShareabilityStatus integer
100      */
getStatus(String packageName)101     public synchronized @ShareabilityStatus int getStatus(String packageName) {
102         @ShareabilityStatus int status = ShareabilityStatus.UNKNOWN;
103         if (mDataMap.containsKey(packageName)) {
104             status = mDataMap.get(packageName);
105         }
106         return status;
107     }
108 
109     /**
110      * Set the status of a given app. This updates the local map as well as the saved database.
111      */
setStatus(String packageName, @ShareabilityStatus int status)112     public synchronized void setStatus(String packageName, @ShareabilityStatus int status) {
113         mDataMap.put(packageName, status);
114 
115         // Write to the database on a separate thread
116         MODEL_EXECUTOR.post(() ->
117                 mDao.insertAppStatus(new AppShareabilityStatus(packageName, status)));
118     }
119 
120     /**
121      * Set the statuses of given apps. This updates the local map as well as the saved database.
122      */
setStatuses(List<AppShareabilityStatus> statuses)123     public synchronized void setStatuses(List<AppShareabilityStatus> statuses) {
124         for (int i = 0, size = statuses.size(); i < size; i++) {
125             AppShareabilityStatus entry = statuses.get(i);
126             mDataMap.put(entry.packageName, entry.status);
127         }
128 
129         // Write to the database on a separate thread
130         MODEL_EXECUTOR.post(() ->
131                 mDao.insertAppStatuses(statuses.toArray(new AppShareabilityStatus[0])));
132     }
133 
134     /**
135      * Request a status update for a specific app
136      * @param packageName The app's package name
137      * @param callback Optional callback to be called when the update is complete. The received
138      *                 Boolean denotes whether the update was successful.
139      */
requestAppStatusUpdate(String packageName, @Nullable Consumer<Boolean> callback)140     public void requestAppStatusUpdate(String packageName, @Nullable Consumer<Boolean> callback) {
141         MODEL_EXECUTOR.post(() -> updateCache(packageName, callback));
142     }
143 
144     /**
145      * Request a status update for all apps
146      */
requestFullUpdate()147     public void requestFullUpdate() {
148         MODEL_EXECUTOR.post(this::updateCache);
149     }
150 
151     /**
152      * Update the cached shareability data for all installed apps
153      */
154     @WorkerThread
updateCache()155     private void updateCache() {
156         updateCache(/* packageName */ null, /* callback */ null);
157     }
158 
159     /**
160      * Update the cached shareability data
161      * @param packageName A specific package to update. If null, all installed apps will be updated.
162      * @param callback Optional callback to be called when the update is complete. The received
163      *                 Boolean denotes whether the update was successful.
164      */
165     @WorkerThread
updateCache(@ullable String packageName, @Nullable Consumer<Boolean> callback)166     private void updateCache(@Nullable String packageName, @Nullable Consumer<Boolean> callback) {
167         if (mShareChecker == null) {
168             Log.e(TAG, "AppShareabilityChecker not set");
169             return;
170         }
171 
172         List<String> packageNames = new ArrayList<>();
173         if (packageName != null) {
174             packageNames.add(packageName);
175         } else {
176             LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
177             List<LauncherActivityInfo> installedApps =
178                     launcherApps.getActivityList(/* packageName */ null, Process.myUserHandle());
179             for (int i = 0, size = installedApps.size(); i < size; i++) {
180                 packageNames.add(installedApps.get(i).getApplicationInfo().packageName);
181             }
182         }
183 
184         mShareChecker.checkApps(packageNames, this, callback);
185     }
186 
187     @WorkerThread
readFromDB()188     private synchronized void readFromDB() {
189         mDataMap.clear();
190         List<AppShareabilityStatus> entries = mDao.getAllEntries();
191         for (int i = 0, size = entries.size(); i < size; i++) {
192             AppShareabilityStatus entry = entries.get(i);
193             mDataMap.put(entry.packageName, entry.status);
194         }
195     }
196 
197     /**
198      * Provides a testable instance of this class
199      * This instance allows database queries on the main thread
200      * @hide */
201     @VisibleForTesting
getTestInstance(Context context)202     public static AppShareabilityManager getTestInstance(Context context) {
203         AppShareabilityManager manager = new AppShareabilityManager(context);
204         manager.mDatabase.close();
205         manager.mDatabase = Room.inMemoryDatabaseBuilder(context, AppShareabilityDatabase.class)
206                 .allowMainThreadQueries()
207                 .build();
208         manager.mDao = manager.mDatabase.shareabilityDao();
209         return manager;
210     }
211 }
212