Home | History | Annotate | Download | only in task
      1 /*
      2  * Copyright 2014, The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.managedprovisioning.task;
     17 
     18 import android.app.DownloadManager;
     19 import android.content.ComponentName;
     20 import android.content.Context;
     21 import android.content.pm.IPackageInstallObserver;
     22 import android.content.pm.ActivityInfo;
     23 import android.content.pm.PackageInfo;
     24 import android.content.pm.PackageManager;
     25 import android.net.Uri;
     26 import android.provider.Settings.Global;
     27 import android.text.TextUtils;
     28 import android.Manifest.permission;
     29 
     30 import com.android.managedprovisioning.ProvisionLogger;
     31 import com.android.managedprovisioning.model.ProvisioningParams;
     32 
     33 import java.io.File;
     34 import java.util.HashSet;
     35 import java.util.Set;
     36 
     37 /**
     38  * Installs all packages that were added. Can install a downloaded apk, or install an existing
     39  * package which is already installed for a different user.
     40  * <p>
     41  * Before installing from a downloaded file, each file is checked to ensure it contains the correct
     42  * package and admin receiver.
     43  * </p>
     44  */
     45 public class InstallPackageTask {
     46     public static final int ERROR_PACKAGE_INVALID = 0;
     47     public static final int ERROR_INSTALLATION_FAILED = 1;
     48     public static final int ERROR_PACKAGE_NAME_INVALID = 2;
     49 
     50     private final Context mContext;
     51     private final Callback mCallback;
     52 
     53     private PackageManager mPm;
     54     private int mPackageVerifierEnable;
     55     private Set<InstallInfo> mPackagesToInstall;
     56 
     57     /**
     58      * Create an InstallPackageTask. When run, this will attempt to install the device admin
     59      * packages if it is non-null.
     60      *
     61      * {@see #run(String, String)} for more detail on package installation.
     62      */
     63     public InstallPackageTask (Context context, Callback callback) {
     64         mCallback = callback;
     65         mContext = context;
     66         mPackagesToInstall = new HashSet<InstallInfo>();
     67         mPm = mContext.getPackageManager();
     68     }
     69 
     70     /**
     71      * Should be called before {@link #run}.
     72      */
     73     public void addInstallIfNecessary(String packageName, String packageLocation) {
     74         if (!TextUtils.isEmpty(packageName)) {
     75             mPackagesToInstall.add(new InstallInfo(packageName, packageLocation));
     76         }
     77     }
     78 
     79     /**
     80      * Install all packages given by {@link #addPackageToInstall}. Each package will be installed
     81      * from the given location if one is provided. If a null or empty location is provided, and the
     82      * package is installed for a different user, it will be enabled for the calling user. If the
     83      * package location is not provided and the package is not installed for any other users, this
     84      * task will produce an error.
     85      *
     86      * Errors will be indicated if a downloaded package is invalid, or installation fails.
     87      */
     88     public void run() {
     89         if (mPackagesToInstall.size() == 0) {
     90             ProvisionLogger.loge("No downloaded packages to install");
     91             mCallback.onSuccess();
     92             return;
     93         }
     94         ProvisionLogger.logi("Installing package(s)");
     95 
     96         for (InstallInfo info : mPackagesToInstall) {
     97             if (TextUtils.isEmpty(info.location)) {
     98                 installExistingPackage(info);
     99 
    100             } else if (packageContentIsCorrect(info.packageName, info.location)) {
    101                 // Temporarily turn off package verification.
    102                 mPackageVerifierEnable = Global.getInt(mContext.getContentResolver(),
    103                         Global.PACKAGE_VERIFIER_ENABLE, 1);
    104                 Global.putInt(mContext.getContentResolver(), Global.PACKAGE_VERIFIER_ENABLE, 0);
    105 
    106                 // Allow for replacing an existing package.
    107                 // Needed in case this task is performed multiple times.
    108                 mPm.installPackage(Uri.parse("file://" + info.location),
    109                         new PackageInstallObserver(info),
    110                         /* flags */ PackageManager.INSTALL_REPLACE_EXISTING,
    111                         mContext.getPackageName());
    112             } else {
    113                 // Error should have been reported in packageContentIsCorrect().
    114                 return;
    115             }
    116         }
    117     }
    118 
    119     private boolean packageContentIsCorrect(String packageName, String packageLocation) {
    120         PackageInfo pi = mPm.getPackageArchiveInfo(packageLocation, PackageManager.GET_RECEIVERS);
    121         if (pi == null) {
    122             ProvisionLogger.loge("Package could not be parsed successfully.");
    123             mCallback.onError(ERROR_PACKAGE_INVALID);
    124             return false;
    125         }
    126         if (!pi.packageName.equals(packageName)) {
    127             ProvisionLogger.loge("Package name in apk (" + pi.packageName
    128                     + ") does not match package name specified by programmer ("
    129                     + packageName + ").");
    130             mCallback.onError(ERROR_PACKAGE_INVALID);
    131             return false;
    132         }
    133         if (pi.receivers != null) {
    134             for (ActivityInfo ai : pi.receivers) {
    135                 if (!TextUtils.isEmpty(ai.permission) &&
    136                         ai.permission.equals(android.Manifest.permission.BIND_DEVICE_ADMIN)) {
    137                     return true;
    138                 }
    139             }
    140         }
    141         ProvisionLogger.loge("Installed package has no admin receiver.");
    142         mCallback.onError(ERROR_PACKAGE_INVALID);
    143         return false;
    144     }
    145 
    146     private class PackageInstallObserver extends IPackageInstallObserver.Stub {
    147         private final InstallInfo mInstallInfo;
    148 
    149         public PackageInstallObserver(InstallInfo installInfo) {
    150             mInstallInfo = installInfo;
    151         }
    152 
    153         @Override
    154         public void packageInstalled(String packageName, int returnCode) {
    155             if (packageName != null && !packageName.equals(mInstallInfo.packageName))  {
    156                 ProvisionLogger.loge("Package doesn't have expected package name.");
    157                 mCallback.onError(ERROR_PACKAGE_INVALID);
    158                 return;
    159             }
    160             if (returnCode == PackageManager.INSTALL_SUCCEEDED) {
    161                 mInstallInfo.doneInstalling = true;
    162                 ProvisionLogger.logd(
    163                         "Package " + mInstallInfo.packageName + " is succesfully installed.");
    164                 checkSuccess();
    165             } else if (returnCode == PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE) {
    166                 mInstallInfo.doneInstalling = true;
    167                 ProvisionLogger.logd("Current version of " + mInstallInfo.packageName
    168                         + " higher than the version to be installed. It was not reinstalled.");
    169                 checkSuccess();
    170             } else {
    171                 ProvisionLogger.logd(
    172                         "Installing package " + mInstallInfo.packageName + " failed.");
    173                 ProvisionLogger.logd(
    174                         "Errorcode returned by IPackageInstallObserver = " + returnCode);
    175                 mCallback.onError(ERROR_INSTALLATION_FAILED);
    176             }
    177             // remove the file containing the apk in order not to use too much space.
    178             new File(mInstallInfo.location).delete();
    179         }
    180     }
    181 
    182     /**
    183      * Calls the success callback once all of the packages that needed to be installed are
    184      * successfully installed.
    185      */
    186     private void checkSuccess() {
    187         for (InstallInfo info : mPackagesToInstall) {
    188             if (!info.doneInstalling) {
    189                 return;
    190             }
    191         }
    192         // Set package verification flag to its original value.
    193         Global.putInt(mContext.getContentResolver(), Global.PACKAGE_VERIFIER_ENABLE,
    194                 mPackageVerifierEnable);
    195         mCallback.onSuccess();
    196     }
    197 
    198     /**
    199      * Attempt to install this package from an existing package installed under a different user.
    200      * If this package is already installed for this user, this is a no-op. If it is not installed
    201      * for another user, this will produce an error.
    202      * @param info The package to install
    203      */
    204     private void installExistingPackage(InstallInfo info) {
    205         try {
    206             ProvisionLogger.logi("Installing existing package " + info.packageName);
    207             mPm.installExistingPackage(info.packageName);
    208             info.doneInstalling = true;
    209         } catch (PackageManager.NameNotFoundException e) {
    210             mCallback.onError(ERROR_PACKAGE_INVALID);
    211             return;
    212         }
    213         checkSuccess();
    214     }
    215 
    216     public abstract static class Callback {
    217         public abstract void onSuccess();
    218         public abstract void onError(int errorCode);
    219     }
    220 
    221     private static class InstallInfo {
    222         public String packageName;
    223         public String location;
    224         public boolean doneInstalling;
    225 
    226         public InstallInfo(String packageName, String location) {
    227             this.packageName = packageName;
    228             this.location = location;
    229         }
    230     }
    231 }
    232