Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0518d610 authored by vinodkrishnan's avatar vinodkrishnan Committed by android-build-merger
Browse files

Merge "WearPackageInstallerSvc move to startForeground" into oc-dev

am: ef54ec21

Change-Id: I675cde8af9fe75aa050b19b7de382ae37f28ee02
parents 53d0dc14 ef54ec21
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@
    <string name="cancel">Cancel</string>
    <string name="unknown">Unknown</string>
    <string name="installing">Installing\u2026</string>
    <string name="installing_app">Installing <xliff:g id="package_label">%1$s</xliff:g>\u2026</string>
    <string name="install_done">App installed.</string>
    <!-- Message for installing a new app that requires some permissions [CHAR LIMIT=NONE] -->
    <string name="install_confirm_question">Do you want to install this application?
@@ -377,4 +378,7 @@
    <!-- Label for button to open manage external sources settings [CHAR LIMIT=45] -->
    <string name="external_sources_settings">Settings</string>

    <!-- Label for the notification channel containing notifications for embedded app operations [CHAR LIMIT=40] -->
    <string name="wear_app_channel">Installing/uninstalling wear apps</string>

</resources>
+3 −2
Original line number Diff line number Diff line
@@ -149,7 +149,8 @@ public class PackageInstallerImpl {
                cancelSession(sessionInfo.getSessionId(), packageName);
            }
        } catch (Exception e) {
            Log.e(TAG, "Unexpected exception while installing " + packageName);
            Log.e(TAG, "Unexpected exception while installing: " + packageName + ": "
                    + e.getMessage());
            callback.installFailed(InstallerConstants.ERROR_INSTALL_SESSION_EXCEPTION,
                    "Unexpected exception while installing " + packageName);
        }
+83 −153
Original line number Diff line number Diff line
@@ -16,8 +16,10 @@

package com.android.packageinstaller.wear;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.FeatureInfo;
@@ -26,9 +28,6 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -40,20 +39,19 @@ import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.Process;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;

import com.android.packageinstaller.DeviceUtils;
import com.android.packageinstaller.PackageUtil;
import com.android.packageinstaller.R;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
@@ -84,22 +82,14 @@ import java.util.Set;
public class WearPackageInstallerService extends Service {
    private static final String TAG = "WearPkgInstallerService";

    private static final String KEY_PACKAGE_NAME =
            "com.google.android.clockwork.EXTRA_PACKAGE_NAME";
    private static final String KEY_APP_LABEL = "com.google.android.clockwork.EXTRA_APP_LABEL";
    private static final String KEY_APP_ICON_URI =
            "com.google.android.clockwork.EXTRA_APP_ICON_URI";
    private static final String KEY_PERMS_LIST = "com.google.android.clockwork.EXTRA_PERMS_LIST";
    private static final String KEY_HAS_LAUNCHER =
            "com.google.android.clockwork.EXTRA_HAS_LAUNCHER";

    private static final String HOME_APP_PACKAGE_NAME = "com.google.android.wearable.app";
    private static final String SHOW_PERMS_SERVICE_CLASS =
            "com.google.android.clockwork.packagemanager.ShowPermsService";
    private static final String WEAR_APPS_CHANNEL = "wear_app_install_uninstall";

    private final int START_INSTALL = 1;
    private final int START_UNINSTALL = 2;

    private int mInstallNotificationId = 1;
    private final Map<String, Integer> mNotifIdMap = new ArrayMap<>();

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
@@ -117,7 +107,7 @@ public class WearPackageInstallerService extends Service {
        }
    }
    private ServiceHandler mServiceHandler;

    private NotificationChannel mNotificationChannel;
    private static volatile PowerManager.WakeLock lockStatic = null;

    @Override
@@ -139,11 +129,13 @@ public class WearPackageInstallerService extends Service {
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (!DeviceUtils.isWear(this)) {
            Log.w(TAG, "Not running on wearable.");
            finishService(null, startId);
            return START_NOT_STICKY;
        }

        if (intent == null) {
            Log.w(TAG, "Got null intent.");
            finishService(null, startId);
            return START_NOT_STICKY;
        }

@@ -154,11 +146,13 @@ public class WearPackageInstallerService extends Service {
        Uri packageUri = intent.getData();
        if (packageUri == null) {
            Log.e(TAG, "No package URI in intent");
            finishService(null, startId);
            return START_NOT_STICKY;
        }
        final String packageName = WearPackageUtil.getSanitizedPackageName(packageUri);
        if (packageName == null) {
            Log.e(TAG, "Invalid package name in URI (expected package:<pkgName>): " + packageUri);
            finishService(null, startId);
            return START_NOT_STICKY;
        }

@@ -173,15 +167,24 @@ public class WearPackageInstallerService extends Service {
        }
        WearPackageArgs.setStartId(intentBundle, startId);
        WearPackageArgs.setPackageName(intentBundle, packageName);
        String notifTitle;
        if (Intent.ACTION_INSTALL_PACKAGE.equals(intent.getAction())) {
            Message msg = mServiceHandler.obtainMessage(START_INSTALL);
            msg.setData(intentBundle);
            mServiceHandler.sendMessage(msg);
            notifTitle = getString(R.string.installing);
        } else if (Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())) {
            Message msg = mServiceHandler.obtainMessage(START_UNINSTALL);
            msg.setData(intentBundle);
            mServiceHandler.sendMessage(msg);
            notifTitle = getString(R.string.uninstalling);
        } else {
            Log.e(TAG, "Unknown action : " + intent.getAction());
            finishService(null, startId);
            return START_NOT_STICKY;
        }
        Pair<Integer, Notification> notifPair = buildNotification(packageName, notifTitle);
        startForeground(notifPair.first, notifPair.second);
        return START_NOT_STICKY;
    }

@@ -253,6 +256,9 @@ public class WearPackageInstallerService extends Service {
                return;
            }

            getLabelAndUpdateNotification(packageName,
                    getString(R.string.installing_app, pkg.applicationInfo.loadLabel(pm)));

            List<String> wearablePerms = pkg.requestedPermissions;

            // Log if the installed pkg has a higher version number.
@@ -357,14 +363,14 @@ public class WearPackageInstallerService extends Service {
        PowerManager.WakeLock lock = getLock(this.getApplicationContext());
        final PackageManager pm = getPackageManager();
        try {
            // Result ignored.
            pm.getPackageInfo(packageName, 0);
            PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0);
            getLabelAndUpdateNotification(packageName,
                    getString(R.string.uninstalling_app, pkgInfo.applicationInfo.loadLabel(pm)));

            // Found package, send uninstall request.
            pm.deletePackage(packageName, new PackageDeleteObserver(lock, startId),
                    PackageManager.DELETE_ALL_USERS);

            startPermsServiceForUninstall(packageName);
            Log.i(TAG, "Sent delete request for " + packageName);
        } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) {
            // Couldn't find the package, no need to call uninstall.
@@ -376,74 +382,50 @@ public class WearPackageInstallerService extends Service {
    private boolean checkPermissions(PackageParser.Package pkg, int companionSdkVersion,
            int companionDeviceVersion, Uri permUri, List<String> wearablePermissions,
            File apkFile) {
        // If the Wear App is targeted for M-release, since the permission model has been changed,
        // permissions may not be granted on the phone yet. We need a different flow for user to
        // accept these permissions.
        //
        // Assumption: Code is running on E-release, so Wear is always running M.
        // - Case 1: If the Wear App(WA) is targeting 23, always choose the M model (4 cases)
        // - Case 2: Else if the Phone App(PA) is targeting 23 and Phone App(P) is running on M,
        // show a Dialog so that the user can accept all perms (1 case)
        //   - Also show a warning to the developer if the watch is targeting M
        // - Case 3: If Case 2 is false, then the behavior on the phone is pre-M. Stick to pre-M
        // behavior on watch (as long as we don't hit case 1).
        //   - 3a: WA(22) PA(22) P(22) -> watch app is not targeting 23
        //   - 3b: WA(22) PA(22) P(23) -> watch app is not targeting 23
        //   - 3c: WA(22) PA(23) P(22) -> watch app is not targeting 23
        // - Case 4: We did not get Companion App's/Device's version, always show dialog to user to
        // accept permissions. (This happens if the AndroidWear Companion App is really old).
        boolean isWearTargetingM =
                pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
        if (isWearTargetingM) { // Case 1
        // Assumption: We are running on Android O.
        // If the Phone App is targeting M, all permissions may not have been granted to the phone
        // app. If the Wear App is then not targeting M, there may be permissions that are not
        // granted on the Phone app (by the user) right now and we cannot just grant it for the Wear
        // app.
        if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
            // Install the app if Wear App is ready for the new perms model.
            return true;
        }

        List<String> unavailableWearablePerms = getWearPermsNotGrantedOnPhone(pkg.packageName,
                permUri, wearablePermissions);
        if (unavailableWearablePerms == null) {
            return false;
        }

        if (unavailableWearablePerms.size() == 0) {
        if (!doesWearHaveUngrantedPerms(pkg.packageName, permUri, wearablePermissions)) {
            // All permissions requested by the watch are already granted on the phone, no need
            // to do anything.
            return true;
        }

        // Cases 2 and 4.
        boolean isCompanionTargetingM = companionSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
        boolean isCompanionRunningM = companionDeviceVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
        if (isCompanionTargetingM) { // Case 2 Warning
            Log.w(TAG, "MNC: Wear app's targetSdkVersion should be at least 23, if " +
                    "phone app is targeting at least 23, will continue.");
        }
        if ((isCompanionTargetingM && isCompanionRunningM) || // Case 2
                companionSdkVersion == 0 || companionDeviceVersion == 0) { // Case 4
            startPermsServiceForInstall(pkg, apkFile, unavailableWearablePerms);
        // Log an error if Wear is targeting < 23 and phone is targeting >= 23.
        if (companionSdkVersion == 0 || companionSdkVersion >= Build.VERSION_CODES.M) {
            Log.e(TAG, "MNC: Wear app's targetSdkVersion should be at least 23, if "
                    + "phone app is targeting at least 23, will continue.");
        }

        // Case 3a-3c.
        return false;
    }

    /**
     * Given a {@string packageName} corresponding to a phone app, query the provider for all the
     * perms that are granted.
     * @return null if there is an error retrieving this info
     *         else, a list of all the wearable perms that are not in the list of granted perms of
     * the phone.
     *
     * @return true if the Wear App has any perms that have not been granted yet on the phone side.
     * @return true if there is any error cases.
     */
    private List<String> getWearPermsNotGrantedOnPhone(String packageName, Uri permUri,
    private boolean doesWearHaveUngrantedPerms(String packageName, Uri permUri,
            List<String> wearablePermissions) {
        if (permUri == null) {
            Log.e(TAG, "Permission URI is null");
            return null;
            // Pretend there is an ungranted permission to avoid installing for error cases.
            return true;
        }
        Cursor permCursor = getContentResolver().query(permUri, null, null, null, null);
        if (permCursor == null) {
            Log.e(TAG, "Could not get the cursor for the permissions");
            return null;
            // Pretend there is an ungranted permission to avoid installing for error cases.
            return true;
        }

        Set<String> grantedPerms = new HashSet<>();
@@ -465,10 +447,10 @@ public class WearPackageInstallerService extends Service {
        }
        permCursor.close();

        ArrayList<String> unavailableWearablePerms = new ArrayList<>();
        boolean hasUngrantedPerm = false;
        for (String wearablePerm : wearablePermissions) {
            if (!grantedPerms.contains(wearablePerm)) {
                unavailableWearablePerms.add(wearablePerm);
                hasUngrantedPerm = true;
                if (!ungrantedPerms.contains(wearablePerm)) {
                    // This is an error condition. This means that the wearable has permissions that
                    // are not even declared in its host app. This is a developer error.
@@ -480,11 +462,11 @@ public class WearPackageInstallerService extends Service {
                }
            }
        }
        return unavailableWearablePerms;
        return hasUngrantedPerm;
    }

    private void finishService(PowerManager.WakeLock lock, int startId) {
        if (lock.isHeld()) {
        if (lock != null && lock.isHeld()) {
            lock.release();
        }
        stopSelf(startId);
@@ -501,86 +483,6 @@ public class WearPackageInstallerService extends Service {
        return lockStatic;
    }

    private void startPermsServiceForInstall(final PackageParser.Package pkg, final File apkFile,
            List<String> unavailableWearablePerms) {
        final String packageName = pkg.packageName;

        Intent showPermsIntent = new Intent()
                .setComponent(new ComponentName(HOME_APP_PACKAGE_NAME, SHOW_PERMS_SERVICE_CLASS))
                .setAction(Intent.ACTION_INSTALL_PACKAGE);
        final PackageManager pm = getPackageManager();
        pkg.applicationInfo.publicSourceDir = apkFile.getPath();
        final CharSequence label = pkg.applicationInfo.loadLabel(pm);
        final Uri iconUri = getIconFileUri(packageName, pkg.applicationInfo.loadIcon(pm));
        if (TextUtils.isEmpty(label) || iconUri == null) {
            Log.e(TAG, "MNC: Could not launch service since either label " + label +
                    ", or icon Uri " + iconUri + " is invalid.");
        } else {
            showPermsIntent.putExtra(KEY_APP_LABEL, label);
            showPermsIntent.putExtra(KEY_APP_ICON_URI, iconUri);
            showPermsIntent.putExtra(KEY_PACKAGE_NAME, packageName);
            showPermsIntent.putExtra(KEY_PERMS_LIST,
                    unavailableWearablePerms.toArray(new String[0]));
            showPermsIntent.putExtra(KEY_HAS_LAUNCHER, WearPackageUtil.hasLauncherActivity(pkg));

            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "MNC: Launching Intent " + showPermsIntent + " for " + packageName +
                        " with name " + label);
            }
            startService(showPermsIntent);
        }
    }

    private void startPermsServiceForUninstall(final String packageName) {
        Intent showPermsIntent = new Intent()
                .setComponent(new ComponentName(HOME_APP_PACKAGE_NAME, SHOW_PERMS_SERVICE_CLASS))
                .setAction(Intent.ACTION_UNINSTALL_PACKAGE);
        showPermsIntent.putExtra(KEY_PACKAGE_NAME, packageName);
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Launching Intent " + showPermsIntent + " for " + packageName);
        }
        startService(showPermsIntent);
    }

    private Uri getIconFileUri(final String packageName, final Drawable d) {
        if (d == null || !(d instanceof BitmapDrawable)) {
            Log.e(TAG, "Drawable is not a BitmapDrawable for " + packageName);
            return null;
        }
        File iconFile = WearPackageUtil.getIconFile(this, packageName);

        if (iconFile == null) {
            Log.e(TAG, "Could not get icon file for " + packageName);
            return null;
        }

        FileOutputStream fos = null;
        try {
            // Convert bitmap to byte array
            Bitmap bitmap = ((BitmapDrawable) d).getBitmap();
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos);

            // Write the bytes into the file
            fos = new FileOutputStream(iconFile);
            fos.write(bos.toByteArray());
            fos.flush();

            return WearPackageIconProvider.getUriForPackage(packageName);
        } catch (IOException e) {
            Log.e(TAG, "Could not convert drawable to icon file for package " + packageName, e);
            return null;
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    // ignore
                }
            }
        }
    }

    private class PackageInstallListener implements PackageInstallerImpl.InstallListener {
        private Context mContext;
        private PowerManager.WakeLock mWakeLock;
@@ -618,7 +520,6 @@ public class WearPackageInstallerService extends Service {
        public void installFailed(int errorCode, String errorDesc) {
            Log.e(TAG, "Package install failed " + mApplicationPackageName
                    + ", errorCode " + errorCode);
            WearPackageUtil.removeFromPermStore(mContext, mApplicationPackageName);
            finishService(mWakeLock, mStartId);
        }
    }
@@ -645,4 +546,33 @@ public class WearPackageInstallerService extends Service {
            }
        }
    }

    private synchronized Pair<Integer, Notification> buildNotification(final String packageName,
            final String title) {
        int notifId;
        if (mNotifIdMap.containsKey(packageName)) {
            notifId = mNotifIdMap.get(packageName);
        } else {
            notifId = mInstallNotificationId++;
            mNotifIdMap.put(packageName, notifId);
        }

        if (mNotificationChannel == null) {
            mNotificationChannel = new NotificationChannel(WEAR_APPS_CHANNEL,
                    getString(R.string.wear_app_channel), NotificationManager.IMPORTANCE_MIN);
            NotificationManager notificationManager = getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(mNotificationChannel);
        }
        return new Pair<>(notifId, new Notification.Builder(this, WEAR_APPS_CHANNEL)
            .setSmallIcon(R.drawable.ic_file_download)
            .setContentTitle(title)
            .build());
    }

    private void getLabelAndUpdateNotification(String packageName, String title) {
        // Update notification since we have a label now.
        NotificationManager notificationManager = getSystemService(NotificationManager.class);
        Pair<Integer, Notification> notifPair = buildNotification(packageName, title);
        notificationManager.notify(notifPair.first, notifPair.second);
    }
}
+0 −46
Original line number Diff line number Diff line
@@ -16,10 +16,7 @@

package com.android.packageinstaller.wear;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageParser;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
@@ -34,7 +31,6 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

public class WearPackageUtil {
    private static final String TAG = "WearablePkgInstaller";
@@ -42,12 +38,6 @@ public class WearPackageUtil {
    private static final String COMPRESSION_LZMA = "lzma";
    private static final String COMPRESSION_XZ = "xz";

    private static final String SHOW_PERMS_SERVICE_PKG_NAME = "com.google.android.wearable.app";
    private static final String SHOW_PERMS_SERVICE_CLASS_NAME =
            "com.google.android.clockwork.packagemanager.ShowPermsService";
    private static final String EXTRA_PACKAGE_NAME
            = "com.google.android.clockwork.EXTRA_PACKAGE_NAME";

    public static File getTemporaryFile(Context context, String packageName) {
        try {
            File newFileDir = new File(context.getFilesDir(), "tmp");
@@ -129,42 +119,6 @@ public class WearPackageUtil {
        }
    }

    public static boolean hasLauncherActivity(PackageParser.Package pkg) {
        if (pkg == null || pkg.activities == null) {
            return false;
        }

        final int activityCount = pkg.activities.size();
        for (int i = 0; i < activityCount; ++i) {
            if (pkg.activities.get(i).intents != null) {
                ArrayList<PackageParser.ActivityIntentInfo> intents =
                        pkg.activities.get(i).intents;
                final int intentsCount = intents.size();
                for (int j = 0; j < intentsCount; ++j) {
                    final PackageParser.ActivityIntentInfo intentInfo = intents.get(j);
                    if (intentInfo.hasAction(Intent.ACTION_MAIN)) {
                        if (intentInfo.hasCategory(Intent.CATEGORY_INFO) ||
                                intentInfo .hasCategory(Intent.CATEGORY_LAUNCHER)) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    public static void removeFromPermStore(Context context, String wearablePackageName) {
        Intent newIntent = new Intent()
                .setComponent(new ComponentName(
                        SHOW_PERMS_SERVICE_PKG_NAME, SHOW_PERMS_SERVICE_CLASS_NAME))
                .setAction(Intent.ACTION_UNINSTALL_PACKAGE);
        newIntent.putExtra(EXTRA_PACKAGE_NAME, wearablePackageName);
        Log.i(TAG, "Sending removeFromPermStore to ShowPermsService " + newIntent
                + " for " + wearablePackageName);
        context.startService(newIntent);
    }

    /**
     * @return com.google.com from expected formats like
     * Uri: package:com.google.com, package:/com.google.com, package://com.google.com