Merge "Activity interceptor dialog for suspended apps" into pi-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
822ce4f3aa
@@ -163,6 +163,7 @@ package android {
|
||||
field public static final java.lang.String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS";
|
||||
field public static final java.lang.String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
|
||||
field public static final java.lang.String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
|
||||
field public static final java.lang.String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS";
|
||||
field public static final java.lang.String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION";
|
||||
field public static final java.lang.String SERIAL_PORT = "android.permission.SERIAL_PORT";
|
||||
field public static final java.lang.String SET_ACTIVITY_WATCHER = "android.permission.SET_ACTIVITY_WATCHER";
|
||||
@@ -878,6 +879,7 @@ package android.content {
|
||||
field public static final java.lang.String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
|
||||
field public static final java.lang.String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE";
|
||||
field public static final java.lang.String ACTION_REVIEW_PERMISSIONS = "android.intent.action.REVIEW_PERMISSIONS";
|
||||
field public static final java.lang.String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
|
||||
field public static final deprecated java.lang.String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
|
||||
field public static final java.lang.String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED";
|
||||
field public static final java.lang.String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
|
||||
|
||||
@@ -2155,10 +2155,10 @@ public class ApplicationPackageManager extends PackageManager {
|
||||
public String[] setPackagesSuspended(String[] packageNames, boolean suspended,
|
||||
PersistableBundle appExtras, PersistableBundle launcherExtras,
|
||||
String dialogMessage) {
|
||||
// TODO (b/75332201): Pass in the dialogMessage and use it in the interceptor dialog
|
||||
try {
|
||||
return mPM.setPackagesSuspendedAsUser(packageNames, suspended, appExtras,
|
||||
launcherExtras, mContext.getOpPackageName(), mContext.getUserId());
|
||||
launcherExtras, dialogMessage, mContext.getOpPackageName(),
|
||||
mContext.getUserId());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
@@ -2281,6 +2281,28 @@ public class Intent implements Parcelable, Cloneable {
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_MY_PACKAGE_SUSPENDED = "android.intent.action.MY_PACKAGE_SUSPENDED";
|
||||
|
||||
/**
|
||||
* Activity Action: Started to show more details about why an application was suspended.
|
||||
*
|
||||
* <p>Apps holding {@link android.Manifest.permission#SUSPEND_APPS} must declare an activity
|
||||
* handling this intent and protect it with
|
||||
* {@link android.Manifest.permission#SEND_SHOW_SUSPENDED_APP_DETAILS}.
|
||||
*
|
||||
* <p>Includes an extra {@link #EXTRA_PACKAGE_NAME} which is the name of the suspended package.
|
||||
*
|
||||
* <p class="note">This is a protected intent that can only be sent
|
||||
* by the system.
|
||||
*
|
||||
* @see PackageManager#isPackageSuspended()
|
||||
* @see #ACTION_PACKAGES_SUSPENDED
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
|
||||
public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS =
|
||||
"android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
|
||||
|
||||
/**
|
||||
* Broadcast Action: Sent to a package that has been unsuspended.
|
||||
*
|
||||
|
||||
@@ -273,8 +273,8 @@ interface IPackageManager {
|
||||
void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage);
|
||||
|
||||
String[] setPackagesSuspendedAsUser(in String[] packageNames, boolean suspended,
|
||||
in PersistableBundle launcherExtras, in PersistableBundle appExtras,
|
||||
String callingPackage, int userId);
|
||||
in PersistableBundle appExtras, in PersistableBundle launcherExtras,
|
||||
String dialogMessage, String callingPackage, int userId);
|
||||
|
||||
boolean isPackageSuspendedForUser(String packageName, int userId);
|
||||
|
||||
|
||||
@@ -191,10 +191,10 @@ public abstract class PackageManagerInternal {
|
||||
/**
|
||||
* Retrieve launcher extras for a suspended package provided to the system in
|
||||
* {@link PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle,
|
||||
* PersistableBundle, String)}
|
||||
* PersistableBundle, String)}.
|
||||
*
|
||||
* @param packageName The package for which to return launcher extras.
|
||||
* @param userId The user for which to check,
|
||||
* @param userId The user for which to check.
|
||||
* @return The launcher extras.
|
||||
*
|
||||
* @see PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle,
|
||||
@@ -213,6 +213,29 @@ public abstract class PackageManagerInternal {
|
||||
*/
|
||||
public abstract boolean isPackageSuspended(String packageName, int userId);
|
||||
|
||||
/**
|
||||
* Get the name of the package that suspended the given package. Packages can be suspended by
|
||||
* device administrators or apps holding {@link android.Manifest.permission#MANAGE_USERS} or
|
||||
* {@link android.Manifest.permission#SUSPEND_APPS}.
|
||||
*
|
||||
* @param suspendedPackage The package that has been suspended.
|
||||
* @param userId The user for which to check.
|
||||
* @return Name of the package that suspended the given package. Returns {@code null} if the
|
||||
* given package is not currently suspended and the platform package name - i.e.
|
||||
* {@code "android"} - if the package was suspended by a device admin.
|
||||
*/
|
||||
public abstract String getSuspendingPackage(String suspendedPackage, int userId);
|
||||
|
||||
/**
|
||||
* Get the dialog message to be shown to the user when they try to launch a suspended
|
||||
* application.
|
||||
*
|
||||
* @param suspendedPackage The package that has been suspended.
|
||||
* @param userId The user for which to check.
|
||||
* @return The dialog message to be shown to the user.
|
||||
*/
|
||||
public abstract String getSuspendedDialogMessage(String suspendedPackage, int userId);
|
||||
|
||||
/**
|
||||
* Do a straight uid lookup for the given package/application in the given user.
|
||||
* @see PackageManager#getPackageUidAsUser(String, int, int)
|
||||
|
||||
@@ -34,6 +34,7 @@ import android.util.ArraySet;
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Per-user state information about a package.
|
||||
@@ -47,6 +48,7 @@ public class PackageUserState {
|
||||
public boolean hidden; // Is the app restricted by owner / admin
|
||||
public boolean suspended;
|
||||
public String suspendingPackage;
|
||||
public String dialogMessage; // Message to show when a suspended package launch attempt is made
|
||||
public PersistableBundle suspendedAppExtras;
|
||||
public PersistableBundle suspendedLauncherExtras;
|
||||
public boolean instantApp;
|
||||
@@ -82,6 +84,7 @@ public class PackageUserState {
|
||||
hidden = o.hidden;
|
||||
suspended = o.suspended;
|
||||
suspendingPackage = o.suspendingPackage;
|
||||
dialogMessage = o.dialogMessage;
|
||||
suspendedAppExtras = o.suspendedAppExtras;
|
||||
suspendedLauncherExtras = o.suspendedLauncherExtras;
|
||||
instantApp = o.instantApp;
|
||||
@@ -208,6 +211,9 @@ public class PackageUserState {
|
||||
|| !suspendingPackage.equals(oldState.suspendingPackage)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(dialogMessage, oldState.dialogMessage)) {
|
||||
return false;
|
||||
}
|
||||
if (!BaseBundle.kindofEquals(suspendedAppExtras,
|
||||
oldState.suspendedAppExtras)) {
|
||||
return false;
|
||||
|
||||
82
core/java/com/android/internal/app/SuspendedAppActivity.java
Normal file
82
core/java/com/android/internal/app/SuspendedAppActivity.java
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.internal.app;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Slog;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.android.internal.R;
|
||||
|
||||
public class SuspendedAppActivity extends AlertActivity
|
||||
implements DialogInterface.OnClickListener {
|
||||
private static final String TAG = "SuspendedAppActivity";
|
||||
|
||||
public static final String EXTRA_DIALOG_MESSAGE = "SuspendedAppActivity.extra.DIALOG_MESSAGE";
|
||||
public static final String EXTRA_MORE_DETAILS_INTENT =
|
||||
"SuspendedAppActivity.extra.MORE_DETAILS_INTENT";
|
||||
|
||||
private Intent mMoreDetailsIntent;
|
||||
private int mUserId;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
Window window = getWindow();
|
||||
window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
|
||||
super.onCreate(icicle);
|
||||
|
||||
final Intent intent = getIntent();
|
||||
mMoreDetailsIntent = intent.getParcelableExtra(EXTRA_MORE_DETAILS_INTENT);
|
||||
mUserId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1);
|
||||
if (mUserId < 0) {
|
||||
Slog.wtf(TAG, "Invalid user: " + mUserId);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
String dialogMessage = intent.getStringExtra(EXTRA_DIALOG_MESSAGE);
|
||||
if (dialogMessage == null) {
|
||||
dialogMessage = getString(R.string.app_suspended_default_message);
|
||||
}
|
||||
|
||||
final AlertController.AlertParams ap = mAlertParams;
|
||||
ap.mTitle = getString(R.string.app_suspended_title);
|
||||
ap.mMessage = String.format(getResources().getConfiguration().getLocales().get(0),
|
||||
dialogMessage, intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
|
||||
ap.mPositiveButtonText = getString(android.R.string.ok);
|
||||
if (mMoreDetailsIntent != null) {
|
||||
ap.mNeutralButtonText = getString(R.string.app_suspended_more_details);
|
||||
}
|
||||
ap.mPositiveButtonListener = ap.mNeutralButtonListener = this;
|
||||
setupAlert();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case AlertDialog.BUTTON_NEUTRAL:
|
||||
startActivityAsUser(mMoreDetailsIntent, UserHandle.of(mUserId));
|
||||
Slog.i(TAG, "Started more details activity");
|
||||
break;
|
||||
}
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -2030,6 +2030,15 @@
|
||||
<permission android:name="android.permission.START_ANY_ACTIVITY"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<!-- @SystemApi Must be required by activities that handle the intent action
|
||||
{@link Intent#ACTION_SEND_SHOW_SUSPENDED_APP_DETAILS}. This is for use by apps that
|
||||
hold {@link Manifest.permission#SUSPEND_APPS} to interact with the system.
|
||||
<p>Not for use by third-party applications.
|
||||
@hide -->
|
||||
<permission android:name="android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS"
|
||||
android:protectionLevel="signature" />
|
||||
<uses-permission android:name="android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS" />
|
||||
|
||||
<!-- @deprecated The {@link android.app.ActivityManager#restartPackage}
|
||||
API is no longer supported. -->
|
||||
<permission android:name="android.permission.RESTART_PACKAGES"
|
||||
@@ -4116,6 +4125,12 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name="com.android.internal.app.SuspendedAppActivity"
|
||||
android:theme="@style/Theme.DeviceDefault.Light.Dialog.Alert"
|
||||
android:excludeFromRecents="true"
|
||||
android:process=":ui">
|
||||
</activity>
|
||||
|
||||
<activity android:name="com.android.internal.app.UnlaunchableAppActivity"
|
||||
android:theme="@style/Theme.DeviceDefault.Light.Dialog.Alert"
|
||||
android:excludeFromRecents="true"
|
||||
|
||||
@@ -4703,6 +4703,13 @@
|
||||
<!-- Menu item in the locale menu [CHAR LIMIT=30] -->
|
||||
<string name="locale_search_menu">Search</string>
|
||||
|
||||
<!-- Title of the dialog that is shown when the user tries to launch a suspended application [CHAR LIMIT=30] -->
|
||||
<string name="app_suspended_title">Action not allowed</string>
|
||||
<!-- Default message shown in the dialog that is shown when the user tries to launch a suspended application [CHAR LIMIT=NONE] -->
|
||||
<string name="app_suspended_default_message">The application <xliff:g id="app_name" example="GMail">%1$s</xliff:g> is currently disabled.</string>
|
||||
<!-- Title of the button to show users more details about why the app has been suspended [CHAR LIMIT=50]-->
|
||||
<string name="app_suspended_more_details">More details</string>
|
||||
|
||||
<!-- Title of a dialog. The string is asking if the user wants to turn on their work profile, which contains work apps that are managed by their employer. "Work" is an adjective. [CHAR LIMIT=30] -->
|
||||
<string name="work_mode_off_title">Turn on work profile?</string>
|
||||
<!-- Text in a dialog. This string describes what will happen if a user decides to turn on their work profile. "Work profile" is used as an adjective. [CHAR LIMIT=NONE] -->
|
||||
|
||||
@@ -2877,6 +2877,10 @@
|
||||
|
||||
<java-symbol type="string" name="suspended_widget_accessibility" />
|
||||
|
||||
<java-symbol type="string" name="app_suspended_title" />
|
||||
<java-symbol type="string" name="app_suspended_more_details" />
|
||||
<java-symbol type="string" name="app_suspended_default_message" />
|
||||
|
||||
<!-- Used internally for assistant to launch activity transitions -->
|
||||
<java-symbol type="id" name="cross_task_transition" />
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
|
||||
import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
|
||||
|
||||
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.KeyguardManager;
|
||||
@@ -41,6 +44,7 @@ import android.content.IIntentSender;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.os.Binder;
|
||||
@@ -51,6 +55,7 @@ import android.os.UserManager;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.app.HarmfulAppWarningActivity;
|
||||
import com.android.internal.app.SuspendedAppActivity;
|
||||
import com.android.internal.app.UnlaunchableAppActivity;
|
||||
import com.android.server.LocalServices;
|
||||
|
||||
@@ -151,7 +156,7 @@ class ActivityStartInterceptor {
|
||||
mInTask = inTask;
|
||||
mActivityOptions = activityOptions;
|
||||
|
||||
if (interceptSuspendPackageIfNeed()) {
|
||||
if (interceptSuspendedPackageIfNeeded()) {
|
||||
// Skip the rest of interceptions as the package is suspended by device admin so
|
||||
// no user action can undo this.
|
||||
return true;
|
||||
@@ -204,12 +209,7 @@ class ActivityStartInterceptor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean interceptSuspendPackageIfNeed() {
|
||||
// Do not intercept if the admin did not suspend the package
|
||||
if (mAInfo == null || mAInfo.applicationInfo == null ||
|
||||
(mAInfo.applicationInfo.flags & FLAG_SUSPENDED) == 0) {
|
||||
return false;
|
||||
}
|
||||
private boolean interceptSuspendedByAdminPackage() {
|
||||
DevicePolicyManagerInternal devicePolicyManager = LocalServices
|
||||
.getService(DevicePolicyManagerInternal.class);
|
||||
if (devicePolicyManager == null) {
|
||||
@@ -232,6 +232,55 @@ class ActivityStartInterceptor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private Intent createSuspendedAppInterceptIntent(String suspendedPackage,
|
||||
String suspendingPackage, String dialogMessage, int userId) {
|
||||
final Intent interceptIntent = new Intent(mServiceContext, SuspendedAppActivity.class)
|
||||
.putExtra(Intent.EXTRA_PACKAGE_NAME, suspendedPackage)
|
||||
.putExtra(SuspendedAppActivity.EXTRA_DIALOG_MESSAGE, dialogMessage)
|
||||
.putExtra(Intent.EXTRA_USER_ID, userId)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
|
||||
|
||||
final Intent moreDetailsIntent = new Intent(Intent.ACTION_SHOW_SUSPENDED_APP_DETAILS)
|
||||
.setPackage(suspendingPackage);
|
||||
final String requiredPermission = Manifest.permission.SEND_SHOW_SUSPENDED_APP_DETAILS;
|
||||
final ResolveInfo resolvedInfo = mSupervisor.resolveIntent(moreDetailsIntent, null, userId);
|
||||
if (resolvedInfo != null && resolvedInfo.activityInfo != null
|
||||
&& requiredPermission.equals(resolvedInfo.activityInfo.permission)) {
|
||||
moreDetailsIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, suspendedPackage)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
interceptIntent.putExtra(SuspendedAppActivity.EXTRA_MORE_DETAILS_INTENT,
|
||||
moreDetailsIntent);
|
||||
}
|
||||
return interceptIntent;
|
||||
}
|
||||
|
||||
private boolean interceptSuspendedPackageIfNeeded() {
|
||||
// Do not intercept if the package is not suspended
|
||||
if (mAInfo == null || mAInfo.applicationInfo == null ||
|
||||
(mAInfo.applicationInfo.flags & FLAG_SUSPENDED) == 0) {
|
||||
return false;
|
||||
}
|
||||
final PackageManagerInternal pmi = mService.getPackageManagerInternalLocked();
|
||||
if (pmi == null) {
|
||||
return false;
|
||||
}
|
||||
final String suspendedPackage = mAInfo.applicationInfo.packageName;
|
||||
final String suspendingPackage = pmi.getSuspendingPackage(suspendedPackage, mUserId);
|
||||
if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
|
||||
return interceptSuspendedByAdminPackage();
|
||||
}
|
||||
final String dialogMessage = pmi.getSuspendedDialogMessage(suspendedPackage, mUserId);
|
||||
mIntent = createSuspendedAppInterceptIntent(suspendedPackage, suspendingPackage,
|
||||
dialogMessage, mUserId);
|
||||
mCallingPid = mRealCallingPid;
|
||||
mCallingUid = mRealCallingUid;
|
||||
mResolvedType = null;
|
||||
mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, 0);
|
||||
mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean interceptWorkProfileChallengeIfNeeded() {
|
||||
final Intent interceptingIntent = interceptWithConfirmCredentialsIfNeeded(mAInfo, mUserId);
|
||||
if (interceptingIntent == null) {
|
||||
|
||||
@@ -274,6 +274,7 @@ import com.android.internal.R;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.app.IMediaContainerService;
|
||||
import com.android.internal.app.ResolverActivity;
|
||||
import com.android.internal.app.SuspendedAppActivity;
|
||||
import com.android.internal.content.NativeLibraryHelper;
|
||||
import com.android.internal.content.PackageHelper;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
@@ -14006,8 +14007,8 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
|
||||
@Override
|
||||
public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
|
||||
PersistableBundle appExtras, PersistableBundle launcherExtras, String callingPackage,
|
||||
int userId) {
|
||||
PersistableBundle appExtras, PersistableBundle launcherExtras, String dialogMessage,
|
||||
String callingPackage, int userId) {
|
||||
try {
|
||||
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SUSPEND_APPS, null);
|
||||
} catch (SecurityException e) {
|
||||
@@ -14055,7 +14056,7 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
unactionedPackages.add(packageName);
|
||||
continue;
|
||||
}
|
||||
pkgSetting.setSuspended(suspended, callingPackage, appExtras,
|
||||
pkgSetting.setSuspended(suspended, callingPackage, dialogMessage, appExtras,
|
||||
launcherExtras, userId);
|
||||
changedPackagesList.add(packageName);
|
||||
}
|
||||
@@ -14177,7 +14178,7 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
for (int user : userIds) {
|
||||
final PackageUserState pus = ps.readUserState(user);
|
||||
if (pus.suspended && packageName.equals(pus.suspendingPackage)) {
|
||||
ps.setSuspended(false, null, null, null, user);
|
||||
ps.setSuspended(false, null, null, null, null, user);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18911,6 +18912,7 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
false /*hidden*/,
|
||||
false /*suspended*/,
|
||||
null, /*suspendingPackage*/
|
||||
null, /*dialogMessage*/
|
||||
null, /*suspendedAppExtras*/
|
||||
null, /*suspendedLauncherExtras*/
|
||||
false /*instantApp*/,
|
||||
@@ -23802,6 +23804,22 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSuspendingPackage(String suspendedPackage, int userId) {
|
||||
synchronized (mPackages) {
|
||||
final PackageSetting ps = mSettings.mPackages.get(suspendedPackage);
|
||||
return (ps != null) ? ps.readUserState(userId).suspendingPackage : null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSuspendedDialogMessage(String suspendedPackage, int userId) {
|
||||
synchronized (mPackages) {
|
||||
final PackageSetting ps = mSettings.mPackages.get(suspendedPackage);
|
||||
return (ps != null) ? ps.readUserState(userId).dialogMessage : null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPackageUid(String packageName, int flags, int userId) {
|
||||
return PackageManagerService.this
|
||||
|
||||
@@ -1505,6 +1505,7 @@ class PackageManagerShellCommand extends ShellCommand {
|
||||
private int runSuspend(boolean suspendedState) {
|
||||
final PrintWriter pw = getOutPrintWriter();
|
||||
int userId = UserHandle.USER_SYSTEM;
|
||||
String dialogMessage = null;
|
||||
final PersistableBundle appExtras = new PersistableBundle();
|
||||
final PersistableBundle launcherExtras = new PersistableBundle();
|
||||
String opt;
|
||||
@@ -1513,6 +1514,9 @@ class PackageManagerShellCommand extends ShellCommand {
|
||||
case "--user":
|
||||
userId = UserHandle.parseUserArg(getNextArgRequired());
|
||||
break;
|
||||
case "--dialogMessage":
|
||||
dialogMessage = getNextArgRequired();
|
||||
break;
|
||||
case "--ael":
|
||||
case "--aes":
|
||||
case "--aed":
|
||||
@@ -1553,7 +1557,7 @@ class PackageManagerShellCommand extends ShellCommand {
|
||||
(Binder.getCallingUid() == Process.ROOT_UID) ? "root" : "com.android.shell";
|
||||
try {
|
||||
mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState,
|
||||
appExtras, launcherExtras, callingPackage, userId);
|
||||
appExtras, launcherExtras, dialogMessage, callingPackage, userId);
|
||||
pw.println("Package " + packageName + " new suspended state: "
|
||||
+ mInterface.isPackageSuspendedForUser(packageName, userId));
|
||||
return 0;
|
||||
|
||||
@@ -398,11 +398,12 @@ public abstract class PackageSettingBase extends SettingBase {
|
||||
return readUserState(userId).suspended;
|
||||
}
|
||||
|
||||
void setSuspended(boolean suspended, String suspendingPackage, PersistableBundle appExtras,
|
||||
PersistableBundle launcherExtras, int userId) {
|
||||
void setSuspended(boolean suspended, String suspendingPackage, String dialogMessage,
|
||||
PersistableBundle appExtras, PersistableBundle launcherExtras, int userId) {
|
||||
final PackageUserState existingUserState = modifyUserState(userId);
|
||||
existingUserState.suspended = suspended;
|
||||
existingUserState.suspendingPackage = suspended ? suspendingPackage : null;
|
||||
existingUserState.dialogMessage = suspended ? dialogMessage : null;
|
||||
existingUserState.suspendedAppExtras = suspended ? appExtras : null;
|
||||
existingUserState.suspendedLauncherExtras = suspended ? launcherExtras : null;
|
||||
}
|
||||
@@ -425,8 +426,8 @@ public abstract class PackageSettingBase extends SettingBase {
|
||||
|
||||
void setUserState(int userId, long ceDataInode, int enabled, boolean installed, boolean stopped,
|
||||
boolean notLaunched, boolean hidden, boolean suspended, String suspendingPackage,
|
||||
PersistableBundle suspendedAppExtras, PersistableBundle suspendedLauncherExtras,
|
||||
boolean instantApp,
|
||||
String dialogMessage, PersistableBundle suspendedAppExtras,
|
||||
PersistableBundle suspendedLauncherExtras, boolean instantApp,
|
||||
boolean virtualPreload, String lastDisableAppCaller,
|
||||
ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
|
||||
int domainVerifState, int linkGeneration, int installReason,
|
||||
@@ -440,6 +441,7 @@ public abstract class PackageSettingBase extends SettingBase {
|
||||
state.hidden = hidden;
|
||||
state.suspended = suspended;
|
||||
state.suspendingPackage = suspendingPackage;
|
||||
state.dialogMessage = dialogMessage;
|
||||
state.suspendedAppExtras = suspendedAppExtras;
|
||||
state.suspendedLauncherExtras = suspendedLauncherExtras;
|
||||
state.lastDisableAppCaller = lastDisableAppCaller;
|
||||
|
||||
@@ -222,6 +222,7 @@ public final class Settings {
|
||||
private static final String ATTR_HIDDEN = "hidden";
|
||||
private static final String ATTR_SUSPENDED = "suspended";
|
||||
private static final String ATTR_SUSPENDING_PACKAGE = "suspending-package";
|
||||
private static final String ATTR_SUSPEND_DIALOG_MESSAGE = "suspend_dialog_message";
|
||||
// Legacy, uninstall blocks are stored separately.
|
||||
@Deprecated
|
||||
private static final String ATTR_BLOCK_UNINSTALL = "blockUninstall";
|
||||
@@ -734,6 +735,7 @@ public final class Settings {
|
||||
false /*hidden*/,
|
||||
false /*suspended*/,
|
||||
null, /*suspendingPackage*/
|
||||
null, /*dialogMessage*/
|
||||
null, /*suspendedAppExtras*/
|
||||
null, /*suspendedLauncherExtras*/
|
||||
instantApp,
|
||||
@@ -1628,6 +1630,7 @@ public final class Settings {
|
||||
false /*hidden*/,
|
||||
false /*suspended*/,
|
||||
null, /*suspendingPackage*/
|
||||
null, /*dialogMessage*/
|
||||
null, /*suspendedAppExtras*/
|
||||
null, /*suspendedLauncherExtras*/
|
||||
false /*instantApp*/,
|
||||
@@ -1704,6 +1707,8 @@ public final class Settings {
|
||||
false);
|
||||
String suspendingPackage = parser.getAttributeValue(null,
|
||||
ATTR_SUSPENDING_PACKAGE);
|
||||
final String dialogMessage = parser.getAttributeValue(null,
|
||||
ATTR_SUSPEND_DIALOG_MESSAGE);
|
||||
if (suspended && suspendingPackage == null) {
|
||||
suspendingPackage = PLATFORM_PACKAGE_NAME;
|
||||
}
|
||||
@@ -1767,7 +1772,7 @@ public final class Settings {
|
||||
setBlockUninstallLPw(userId, name, true);
|
||||
}
|
||||
ps.setUserState(userId, ceDataInode, enabled, installed, stopped, notLaunched,
|
||||
hidden, suspended, suspendingPackage, suspendedAppExtras,
|
||||
hidden, suspended, suspendingPackage, dialogMessage, suspendedAppExtras,
|
||||
suspendedLauncherExtras, instantApp, virtualPreload, enabledCaller,
|
||||
enabledComponents, disabledComponents, verifState, linkGeneration,
|
||||
installReason, harmfulAppWarning);
|
||||
@@ -2077,7 +2082,14 @@ public final class Settings {
|
||||
}
|
||||
if (ustate.suspended) {
|
||||
serializer.attribute(null, ATTR_SUSPENDED, "true");
|
||||
serializer.attribute(null, ATTR_SUSPENDING_PACKAGE, ustate.suspendingPackage);
|
||||
if (ustate.suspendingPackage != null) {
|
||||
serializer.attribute(null, ATTR_SUSPENDING_PACKAGE,
|
||||
ustate.suspendingPackage);
|
||||
}
|
||||
if (ustate.dialogMessage != null) {
|
||||
serializer.attribute(null, ATTR_SUSPEND_DIALOG_MESSAGE,
|
||||
ustate.dialogMessage);
|
||||
}
|
||||
if (ustate.suspendedAppExtras != null) {
|
||||
serializer.startTag(null, TAG_SUSPENDED_APP_EXTRAS);
|
||||
try {
|
||||
@@ -4750,8 +4762,11 @@ public final class Settings {
|
||||
pw.print(" suspended=");
|
||||
pw.print(ps.getSuspended(user.id));
|
||||
if (ps.getSuspended(user.id)) {
|
||||
final PackageUserState pus = ps.readUserState(user.id);
|
||||
pw.print(" suspendingPackage=");
|
||||
pw.print(ps.readUserState(user.id).suspendingPackage);
|
||||
pw.print(pus.suspendingPackage);
|
||||
pw.print(" dialogMessage=");
|
||||
pw.print(pus.dialogMessage);
|
||||
}
|
||||
pw.print(" stopped=");
|
||||
pw.print(ps.getStopped(user.id));
|
||||
|
||||
@@ -9193,7 +9193,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
|
||||
long id = mInjector.binderClearCallingIdentity();
|
||||
try {
|
||||
return mIPackageManager.setPackagesSuspendedAsUser(
|
||||
packageNames, suspended, null, null, "android", callingUserId);
|
||||
packageNames, suspended, null, null, null, "android", callingUserId);
|
||||
} catch (RemoteException re) {
|
||||
// Shouldn't happen.
|
||||
Slog.e(LOG_TAG, "Failed talking to the package manager", re);
|
||||
|
||||
@@ -29,6 +29,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
|
||||
truth-prebuilt \
|
||||
testables \
|
||||
testng \
|
||||
ub-uiautomator\
|
||||
platformprotosnano
|
||||
|
||||
LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/aidl
|
||||
@@ -69,8 +70,6 @@ LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
|
||||
LOCAL_JACK_FLAGS := --multi-dex native
|
||||
LOCAL_DX_FLAGS := --multi-dex
|
||||
|
||||
LOCAL_STATIC_JAVA_LIBRARIES += ub-uiautomator
|
||||
|
||||
LOCAL_PROGUARD_ENABLED := disabled
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
<uses-permission android:name="android.permission.WATCH_APPOPS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.SUSPEND_APPS"/>
|
||||
<uses-permission android:name="android.permission.CONTROL_KEYGUARD"/>
|
||||
|
||||
<!-- Uses API introduced in O (26) -->
|
||||
<uses-sdk android:minSdkVersion="1"
|
||||
@@ -145,6 +146,15 @@
|
||||
<activity android:name="com.android.server.pm.ShortcutTestActivity"
|
||||
android:enabled="true" android:exported="true" />
|
||||
|
||||
<activity android:name="com.android.server.pm.SuspendedDetailsActivity"
|
||||
android:enabled="true"
|
||||
android:permission="android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SHOW_SUSPENDED_APP_DETAILS" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name="com.android.server.accounts.AccountAuthenticatorDummyActivity" />
|
||||
|
||||
<activity-alias android:name="a.ShortcutEnabled"
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
package com.android.server.am;
|
||||
|
||||
import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
|
||||
|
||||
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -30,6 +33,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
@@ -79,6 +83,8 @@ public class ActivityStartInterceptorTest {
|
||||
@Mock
|
||||
private DevicePolicyManagerInternal mDevicePolicyManager;
|
||||
@Mock
|
||||
private PackageManagerInternal mPackageManagerInternal;
|
||||
@Mock
|
||||
private UserManager mUserManager;
|
||||
@Mock
|
||||
private UserController mUserController;
|
||||
@@ -103,6 +109,7 @@ public class ActivityStartInterceptorTest {
|
||||
when(mDevicePolicyManager
|
||||
.createShowAdminSupportIntent(TEST_USER_ID, true))
|
||||
.thenReturn(ADMIN_SUPPORT_INTENT);
|
||||
when(mService.getPackageManagerInternalLocked()).thenReturn(mPackageManagerInternal);
|
||||
|
||||
// Mock UserManager
|
||||
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
|
||||
@@ -115,15 +122,18 @@ public class ActivityStartInterceptorTest {
|
||||
thenReturn(CONFIRM_CREDENTIALS_INTENT);
|
||||
|
||||
// Initialise activity info
|
||||
mAInfo.packageName = TEST_PACKAGE_NAME;
|
||||
mAInfo.applicationInfo = new ApplicationInfo();
|
||||
mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuspendedPackage() {
|
||||
public void testSuspendedByAdminPackage() {
|
||||
// GIVEN the package we're about to launch is currently suspended
|
||||
mAInfo.applicationInfo.flags = FLAG_SUSPENDED;
|
||||
|
||||
when(mPackageManagerInternal.getSuspendingPackage(TEST_PACKAGE_NAME, TEST_USER_ID))
|
||||
.thenReturn(PLATFORM_PACKAGE_NAME);
|
||||
|
||||
// THEN calling intercept returns true
|
||||
assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
|
||||
|
||||
|
||||
@@ -199,13 +199,13 @@ public class PackageManagerSettingsTests {
|
||||
PACKAGE_NAME_1, 1L, 0.01, true, "appString1");
|
||||
final PersistableBundle launcherExtras1 = getPersistableBundle(
|
||||
PACKAGE_NAME_1, 10L, 0.1, false, "launcherString1");
|
||||
ps1.setSuspended(true, "suspendingPackage1", appExtras1, launcherExtras1, 0);
|
||||
ps1.setSuspended(true, "suspendingPackage1", "dialogMsg1", appExtras1, launcherExtras1, 0);
|
||||
settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
|
||||
|
||||
ps2.setSuspended(true, "suspendingPackage2", null, null, 0);
|
||||
ps2.setSuspended(true, "suspendingPackage2", "dialogMsg2", null, null, 0);
|
||||
settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
|
||||
|
||||
ps3.setSuspended(false, "irrelevant", null, null, 0);
|
||||
ps3.setSuspended(false, "irrelevant", "irrevelant2", null, null, 0);
|
||||
settingsUnderTest.mPackages.put(PACKAGE_NAME_3, ps3);
|
||||
|
||||
settingsUnderTest.writePackageRestrictionsLPr(0);
|
||||
@@ -220,6 +220,7 @@ public class PackageManagerSettingsTests {
|
||||
readUserState(0);
|
||||
assertThat(readPus1.suspended, is(true));
|
||||
assertThat(readPus1.suspendingPackage, equalTo("suspendingPackage1"));
|
||||
assertThat(readPus1.dialogMessage, equalTo("dialogMsg1"));
|
||||
assertThat(BaseBundle.kindofEquals(readPus1.suspendedAppExtras, appExtras1), is(true));
|
||||
assertThat(BaseBundle.kindofEquals(readPus1.suspendedLauncherExtras, launcherExtras1),
|
||||
is(true));
|
||||
@@ -228,12 +229,17 @@ public class PackageManagerSettingsTests {
|
||||
readUserState(0);
|
||||
assertThat(readPus2.suspended, is(true));
|
||||
assertThat(readPus2.suspendingPackage, equalTo("suspendingPackage2"));
|
||||
assertThat(readPus2.dialogMessage, equalTo("dialogMsg2"));
|
||||
assertThat(readPus2.suspendedAppExtras, is(nullValue()));
|
||||
assertThat(readPus2.suspendedLauncherExtras, is(nullValue()));
|
||||
|
||||
final PackageUserState readPus3 = settingsUnderTest.mPackages.get(PACKAGE_NAME_3).
|
||||
readUserState(0);
|
||||
assertThat(readPus3.suspended, is(false));
|
||||
assertThat(readPus3.suspendingPackage, is(nullValue()));
|
||||
assertThat(readPus3.dialogMessage, is(nullValue()));
|
||||
assertThat(readPus3.suspendedAppExtras, is(nullValue()));
|
||||
assertThat(readPus3.suspendedLauncherExtras, is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -184,30 +184,40 @@ public class PackageUserStateTest {
|
||||
launcherExtras2.putString("name", "launcherExtras2");
|
||||
final String suspendingPackage1 = "package1";
|
||||
final String suspendingPackage2 = "package2";
|
||||
final String dialogMessage1 = "dialogMessage1";
|
||||
final String dialogMessage2 = "dialogMessage2";
|
||||
|
||||
final PackageUserState testUserState1 = new PackageUserState();
|
||||
testUserState1.suspended = true;
|
||||
testUserState1.suspendedAppExtras = appExtras1;
|
||||
testUserState1.suspendedLauncherExtras = launcherExtras1;
|
||||
testUserState1.suspendingPackage = suspendingPackage1;
|
||||
testUserState1.dialogMessage = dialogMessage1;
|
||||
|
||||
final PackageUserState testUserState2 = new PackageUserState(testUserState1);
|
||||
PackageUserState testUserState2 = new PackageUserState(testUserState1);
|
||||
assertThat(testUserState1.equals(testUserState2), is(true));
|
||||
testUserState2.suspendingPackage = suspendingPackage2;
|
||||
assertThat(testUserState1.equals(testUserState2), is(false));
|
||||
|
||||
testUserState2.suspendingPackage = testUserState1.suspendingPackage;
|
||||
testUserState2 = new PackageUserState(testUserState1);
|
||||
testUserState2.suspendedAppExtras = appExtras2;
|
||||
assertThat(testUserState1.equals(testUserState2), is(false));
|
||||
|
||||
testUserState2.suspendedAppExtras = testUserState1.suspendedAppExtras;
|
||||
testUserState2 = new PackageUserState(testUserState1);
|
||||
testUserState2.suspendedLauncherExtras = launcherExtras2;
|
||||
assertThat(testUserState1.equals(testUserState2), is(false));
|
||||
|
||||
// Everything is different but irrelevant if suspended is false
|
||||
testUserState2 = new PackageUserState(testUserState1);
|
||||
testUserState2.dialogMessage = dialogMessage2;
|
||||
assertThat(testUserState1.equals(testUserState2), is(false));
|
||||
|
||||
testUserState2 = new PackageUserState(testUserState1);
|
||||
testUserState2.suspended = testUserState1.suspended = false;
|
||||
testUserState2.suspendedAppExtras = appExtras2;
|
||||
// Everything is different but irrelevant if suspended is false
|
||||
testUserState2.suspendingPackage = suspendingPackage2;
|
||||
testUserState2.dialogMessage = dialogMessage2;
|
||||
testUserState2.suspendedAppExtras = appExtras2;
|
||||
testUserState2.suspendedLauncherExtras = launcherExtras2;
|
||||
assertThat(testUserState1.equals(testUserState2), is(true));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.app.AppGlobals;
|
||||
import android.content.BroadcastReceiver;
|
||||
@@ -41,8 +42,15 @@ import android.os.UserHandle;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.filters.LargeTest;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.support.test.uiautomator.By;
|
||||
import android.support.test.uiautomator.UiDevice;
|
||||
import android.support.test.uiautomator.UiObject2;
|
||||
import android.support.test.uiautomator.Until;
|
||||
import android.util.Log;
|
||||
import android.view.IWindowManager;
|
||||
import android.view.WindowManagerGlobal;
|
||||
|
||||
import com.android.servicestests.apps.suspendtestapp.SuspendTestActivity;
|
||||
import com.android.servicestests.apps.suspendtestapp.SuspendTestReceiver;
|
||||
|
||||
import org.junit.After;
|
||||
@@ -68,14 +76,25 @@ public class SuspendPackagesTest {
|
||||
INSTRUMENTATION_PACKAGE + ".action.REPORT_MY_PACKAGE_SUSPENDED";
|
||||
public static final String ACTION_REPORT_MY_PACKAGE_UNSUSPENDED =
|
||||
INSTRUMENTATION_PACKAGE + ".action.REPORT_MY_PACKAGE_UNSUSPENDED";
|
||||
public static final String ACTION_REPORT_TEST_ACTIVITY_STARTED =
|
||||
INSTRUMENTATION_PACKAGE + ".action.REPORT_TEST_ACTIVITY_STARTED";
|
||||
public static final String ACTION_REPORT_TEST_ACTIVITY_STOPPED =
|
||||
INSTRUMENTATION_PACKAGE + ".action.REPORT_TEST_ACTIVITY_STOPPED";
|
||||
public static final String ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED =
|
||||
INSTRUMENTATION_PACKAGE + ".action.REPORT_MORE_DETAILS_ACTIVITY_STARTED";
|
||||
public static final String ACTION_FINISH_TEST_ACTIVITY =
|
||||
INSTRUMENTATION_PACKAGE + ".action.FINISH_TEST_ACTIVITY";
|
||||
public static final String EXTRA_RECEIVED_PACKAGE_NAME =
|
||||
SuspendPackagesTest.INSTRUMENTATION_PACKAGE + ".extra.RECEIVED_PACKAGE_NAME";
|
||||
|
||||
|
||||
private Context mContext;
|
||||
private PackageManager mPackageManager;
|
||||
private LauncherApps mLauncherApps;
|
||||
private Handler mReceiverHandler;
|
||||
private ComponentName mTestReceiverComponent;
|
||||
private AppCommunicationReceiver mAppCommsReceiver;
|
||||
private StubbedCallback mTestCallback;
|
||||
private UiDevice mUiDevice;
|
||||
|
||||
private static final class AppCommunicationReceiver extends BroadcastReceiver {
|
||||
private Context context;
|
||||
@@ -86,11 +105,12 @@ public class SuspendPackagesTest {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
void register(Handler handler) {
|
||||
void register(Handler handler, String... actions) {
|
||||
registered = true;
|
||||
final IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(ACTION_REPORT_MY_PACKAGE_SUSPENDED);
|
||||
intentFilter.addAction(ACTION_REPORT_MY_PACKAGE_UNSUSPENDED);
|
||||
for (String action : actions) {
|
||||
intentFilter.addAction(action);
|
||||
}
|
||||
context.registerReceiver(this, intentFilter, null, handler);
|
||||
}
|
||||
|
||||
@@ -110,19 +130,28 @@ public class SuspendPackagesTest {
|
||||
}
|
||||
}
|
||||
|
||||
Intent receiveIntentFromApp() {
|
||||
Intent pollForIntent(long secondsToWait) {
|
||||
if (!registered) {
|
||||
throw new IllegalStateException("Receiver not registered");
|
||||
}
|
||||
final Intent intent;
|
||||
try {
|
||||
intent = intentQueue.poll(5, TimeUnit.SECONDS);
|
||||
intent = intentQueue.poll(secondsToWait, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException ie) {
|
||||
throw new RuntimeException("Interrupted while waiting for app broadcast", ie);
|
||||
}
|
||||
assertNotNull("No intent received from app within 5 seconds", intent);
|
||||
return intent;
|
||||
}
|
||||
|
||||
void drainPendingBroadcasts() {
|
||||
while (pollForIntent(5) != null);
|
||||
}
|
||||
|
||||
Intent receiveIntentFromApp() {
|
||||
final Intent intentReceived = pollForIntent(5);
|
||||
assertNotNull("No intent received from app within 5 seconds", intentReceived);
|
||||
return intentReceived;
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
@@ -131,8 +160,7 @@ public class SuspendPackagesTest {
|
||||
mPackageManager = mContext.getPackageManager();
|
||||
mLauncherApps = (LauncherApps) mContext.getSystemService(Context.LAUNCHER_APPS_SERVICE);
|
||||
mReceiverHandler = new Handler(Looper.getMainLooper());
|
||||
mTestReceiverComponent = new ComponentName(TEST_APP_PACKAGE_NAME,
|
||||
SuspendTestReceiver.class.getCanonicalName());
|
||||
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
|
||||
IPackageManager ipm = AppGlobals.getPackageManager();
|
||||
try {
|
||||
// Otherwise implicit broadcasts will not be delivered.
|
||||
@@ -151,9 +179,10 @@ public class SuspendPackagesTest {
|
||||
private Bundle requestAppAction(String action) throws InterruptedException {
|
||||
final AtomicReference<Bundle> result = new AtomicReference<>();
|
||||
final CountDownLatch receiverLatch = new CountDownLatch(1);
|
||||
|
||||
final ComponentName testReceiverComponent = new ComponentName(TEST_APP_PACKAGE_NAME,
|
||||
SuspendTestReceiver.class.getCanonicalName());
|
||||
final Intent broadcastIntent = new Intent(action)
|
||||
.setComponent(mTestReceiverComponent)
|
||||
.setComponent(testReceiverComponent)
|
||||
.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
|
||||
mContext.sendOrderedBroadcast(broadcastIntent, null, new BroadcastReceiver() {
|
||||
@Override
|
||||
@@ -175,9 +204,10 @@ public class SuspendPackagesTest {
|
||||
return extras;
|
||||
}
|
||||
|
||||
private void suspendTestPackage(PersistableBundle appExtras, PersistableBundle launcherExtras) {
|
||||
private void suspendTestPackage(PersistableBundle appExtras, PersistableBundle launcherExtras,
|
||||
String dialogMessage) {
|
||||
final String[] unchangedPackages = mPackageManager.setPackagesSuspended(
|
||||
PACKAGES_TO_SUSPEND, true, appExtras, launcherExtras, null);
|
||||
PACKAGES_TO_SUSPEND, true, appExtras, launcherExtras, dialogMessage);
|
||||
assertTrue("setPackagesSuspended returned non-empty list", unchangedPackages.length == 0);
|
||||
}
|
||||
|
||||
@@ -187,6 +217,13 @@ public class SuspendPackagesTest {
|
||||
assertTrue("setPackagesSuspended returned non-empty list", unchangedPackages.length == 0);
|
||||
}
|
||||
|
||||
private void startTestAppActivity() {
|
||||
final Intent testActivity = new Intent()
|
||||
.setComponent(new ComponentName(TEST_APP_PACKAGE_NAME,
|
||||
SuspendTestActivity.class.getCanonicalName()))
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
mContext.startActivity(testActivity);
|
||||
}
|
||||
|
||||
private static boolean areSameExtras(BaseBundle expected, BaseBundle received) {
|
||||
if (expected != null) {
|
||||
@@ -199,13 +236,14 @@ public class SuspendPackagesTest {
|
||||
}
|
||||
|
||||
private static void assertSameExtras(String message, BaseBundle expected, BaseBundle received) {
|
||||
assertTrue(message + ": [expected: " + expected + "; received: " + received + "]",
|
||||
areSameExtras(expected, received));
|
||||
if (!areSameExtras(expected, received)) {
|
||||
fail(message + ": [expected: " + expected + "; received: " + received + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsPackageSuspended() {
|
||||
suspendTestPackage(null, null);
|
||||
suspendTestPackage(null, null, null);
|
||||
assertTrue("isPackageSuspended is false",
|
||||
mPackageManager.isPackageSuspended(TEST_APP_PACKAGE_NAME));
|
||||
}
|
||||
@@ -217,7 +255,7 @@ public class SuspendPackagesTest {
|
||||
assertNull(resultFromApp.getBundle(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
|
||||
|
||||
final PersistableBundle appExtras = getExtras("testSuspendedStateFromApp", 20, "20", 0.2);
|
||||
suspendTestPackage(appExtras, null);
|
||||
suspendTestPackage(appExtras, null, null);
|
||||
|
||||
resultFromApp = requestAppAction(SuspendTestReceiver.ACTION_GET_SUSPENDED_STATE);
|
||||
assertTrue("resultFromApp:suspended is false",
|
||||
@@ -230,37 +268,39 @@ public class SuspendPackagesTest {
|
||||
|
||||
@Test
|
||||
public void testMyPackageSuspendedUnsuspended() {
|
||||
mAppCommsReceiver.register(mReceiverHandler);
|
||||
mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_SUSPENDED,
|
||||
ACTION_REPORT_MY_PACKAGE_UNSUSPENDED);
|
||||
mAppCommsReceiver.drainPendingBroadcasts();
|
||||
final PersistableBundle appExtras = getExtras("testMyPackageSuspendBroadcasts", 1, "1", .1);
|
||||
suspendTestPackage(appExtras, null);
|
||||
suspendTestPackage(appExtras, null, null);
|
||||
Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
|
||||
assertTrue("MY_PACKAGE_SUSPENDED delivery not reported",
|
||||
ACTION_REPORT_MY_PACKAGE_SUSPENDED.equals(intentFromApp.getAction()));
|
||||
assertEquals("MY_PACKAGE_SUSPENDED delivery not reported",
|
||||
ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
|
||||
assertSameExtras("Received app extras different to the ones supplied", appExtras,
|
||||
intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
|
||||
unsuspendTestPackage();
|
||||
intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
|
||||
assertTrue("MY_PACKAGE_UNSUSPENDED delivery not reported",
|
||||
ACTION_REPORT_MY_PACKAGE_UNSUSPENDED.equals(intentFromApp.getAction()));
|
||||
assertEquals("MY_PACKAGE_UNSUSPENDED delivery not reported",
|
||||
ACTION_REPORT_MY_PACKAGE_UNSUSPENDED, intentFromApp.getAction());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdatingAppExtras() {
|
||||
mAppCommsReceiver.register(mReceiverHandler);
|
||||
mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MY_PACKAGE_SUSPENDED);
|
||||
final PersistableBundle extras1 = getExtras("testMyPackageSuspendedOnChangingExtras", 1,
|
||||
"1", 0.1);
|
||||
suspendTestPackage(extras1, null);
|
||||
suspendTestPackage(extras1, null, null);
|
||||
Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
|
||||
assertTrue("MY_PACKAGE_SUSPENDED delivery not reported",
|
||||
ACTION_REPORT_MY_PACKAGE_SUSPENDED.equals(intentFromApp.getAction()));
|
||||
assertEquals("MY_PACKAGE_SUSPENDED delivery not reported",
|
||||
ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
|
||||
assertSameExtras("Received app extras different to the ones supplied", extras1,
|
||||
intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
|
||||
final PersistableBundle extras2 = getExtras("testMyPackageSuspendedOnChangingExtras", 2,
|
||||
"2", 0.2);
|
||||
mPackageManager.setSuspendedPackageAppExtras(TEST_APP_PACKAGE_NAME, extras2);
|
||||
intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
|
||||
assertTrue("MY_PACKAGE_SUSPENDED delivery not reported",
|
||||
ACTION_REPORT_MY_PACKAGE_SUSPENDED.equals(intentFromApp.getAction()));
|
||||
assertEquals("MY_PACKAGE_SUSPENDED delivery not reported",
|
||||
ACTION_REPORT_MY_PACKAGE_SUSPENDED, intentFromApp.getAction());
|
||||
assertSameExtras("Received app extras different to the updated extras", extras2,
|
||||
intentFromApp.getBundleExtra(SuspendTestReceiver.EXTRA_SUSPENDED_APP_EXTRAS));
|
||||
}
|
||||
@@ -273,13 +313,27 @@ public class SuspendPackagesTest {
|
||||
assertEquals(mContext.getOpPackageName(), unchangedPkgs[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActivityStoppedOnSuspend() {
|
||||
mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_TEST_ACTIVITY_STARTED,
|
||||
ACTION_REPORT_TEST_ACTIVITY_STOPPED);
|
||||
startTestAppActivity();
|
||||
Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
|
||||
assertEquals("Test activity start not reported",
|
||||
ACTION_REPORT_TEST_ACTIVITY_STARTED, intentFromApp.getAction());
|
||||
suspendTestPackage(null, null, null);
|
||||
intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
|
||||
assertEquals("Test activity stop not reported on suspending the test app",
|
||||
ACTION_REPORT_TEST_ACTIVITY_STOPPED, intentFromApp.getAction());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLauncherExtrasNonNull() {
|
||||
final Bundle extrasWhenUnsuspended = mLauncherApps.getSuspendedPackageLauncherExtras(
|
||||
TEST_APP_PACKAGE_NAME, mContext.getUser());
|
||||
assertNull("Non null extras when package unsuspended:", extrasWhenUnsuspended);
|
||||
final PersistableBundle launcherExtras = getExtras("testGetLauncherExtras", 1, "1", 0.1);
|
||||
suspendTestPackage(null, launcherExtras);
|
||||
suspendTestPackage(null, launcherExtras, null);
|
||||
final Bundle receivedExtras = mLauncherApps.getSuspendedPackageLauncherExtras(
|
||||
TEST_APP_PACKAGE_NAME, mContext.getUser());
|
||||
assertSameExtras("Received launcher extras different to the ones supplied", launcherExtras,
|
||||
@@ -288,7 +342,7 @@ public class SuspendPackagesTest {
|
||||
|
||||
@Test
|
||||
public void testGetLauncherExtrasNull() {
|
||||
suspendTestPackage(null, null);
|
||||
suspendTestPackage(null, null, null);
|
||||
final Bundle extrasWhenNoneGiven = mLauncherApps.getSuspendedPackageLauncherExtras(
|
||||
TEST_APP_PACKAGE_NAME, mContext.getUser());
|
||||
assertNull("Non null extras when null extras provided:", extrasWhenNoneGiven);
|
||||
@@ -339,7 +393,7 @@ public class SuspendPackagesTest {
|
||||
}
|
||||
};
|
||||
mLauncherApps.registerCallback(mTestCallback, mReceiverHandler);
|
||||
suspendTestPackage(null, suppliedExtras);
|
||||
suspendTestPackage(null, suppliedExtras, null);
|
||||
assertFalse("Both callbacks were invoked", twoCallbackLatch.await(5, TimeUnit.SECONDS));
|
||||
twoCallbackLatch.countDown();
|
||||
assertTrue("No callback was invoked", twoCallbackLatch.await(2, TimeUnit.SECONDS));
|
||||
@@ -373,19 +427,53 @@ public class SuspendPackagesTest {
|
||||
}
|
||||
};
|
||||
mLauncherApps.registerCallback(mTestCallback, mReceiverHandler);
|
||||
suspendTestPackage(null, suppliedExtras);
|
||||
suspendTestPackage(null, suppliedExtras, null);
|
||||
assertTrue("Callback not invoked", oneCallbackLatch.await(5, TimeUnit.SECONDS));
|
||||
final String result = overridingOneCallbackResult.get();
|
||||
assertTrue("Callback did not complete as expected: " + result, result.isEmpty());
|
||||
}
|
||||
|
||||
private void turnScreenOn() throws Exception {
|
||||
if (!mUiDevice.isScreenOn()) {
|
||||
mUiDevice.wakeUp();
|
||||
}
|
||||
final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
|
||||
wm.dismissKeyguard(null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInterceptorActivity() throws Exception {
|
||||
turnScreenOn();
|
||||
mAppCommsReceiver.register(mReceiverHandler, ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED,
|
||||
ACTION_REPORT_TEST_ACTIVITY_STARTED);
|
||||
final String testMessage = "This is a test message";
|
||||
suspendTestPackage(null, null, testMessage);
|
||||
startTestAppActivity();
|
||||
assertNull("No broadcast was expected from app", mAppCommsReceiver.pollForIntent(2));
|
||||
assertNotNull("Given dialog message not shown",
|
||||
mUiDevice.wait(Until.findObject(By.text(testMessage)), 5000));
|
||||
final String buttonText = "More details";
|
||||
final UiObject2 moreDetailsButton = mUiDevice.findObject(
|
||||
By.clickable(true).text(buttonText));
|
||||
assertNotNull("\"More Details\" button not shown", moreDetailsButton);
|
||||
moreDetailsButton.click();
|
||||
final Intent intentFromApp = mAppCommsReceiver.receiveIntentFromApp();
|
||||
assertEquals("\"More Details\" activity start not reported",
|
||||
ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED, intentFromApp.getAction());
|
||||
final String receivedPackageName = intentFromApp.getStringExtra(
|
||||
EXTRA_RECEIVED_PACKAGE_NAME);
|
||||
assertEquals("Wrong package name received by \"More Details\" activity",
|
||||
TEST_APP_PACKAGE_NAME, receivedPackageName);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
public void tearDown() {
|
||||
mAppCommsReceiver.unregister();
|
||||
if (mTestCallback != null) {
|
||||
mLauncherApps.unregisterCallback(mTestCallback);
|
||||
}
|
||||
Thread.sleep(250); // To prevent any race with the next registerReceiver
|
||||
mContext.sendBroadcast(new Intent(ACTION_FINISH_TEST_ACTIVITY)
|
||||
.setPackage(TEST_APP_PACKAGE_NAME));
|
||||
}
|
||||
|
||||
private static abstract class StubbedCallback extends LauncherApps.Callback {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.SuspendPackagesTest.ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED;
|
||||
import static com.android.server.pm.SuspendPackagesTest.EXTRA_RECEIVED_PACKAGE_NAME;
|
||||
import static com.android.server.pm.SuspendPackagesTest.INSTRUMENTATION_PACKAGE;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
public class SuspendedDetailsActivity extends Activity {
|
||||
private static final String TAG = SuspendedDetailsActivity.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
Log.d(TAG, "onStart");
|
||||
final String suspendedPackage = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
|
||||
super.onStart();
|
||||
final Intent reportStart = new Intent(ACTION_REPORT_MORE_DETAILS_ACTIVITY_STARTED)
|
||||
.putExtra(EXTRA_RECEIVED_PACKAGE_NAME, suspendedPackage)
|
||||
.setPackage(INSTRUMENTATION_PACKAGE);
|
||||
sendBroadcast(reportStart);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ LOCAL_MODULE_TAGS := tests
|
||||
|
||||
LOCAL_COMPATIBILITY_SUITE := device-tests
|
||||
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator
|
||||
|
||||
LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
||||
LOCAL_SRC_FILES += ../../src/com/android/server/pm/SuspendPackagesTest.java
|
||||
|
||||
@@ -16,9 +16,45 @@
|
||||
|
||||
package com.android.servicestests.apps.suspendtestapp;
|
||||
|
||||
import static com.android.server.pm.SuspendPackagesTest.ACTION_FINISH_TEST_ACTIVITY;
|
||||
import static com.android.server.pm.SuspendPackagesTest.ACTION_REPORT_TEST_ACTIVITY_STARTED;
|
||||
import static com.android.server.pm.SuspendPackagesTest.ACTION_REPORT_TEST_ACTIVITY_STOPPED;
|
||||
import static com.android.server.pm.SuspendPackagesTest.INSTRUMENTATION_PACKAGE;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.util.Log;
|
||||
|
||||
public class SuspendTestActivity extends Activity {
|
||||
private static final String TAG = SuspendTestActivity.class.getSimpleName();
|
||||
|
||||
}
|
||||
private BroadcastReceiver mFinishReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d(TAG, "Finishing test activity from receiver");
|
||||
SuspendTestActivity.this.finish();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
Log.d(TAG, "onStart");
|
||||
final Intent reportStart = new Intent(ACTION_REPORT_TEST_ACTIVITY_STARTED)
|
||||
.setPackage(INSTRUMENTATION_PACKAGE);
|
||||
sendBroadcast(reportStart);
|
||||
registerReceiver(mFinishReceiver, new IntentFilter(ACTION_FINISH_TEST_ACTIVITY));
|
||||
super.onStart();
|
||||
}
|
||||
@Override
|
||||
public void onStop() {
|
||||
Log.d(TAG, "onStop");
|
||||
final Intent reportStop = new Intent(ACTION_REPORT_TEST_ACTIVITY_STOPPED)
|
||||
.setPackage(INSTRUMENTATION_PACKAGE);
|
||||
sendBroadcast(reportStop);
|
||||
unregisterReceiver(mFinishReceiver);
|
||||
super.onStop();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user