diff options
16 files changed, 1025 insertions, 322 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 2898705b75d4..1c60db9f250d 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -862,6 +862,10 @@ package android.app { field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.PackageOps> CREATOR; } + @FlaggedApi("android.app.bic_client") public final class BackgroundInstallControlManager { + method @FlaggedApi("android.app.bic_client") @NonNull @RequiresPermission(android.Manifest.permission.QUERY_ALL_PACKAGES) public java.util.List<android.content.pm.PackageInfo> getBackgroundInstalledPackages(long); + } + public class BroadcastOptions { method public void clearRequireCompatChange(); method public int getPendingIntentBackgroundActivityStartMode(); diff --git a/core/java/android/app/BackgroundInstallControlManager.java b/core/java/android/app/BackgroundInstallControlManager.java new file mode 100644 index 000000000000..f5b68788f0ea --- /dev/null +++ b/core/java/android/app/BackgroundInstallControlManager.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import static android.Manifest.permission.QUERY_ALL_PACKAGES; +import static android.annotation.SystemApi.Client.PRIVILEGED_APPS; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.content.Context; +import android.content.pm.IBackgroundInstallControlService; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.ServiceManager; + +import java.util.List; + +/** + * BackgroundInstallControlManager client allows apps to query apps installed in background. + * + * <p>Any applications that was installed without an accompanying installer UI activity paired + * with recorded user interaction event is considered background installed. This is determined by + * analysis of user-activity logs. + * + * <p>Warning: BackgroundInstallControl should not be considered a reliable or accurate + * determination of background install application. Consumers can use this as a supplementary + * signal, but must perform additional due diligence to confirm the install nature of the package. + * + * @hide + */ +@FlaggedApi(Flags.FLAG_BIC_CLIENT) +@SystemApi(client = PRIVILEGED_APPS) +@SystemService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE) +public final class BackgroundInstallControlManager { + + private static final String TAG = "BackgroundInstallControlManager"; + private static IBackgroundInstallControlService sService; + private final Context mContext; + + BackgroundInstallControlManager(Context context) { + mContext = context; + } + + private static IBackgroundInstallControlService getService() { + if (sService == null) { + sService = + IBackgroundInstallControlService.Stub.asInterface( + ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE)); + } + return sService; + } + + /** + * Returns a full list of {@link PackageInfo} of apps currently installed that are considered + * installed in the background. + * + * <p>Refer to top level doc {@link BackgroundInstallControlManager} for more details on + * background-installed applications. + * <p> + * + * @param flags - Flags will be used to call + * {@link PackageManager#getInstalledPackages(PackageInfoFlags)} to retrieve installed packages. + * @return A list of packages retrieved from {@link PackageManager} with non-background + * installed app filter applied. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_BIC_CLIENT) + @SystemApi + @RequiresPermission(QUERY_ALL_PACKAGES) + public @NonNull List<PackageInfo> getBackgroundInstalledPackages( + @PackageManager.PackageInfoFlagsBits long flags) { + try { + return getService() + .getBackgroundInstalledPackages(flags, mContext.getUserId()) + .getList(); + } catch (SecurityException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 9cf732abb86a..390fa2212298 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -1603,6 +1603,20 @@ public final class SystemServiceRegistry { } }); + // DO NOT do a flag check like this unless the flag is read-only. + // (because this code is executed during preload in zygote.) + // If the flag is mutable, the check should be inside CachedServiceFetcher. + if (Flags.bicClient()) { + registerService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, + BackgroundInstallControlManager.class, + new CachedServiceFetcher<BackgroundInstallControlManager>() { + @Override + public BackgroundInstallControlManager createService(ContextImpl ctx) { + return new BackgroundInstallControlManager(ctx); + } + }); + } + sInitializing = true; try { // Note: the following functions need to be @SystemApis, once they become mainline diff --git a/core/java/android/app/background_install_control_manager.aconfig b/core/java/android/app/background_install_control_manager.aconfig new file mode 100644 index 000000000000..029b93ab4534 --- /dev/null +++ b/core/java/android/app/background_install_control_manager.aconfig @@ -0,0 +1,9 @@ +package: "android.app" + +flag { + namespace: "background_install_control" + name: "bic_client" + description: "System API for background install control." + is_fixed_read_only: true + bug: "287507984" +} diff --git a/core/java/android/content/pm/IBackgroundInstallControlService.aidl b/core/java/android/content/pm/IBackgroundInstallControlService.aidl index c8e7caebc821..4bc8fe16b249 100644 --- a/core/java/android/content/pm/IBackgroundInstallControlService.aidl +++ b/core/java/android/content/pm/IBackgroundInstallControlService.aidl @@ -16,11 +16,20 @@ package android.content.pm; + import android.content.pm.ParceledListSlice; +import android.os.IRemoteCallback; /** * {@hide} */ interface IBackgroundInstallControlService { + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest.permission.QUERY_ALL_PACKAGES)") ParceledListSlice getBackgroundInstalledPackages(long flags, int userId); -} + + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(allOf = {android.Manifest.permission.QUERY_ALL_PACKAGES, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})") + void registerBackgroundInstallCallback(IRemoteCallback callback); + + @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(allOf = {android.Manifest.permission.QUERY_ALL_PACKAGES, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})") + void unregisterBackgroundInstallCallback(IRemoteCallback callback); +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java new file mode 100644 index 000000000000..4454601f2254 --- /dev/null +++ b/services/core/java/com/android/server/pm/BackgroundInstallControlCallbackHelper.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import static android.os.Process.THREAD_PRIORITY_BACKGROUND; + +import android.annotation.NonNull; +import android.app.BackgroundInstallControlManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IRemoteCallback; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.ServiceThread; + +public class BackgroundInstallControlCallbackHelper { + + @VisibleForTesting static final String FLAGGED_PACKAGE_NAME_KEY = "packageName"; + @VisibleForTesting static final String FLAGGED_USER_ID_KEY = "userId"; + private static final String TAG = "BackgroundInstallControlCallbackHelper"; + + private final Handler mHandler; + + BackgroundInstallControlCallbackHelper() { + HandlerThread backgroundThread = + new ServiceThread( + "BackgroundInstallControlCallbackHelperBg", + THREAD_PRIORITY_BACKGROUND, + true); + backgroundThread.start(); + mHandler = new Handler(backgroundThread.getLooper()); + } + + @NonNull @VisibleForTesting + final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>(); + + /** Registers callback that gets invoked upon detection of an MBA + * + * NOTE: The callback is user context agnostic and currently broadcasts to all users of other + * users app installs. This is fine because the API is for SystemServer use only. + */ + public void registerBackgroundInstallCallback(IRemoteCallback callback) { + synchronized (mCallbacks) { + mCallbacks.register(callback, null); + } + } + + /** Unregisters callback */ + public void unregisterBackgroundInstallCallback(IRemoteCallback callback) { + synchronized (mCallbacks) { + mCallbacks.unregister(callback); + } + } + + /** + * Invokes all registered callbacks Callbacks are processed through user provided-threads and + * parameters are passed in via {@link BackgroundInstallControlManager} InstallEvent + */ + public void notifyAllCallbacks(int userId, String packageName) { + Bundle extras = new Bundle(); + extras.putCharSequence(FLAGGED_PACKAGE_NAME_KEY, packageName); + extras.putInt(FLAGGED_USER_ID_KEY, userId); + synchronized (mCallbacks) { + mHandler.post( + () -> + mCallbacks.broadcast( + callback -> { + try { + callback.sendResult(extras); + } catch (RemoteException e) { + Slog.e( + TAG, + "error detected: " + e.getLocalizedMessage(), + e); + } + })); + } + } +} diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java index 7f0aadce3143..3a9dedcf2d7b 100644 --- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java +++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java @@ -16,7 +16,12 @@ package com.android.server.pm; +import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.Manifest.permission.QUERY_ALL_PACKAGES; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import android.annotation.NonNull; +import android.annotation.RequiresPermission; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.content.Context; @@ -30,6 +35,7 @@ import android.content.pm.ParceledListSlice; import android.os.Build; import android.os.Environment; import android.os.Handler; +import android.os.IRemoteCallback; import android.os.Looper; import android.os.Message; import android.os.SystemClock; @@ -69,8 +75,10 @@ public class BackgroundInstallControlService extends SystemService { private static final String DISK_FILE_NAME = "states"; private static final String DISK_DIR_NAME = "bic"; - private static final int MAX_FOREGROUND_TIME_FRAMES_SIZE = 10; + private static final String ENFORCE_PERMISSION_ERROR_MSG = + "User is not permitted to call service: "; + private static final int MAX_FOREGROUND_TIME_FRAMES_SIZE = 10; private static final int MSG_USAGE_EVENT_RECEIVED = 0; private static final int MSG_PACKAGE_ADDED = 1; private static final int MSG_PACKAGE_REMOVED = 2; @@ -78,19 +86,20 @@ public class BackgroundInstallControlService extends SystemService { private final Context mContext; private final BinderService mBinderService; private final PackageManager mPackageManager; + // TODO migrate all internal PackageManager calls to PackageManagerInternal where possible. + // b/310983905 private final PackageManagerInternal mPackageManagerInternal; private final UsageStatsManagerInternal mUsageStatsManagerInternal; private final PermissionManagerServiceInternal mPermissionManager; private final Handler mHandler; private final File mDiskFile; - private SparseSetArray<String> mBackgroundInstalledPackages = null; + private final BackgroundInstallControlCallbackHelper mCallbackHelper; // User ID -> package name -> set of foreground time frame - private final SparseArrayMap<String, - TreeSet<ForegroundTimeFrame>> mInstallerForegroundTimeFrames = - new SparseArrayMap<>(); + private final SparseArrayMap<String, TreeSet<ForegroundTimeFrame>> + mInstallerForegroundTimeFrames = new SparseArrayMap<>(); public BackgroundInstallControlService(@NonNull Context context) { this(new InjectorImpl(context)); @@ -106,13 +115,11 @@ public class BackgroundInstallControlService extends SystemService { mHandler = new EventHandler(injector.getLooper(), this); mDiskFile = injector.getDiskFile(); mUsageStatsManagerInternal = injector.getUsageStatsManagerInternal(); + mCallbackHelper = injector.getBackgroundInstallControlCallbackHelper(); mUsageStatsManagerInternal.registerListener( (userId, event) -> - mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED, - userId, - 0, - event).sendToTarget() - ); + mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED, userId, 0, event) + .sendToTarget()); mBinderService = new BinderService(this); } @@ -126,12 +133,15 @@ public class BackgroundInstallControlService extends SystemService { @Override public ParceledListSlice<PackageInfo> getBackgroundInstalledPackages( @PackageManager.PackageInfoFlagsBits long flags, int userId) { + mService.enforceCallerQueryPackagesPermissions(); if (!Build.IS_DEBUGGABLE) { return mService.getBackgroundInstalledPackages(flags, userId); } // The debug.transparency.bg-install-apps (only works for debuggable builds) // is used to set mock list of background installed apps for testing. // The list of apps' names is delimited by ",". + // TODO: Remove after migrating test to new background install method using + // {@link BackgroundInstallControlCallbackHelperTest}.installPackage b/310983905 String propertyString = SystemProperties.get("debug.transparency.bg-install-apps"); if (TextUtils.isEmpty(propertyString)) { return mService.getBackgroundInstalledPackages(flags, userId); @@ -139,16 +149,41 @@ public class BackgroundInstallControlService extends SystemService { return mService.getMockBackgroundInstalledPackages(propertyString); } } + + @Override + public void registerBackgroundInstallCallback(IRemoteCallback callback) { + mService.enforceCallerQueryPackagesPermissions(); + mService.enforceCallerInteractCrossUserPermissions(); + mService.mCallbackHelper.registerBackgroundInstallCallback(callback); + } + + @Override + public void unregisterBackgroundInstallCallback(IRemoteCallback callback) { + mService.enforceCallerQueryPackagesPermissions(); + mService.enforceCallerInteractCrossUserPermissions(); + mService.mCallbackHelper.unregisterBackgroundInstallCallback(callback); + } + } + + @RequiresPermission(QUERY_ALL_PACKAGES) + void enforceCallerQueryPackagesPermissions() throws SecurityException { + mContext.enforceCallingPermission(QUERY_ALL_PACKAGES, + ENFORCE_PERMISSION_ERROR_MSG + QUERY_ALL_PACKAGES); + } + + @RequiresPermission(INTERACT_ACROSS_USERS_FULL) + void enforceCallerInteractCrossUserPermissions() throws SecurityException { + mContext.enforceCallingPermission(INTERACT_ACROSS_USERS_FULL, + ENFORCE_PERMISSION_ERROR_MSG + INTERACT_ACROSS_USERS_FULL); } @VisibleForTesting ParceledListSlice<PackageInfo> getBackgroundInstalledPackages( @PackageManager.PackageInfoFlagsBits long flags, int userId) { List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser( - PackageManager.PackageInfoFlags.of(flags), userId); + PackageManager.PackageInfoFlags.of(flags), userId); initBackgroundInstalledPackages(); - ListIterator<PackageInfo> iter = packages.listIterator(); while (iter.hasNext()) { String packageName = iter.next().packageName; @@ -170,8 +205,9 @@ public class BackgroundInstallControlService extends SystemService { List<PackageInfo> mockPackages = new ArrayList<>(); for (String name : mockPackageNames) { try { - PackageInfo packageInfo = mPackageManager.getPackageInfo(name, - PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL)); + PackageInfo packageInfo = + mPackageManager.getPackageInfo( + name, PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL)); mockPackages.add(packageInfo); } catch (PackageManager.NameNotFoundException e) { Slog.w(TAG, "Package's PackageInfo not found " + name); @@ -192,18 +228,16 @@ public class BackgroundInstallControlService extends SystemService { @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_USAGE_EVENT_RECEIVED: { - mService.handleUsageEvent((UsageEvents.Event) msg.obj, msg.arg1 /* userId */); + case MSG_USAGE_EVENT_RECEIVED: + mService.handleUsageEvent( + (UsageEvents.Event) msg.obj, msg.arg1 /* userId */); break; - } - case MSG_PACKAGE_ADDED: { + case MSG_PACKAGE_ADDED: mService.handlePackageAdd((String) msg.obj, msg.arg1 /* userId */); break; - } - case MSG_PACKAGE_REMOVED: { + case MSG_PACKAGE_REMOVED: mService.handlePackageRemove((String) msg.obj, msg.arg1 /* userId */); break; - } default: Slog.w(TAG, "Unknown message: " + msg.what); } @@ -213,8 +247,9 @@ public class BackgroundInstallControlService extends SystemService { void handlePackageAdd(String packageName, int userId) { ApplicationInfo appInfo = null; try { - appInfo = mPackageManager.getApplicationInfoAsUser(packageName, - PackageManager.ApplicationInfoFlags.of(0), userId); + appInfo = + mPackageManager.getApplicationInfoAsUser( + packageName, PackageManager.ApplicationInfoFlags.of(0), userId); } catch (PackageManager.NameNotFoundException e) { Slog.w(TAG, "Package's appInfo not found " + packageName); return; @@ -233,15 +268,18 @@ public class BackgroundInstallControlService extends SystemService { // the installers without INSTALL_PACKAGES perm can't perform // the installation in background. So we can just filter out them. - if (mPermissionManager.checkPermission(installerPackageName, - android.Manifest.permission.INSTALL_PACKAGES, Context.DEVICE_ID_DEFAULT, - userId) != PackageManager.PERMISSION_GRANTED) { + if (mPermissionManager.checkPermission( + installerPackageName, + android.Manifest.permission.INSTALL_PACKAGES, + Context.DEVICE_ID_DEFAULT, + userId) + != PERMISSION_GRANTED) { return; } // convert up-time to current time. - final long installTimestamp = System.currentTimeMillis() - - (SystemClock.uptimeMillis() - appInfo.createTimestamp); + final long installTimestamp = + System.currentTimeMillis() - (SystemClock.uptimeMillis() - appInfo.createTimestamp); if (installedByAdb(initiatingPackageName) || wasForegroundInstallation(installerPackageName, userId, installTimestamp)) { @@ -250,6 +288,7 @@ public class BackgroundInstallControlService extends SystemService { initBackgroundInstalledPackages(); mBackgroundInstalledPackages.add(userId, packageName); + mCallbackHelper.notifyAllCallbacks(userId, packageName); writeBackgroundInstalledPackagesToDisk(); } @@ -259,8 +298,8 @@ public class BackgroundInstallControlService extends SystemService { return PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName); } - private boolean wasForegroundInstallation(String installerPackageName, - int userId, long installTimestamp) { + private boolean wasForegroundInstallation( + String installerPackageName, int userId, long installTimestamp) { TreeSet<BackgroundInstallControlService.ForegroundTimeFrame> foregroundTimeFrames = mInstallerForegroundTimeFrames.get(userId, installerPackageName); @@ -349,12 +388,12 @@ public class BackgroundInstallControlService extends SystemService { for (int i = 0; i < mBackgroundInstalledPackages.size(); i++) { int userId = mBackgroundInstalledPackages.keyAt(i); for (String packageName : mBackgroundInstalledPackages.get(userId)) { - long token = protoOutputStream.start( - BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); + long token = + protoOutputStream.start( + BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); protoOutputStream.write( BackgroundInstalledPackageProto.PACKAGE_NAME, packageName); - protoOutputStream.write( - BackgroundInstalledPackageProto.USER_ID, userId + 1); + protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, userId + 1); protoOutputStream.end(token); } } @@ -387,23 +426,28 @@ public class BackgroundInstallControlService extends SystemService { != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) { continue; } - long token = protoInputStream.start( - BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); + long token = + protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); String packageName = null; int userId = UserHandle.USER_NULL; while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { switch (protoInputStream.getFieldNumber()) { case (int) BackgroundInstalledPackageProto.PACKAGE_NAME: - packageName = protoInputStream.readString( - BackgroundInstalledPackageProto.PACKAGE_NAME); + packageName = + protoInputStream.readString( + BackgroundInstalledPackageProto.PACKAGE_NAME); break; case (int) BackgroundInstalledPackageProto.USER_ID: - userId = protoInputStream.readInt( - BackgroundInstalledPackageProto.USER_ID) - 1; + userId = + protoInputStream.readInt( + BackgroundInstalledPackageProto.USER_ID) + - 1; break; default: - Slog.w(TAG, "Undefined field in proto: " - + protoInputStream.getFieldNumber()); + Slog.w( + TAG, + "Undefined field in proto: " + + protoInputStream.getFieldNumber()); } } protoInputStream.end(token); @@ -432,9 +476,12 @@ public class BackgroundInstallControlService extends SystemService { if (mInstallerForegroundTimeFrames.contains(userId, pkgName)) { return true; } - return mPermissionManager.checkPermission(pkgName, - android.Manifest.permission.INSTALL_PACKAGES, Context.DEVICE_ID_DEFAULT, - userId) == PackageManager.PERMISSION_GRANTED; + return mPermissionManager.checkPermission( + pkgName, + android.Manifest.permission.INSTALL_PACKAGES, + Context.DEVICE_ID_DEFAULT, + userId) + == PERMISSION_GRANTED; } @Override @@ -448,21 +495,22 @@ public class BackgroundInstallControlService extends SystemService { publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService); } - mPackageManagerInternal.getPackageList(new PackageManagerInternal.PackageListObserver() { - @Override - public void onPackageAdded(String packageName, int uid) { - final int userId = UserHandle.getUserId(uid); - mHandler.obtainMessage(MSG_PACKAGE_ADDED, - userId, 0, packageName).sendToTarget(); - } + mPackageManagerInternal.getPackageList( + new PackageManagerInternal.PackageListObserver() { + @Override + public void onPackageAdded(String packageName, int uid) { + final int userId = UserHandle.getUserId(uid); + mHandler.obtainMessage(MSG_PACKAGE_ADDED, userId, 0, packageName) + .sendToTarget(); + } - @Override - public void onPackageRemoved(String packageName, int uid) { - final int userId = UserHandle.getUserId(uid); - mHandler.obtainMessage(MSG_PACKAGE_REMOVED, - userId, 0, packageName).sendToTarget(); - } - }); + @Override + public void onPackageRemoved(String packageName, int uid) { + final int userId = UserHandle.getUserId(uid); + mHandler.obtainMessage(MSG_PACKAGE_REMOVED, userId, 0, packageName) + .sendToTarget(); + } + }); } // The foreground time frame (ForegroundTimeFrame) represents the period @@ -518,7 +566,7 @@ public class BackgroundInstallControlService extends SystemService { } /** - * Dependency injector for {@link #BackgroundInstallControlService)}. + * Dependency injector for {@link BackgroundInstallControlService}. */ interface Injector { Context getContext(); @@ -534,6 +582,8 @@ public class BackgroundInstallControlService extends SystemService { Looper getLooper(); File getDiskFile(); + + BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper(); } private static final class InjectorImpl implements Injector { @@ -570,11 +620,11 @@ public class BackgroundInstallControlService extends SystemService { @Override public Looper getLooper() { - ServiceThread serviceThread = new ServiceThread(TAG, - android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */); + ServiceThread serviceThread = + new ServiceThread( + TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */); serviceThread.start(); return serviceThread.getLooper(); - } @Override @@ -583,5 +633,10 @@ public class BackgroundInstallControlService extends SystemService { File file = new File(dir, DISK_FILE_NAME); return file; } + + @Override + public BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper() { + return new BackgroundInstallControlCallbackHelper(); + } } } diff --git a/services/tests/BackgroundInstallControlServiceTests/host/Android.bp b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp index 4fcdbfc21f6c..e3954355491d 100644 --- a/services/tests/BackgroundInstallControlServiceTests/host/Android.bp +++ b/services/tests/BackgroundInstallControlServiceTests/host/Android.bp @@ -33,6 +33,7 @@ java_test_host { ":BackgroundInstallControlServiceTestApp", ":BackgroundInstallControlMockApp1", ":BackgroundInstallControlMockApp2", + ":BackgroundInstallControlMockApp3", ], test_suites: [ "general-tests", diff --git a/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml b/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml index 1e7a78aa6f93..031d57fbe182 100644 --- a/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml +++ b/services/tests/BackgroundInstallControlServiceTests/host/AndroidTest.xml @@ -29,11 +29,14 @@ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> <option name="cleanup" value="true" /> <option name="push-file" - key="BackgroundInstallControlMockApp1.apk" - value="/data/local/tmp/BackgroundInstallControlMockApp1.apk" /> + key="BackgroundInstallControlMockApp1.apk" + value="/data/local/tmp/BackgroundInstallControlMockApp1.apk" /> <option name="push-file" - key="BackgroundInstallControlMockApp2.apk" - value="/data/local/tmp/BackgroundInstallControlMockApp2.apk" /> + key="BackgroundInstallControlMockApp2.apk" + value="/data/local/tmp/BackgroundInstallControlMockApp2.apk" /> + <option name="push-file" + key="BackgroundInstallControlMockApp3.apk" + value="/data/local/tmp/BackgroundInstallControlMockApp3.apk" /> </target_preparer> <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" > diff --git a/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java index 74506076d82f..5092a4659eb9 100644 --- a/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java +++ b/services/tests/BackgroundInstallControlServiceTests/host/src/com/android/server/pm/test/BackgroundInstallControlServiceHostTest.java @@ -41,17 +41,26 @@ public final class BackgroundInstallControlServiceHostTest extends BaseHostJUnit private static final String MOCK_APK_FILE_1 = "BackgroundInstallControlMockApp1.apk"; private static final String MOCK_APK_FILE_2 = "BackgroundInstallControlMockApp2.apk"; + // TODO: Move the silent installs to test-app using {@link + // BackgroundInstallControlServiceTest#installPackage(String, String)} and remove deviceConfig + // branch in BICS. + // b/310983905 @Test public void testGetMockBackgroundInstalledPackages() throws Exception { - installPackage(TEST_DATA_DIR + MOCK_APK_FILE_1); + installPackage(TEST_DATA_DIR + MOCK_APK_FILE_1); installPackage(TEST_DATA_DIR + MOCK_APK_FILE_2); assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_1)).isNotNull(); assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_2)).isNotNull(); - assertThat(getDevice().setProperty("debug.transparency.bg-install-apps", - MOCK_PACKAGE_NAME_1 + "," + MOCK_PACKAGE_NAME_2)).isTrue(); - runDeviceTest("testGetMockBackgroundInstalledPackages"); + assertThat( + getDevice() + .setProperty( + "debug.transparency.bg-install-apps", + MOCK_PACKAGE_NAME_1 + "," + MOCK_PACKAGE_NAME_2)) + .isTrue(); + runDeviceTest( + "BackgroundInstallControlServiceTest", "testGetMockBackgroundInstalledPackages"); assertThat(getDevice().uninstallPackage(MOCK_PACKAGE_NAME_1)).isNull(); assertThat(getDevice().uninstallPackage(MOCK_PACKAGE_NAME_2)).isNull(); @@ -59,16 +68,30 @@ public final class BackgroundInstallControlServiceHostTest extends BaseHostJUnit assertThat(getDevice().getAppPackageInfo(MOCK_PACKAGE_NAME_2)).isNull(); } + @Test + public void testRegisterCallback() throws Exception { + runDeviceTest( + "BackgroundInstallControlServiceTest", + "testRegisterBackgroundInstallControlCallback"); + } + + @Test + public void testUnregisterCallback() throws Exception { + runDeviceTest( + "BackgroundInstallControlServiceTest", + "testUnregisterBackgroundInstallControlCallback"); + } + private void installPackage(String path) throws DeviceNotAvailableException { String cmd = "pm install -t --force-queryable " + path; CommandResult result = getDevice().executeShellV2Command(cmd); assertThat(result.getStatus() == CommandStatus.SUCCESS).isTrue(); } - private void runDeviceTest(String method) throws DeviceNotAvailableException { + private void runDeviceTest(String testName, String method) throws DeviceNotAvailableException { var options = new DeviceTestRunOptions(PACKAGE_NAME); - options.setTestClassName(PACKAGE_NAME + ".BackgroundInstallControlServiceTest"); + options.setTestClassName(PACKAGE_NAME + "." + testName); options.setTestMethodName(method); runDeviceTests(options); } -} +}
\ No newline at end of file diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml index 1fa1f84cd04e..b5b8ea0f40c7 100644 --- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml +++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/AndroidManifest.xml @@ -21,6 +21,9 @@ <uses-library android:name="android.test.runner" /> </application> + <uses-permission android:name="android.permission.INSTALL_PACKAGES" /> + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:label="APCT tests for background install control service" android:targetPackage="com.android.server.pm.test.app" /> diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java index b74e5619fd0c..f033fed73b27 100644 --- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java +++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/BackgroundInstallControlServiceTestApp/src/com/android/server/pm/test/app/BackgroundInstallControlServiceTest.java @@ -16,54 +16,256 @@ package com.android.server.pm.test.app; +import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.Manifest.permission.QUERY_ALL_PACKAGES; + +import static com.android.compatibility.common.util.SystemUtil.runShellCommand; + import static com.google.common.truth.Truth.assertThat; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.IBackgroundInstallControlService; import android.content.pm.PackageInfo; +import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; +import android.os.Bundle; +import android.os.IRemoteCallback; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.UserHandle; +import android.util.Pair; +import androidx.annotation.NonNull; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import com.android.compatibility.common.util.ShellIdentityUtils; +import com.android.compatibility.common.util.ThrowingRunnable; + +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.FileInputStream; +import java.io.OutputStream; +import java.util.ArrayList; import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import java.util.stream.Collectors; @RunWith(AndroidJUnit4.class) public class BackgroundInstallControlServiceTest { private static final String TAG = "BackgroundInstallControlServiceTest"; + private static final String ACTION_INSTALL_COMMIT = + "com.android.server.pm.test.app.BackgroundInstallControlServiceTest" + + ".ACTION_INSTALL_COMMIT"; + private static final String MOCK_PACKAGE_NAME = "com.android.servicestests.apps.bicmockapp3"; + + private static final String TEST_DATA_DIR = "/data/local/tmp/"; + private static final String MOCK_APK_FILE = "BackgroundInstallControlMockApp3.apk"; private IBackgroundInstallControlService mIBics; @Before public void setUp() { - mIBics = IBackgroundInstallControlService.Stub.asInterface( - ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE)); + mIBics = + IBackgroundInstallControlService.Stub.asInterface( + ServiceManager.getService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE)); assertThat(mIBics).isNotNull(); } + @After + public void tearDown() { + runShellCommand("pm uninstall " + MOCK_PACKAGE_NAME); + } + @Test public void testGetMockBackgroundInstalledPackages() throws RemoteException { - ParceledListSlice<PackageInfo> slice = mIBics.getBackgroundInstalledPackages( - PackageManager.MATCH_ALL, - UserHandle.USER_ALL); + ParceledListSlice<PackageInfo> slice = + ShellIdentityUtils.invokeMethodWithShellPermissions( + mIBics, + (bics) -> { + try { + return bics.getBackgroundInstalledPackages( + PackageManager.MATCH_ALL, Process.myUserHandle() + .getIdentifier()); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + }, + QUERY_ALL_PACKAGES); assertThat(slice).isNotNull(); var packageList = slice.getList(); assertThat(packageList).isNotNull(); assertThat(packageList).hasSize(2); - var expectedPackageNames = Set.of("com.android.servicestests.apps.bicmockapp1", - "com.android.servicestests.apps.bicmockapp2"); - var actualPackageNames = packageList.stream().map((packageInfo) -> packageInfo.packageName) - .collect(Collectors.toSet()); + var expectedPackageNames = + Set.of( + "com.android.servicestests.apps.bicmockapp1", + "com.android.servicestests.apps.bicmockapp2"); + var actualPackageNames = + packageList.stream() + .map((packageInfo) -> packageInfo.packageName) + .collect(Collectors.toSet()); assertThat(actualPackageNames).containsExactlyElementsIn(expectedPackageNames); } + + @Test + public void testRegisterBackgroundInstallControlCallback() + throws Exception { + String testPackageName = "test"; + int testUserId = 1; + ArrayList<Pair<String, Integer>> sharedResource = new ArrayList<>(); + IRemoteCallback testCallback = + new IRemoteCallback.Stub() { + private final ArrayList<Pair<String, Integer>> mArray = sharedResource; + + @Override + public void sendResult(Bundle data) throws RemoteException { + mArray.add(new Pair(testPackageName, testUserId)); + } + }; + ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn( + mIBics, + (bics) -> { + try { + bics.registerBackgroundInstallCallback(testCallback); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + }, + QUERY_ALL_PACKAGES, + INTERACT_ACROSS_USERS_FULL); + installPackage(TEST_DATA_DIR + MOCK_APK_FILE, MOCK_PACKAGE_NAME); + + assertUntil(() -> sharedResource.size() == 1, 2000); + assertThat(sharedResource.get(0).first).isEqualTo(testPackageName); + assertThat(sharedResource.get(0).second).isEqualTo(testUserId); + } + + @Test + public void testUnregisterBackgroundInstallControlCallback() { + String testValue = "test"; + ArrayList<String> sharedResource = new ArrayList<>(); + IRemoteCallback testCallback = + new IRemoteCallback.Stub() { + private final ArrayList<String> mArray = sharedResource; + + @Override + public void sendResult(Bundle data) throws RemoteException { + mArray.add(testValue); + } + }; + ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn( + mIBics, + (bics) -> { + try { + bics.registerBackgroundInstallCallback(testCallback); + bics.unregisterBackgroundInstallCallback(testCallback); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + }, + QUERY_ALL_PACKAGES, + INTERACT_ACROSS_USERS_FULL); + installPackage(TEST_DATA_DIR + MOCK_APK_FILE, MOCK_PACKAGE_NAME); + + assertUntil(() -> sharedResource.isEmpty(), 2000); + } + + private static boolean installPackage(String apkPath, String packageName) { + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + final CountDownLatch installLatch = new CountDownLatch(1); + final BroadcastReceiver installReceiver = + new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + int packageInstallStatus = + intent.getIntExtra( + PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_FAILURE_INVALID); + if (packageInstallStatus == PackageInstaller.STATUS_SUCCESS) { + installLatch.countDown(); + } + } + }; + final IntentFilter intentFilter = new IntentFilter(ACTION_INSTALL_COMMIT); + context.registerReceiver(installReceiver, intentFilter, Context.RECEIVER_EXPORTED); + + PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller(); + PackageInstaller.SessionParams params = + new PackageInstaller.SessionParams( + PackageInstaller.SessionParams.MODE_FULL_INSTALL); + params.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED); + try { + int sessionId = packageInstaller.createSession(params); + PackageInstaller.Session session = packageInstaller.openSession(sessionId); + OutputStream out = session.openWrite(packageName, 0, -1); + FileInputStream fis = new FileInputStream(apkPath); + byte[] buffer = new byte[65536]; + int size; + while ((size = fis.read(buffer)) != -1) { + out.write(buffer, 0, size); + } + session.fsync(out); + fis.close(); + out.close(); + + runWithShellPermissionIdentity( + () -> { + session.commit(createPendingIntent(context).getIntentSender()); + installLatch.await(5, TimeUnit.SECONDS); + }); + return true; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static PendingIntent createPendingIntent(Context context) { + PendingIntent pendingIntent = + PendingIntent.getBroadcast( + context, + 1, + new Intent(ACTION_INSTALL_COMMIT) + .setPackage( + BackgroundInstallControlServiceTest.class.getPackageName()), + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE); + return pendingIntent; + } + + private static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable command) + throws Exception { + InstrumentationRegistry.getInstrumentation() + .getUiAutomation() + .adoptShellPermissionIdentity(); + try { + command.run(); + } finally { + InstrumentationRegistry.getInstrumentation() + .getUiAutomation() + .dropShellPermissionIdentity(); + } + } + + private static void assertUntil(Supplier<Boolean> condition, int timeoutMs) { + long endTime = System.currentTimeMillis() + timeoutMs; + while (System.currentTimeMillis() <= endTime) { + if (condition.get()) return; + try { + Thread.sleep(10); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + assertThat(condition.get()).isTrue(); + } } diff --git a/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp b/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp index 7804f4ce9d02..39b0ff782b72 100644 --- a/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp +++ b/services/tests/BackgroundInstallControlServiceTests/host/test-app/MockApp/Android.bp @@ -50,3 +50,11 @@ android_test_helper_app { "--rename-manifest-package com.android.servicestests.apps.bicmockapp2", ], } + +android_test_helper_app { + name: "BackgroundInstallControlMockApp3", + defaults: ["bic-mock-app-defaults"], + aaptflags: [ + "--rename-manifest-package com.android.servicestests.apps.bicmockapp3", + ], +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java new file mode 100644 index 000000000000..e1fce9b75906 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundInstallControlCallbackHelperTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import static com.android.server.pm.BackgroundInstallControlCallbackHelper.FLAGGED_PACKAGE_NAME_KEY; +import static com.android.server.pm.BackgroundInstallControlCallbackHelper.FLAGGED_USER_ID_KEY; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.after; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.os.Bundle; +import android.os.IRemoteCallback; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; + +/** Unit tests for {@link BackgroundInstallControlCallbackHelperTest} */ +@Presubmit +@RunWith(JUnit4.class) +public class BackgroundInstallControlCallbackHelperTest { + + private final IRemoteCallback mCallback = + spy( + new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle extras) {} + }); + + private BackgroundInstallControlCallbackHelper mCallbackHelper; + + @Before + public void setup() { + mCallbackHelper = new BackgroundInstallControlCallbackHelper(); + } + + @Test + public void registerBackgroundInstallControlCallback_registers_successfully() { + mCallbackHelper.registerBackgroundInstallCallback(mCallback); + + synchronized (mCallbackHelper.mCallbacks) { + assertEquals(1, mCallbackHelper.mCallbacks.getRegisteredCallbackCount()); + assertEquals(mCallback, mCallbackHelper.mCallbacks.getRegisteredCallbackItem(0)); + } + } + + @Test + public void unregisterBackgroundInstallControlCallback_unregisters_successfully() { + synchronized (mCallbackHelper.mCallbacks) { + mCallbackHelper.mCallbacks.register(mCallback); + } + + mCallbackHelper.unregisterBackgroundInstallCallback(mCallback); + + synchronized (mCallbackHelper.mCallbacks) { + assertEquals(0, mCallbackHelper.mCallbacks.getRegisteredCallbackCount()); + } + } + + @Test + public void notifyAllCallbacks_broadcastsToCallbacks() + throws RemoteException { + String testPackageName = "testname"; + int testUserId = 1; + mCallbackHelper.registerBackgroundInstallCallback(mCallback); + + mCallbackHelper.notifyAllCallbacks(testUserId, testPackageName); + + ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(mCallback, after(1000).times(1)).sendResult(bundleCaptor.capture()); + Bundle receivedBundle = bundleCaptor.getValue(); + assertEquals(testPackageName, receivedBundle.getString(FLAGGED_PACKAGE_NAME_KEY)); + assertEquals(testUserId, receivedBundle.getInt(FLAGGED_USER_ID_KEY)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java index daf18edaf2de..3069d25e39d7 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java @@ -16,6 +16,10 @@ package com.android.server.pm; +import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.Manifest.permission.QUERY_ALL_PACKAGES; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -27,6 +31,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -97,7 +102,6 @@ public final class BackgroundInstallControlServiceTest { private Looper mLooper; private File mFile; - @Mock private Context mContext; @Mock @@ -108,8 +112,12 @@ public final class BackgroundInstallControlServiceTest { private UsageStatsManagerInternal mUsageStatsManagerInternal; @Mock private PermissionManagerServiceInternal mPermissionManager; + @Mock + private BackgroundInstallControlCallbackHelper mCallbackHelper; + @Captor private ArgumentCaptor<PackageManagerInternal.PackageListObserver> mPackageListObserverCaptor; + @Captor private ArgumentCaptor<UsageEventListener> mUsageEventListenerCaptor; @@ -119,11 +127,12 @@ public final class BackgroundInstallControlServiceTest { mTestLooper = new TestLooper(); mLooper = mTestLooper.getLooper(); - mFile = new File( - InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(), - "test"); - mBackgroundInstallControlService = new BackgroundInstallControlService( - new MockInjector(mContext)); + mFile = + new File( + InstrumentationRegistry.getInstrumentation().getContext().getCacheDir(), + "test"); + mBackgroundInstallControlService = + new BackgroundInstallControlService(new MockInjector(mContext)); verify(mUsageStatsManagerInternal).registerListener(mUsageEventListenerCaptor.capture()); mUsageEventListener = mUsageEventListenerCaptor.getValue(); @@ -143,8 +152,7 @@ public final class BackgroundInstallControlServiceTest { assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); mBackgroundInstallControlService.initBackgroundInstalledPackages(); assertNotNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); - assertEquals(0, - mBackgroundInstallControlService.getBackgroundInstalledPackages().size()); + assertEquals(0, mBackgroundInstallControlService.getBackgroundInstalledPackages().size()); } @Test @@ -161,12 +169,9 @@ public final class BackgroundInstallControlServiceTest { // Write test data to the file on the disk. try { ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream); - long token = protoOutputStream.start( - BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); - protoOutputStream.write( - BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1); - protoOutputStream.write( - BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1); + long token = protoOutputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); + protoOutputStream.write(BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1); + protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1); protoOutputStream.end(token); protoOutputStream.flush(); atomicFile.finishWrite(fileOutputStream); @@ -198,20 +203,14 @@ public final class BackgroundInstallControlServiceTest { try { ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream); - long token = protoOutputStream.start( - BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); - protoOutputStream.write( - BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1); - protoOutputStream.write( - BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1); + long token = protoOutputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); + protoOutputStream.write(BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_1); + protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, USER_ID_1 + 1); protoOutputStream.end(token); - token = protoOutputStream.start( - BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); - protoOutputStream.write( - BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_2); - protoOutputStream.write( - BackgroundInstalledPackageProto.USER_ID, USER_ID_2 + 1); + token = protoOutputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); + protoOutputStream.write(BackgroundInstalledPackageProto.PACKAGE_NAME, PACKAGE_NAME_2); + protoOutputStream.write(BackgroundInstalledPackageProto.USER_ID, USER_ID_2 + 1); protoOutputStream.end(token); protoOutputStream.flush(); @@ -241,7 +240,7 @@ public final class BackgroundInstallControlServiceTest { // Read the file on the disk to verify var packagesInDisk = new SparseSetArray<>(); AtomicFile atomicFile = new AtomicFile(mFile); - try (FileInputStream fileInputStream = atomicFile.openRead()) { + try (FileInputStream fileInputStream = atomicFile.openRead()) { ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream); while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { @@ -249,23 +248,25 @@ public final class BackgroundInstallControlServiceTest { != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) { continue; } - long token = protoInputStream.start( - BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); + long token = + protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); String packageName = null; int userId = UserHandle.USER_NULL; while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { switch (protoInputStream.getFieldNumber()) { case (int) BackgroundInstalledPackageProto.PACKAGE_NAME: - packageName = protoInputStream.readString( - BackgroundInstalledPackageProto.PACKAGE_NAME); + packageName = + protoInputStream.readString( + BackgroundInstalledPackageProto.PACKAGE_NAME); break; case (int) BackgroundInstalledPackageProto.USER_ID: - userId = protoInputStream.readInt( - BackgroundInstalledPackageProto.USER_ID) - 1; + userId = + protoInputStream.readInt( + BackgroundInstalledPackageProto.USER_ID) + - 1; break; default: - fail("Undefined field in proto: " - + protoInputStream.getFieldNumber()); + fail("Undefined field in proto: " + protoInputStream.getFieldNumber()); } } protoInputStream.end(token); @@ -296,7 +297,7 @@ public final class BackgroundInstallControlServiceTest { // Read the file on the disk to verify var packagesInDisk = new SparseSetArray<>(); AtomicFile atomicFile = new AtomicFile(mFile); - try (FileInputStream fileInputStream = atomicFile.openRead()) { + try (FileInputStream fileInputStream = atomicFile.openRead()) { ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream); while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { @@ -304,23 +305,25 @@ public final class BackgroundInstallControlServiceTest { != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) { continue; } - long token = protoInputStream.start( - BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); + long token = + protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); String packageName = null; int userId = UserHandle.USER_NULL; while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { switch (protoInputStream.getFieldNumber()) { case (int) BackgroundInstalledPackageProto.PACKAGE_NAME: - packageName = protoInputStream.readString( - BackgroundInstalledPackageProto.PACKAGE_NAME); + packageName = + protoInputStream.readString( + BackgroundInstalledPackageProto.PACKAGE_NAME); break; case (int) BackgroundInstalledPackageProto.USER_ID: - userId = protoInputStream.readInt( - BackgroundInstalledPackageProto.USER_ID) - 1; + userId = + protoInputStream.readInt( + BackgroundInstalledPackageProto.USER_ID) + - 1; break; default: - fail("Undefined field in proto: " - + protoInputStream.getFieldNumber()); + fail("Undefined field in proto: " + protoInputStream.getFieldNumber()); } } protoInputStream.end(token); @@ -353,7 +356,7 @@ public final class BackgroundInstallControlServiceTest { // Read the file on the disk to verify var packagesInDisk = new SparseSetArray<>(); AtomicFile atomicFile = new AtomicFile(mFile); - try (FileInputStream fileInputStream = atomicFile.openRead()) { + try (FileInputStream fileInputStream = atomicFile.openRead()) { ProtoInputStream protoInputStream = new ProtoInputStream(fileInputStream); while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { @@ -361,23 +364,25 @@ public final class BackgroundInstallControlServiceTest { != (int) BackgroundInstalledPackagesProto.BG_INSTALLED_PKG) { continue; } - long token = protoInputStream.start( - BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); + long token = + protoInputStream.start(BackgroundInstalledPackagesProto.BG_INSTALLED_PKG); String packageName = null; int userId = UserHandle.USER_NULL; while (protoInputStream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { switch (protoInputStream.getFieldNumber()) { case (int) BackgroundInstalledPackageProto.PACKAGE_NAME: - packageName = protoInputStream.readString( - BackgroundInstalledPackageProto.PACKAGE_NAME); + packageName = + protoInputStream.readString( + BackgroundInstalledPackageProto.PACKAGE_NAME); break; case (int) BackgroundInstalledPackageProto.USER_ID: - userId = protoInputStream.readInt( - BackgroundInstalledPackageProto.USER_ID) - 1; + userId = + protoInputStream.readInt( + BackgroundInstalledPackageProto.USER_ID) + - 1; break; default: - fail("Undefined field in proto: " - + protoInputStream.getFieldNumber()); + fail("Undefined field in proto: " + protoInputStream.getFieldNumber()); } } protoInputStream.end(token); @@ -399,51 +404,55 @@ public final class BackgroundInstallControlServiceTest { @Test public void testHandleUsageEvent_permissionDenied() { - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); - doReturn(PackageManager.PERMISSION_DENIED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, 0); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + doReturn(PackageManager.PERMISSION_DENIED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, USER_ID_1, INSTALLER_NAME_1, 0); mTestLooper.dispatchAll(); - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); } @Test public void testHandleUsageEvent_permissionGranted() { - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, 0); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, USER_ID_1, INSTALLER_NAME_1, 0); mTestLooper.dispatchAll(); - assertEquals(1, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + assertEquals( + 1, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); } @Test public void testHandleUsageEvent_ignoredEvent() { - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.USER_INTERACTION, - USER_ID_1, INSTALLER_NAME_1, 0); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent(UsageEvents.Event.USER_INTERACTION, USER_ID_1, INSTALLER_NAME_1, 0); mTestLooper.dispatchAll(); - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); } @Test public void testHandleUsageEvent_firstActivityResumedHalfTimeFrame() { - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_1, + INSTALLER_NAME_1, + USAGE_EVENT_TIMESTAMP_1); mTestLooper.dispatchAll(); var installerForegroundTimeFrames = @@ -461,14 +470,18 @@ public final class BackgroundInstallControlServiceTest { @Test public void testHandleUsageEvent_firstActivityResumedOneTimeFrame() { - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1); - generateUsageEvent(Event.ACTIVITY_STOPPED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_1, + INSTALLER_NAME_1, + USAGE_EVENT_TIMESTAMP_1); + generateUsageEvent( + Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); mTestLooper.dispatchAll(); var installerForegroundTimeFrames = @@ -486,16 +499,23 @@ public final class BackgroundInstallControlServiceTest { @Test public void testHandleUsageEvent_firstActivityResumedOneAndHalfTimeFrame() { - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1); - generateUsageEvent(Event.ACTIVITY_STOPPED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_1, + INSTALLER_NAME_1, + USAGE_EVENT_TIMESTAMP_1); + generateUsageEvent( + Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_1, + INSTALLER_NAME_1, + USAGE_EVENT_TIMESTAMP_3); mTestLooper.dispatchAll(); var installerForegroundTimeFrames = @@ -517,12 +537,13 @@ public final class BackgroundInstallControlServiceTest { @Test public void testHandleUsageEvent_firstNoneActivityResumed() { - assertEquals(0, - mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(Event.ACTIVITY_STOPPED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1); + assertEquals( + 0, mBackgroundInstallControlService.getInstallerForegroundTimeFrames().numMaps()); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1); mTestLooper.dispatchAll(); var installerForegroundTimeFrames = @@ -535,27 +556,26 @@ public final class BackgroundInstallControlServiceTest { } @Test - public void testHandleUsageEvent_packageAddedNoUsageEvent() throws - NoSuchFieldException, PackageManager.NameNotFoundException { + public void testHandleUsageEvent_packageAddedNoUsageEvent() + throws NoSuchFieldException, PackageManager.NameNotFoundException { assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); - InstallSourceInfo installSourceInfo = new InstallSourceInfo( - /* initiatingPackageName = */ INSTALLER_NAME_1, - /* initiatingPackageSigningInfo = */ null, - /* originatingPackageName = */ null, - /* installingPackageName = */ INSTALLER_NAME_1); + InstallSourceInfo installSourceInfo = + new InstallSourceInfo( + /* initiatingPackageName= */ INSTALLER_NAME_1, + /* initiatingPackageSigningInfo= */ null, + /* originatingPackageName= */ null, + /* installingPackageName= */ INSTALLER_NAME_1); assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1); when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo); ApplicationInfo appInfo = mock(ApplicationInfo.class); - when(mPackageManager.getApplicationInfoAsUser( - eq(PACKAGE_NAME_1), - any(), - anyInt()) - ).thenReturn(appInfo); + when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt())) + .thenReturn(appInfo); - long createTimestamp = PACKAGE_ADD_TIMESTAMP_1 - - (System.currentTimeMillis() - SystemClock.uptimeMillis()); - FieldSetter.setField(appInfo, + long createTimestamp = + PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis()); + FieldSetter.setField( + appInfo, ApplicationInfo.class.getDeclaredField("createTimestamp"), createTimestamp); @@ -572,27 +592,26 @@ public final class BackgroundInstallControlServiceTest { } @Test - public void testHandleUsageEvent_packageAddedInsideTimeFrame() throws - NoSuchFieldException, PackageManager.NameNotFoundException { + public void testHandleUsageEvent_packageAddedInsideTimeFrame() + throws NoSuchFieldException, PackageManager.NameNotFoundException { assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); - InstallSourceInfo installSourceInfo = new InstallSourceInfo( - /* initiatingPackageName = */ INSTALLER_NAME_1, - /* initiatingPackageSigningInfo = */ null, - /* originatingPackageName = */ null, - /* installingPackageName = */ INSTALLER_NAME_1); + InstallSourceInfo installSourceInfo = + new InstallSourceInfo( + /* initiatingPackageName= */ INSTALLER_NAME_1, + /* initiatingPackageSigningInfo= */ null, + /* originatingPackageName= */ null, + /* installingPackageName= */ INSTALLER_NAME_1); assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1); when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo); ApplicationInfo appInfo = mock(ApplicationInfo.class); - when(mPackageManager.getApplicationInfoAsUser( - eq(PACKAGE_NAME_1), - any(), - anyInt()) - ).thenReturn(appInfo); + when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt())) + .thenReturn(appInfo); - long createTimestamp = PACKAGE_ADD_TIMESTAMP_1 - - (System.currentTimeMillis() - SystemClock.uptimeMillis()); - FieldSetter.setField(appInfo, + long createTimestamp = + PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis()); + FieldSetter.setField( + appInfo, ApplicationInfo.class.getDeclaredField("createTimestamp"), createTimestamp); @@ -604,12 +623,16 @@ public final class BackgroundInstallControlServiceTest { // The 2 usage events make the package adding inside a time frame. // So it's not a background install. Thus, it's null for the return of // mBackgroundInstallControlService.getBackgroundInstalledPackages() - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_1); - generateUsageEvent(Event.ACTIVITY_STOPPED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_1, + INSTALLER_NAME_1, + USAGE_EVENT_TIMESTAMP_1); + generateUsageEvent( + Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid); mTestLooper.dispatchAll(); @@ -617,27 +640,26 @@ public final class BackgroundInstallControlServiceTest { } @Test - public void testHandleUsageEvent_packageAddedOutsideTimeFrame1() throws - NoSuchFieldException, PackageManager.NameNotFoundException { + public void testHandleUsageEvent_packageAddedOutsideTimeFrame1() + throws NoSuchFieldException, PackageManager.NameNotFoundException { assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); - InstallSourceInfo installSourceInfo = new InstallSourceInfo( - /* initiatingPackageName = */ INSTALLER_NAME_1, - /* initiatingPackageSigningInfo = */ null, - /* originatingPackageName = */ null, - /* installingPackageName = */ INSTALLER_NAME_1); + InstallSourceInfo installSourceInfo = + new InstallSourceInfo( + /* initiatingPackageName= */ INSTALLER_NAME_1, + /* initiatingPackageSigningInfo= */ null, + /* originatingPackageName= */ null, + /* installingPackageName= */ INSTALLER_NAME_1); assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1); when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo); ApplicationInfo appInfo = mock(ApplicationInfo.class); - when(mPackageManager.getApplicationInfoAsUser( - eq(PACKAGE_NAME_1), - any(), - anyInt()) - ).thenReturn(appInfo); + when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt())) + .thenReturn(appInfo); - long createTimestamp = PACKAGE_ADD_TIMESTAMP_1 - - (System.currentTimeMillis() - SystemClock.uptimeMillis()); - FieldSetter.setField(appInfo, + long createTimestamp = + PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis()); + FieldSetter.setField( + appInfo, ApplicationInfo.class.getDeclaredField("createTimestamp"), createTimestamp); @@ -650,12 +672,16 @@ public final class BackgroundInstallControlServiceTest { // Compared to testHandleUsageEvent_packageAddedInsideTimeFrame, // it's a background install. Thus, it's not null for the return of // mBackgroundInstallControlService.getBackgroundInstalledPackages() - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); - generateUsageEvent(Event.ACTIVITY_STOPPED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_1, + INSTALLER_NAME_1, + USAGE_EVENT_TIMESTAMP_2); + generateUsageEvent( + Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3); mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid); mTestLooper.dispatchAll(); @@ -665,28 +691,28 @@ public final class BackgroundInstallControlServiceTest { assertEquals(1, packages.size()); assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1)); } + @Test - public void testHandleUsageEvent_packageAddedOutsideTimeFrame2() throws - NoSuchFieldException, PackageManager.NameNotFoundException { + public void testHandleUsageEvent_packageAddedOutsideTimeFrame2() + throws NoSuchFieldException, PackageManager.NameNotFoundException { assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); - InstallSourceInfo installSourceInfo = new InstallSourceInfo( - /* initiatingPackageName = */ INSTALLER_NAME_1, - /* initiatingPackageSigningInfo = */ null, - /* originatingPackageName = */ null, - /* installingPackageName = */ INSTALLER_NAME_1); + InstallSourceInfo installSourceInfo = + new InstallSourceInfo( + /* initiatingPackageName= */ INSTALLER_NAME_1, + /* initiatingPackageSigningInfo= */ null, + /* originatingPackageName= */ null, + /* installingPackageName= */ INSTALLER_NAME_1); assertEquals(installSourceInfo.getInstallingPackageName(), INSTALLER_NAME_1); when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo); ApplicationInfo appInfo = mock(ApplicationInfo.class); - when(mPackageManager.getApplicationInfoAsUser( - eq(PACKAGE_NAME_1), - any(), - anyInt()) - ).thenReturn(appInfo); + when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt())) + .thenReturn(appInfo); - long createTimestamp = PACKAGE_ADD_TIMESTAMP_1 - - (System.currentTimeMillis() - SystemClock.uptimeMillis()); - FieldSetter.setField(appInfo, + long createTimestamp = + PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis()); + FieldSetter.setField( + appInfo, ApplicationInfo.class.getDeclaredField("createTimestamp"), createTimestamp); @@ -700,12 +726,16 @@ public final class BackgroundInstallControlServiceTest { // Compared to testHandleUsageEvent_packageAddedInsideTimeFrame, // it's a background install. Thus, it's not null for the return of // mBackgroundInstallControlService.getBackgroundInstalledPackages() - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_2); - generateUsageEvent(Event.ACTIVITY_STOPPED, - USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_3); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_2, + INSTALLER_NAME_2, + USAGE_EVENT_TIMESTAMP_2); + generateUsageEvent( + Event.ACTIVITY_STOPPED, USER_ID_2, INSTALLER_NAME_2, USAGE_EVENT_TIMESTAMP_3); mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid); mTestLooper.dispatchAll(); @@ -715,31 +745,31 @@ public final class BackgroundInstallControlServiceTest { assertEquals(1, packages.size()); assertTrue(packages.contains(USER_ID_1, PACKAGE_NAME_1)); } + @Test - public void testHandleUsageEvent_packageAddedThroughAdb() throws - NoSuchFieldException, PackageManager.NameNotFoundException { + public void testHandleUsageEvent_packageAddedThroughAdb() + throws NoSuchFieldException, PackageManager.NameNotFoundException { assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); // This test is a duplicate of testHandleUsageEvent_packageAddedThroughAdb except the // initiatingPackageName used to be null but is now "com.android.shell". This test ensures // that the behavior is still the same for when the initiatingPackageName is null. - InstallSourceInfo installSourceInfo = new InstallSourceInfo( - /* initiatingPackageName = */ null, - /* initiatingPackageSigningInfo = */ null, - /* originatingPackageName = */ null, - /* installingPackageName = */ INSTALLER_NAME_1); + InstallSourceInfo installSourceInfo = + new InstallSourceInfo( + /* initiatingPackageName= */ null, + /* initiatingPackageSigningInfo= */ null, + /* originatingPackageName= */ null, + /* installingPackageName= */ INSTALLER_NAME_1); // b/265203007 when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo); ApplicationInfo appInfo = mock(ApplicationInfo.class); - when(mPackageManager.getApplicationInfoAsUser( - eq(PACKAGE_NAME_1), - any(), - anyInt()) - ).thenReturn(appInfo); + when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt())) + .thenReturn(appInfo); - long createTimestamp = PACKAGE_ADD_TIMESTAMP_1 - - (System.currentTimeMillis() - SystemClock.uptimeMillis()); - FieldSetter.setField(appInfo, + long createTimestamp = + PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis()); + FieldSetter.setField( + appInfo, ApplicationInfo.class.getDeclaredField("createTimestamp"), createTimestamp); @@ -751,12 +781,16 @@ public final class BackgroundInstallControlServiceTest { // for ADB installs the initiatingPackageName used to be null, despite being detected // as a background install. Since we do not want to treat side-loaded apps as background // install getBackgroundInstalledPackages() is expected to return null - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); - generateUsageEvent(Event.ACTIVITY_STOPPED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_1, + INSTALLER_NAME_1, + USAGE_EVENT_TIMESTAMP_2); + generateUsageEvent( + Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3); mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid); mTestLooper.dispatchAll(); @@ -764,31 +798,31 @@ public final class BackgroundInstallControlServiceTest { var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages(); assertNull(packages); } + @Test - public void testHandleUsageEvent_packageAddedThroughAdb2() throws - NoSuchFieldException, PackageManager.NameNotFoundException { + public void testHandleUsageEvent_packageAddedThroughAdb2() + throws NoSuchFieldException, PackageManager.NameNotFoundException { assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); // This test is a duplicate of testHandleUsageEvent_packageAddedThroughAdb except the // initiatingPackageName used to be null but is now "com.android.shell". This test ensures // that the behavior is still the same after this change. - InstallSourceInfo installSourceInfo = new InstallSourceInfo( - /* initiatingPackageName = */ "com.android.shell", - /* initiatingPackageSigningInfo = */ null, - /* originatingPackageName = */ null, - /* installingPackageName = */ INSTALLER_NAME_1); + InstallSourceInfo installSourceInfo = + new InstallSourceInfo( + /* initiatingPackageName= */ "com.android.shell", + /* initiatingPackageSigningInfo= */ null, + /* originatingPackageName= */ null, + /* installingPackageName= */ INSTALLER_NAME_1); // b/265203007 when(mPackageManager.getInstallSourceInfo(anyString())).thenReturn(installSourceInfo); ApplicationInfo appInfo = mock(ApplicationInfo.class); - when(mPackageManager.getApplicationInfoAsUser( - eq(PACKAGE_NAME_1), - any(), - anyInt()) - ).thenReturn(appInfo); + when(mPackageManager.getApplicationInfoAsUser(eq(PACKAGE_NAME_1), any(), anyInt())) + .thenReturn(appInfo); - long createTimestamp = PACKAGE_ADD_TIMESTAMP_1 - - (System.currentTimeMillis() - SystemClock.uptimeMillis()); - FieldSetter.setField(appInfo, + long createTimestamp = + PACKAGE_ADD_TIMESTAMP_1 - (System.currentTimeMillis() - SystemClock.uptimeMillis()); + FieldSetter.setField( + appInfo, ApplicationInfo.class.getDeclaredField("createTimestamp"), createTimestamp); @@ -800,12 +834,16 @@ public final class BackgroundInstallControlServiceTest { // for ADB installs the initiatingPackageName is com.android.shell, despite being detected // as a background install. Since we do not want to treat side-loaded apps as background // install getBackgroundInstalledPackages() is expected to return null - doReturn(PackageManager.PERMISSION_GRANTED).when(mPermissionManager).checkPermission( - anyString(), anyString(), anyInt(), anyInt()); - generateUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_2); - generateUsageEvent(Event.ACTIVITY_STOPPED, - USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManager) + .checkPermission(anyString(), anyString(), anyInt(), anyInt()); + generateUsageEvent( + UsageEvents.Event.ACTIVITY_RESUMED, + USER_ID_1, + INSTALLER_NAME_1, + USAGE_EVENT_TIMESTAMP_2); + generateUsageEvent( + Event.ACTIVITY_STOPPED, USER_ID_1, INSTALLER_NAME_1, USAGE_EVENT_TIMESTAMP_3); mPackageListObserver.onPackageAdded(PACKAGE_NAME_1, uid); mTestLooper.dispatchAll(); @@ -813,6 +851,7 @@ public final class BackgroundInstallControlServiceTest { var packages = mBackgroundInstallControlService.getBackgroundInstalledPackages(); assertNull(packages); } + @Test public void testPackageRemoved() { assertNull(mBackgroundInstallControlService.getBackgroundInstalledPackages()); @@ -859,8 +898,7 @@ public final class BackgroundInstallControlServiceTest { packages.add(packageInfo2); var packageInfo3 = makePackageInfo(PACKAGE_NAME_3); packages.add(packageInfo3); - doReturn(packages).when(mPackageManager).getInstalledPackagesAsUser( - any(), anyInt()); + doReturn(packages).when(mPackageManager).getInstalledPackagesAsUser(any(), anyInt()); var resultPackages = mBackgroundInstallControlService.getBackgroundInstalledPackages(0L, USER_ID_1); @@ -870,18 +908,44 @@ public final class BackgroundInstallControlServiceTest { assertFalse(resultPackages.getList().contains(packageInfo3)); } + @Test(expected = SecurityException.class) + public void enforceCallerQueryPackagesPermissionsThrowsSecurityException() { + doThrow(new SecurityException("test")).when(mContext) + .enforceCallingPermission(eq(QUERY_ALL_PACKAGES), anyString()); + + mBackgroundInstallControlService.enforceCallerQueryPackagesPermissions(); + } + + @Test + public void enforceCallerQueryPackagesPermissionsDoesNotThrowSecurityException() { + //enforceCallerQueryPackagesPermissions do not throw + + mBackgroundInstallControlService.enforceCallerQueryPackagesPermissions(); + } + + @Test(expected = SecurityException.class) + public void enforceCallerInteractCrossUserPermissionsThrowsSecurityException() { + doThrow(new SecurityException("test")).when(mContext) + .enforceCallingPermission(eq(INTERACT_ACROSS_USERS_FULL), anyString()); + + mBackgroundInstallControlService.enforceCallerInteractCrossUserPermissions(); + } + @Test + public void enforceCallerInteractCrossUserPermissionsDoesNotThrowSecurityException() { + //enforceCallerQueryPackagesPermissions do not throw + + mBackgroundInstallControlService.enforceCallerInteractCrossUserPermissions(); + } + /** * Mock a usage event occurring. * * @param usageEventId id of a usage event - * @param userId user id of a usage event - * @param pkgName package name of a usage event - * @param timestamp timestamp of a usage event + * @param userId user id of a usage event + * @param pkgName package name of a usage event + * @param timestamp timestamp of a usage event */ - private void generateUsageEvent(int usageEventId, - int userId, - String pkgName, - long timestamp) { + private void generateUsageEvent(int usageEventId, int userId, String pkgName, long timestamp) { Event event = new Event(usageEventId, timestamp); event.mPackage = pkgName; mUsageEventListener.onUsageEvent(userId, event); @@ -935,5 +999,10 @@ public final class BackgroundInstallControlServiceTest { public File getDiskFile() { return mFile; } + + @Override + public BackgroundInstallControlCallbackHelper getBackgroundInstallControlCallbackHelper() { + return mCallbackHelper; + } } } diff --git a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java index 2bc056ee743f..fee1b25f04e5 100644 --- a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java +++ b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java @@ -16,6 +16,9 @@ package android.transparency.test.app; +import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.Manifest.permission.QUERY_ALL_PACKAGES; + import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -27,6 +30,7 @@ import android.util.Log; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; +import com.android.compatibility.common.util.ShellIdentityUtils; import com.android.internal.os.IBinaryTransparencyService.AppInfo; import org.junit.Before; @@ -116,7 +120,13 @@ public class BinaryTransparencyTest { @Test public void testCollectAllSilentInstalledMbaInfo() { // Action - var appInfoList = mBt.collectAllSilentInstalledMbaInfo(new Bundle()); + var appInfoList = + ShellIdentityUtils.invokeMethodWithShellPermissions( + mBt, + (Bt) -> + mBt.collectAllSilentInstalledMbaInfo(new Bundle()), + QUERY_ALL_PACKAGES, + INTERACT_ACROSS_USERS_FULL); // Verify assertThat(appInfoList).isNotEmpty(); // because we just installed from the host side |