diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 23a8629936d0b..b43c4626817f4 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -1385,7 +1385,8 @@ public final class Pm {
}
final LocalIntentReceiver receiver = new LocalIntentReceiver();
- mInstaller.uninstall(pkg, flags, receiver.getIntentSender(), userId);
+ mInstaller.uninstall(pkg, null /* callerPackageName */, flags,
+ receiver.getIntentSender(), userId);
final Intent result = receiver.getResult();
final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index ba62cd629ebbf..154ff85b01138 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -44,7 +44,8 @@ interface IPackageInstaller {
void registerCallback(IPackageInstallerCallback callback, int userId);
void unregisterCallback(IPackageInstallerCallback callback);
- void uninstall(String packageName, int flags, in IntentSender statusReceiver, int userId);
+ void uninstall(String packageName, String callerPackageName, int flags,
+ in IntentSender statusReceiver, int userId);
void setPermissionsResult(int sessionId, boolean accepted);
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 15a7bf9948ea1..b7ee82d416932 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -423,7 +423,7 @@ public class PackageInstaller {
*/
public void uninstall(@NonNull String packageName, @NonNull IntentSender statusReceiver) {
try {
- mInstaller.uninstall(packageName, 0, statusReceiver, mUserId);
+ mInstaller.uninstall(packageName, mInstallerPackageName, 0, statusReceiver, mUserId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
diff --git a/core/java/com/android/internal/util/ImageUtils.java b/core/java/com/android/internal/util/ImageUtils.java
index c15390450bce4..7d56e9e53b1cd 100644
--- a/core/java/com/android/internal/util/ImageUtils.java
+++ b/core/java/com/android/internal/util/ImageUtils.java
@@ -17,10 +17,13 @@
package com.android.internal.util;
import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
/**
* Utility class for image analysis and processing.
@@ -117,4 +120,40 @@ public class ImageUtils {
&& Math.abs(r - b) < TOLERANCE
&& Math.abs(g - b) < TOLERANCE;
}
+
+ /**
+ * Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight.
+ */
+ public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth,
+ int maxHeight) {
+ if (drawable == null) {
+ return null;
+ }
+ int originalWidth = drawable.getIntrinsicWidth();
+ int originalHeight = drawable.getIntrinsicHeight();
+
+ if ((originalWidth <= maxWidth) && (originalHeight <= maxHeight) &&
+ (drawable instanceof BitmapDrawable)) {
+ return ((BitmapDrawable) drawable).getBitmap();
+ }
+ if (originalHeight <= 0 || originalWidth <= 0) {
+ return null;
+ }
+
+ // create a new bitmap, scaling down to fit the max dimensions of
+ // a large notification icon if necessary
+ float ratio = Math.min((float) maxWidth / (float) originalWidth,
+ (float) maxHeight / (float) originalHeight);
+ ratio = Math.min(1.0f, ratio);
+ int scaledWidth = (int) (ratio * originalWidth);
+ int scaledHeight = (int) (ratio * originalHeight);
+ Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888);
+
+ // and paint our app bitmap on it
+ Canvas canvas = new Canvas(result);
+ drawable.setBounds(0, 0, scaledWidth, scaledHeight);
+ drawable.draw(canvas);
+
+ return result;
+ }
}
diff --git a/core/res/res/drawable/ic_check_circle_24px.xml b/core/res/res/drawable/ic_check_circle_24px.xml
new file mode 100644
index 0000000000000..066a8a73644b0
--- /dev/null
+++ b/core/res/res/drawable/ic_check_circle_24px.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4d9093295556a..7dc3ff79d273a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5162,6 +5162,11 @@
Ask for password before unpinning
+
+ Installed by your administrator
+
+ Deleted by your administrator
+
To help improve battery life, battery saver reduces your device’s performance and limits vibration, location services, and most background data. Email, messaging, and other apps that rely on syncing may not update unless you open them.\n\nBattery saver turns off automatically when your device is charging.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 220d5e7a32ca7..9b10c67bfd3bc 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -694,6 +694,8 @@
+
+
@@ -1172,6 +1174,7 @@
+
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index aafb7a9ca49c0..591dbee93ef7e 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -31,16 +31,22 @@ import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.app.ActivityManager;
+import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.Notification;
+import android.app.NotificationManager;
import android.app.PackageDeleteObserver;
import android.app.PackageInstallObserver;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageInstallerCallback;
import android.content.pm.IPackageInstallerSession;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
@@ -79,9 +85,11 @@ import android.util.Xml;
import libcore.io.IoUtils;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageHelper;
import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.ImageUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.IoThread;
import com.google.android.collect.Sets;
@@ -784,16 +792,34 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
}
@Override
- public void uninstall(String packageName, int flags, IntentSender statusReceiver, int userId) {
- mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, true, "uninstall");
+ public void uninstall(String packageName, String callerPackageName, int flags,
+ IntentSender statusReceiver, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ mPm.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
+ if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {
+ mAppOps.checkPackage(callingUid, callerPackageName);
+ }
+
+ // Check whether the caller is device owner
+ DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ boolean isDeviceOwner = (dpm != null) && dpm.isDeviceOwnerApp(callerPackageName);
final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
- statusReceiver, packageName);
+ statusReceiver, packageName, isDeviceOwner, userId);
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES)
== PackageManager.PERMISSION_GRANTED) {
// Sweet, call straight through!
mPm.deletePackage(packageName, adapter.getBinder(), userId, flags);
-
+ } else if (isDeviceOwner) {
+ // Allow the DeviceOwner to silently delete packages
+ // Need to clear the calling identity to get DELETE_PACKAGES permission
+ long ident = Binder.clearCallingIdentity();
+ try {
+ mPm.deletePackage(packageName, adapter.getBinder(), userId, flags);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
} else {
// Take a short detour to confirm with user
final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
@@ -849,12 +875,21 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
private final Context mContext;
private final IntentSender mTarget;
private final String mPackageName;
+ private final Notification mNotification;
public PackageDeleteObserverAdapter(Context context, IntentSender target,
- String packageName) {
+ String packageName, boolean showNotification, int userId) {
mContext = context;
mTarget = target;
mPackageName = packageName;
+ if (showNotification) {
+ mNotification = buildSuccessNotification(mContext,
+ mContext.getResources().getString(R.string.package_deleted_device_owner),
+ packageName,
+ userId);
+ } else {
+ mNotification = null;
+ }
}
@Override
@@ -872,6 +907,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
@Override
public void onPackageDeleted(String basePackageName, int returnCode, String msg) {
+ if (PackageManager.DELETE_SUCCEEDED == returnCode && mNotification != null) {
+ NotificationManager notificationManager = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.notify(basePackageName, 0, mNotification);
+ }
final Intent fillIn = new Intent();
fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, mPackageName);
fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
@@ -890,11 +930,16 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
private final Context mContext;
private final IntentSender mTarget;
private final int mSessionId;
+ private final boolean mShowNotification;
+ private final int mUserId;
- public PackageInstallObserverAdapter(Context context, IntentSender target, int sessionId) {
+ public PackageInstallObserverAdapter(Context context, IntentSender target, int sessionId,
+ boolean showNotification, int userId) {
mContext = context;
mTarget = target;
mSessionId = sessionId;
+ mShowNotification = showNotification;
+ mUserId = userId;
}
@Override
@@ -913,6 +958,17 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
@Override
public void onPackageInstalled(String basePackageName, int returnCode, String msg,
Bundle extras) {
+ if (PackageManager.INSTALL_SUCCEEDED == returnCode && mShowNotification) {
+ Notification notification = buildSuccessNotification(mContext,
+ mContext.getResources().getString(R.string.package_installed_device_owner),
+ basePackageName,
+ mUserId);
+ if (notification != null) {
+ NotificationManager notificationManager = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.notify(basePackageName, 0, notification);
+ }
+ }
final Intent fillIn = new Intent();
fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId);
fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
@@ -934,6 +990,40 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
}
}
+ /**
+ * Build a notification for package installation / deletion by device owners that is shown if
+ * the operation succeeds.
+ */
+ private static Notification buildSuccessNotification(Context context, String contentText,
+ String basePackageName, int userId) {
+ PackageInfo packageInfo = null;
+ try {
+ packageInfo = AppGlobals.getPackageManager().getPackageInfo(
+ basePackageName, 0, userId);
+ } catch (RemoteException ignored) {
+ }
+ if (packageInfo == null || packageInfo.applicationInfo == null) {
+ Slog.w(TAG, "Notification not built for package: " + basePackageName);
+ return null;
+ }
+ PackageManager pm = context.getPackageManager();
+ Bitmap packageIcon = ImageUtils.buildScaledBitmap(
+ packageInfo.applicationInfo.loadIcon(pm),
+ context.getResources().getDimensionPixelSize(
+ android.R.dimen.notification_large_icon_width),
+ context.getResources().getDimensionPixelSize(
+ android.R.dimen.notification_large_icon_height));
+ CharSequence packageLabel = packageInfo.applicationInfo.loadLabel(pm);
+ return new Notification.Builder(context)
+ .setSmallIcon(R.drawable.ic_check_circle_24px)
+ .setColor(context.getResources().getColor(
+ R.color.system_notification_accent_color))
+ .setContentTitle(packageLabel)
+ .setContentText(contentText)
+ .setLargeIcon(packageIcon)
+ .build();
+ }
+
private static class Callbacks extends Handler {
private static final int MSG_SESSION_CREATED = 1;
private static final int MSG_SESSION_BADGING_CHANGED = 2;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 09e990c6391f3..46db2d8895ec4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -27,6 +27,7 @@ import static android.system.OsConstants.O_WRONLY;
import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid;
import static com.android.server.pm.PackageInstallerService.prepareStageDir;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
@@ -92,6 +93,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private final Context mContext;
private final PackageManagerService mPm;
private final Handler mHandler;
+ private final boolean mIsInstallerDeviceOwner;
final int sessionId;
final int userId;
@@ -208,8 +210,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mPrepared = prepared;
mSealed = sealed;
+ // Device owners are allowed to silently install packages, so the permission check is
+ // waived if the installer is the device owner.
+ DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+ mIsInstallerDeviceOwner = (dpm != null) && dpm.isDeviceOwnerApp(installerPackageName);
if ((mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid)
- == PackageManager.PERMISSION_GRANTED) || (installerUid == Process.ROOT_UID)) {
+ == PackageManager.PERMISSION_GRANTED)
+ || (installerUid == Process.ROOT_UID)
+ || mIsInstallerDeviceOwner) {
mPermissionsAccepted = true;
} else {
mPermissionsAccepted = false;
@@ -440,7 +449,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
mActiveCount.incrementAndGet();
final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
- statusReceiver, sessionId);
+ statusReceiver, sessionId, mIsInstallerDeviceOwner, userId);
mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
}