1 /*
2  * Copyright (C) 2014 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.pm;
18 
19 import static com.android.launcher3.Utilities.ATLEAST_U;
20 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
21 
22 import android.content.Context;
23 import android.content.Intent;
24 import android.os.Process;
25 import android.os.UserHandle;
26 import android.os.UserManager;
27 import android.util.ArrayMap;
28 
29 import androidx.annotation.AnyThread;
30 import androidx.annotation.NonNull;
31 import androidx.annotation.Nullable;
32 import androidx.annotation.VisibleForTesting;
33 import androidx.annotation.WorkerThread;
34 
35 import com.android.launcher3.icons.BitmapInfo;
36 import com.android.launcher3.icons.UserBadgeDrawable;
37 import com.android.launcher3.util.ApiWrapper;
38 import com.android.launcher3.util.FlagOp;
39 import com.android.launcher3.util.MainThreadInitializedObject;
40 import com.android.launcher3.util.SafeCloseable;
41 import com.android.launcher3.util.SimpleBroadcastReceiver;
42 import com.android.launcher3.util.UserIconInfo;
43 
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.function.BiConsumer;
49 
50 /**
51  * Class which manages a local cache of user handles to avoid system rpc
52  */
53 public class UserCache implements SafeCloseable {
54 
55     public static final String ACTION_PROFILE_ADDED = ATLEAST_U
56             ? Intent.ACTION_PROFILE_ADDED : Intent.ACTION_MANAGED_PROFILE_ADDED;
57     public static final String ACTION_PROFILE_REMOVED = ATLEAST_U
58             ? Intent.ACTION_PROFILE_REMOVED : Intent.ACTION_MANAGED_PROFILE_REMOVED;
59 
60     public static final String ACTION_PROFILE_UNLOCKED = ATLEAST_U
61             ? Intent.ACTION_PROFILE_ACCESSIBLE : Intent.ACTION_MANAGED_PROFILE_UNLOCKED;
62     public static final String ACTION_PROFILE_LOCKED = ATLEAST_U
63             ? Intent.ACTION_PROFILE_INACCESSIBLE : Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;
64     public static final String ACTION_PROFILE_AVAILABLE = "android.intent.action.PROFILE_AVAILABLE";
65     public static final String ACTION_PROFILE_UNAVAILABLE =
66             "android.intent.action.PROFILE_UNAVAILABLE";
67 
68     public static final MainThreadInitializedObject<UserCache> INSTANCE =
69             new MainThreadInitializedObject<>(UserCache::new);
70 
71     /** Returns an instance of UserCache bound to the context provided. */
getInstance(Context context)72     public static UserCache getInstance(Context context) {
73         return INSTANCE.get(context);
74     }
75 
76     private final List<BiConsumer<UserHandle, String>> mUserEventListeners = new ArrayList<>();
77     private final SimpleBroadcastReceiver mUserChangeReceiver =
78             new SimpleBroadcastReceiver(this::onUsersChanged);
79 
80     private final Context mContext;
81 
82     @NonNull
83     private Map<UserHandle, UserIconInfo> mUserToSerialMap;
84 
85     @NonNull
86     private Map<UserHandle, List<String>> mUserToPreInstallAppMap;
87 
UserCache(Context context)88     private UserCache(Context context) {
89         mContext = context;
90         mUserToSerialMap = Collections.emptyMap();
91         MODEL_EXECUTOR.execute(this::initAsync);
92     }
93 
94     @Override
close()95     public void close() {
96         MODEL_EXECUTOR.execute(() -> mUserChangeReceiver.unregisterReceiverSafely(mContext));
97     }
98 
99     @WorkerThread
initAsync()100     private void initAsync() {
101         mUserChangeReceiver.register(mContext,
102                 Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
103                 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
104                 Intent.ACTION_MANAGED_PROFILE_REMOVED,
105                 ACTION_PROFILE_ADDED,
106                 ACTION_PROFILE_REMOVED,
107                 ACTION_PROFILE_UNLOCKED,
108                 ACTION_PROFILE_LOCKED,
109                 ACTION_PROFILE_AVAILABLE,
110                 ACTION_PROFILE_UNAVAILABLE);
111         updateCache();
112     }
113 
114     @AnyThread
onUsersChanged(Intent intent)115     private void onUsersChanged(Intent intent) {
116         MODEL_EXECUTOR.execute(this::updateCache);
117         UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
118         if (user == null) {
119             return;
120         }
121         String action = intent.getAction();
122         mUserEventListeners.forEach(l -> l.accept(user, action));
123     }
124 
125     @WorkerThread
updateCache()126     private void updateCache() {
127         mUserToSerialMap = ApiWrapper.INSTANCE.get(mContext).queryAllUsers();
128         mUserToPreInstallAppMap = fetchPreInstallApps();
129     }
130 
131     @WorkerThread
fetchPreInstallApps()132     private Map<UserHandle, List<String>> fetchPreInstallApps() {
133         Map<UserHandle, List<String>> userToPreInstallApp = new ArrayMap<>();
134         mUserToSerialMap.forEach((userHandle, userIconInfo) -> {
135             // Fetch only for private profile, as other profiles have no usages yet.
136             List<String> preInstallApp = userIconInfo.isPrivate()
137                     ? ApiWrapper.INSTANCE.get(mContext).getPreInstalledSystemPackages(userHandle)
138                     : new ArrayList<>();
139             userToPreInstallApp.put(userHandle, preInstallApp);
140         });
141         return userToPreInstallApp;
142     }
143 
144     /**
145      * Adds a listener for user additions and removals
146      */
addUserEventListener(BiConsumer<UserHandle, String> listener)147     public SafeCloseable addUserEventListener(BiConsumer<UserHandle, String> listener) {
148         mUserEventListeners.add(listener);
149         return () -> mUserEventListeners.remove(listener);
150     }
151 
152     /**
153      * @see UserManager#getSerialNumberForUser(UserHandle)
154      */
getSerialNumberForUser(UserHandle user)155     public long getSerialNumberForUser(UserHandle user) {
156         return getUserInfo(user).userSerial;
157     }
158 
159     /**
160      * Returns the user properties for the provided user or default values
161      */
162     @NonNull
getUserInfo(UserHandle user)163     public UserIconInfo getUserInfo(UserHandle user) {
164         UserIconInfo info = mUserToSerialMap.get(user);
165         return info == null ? new UserIconInfo(user, UserIconInfo.TYPE_MAIN) : info;
166     }
167 
168     /**
169      * @see UserManager#getUserForSerialNumber(long)
170      */
getUserForSerialNumber(long serialNumber)171     public UserHandle getUserForSerialNumber(long serialNumber) {
172         return mUserToSerialMap
173                 .entrySet()
174                 .stream()
175                 .filter(entry -> serialNumber == entry.getValue().userSerial)
176                 .findFirst()
177                 .map(Map.Entry::getKey)
178                 .orElse(Process.myUserHandle());
179     }
180 
181     @VisibleForTesting
putToCache(UserHandle userHandle, UserIconInfo info)182     public void putToCache(UserHandle userHandle, UserIconInfo info) {
183         mUserToSerialMap.put(userHandle, info);
184     }
185 
186     /**
187      * @see UserManager#getUserProfiles()
188      */
getUserProfiles()189     public List<UserHandle> getUserProfiles() {
190         return List.copyOf(mUserToSerialMap.keySet());
191     }
192 
193     /**
194      * Returns the pre-installed apps for a user.
195      */
196     @NonNull
getPreInstallApps(UserHandle user)197     public List<String> getPreInstallApps(UserHandle user) {
198         List<String> preInstallApp = mUserToPreInstallAppMap.get(user);
199         return preInstallApp == null ? new ArrayList<>() : preInstallApp;
200     }
201 
202     /**
203      * Get a non-themed {@link UserBadgeDrawable} based on the provided {@link UserHandle}.
204      */
205     @Nullable
getBadgeDrawable(Context context, UserHandle userHandle)206     public static UserBadgeDrawable getBadgeDrawable(Context context, UserHandle userHandle) {
207         return (UserBadgeDrawable) BitmapInfo.LOW_RES_INFO.withFlags(UserCache.getInstance(context)
208                         .getUserInfo(userHandle).applyBitmapInfoFlags(FlagOp.NO_OP))
209                 .getBadgeDrawable(context, false /* isThemed */);
210     }
211 }
212