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