Allow silent package install for device owner.

Allow the device owner to silently install and remove packages using the
PackageInstaller APIs. Show notifications to the user after the
installation / deletion was successful.

Bug: 19422461
Change-Id: I0506e18c510efd9d04c4aea9b60a37456e689615
This commit is contained in:
Benjamin Franz
2015-02-18 16:11:18 +00:00
parent 80ac603651
commit 39fb7fd730
9 changed files with 186 additions and 11 deletions

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,27 @@
<!--
Copyright (C) 2015 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M0 0h24v24H0z"
android:fillColor="#00000000"/>
<path
android:fillColor="#FF000000"
android:pathData="M12.0,2.0C6.48,2.0 2.0,6.48 2.0,12.0s4.48,10.0 10.0,10.0 10.0,-4.48 10.0,-10.0S17.52,2.0 12.0,2.0zm-2.0,15.0l-5.0,-5.0 1.41,-1.41L10.0,14.17l7.59,-7.59L19.0,8.0l-9.0,9.0z"/>
</vector>

View File

@@ -5162,6 +5162,11 @@
<!-- Lock-to-app unlock password string -->
<string name="lock_to_app_unlock_password">Ask for password before unpinning</string>
<!-- Notification shown when device owner silently installs a package -->
<string name="package_installed_device_owner">Installed by your administrator</string>
<!-- Notification shown when device owner silently deletes a package -->
<string name="package_deleted_device_owner">Deleted by your administrator</string>
<!-- [CHAR_LIMIT=NONE] Battery saver: Feature description -->
<string name="battery_saver_description">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.</string>

View File

@@ -694,6 +694,8 @@
<java-symbol type="string" name="lock_to_app_unlock_pin" />
<java-symbol type="string" name="lock_to_app_unlock_pattern" />
<java-symbol type="string" name="lock_to_app_unlock_password" />
<java-symbol type="string" name="package_installed_device_owner" />
<java-symbol type="string" name="package_deleted_device_owner" />
<java-symbol type="string" name="lockscreen_access_pattern_cell_added" />
<java-symbol type="string" name="lockscreen_access_pattern_cleared" />
<java-symbol type="string" name="lockscreen_access_pattern_detected" />
@@ -1172,6 +1174,7 @@
<java-symbol type="drawable" name="ic_audio_vol" />
<java-symbol type="drawable" name="ic_audio_vol_mute" />
<java-symbol type="drawable" name="ic_bullet_key_permission" />
<java-symbol type="drawable" name="ic_check_circle_24px" />
<java-symbol type="drawable" name="ic_contact_picture" />
<java-symbol type="drawable" name="ic_dialog_usb" />
<java-symbol type="drawable" name="ic_emergency" />

View File

@@ -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;

View File

@@ -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();
}