1 /*
2  * Copyright (C) 2022 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.server.pm;
18 
19 import android.annotation.NonNull;
20 import android.app.usage.UsageEvents;
21 import android.app.usage.UsageStatsManagerInternal;
22 import android.content.Context;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.IBackgroundInstallControlService;
25 import android.content.pm.InstallSourceInfo;
26 import android.content.pm.PackageInfo;
27 import android.content.pm.PackageManager;
28 import android.content.pm.PackageManagerInternal;
29 import android.content.pm.ParceledListSlice;
30 import android.os.Build;
31 import android.os.Environment;
32 import android.os.Handler;
33 import android.os.Looper;
34 import android.os.Message;
35 import android.os.SystemClock;
36 import android.os.SystemProperties;
37 import android.os.UserHandle;
38 import android.text.TextUtils;
39 import android.util.ArraySet;
40 import android.util.AtomicFile;
41 import android.util.Slog;
42 import android.util.SparseArrayMap;
43 import android.util.SparseSetArray;
44 import android.util.proto.ProtoInputStream;
45 import android.util.proto.ProtoOutputStream;
46 
47 import com.android.internal.annotations.VisibleForTesting;
48 import com.android.server.LocalServices;
49 import com.android.server.ServiceThread;
50 import com.android.server.SystemService;
51 import com.android.server.pm.permission.PermissionManagerServiceInternal;
52 
53 import java.io.File;
54 import java.io.FileInputStream;
55 import java.io.FileOutputStream;
56 import java.io.IOException;
57 import java.util.ArrayList;
58 import java.util.List;
59 import java.util.ListIterator;
60 import java.util.Set;
61 import java.util.TreeSet;
62 
63 /**
64  * @hide
65  */
66 public class BackgroundInstallControlService extends SystemService {
67     private static final String TAG = "BackgroundInstallControlService";
68 
69     private static final String DISK_FILE_NAME = "states";
70     private static final String DISK_DIR_NAME = "bic";
71 
72     private static final int MAX_FOREGROUND_TIME_FRAMES_SIZE = 10;
73 
74     private static final int MSG_USAGE_EVENT_RECEIVED = 0;
75     private static final int MSG_PACKAGE_ADDED = 1;
76     private static final int MSG_PACKAGE_REMOVED = 2;
77 
78     private final Context mContext;
79     private final BinderService mBinderService;
80     private final PackageManager mPackageManager;
81     private final PackageManagerInternal mPackageManagerInternal;
82     private final UsageStatsManagerInternal mUsageStatsManagerInternal;
83     private final PermissionManagerServiceInternal mPermissionManager;
84     private final Handler mHandler;
85     private final File mDiskFile;
86 
87 
88     private SparseSetArray<String> mBackgroundInstalledPackages = null;
89 
90     // User ID -> package name -> set of foreground time frame
91     private final SparseArrayMap<String,
92             TreeSet<ForegroundTimeFrame>> mInstallerForegroundTimeFrames =
93             new SparseArrayMap<>();
94 
BackgroundInstallControlService(@onNull Context context)95     public BackgroundInstallControlService(@NonNull Context context) {
96         this(new InjectorImpl(context));
97     }
98 
99     @VisibleForTesting
BackgroundInstallControlService(@onNull Injector injector)100     BackgroundInstallControlService(@NonNull Injector injector) {
101         super(injector.getContext());
102         mContext = injector.getContext();
103         mPackageManager = injector.getPackageManager();
104         mPackageManagerInternal = injector.getPackageManagerInternal();
105         mPermissionManager = injector.getPermissionManager();
106         mHandler = new EventHandler(injector.getLooper(), this);
107         mDiskFile = injector.getDiskFile();
108         mUsageStatsManagerInternal = injector.getUsageStatsManagerInternal();
109         mUsageStatsManagerInternal.registerListener(
110                 (userId, event) ->
111                         mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED,
112                                 userId,
113                                 0,
114                                 event).sendToTarget()
115         );
116         mBinderService = new BinderService(this);
117     }
118 
119     private static final class BinderService extends IBackgroundInstallControlService.Stub {
120         final BackgroundInstallControlService mService;
121 
BinderService(BackgroundInstallControlService service)122         BinderService(BackgroundInstallControlService service) {
123             mService = service;
124         }
125 
126         @Override
getBackgroundInstalledPackages( @ackageManager.PackageInfoFlagsBits long flags, int userId)127         public ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
128                 @PackageManager.PackageInfoFlagsBits long flags, int userId) {
129             if (!Build.IS_DEBUGGABLE) {
130                 return mService.getBackgroundInstalledPackages(flags, userId);
131             }
132             // The debug.transparency.bg-install-apps (only works for debuggable builds)
133             // is used to set mock list of background installed apps for testing.
134             // The list of apps' names is delimited by ",".
135             String propertyString = SystemProperties.get("debug.transparency.bg-install-apps");
136             if (TextUtils.isEmpty(propertyString)) {
137                 return mService.getBackgroundInstalledPackages(flags, userId);
138             } else {
139                 return mService.getMockBackgroundInstalledPackages(propertyString);
140             }
141         }
142     }
143 
144     @VisibleForTesting
getBackgroundInstalledPackages( @ackageManager.PackageInfoFlagsBits long flags, int userId)145     ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
146             @PackageManager.PackageInfoFlagsBits long flags, int userId) {
147         List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
148                     PackageManager.PackageInfoFlags.of(flags), userId);
149 
150         initBackgroundInstalledPackages();
151 
152         ListIterator<PackageInfo> iter = packages.listIterator();
153         while (iter.hasNext()) {
154             String packageName = iter.next().packageName;
155             if (!mBackgroundInstalledPackages.contains(userId, packageName)) {
156                 iter.remove();
157             }
158         }
159 
160         return new ParceledListSlice<>(packages);
161     }
162 
163     /**
164      * Mock a list of background installed packages based on the property string.
165      */
166     @NonNull
getMockBackgroundInstalledPackages( @onNull String propertyString)167     ParceledListSlice<PackageInfo> getMockBackgroundInstalledPackages(
168             @NonNull String propertyString) {
169         String[] mockPackageNames = propertyString.split(",");
170         List<PackageInfo> mockPackages = new ArrayList<>();
171         for (String name : mockPackageNames) {
172             try {
173                 PackageInfo packageInfo = mPackageManager.getPackageInfo(name,
174                         PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL));
175                 mockPackages.add(packageInfo);
176             } catch (PackageManager.NameNotFoundException e) {
177                 Slog.w(TAG, "Package's PackageInfo not found " + name);
178                 continue;
179             }
180         }
181         return new ParceledListSlice<>(mockPackages);
182     }
183 
184     private static class EventHandler extends Handler {
185         private final BackgroundInstallControlService mService;
186 
EventHandler(Looper looper, BackgroundInstallControlService service)187         EventHandler(Looper looper, BackgroundInstallControlService service) {
188             super(looper);
189             mService = service;
190         }
191 
192         @Override
handleMessage(Message msg)193         public void handleMessage(Message msg) {
194             switch (msg.what) {
195                 case MSG_USAGE_EVENT_RECEIVED: {
196                     mService.handleUsageEvent((UsageEvents.Event) msg.obj, msg.arg1 /* userId */);
197                     break;
198                 }
199                 case MSG_PACKAGE_ADDED: {
200                     mService.handlePackageAdd((String) msg.obj, msg.arg1 /* userId */);
201                     break;
202                 }
203                 case MSG_PACKAGE_REMOVED: {
204                     mService.handlePackageRemove((String) msg.obj, msg.arg1 /* userId */);
205                     break;
206                 }
207                 default:
208                     Slog.w(TAG, "Unknown message: " + msg.what);
209             }
210         }
211     }
212 
handlePackageAdd(String packageName, int userId)213     void handlePackageAdd(String packageName, int userId) {
214         ApplicationInfo appInfo = null;
215         try {
216             appInfo = mPackageManager.getApplicationInfoAsUser(packageName,
217                     PackageManager.ApplicationInfoFlags.of(0), userId);
218         } catch (PackageManager.NameNotFoundException e) {
219             Slog.w(TAG, "Package's appInfo not found " + packageName);
220             return;
221         }
222 
223         String installerPackageName;
224         String initiatingPackageName;
225         try {
226             final InstallSourceInfo installInfo = mPackageManager.getInstallSourceInfo(packageName);
227             installerPackageName = installInfo.getInstallingPackageName();
228             initiatingPackageName = installInfo.getInitiatingPackageName();
229         } catch (PackageManager.NameNotFoundException e) {
230             Slog.w(TAG, "Package's installer not found " + packageName);
231             return;
232         }
233 
234         // the installers without INSTALL_PACKAGES perm can't perform
235         // the installation in background. So we can just filter out them.
236         if (mPermissionManager.checkPermission(installerPackageName,
237                 android.Manifest.permission.INSTALL_PACKAGES,
238                 userId) != PackageManager.PERMISSION_GRANTED) {
239             return;
240         }
241 
242         // convert up-time to current time.
243         final long installTimestamp = System.currentTimeMillis()
244                 - (SystemClock.uptimeMillis() - appInfo.createTimestamp);
245 
246         if (installedByAdb(initiatingPackageName)
247                 || wasForegroundInstallation(installerPackageName, userId, installTimestamp)) {
248             return;
249         }
250 
251         initBackgroundInstalledPackages();
252         mBackgroundInstalledPackages.add(userId, packageName);
253         writeBackgroundInstalledPackagesToDisk();
254     }
255 
256     // ADB sets installerPackageName to null, this creates a loophole to bypass BIC which will be
257     // addressed with b/265203007
installedByAdb(String initiatingPackageName)258     private boolean installedByAdb(String initiatingPackageName) {
259         return PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName);
260     }
261 
wasForegroundInstallation(String installerPackageName, int userId, long installTimestamp)262     private boolean wasForegroundInstallation(String installerPackageName,
263             int userId, long installTimestamp) {
264         TreeSet<BackgroundInstallControlService.ForegroundTimeFrame> foregroundTimeFrames =
265                 mInstallerForegroundTimeFrames.get(userId, installerPackageName);
266 
267         // The installer never run in foreground.
268         if (foregroundTimeFrames == null) {
269             return false;
270         }
271 
272         for (var foregroundTimeFrame : foregroundTimeFrames) {
273             // the foreground time frame starts later than the installation.
274             // so the installation is outside the foreground time frame.
275             if (foregroundTimeFrame.startTimeStampMillis > installTimestamp) {
276                 continue;
277             }
278 
279             // the foreground time frame is not over yet.
280             // the installation is inside the foreground time frame.
281             if (!foregroundTimeFrame.isDone()) {
282                 return true;
283             }
284 
285             // the foreground time frame ends later than the installation.
286             // the installation is inside the foreground time frame.
287             if (installTimestamp <= foregroundTimeFrame.endTimeStampMillis) {
288                 return true;
289             }
290         }
291 
292         // the installation is not inside any of foreground time frames.
293         // so it is not a foreground installation.
294         return false;
295     }
296 
handlePackageRemove(String packageName, int userId)297     void handlePackageRemove(String packageName, int userId) {
298         initBackgroundInstalledPackages();
299         mBackgroundInstalledPackages.remove(userId, packageName);
300         writeBackgroundInstalledPackagesToDisk();
301     }
302 
handleUsageEvent(UsageEvents.Event event, int userId)303     void handleUsageEvent(UsageEvents.Event event, int userId) {
304         if (event.mEventType != UsageEvents.Event.ACTIVITY_RESUMED
305                 && event.mEventType != UsageEvents.Event.ACTIVITY_PAUSED
306                 && event.mEventType != UsageEvents.Event.ACTIVITY_STOPPED) {
307             return;
308         }
309 
310         if (!isInstaller(event.mPackage, userId)) {
311             return;
312         }
313 
314         if (!mInstallerForegroundTimeFrames.contains(userId, event.mPackage)) {
315             mInstallerForegroundTimeFrames.add(userId, event.mPackage, new TreeSet<>());
316         }
317 
318         TreeSet<BackgroundInstallControlService.ForegroundTimeFrame> foregroundTimeFrames =
319                 mInstallerForegroundTimeFrames.get(userId, event.mPackage);
320 
321         if ((foregroundTimeFrames.size() == 0) || foregroundTimeFrames.last().isDone()) {
322             // ignore the other events if there is no open ForegroundTimeFrame.
323             if (event.mEventType != UsageEvents.Event.ACTIVITY_RESUMED) {
324                 return;
325             }
326             foregroundTimeFrames.add(new ForegroundTimeFrame(event.mTimeStamp));
327         }
328 
329         foregroundTimeFrames.last().addEvent(event);
330 
331         if (foregroundTimeFrames.size() > MAX_FOREGROUND_TIME_FRAMES_SIZE) {
332             foregroundTimeFrames.pollFirst();
333         }
334     }
335 
336     @VisibleForTesting
writeBackgroundInstalledPackagesToDisk()337     void writeBackgroundInstalledPackagesToDisk() {
338         AtomicFile atomicFile = new AtomicFile(mDiskFile);
339         FileOutputStream fileOutputStream;
340         try {
341             fileOutputStream = atomicFile.startWrite();
342         } catch (IOException e) {
343             Slog.e(TAG, "Failed to start write to states protobuf.", e);
344             return;
345         }
346 
347         try {
348             ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream);
349             for (int i = 0; i < mBackgroundInstalledPackages.size(); i++) {
350                 int userId = mBackgroundInstalledPackages.keyAt(i);
351                 for (String packageName : mBackgroundInstalledPackages.get(userId)) {
352                     long token = protoOutputStream.start(
353                             BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
354                     protoOutputStream.write(
355                             BackgroundInstalledPackageProto.PACKAGE_NAME, packageName);
356                     protoOutputStream.write(
357                             BackgroundInstalledPackageProto.USER_ID, userId + 1);
358                     protoOutputStream.end(token);
359                 }
360             }
361             protoOutputStream.flush();
362             atomicFile.finishWrite(fileOutputStream);
363         } catch (Exception e) {
364             Slog.e(TAG, "Failed to finish write to states protobuf.", e);
365             atomicFile.failWrite(fileOutputStream);
366         }
367     }
368 
369     @VisibleForTesting
initBackgroundInstalledPackages()370     void initBackgroundInstalledPackages() {
371         if (mBackgroundInstalledPackages != null) {
372             return;
373         }
374 
375         mBackgroundInstalledPackages = new SparseSetArray<>();
376 
377         if (!mDiskFile.exists()) {
378             return;
379         }
380 
381         AtomicFile atomicFile = new AtomicFile(mDiskFile);
382         try (FileInputStream fileInputStream = atomicFile.openRead()) {
383             ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream);
384 
385             while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
386                 if (protoInputStream.getFieldNumber()
387                         != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) {
388                     continue;
389                 }
390                 long token = protoInputStream.start(
391                         BackgroundInstalledPackagesProto.BG_INSTALLED_PKG);
392                 String packageName = null;
393                 int userId = UserHandle.USER_NULL;
394                 while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
395                     switch (protoInputStream.getFieldNumber()) {
396                         case (int) BackgroundInstalledPackageProto.PACKAGE_NAME:
397                             packageName = protoInputStream.readString(
398                                     BackgroundInstalledPackageProto.PACKAGE_NAME);
399                             break;
400                         case (int) BackgroundInstalledPackageProto.USER_ID:
401                             userId = protoInputStream.readInt(
402                                     BackgroundInstalledPackageProto.USER_ID) - 1;
403                             break;
404                         default:
405                             Slog.w(TAG, "Undefined field in proto: "
406                                     + protoInputStream.getFieldNumber());
407                     }
408                 }
409                 protoInputStream.end(token);
410                 if (packageName != null && userId != UserHandle.USER_NULL) {
411                     mBackgroundInstalledPackages.add(userId, packageName);
412                 } else {
413                     Slog.w(TAG, "Fails to get packageName or UserId from proto file");
414                 }
415             }
416         } catch (IOException e) {
417             Slog.w(TAG, "Error reading state from the disk", e);
418         }
419     }
420 
421     @VisibleForTesting
getBackgroundInstalledPackages()422     SparseSetArray<String> getBackgroundInstalledPackages() {
423         return mBackgroundInstalledPackages;
424     }
425 
426     @VisibleForTesting
getInstallerForegroundTimeFrames()427     SparseArrayMap<String, TreeSet<ForegroundTimeFrame>> getInstallerForegroundTimeFrames() {
428         return mInstallerForegroundTimeFrames;
429     }
430 
isInstaller(String pkgName, int userId)431     private boolean isInstaller(String pkgName, int userId) {
432         if (mInstallerForegroundTimeFrames.contains(userId, pkgName)) {
433             return true;
434         }
435         return mPermissionManager.checkPermission(pkgName,
436                 android.Manifest.permission.INSTALL_PACKAGES,
437                 userId) == PackageManager.PERMISSION_GRANTED;
438     }
439 
440     @Override
onStart()441     public void onStart() {
442         onStart(/* isForTesting= */ false);
443     }
444 
445     @VisibleForTesting
onStart(boolean isForTesting)446     void onStart(boolean isForTesting) {
447         if (!isForTesting) {
448             publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService);
449         }
450 
451         mPackageManagerInternal.getPackageList(new PackageManagerInternal.PackageListObserver() {
452             @Override
453             public void onPackageAdded(String packageName, int uid) {
454                 final int userId = UserHandle.getUserId(uid);
455                 mHandler.obtainMessage(MSG_PACKAGE_ADDED,
456                         userId, 0, packageName).sendToTarget();
457             }
458 
459             @Override
460             public void onPackageRemoved(String packageName, int uid) {
461                 final int userId = UserHandle.getUserId(uid);
462                 mHandler.obtainMessage(MSG_PACKAGE_REMOVED,
463                         userId, 0, packageName).sendToTarget();
464             }
465         });
466     }
467 
468     // The foreground time frame (ForegroundTimeFrame) represents the period
469     // when a package's activities continuously occupy the foreground.
470     // Each ForegroundTimeFrame starts with an ACTIVITY_RESUMED event,
471     // and then ends with an ACTIVITY_PAUSED or ACTIVITY_STOPPED event.
472     // The startTimeStampMillis stores the timestamp of the ACTIVITY_RESUMED event.
473     // The endTimeStampMillis stores the timestamp of the ACTIVITY_PAUSED or ACTIVITY_STOPPED event
474     // that wraps up the ForegroundTimeFrame.
475     // The activities are designed to handle the edge case in which a package's one activity
476     // seamlessly replace another activity of the same package. Thus, we count these activities
477     // together as a ForegroundTimeFrame. For this scenario, only when all the activities terminate
478     // shall consider the completion of the ForegroundTimeFrame.
479     static final class ForegroundTimeFrame implements Comparable<ForegroundTimeFrame> {
480         public final long startTimeStampMillis;
481         public long endTimeStampMillis;
482         public final Set<Integer> activities;
483 
compareTo(ForegroundTimeFrame o)484         public int compareTo(ForegroundTimeFrame o) {
485             int comp = Long.compare(startTimeStampMillis, o.startTimeStampMillis);
486             if (comp != 0) return comp;
487 
488             return Integer.compare(hashCode(), o.hashCode());
489         }
490 
ForegroundTimeFrame(long startTimeStampMillis)491         ForegroundTimeFrame(long startTimeStampMillis) {
492             this.startTimeStampMillis = startTimeStampMillis;
493             endTimeStampMillis = 0;
494             activities = new ArraySet<>();
495         }
496 
isDone()497         public boolean isDone() {
498             return endTimeStampMillis != 0;
499         }
500 
addEvent(UsageEvents.Event event)501         public void addEvent(UsageEvents.Event event) {
502             switch (event.mEventType) {
503                 case UsageEvents.Event.ACTIVITY_RESUMED:
504                     activities.add(event.mInstanceId);
505                     break;
506                 case UsageEvents.Event.ACTIVITY_PAUSED:
507                 case UsageEvents.Event.ACTIVITY_STOPPED:
508                     if (activities.contains(event.mInstanceId)) {
509                         activities.remove(event.mInstanceId);
510                         if (activities.size() == 0) {
511                             endTimeStampMillis = event.mTimeStamp;
512                         }
513                     }
514                     break;
515                 default:
516             }
517         }
518     }
519 
520     /**
521      * Dependency injector for {@link #BackgroundInstallControlService)}.
522      */
523     interface Injector {
getContext()524         Context getContext();
525 
getPackageManager()526         PackageManager getPackageManager();
527 
getPackageManagerInternal()528         PackageManagerInternal getPackageManagerInternal();
529 
getUsageStatsManagerInternal()530         UsageStatsManagerInternal getUsageStatsManagerInternal();
531 
getPermissionManager()532         PermissionManagerServiceInternal getPermissionManager();
533 
getLooper()534         Looper getLooper();
535 
getDiskFile()536         File getDiskFile();
537     }
538 
539     private static final class InjectorImpl implements Injector {
540         private final Context mContext;
541 
InjectorImpl(Context context)542         InjectorImpl(Context context) {
543             mContext = context;
544         }
545 
546         @Override
getContext()547         public Context getContext() {
548             return mContext;
549         }
550 
551         @Override
getPackageManager()552         public PackageManager getPackageManager() {
553             return mContext.getPackageManager();
554         }
555 
556         @Override
getPackageManagerInternal()557         public PackageManagerInternal getPackageManagerInternal() {
558             return LocalServices.getService(PackageManagerInternal.class);
559         }
560 
561         @Override
getUsageStatsManagerInternal()562         public UsageStatsManagerInternal getUsageStatsManagerInternal() {
563             return LocalServices.getService(UsageStatsManagerInternal.class);
564         }
565 
566         @Override
getPermissionManager()567         public PermissionManagerServiceInternal getPermissionManager() {
568             return LocalServices.getService(PermissionManagerServiceInternal.class);
569         }
570 
571         @Override
getLooper()572         public Looper getLooper() {
573             ServiceThread serviceThread = new ServiceThread(TAG,
574                     android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
575             serviceThread.start();
576             return serviceThread.getLooper();
577 
578         }
579 
580         @Override
getDiskFile()581         public File getDiskFile() {
582             File dir = new File(Environment.getDataSystemDirectory(), DISK_DIR_NAME);
583             File file = new File(dir, DISK_FILE_NAME);
584             return file;
585         }
586     }
587 }
588