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 static com.android.internal.logging.nano.MetricsProto.MetricsEvent
     19         .PROVISIONING_INSTALL_PACKAGE_TASK_MS;
     20 import static com.android.internal.util.Preconditions.checkNotNull;
     21 
     22 import android.annotation.NonNull;
     23 import android.app.PendingIntent;
     24 import android.app.admin.DevicePolicyManager;
     25 import android.content.BroadcastReceiver;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.content.IntentFilter;
     29 import android.content.pm.PackageInstaller;
     30 import android.content.pm.PackageManager;
     31 import android.text.TextUtils;
     32 
     33 import com.android.internal.annotations.VisibleForTesting;
     34 import com.android.managedprovisioning.R;
     35 import com.android.managedprovisioning.common.ProvisionLogger;
     36 import com.android.managedprovisioning.common.SettingsFacade;
     37 import com.android.managedprovisioning.model.ProvisioningParams;
     38 
     39 import java.io.File;
     40 import java.io.FileInputStream;
     41 import java.io.IOException;
     42 import java.io.InputStream;
     43 import java.io.OutputStream;
     44 
     45 /**
     46  * Installs the management app apk from a download location provided by
     47  * {@link DownloadPackageTask#getDownloadedPackageLocation()}.
     48  */
     49 public class InstallPackageTask extends AbstractProvisioningTask {
     50     private static final String ACTION_INSTALL_DONE = InstallPackageTask.class.getName() + ".DONE.";
     51 
     52     public static final int ERROR_PACKAGE_INVALID = 0;
     53     public static final int ERROR_INSTALLATION_FAILED = 1;
     54 
     55     private final SettingsFacade mSettingsFacade;
     56     private final DownloadPackageTask mDownloadPackageTask;
     57 
     58     private final PackageManager mPm;
     59     private final DevicePolicyManager mDpm;
     60     private boolean mInitialPackageVerifierEnabled;
     61 
     62     /**
     63      * Create an InstallPackageTask. When run, this will attempt to install the device admin package
     64      * if it is non-null.
     65      *
     66      * {@see #run(String, String)} for more detail on package installation.
     67      */
     68     public InstallPackageTask(
     69             DownloadPackageTask downloadPackageTask,
     70             Context context,
     71             ProvisioningParams params,
     72             Callback callback) {
     73         this(new SettingsFacade(), downloadPackageTask, context, params, callback);
     74     }
     75 
     76     @VisibleForTesting
     77     InstallPackageTask(
     78             SettingsFacade settingsFacade,
     79             DownloadPackageTask downloadPackageTask,
     80             Context context,
     81             ProvisioningParams params,
     82             Callback callback) {
     83         super(context, params, callback);
     84 
     85         mPm = context.getPackageManager();
     86         mDpm = context.getSystemService(DevicePolicyManager.class);
     87         mSettingsFacade = checkNotNull(settingsFacade);
     88         mDownloadPackageTask = checkNotNull(downloadPackageTask);
     89     }
     90 
     91     @Override
     92     public int getStatusMsgId() {
     93         return R.string.progress_install;
     94     }
     95 
     96     private static void copyStream(@NonNull InputStream in, @NonNull OutputStream out)
     97             throws IOException {
     98         byte[] buffer = new byte[16 * 1024];
     99         int numRead;
    100         while ((numRead = in.read(buffer)) != -1) {
    101             out.write(buffer, 0, numRead);
    102         }
    103     }
    104 
    105     /**
    106      * Installs a package. The package will be installed from the given location if one is provided.
    107      * If a null or empty location is provided, and the package is installed for a different user,
    108      * it will be enabled for the calling user. If the package location is not provided and the
    109      * package is not installed for any other users, this task will produce an error.
    110      *
    111      * Errors will be indicated if a downloaded package is invalid, or installation fails.
    112      */
    113     @Override
    114     public void run(int userId) {
    115         startTaskTimer();
    116         String packageLocation = mDownloadPackageTask.getDownloadedPackageLocation();
    117         String packageName = mProvisioningParams.inferDeviceAdminPackageName();
    118 
    119         ProvisionLogger.logi("Installing package " + packageName);
    120         mInitialPackageVerifierEnabled = mSettingsFacade.isPackageVerifierEnabled(mContext);
    121         if (TextUtils.isEmpty(packageLocation)) {
    122             // Do not log time if not installing any package, as that isn't useful.
    123             success();
    124             return;
    125         }
    126 
    127         // Temporarily turn off package verification.
    128         mSettingsFacade.setPackageVerifierEnabled(mContext, false);
    129 
    130         int installFlags = PackageManager.INSTALL_REPLACE_EXISTING;
    131         // Current device owner (if exists) must be test-only, so it is fine to replace it with a
    132         // test-only package of same package name. No need to further verify signature as
    133         // installation will fail if signatures don't match.
    134         if (mDpm.isDeviceOwnerApp(packageName)) {
    135             installFlags |= PackageManager.INSTALL_ALLOW_TEST;
    136         }
    137 
    138         PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
    139                 PackageInstaller.SessionParams.MODE_FULL_INSTALL);
    140         params.installFlags = installFlags;
    141 
    142         File source = new File(packageLocation);
    143         PackageInstaller pi = mPm.getPackageInstaller();
    144         try {
    145             int sessionId = pi.createSession(params);
    146             try (PackageInstaller.Session session = pi.openSession(sessionId)) {
    147                 try (FileInputStream in = new FileInputStream(source);
    148                      OutputStream out = session.openWrite(source.getName(), 0, -1)) {
    149                     copyStream(in, out);
    150                 } catch (IOException e) {
    151                     session.abandon();
    152                     throw e;
    153                 }
    154 
    155                 String action = ACTION_INSTALL_DONE + sessionId;
    156                 mContext.registerReceiver(new PackageInstallReceiver(packageName),
    157                         new IntentFilter(action));
    158 
    159                 PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, sessionId,
    160                         new Intent(action),
    161                         PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
    162                 session.commit(pendingIntent.getIntentSender());
    163             }
    164         } catch (IOException e) {
    165             mSettingsFacade.setPackageVerifierEnabled(mContext, mInitialPackageVerifierEnabled);
    166 
    167             ProvisionLogger.loge("Installing package " + packageName + " failed.", e);
    168             error(ERROR_INSTALLATION_FAILED);
    169         } finally {
    170             source.delete();
    171         }
    172     }
    173 
    174     @Override
    175     protected int getMetricsCategory() {
    176         return PROVISIONING_INSTALL_PACKAGE_TASK_MS;
    177     }
    178 
    179     private class PackageInstallReceiver extends BroadcastReceiver {
    180         private final String mPackageName;
    181 
    182         public PackageInstallReceiver(String packageName) {
    183             mPackageName = packageName;
    184         }
    185 
    186         @Override
    187         public void onReceive(Context context, Intent intent) {
    188             mSettingsFacade.setPackageVerifierEnabled(mContext, mInitialPackageVerifierEnabled);
    189 
    190             // Should not happen as we use a one shot pending intent specifically for this receiver
    191             if (intent.getAction() == null || !intent.getAction().startsWith(ACTION_INSTALL_DONE)) {
    192                 ProvisionLogger.logw("Incorrect action");
    193 
    194                 error(ERROR_INSTALLATION_FAILED);
    195                 return;
    196             }
    197 
    198             // Should not happen as we use a one shot pending intent specifically for this receiver
    199             if (!intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME).equals(mPackageName)) {
    200                 ProvisionLogger.loge("Package doesn't have expected package name.");
    201                 error(ERROR_PACKAGE_INVALID);
    202                 return;
    203             }
    204 
    205             int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0);
    206             String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
    207             int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS, 0);
    208 
    209             mContext.unregisterReceiver(this);
    210             ProvisionLogger.logi(status + " " + legacyStatus + " " + statusMessage);
    211 
    212             if (status == PackageInstaller.STATUS_SUCCESS) {
    213                 ProvisionLogger.logd("Package " + mPackageName + " is succesfully installed.");
    214                 stopTaskTimer();
    215                 success();
    216             } else if (legacyStatus == PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE) {
    217                 ProvisionLogger.logd("Current version of " + mPackageName
    218                         + " higher than the version to be installed. It was not reinstalled.");
    219                 // If the package is already at a higher version: success.
    220                 // Do not log time if package is already at a higher version, as that isn't useful.
    221                 success();
    222             } else {
    223                 ProvisionLogger.logd("Installing package " + mPackageName + " failed.");
    224                 ProvisionLogger.logd("Status message returned  = " + statusMessage);
    225                 error(ERROR_INSTALLATION_FAILED);
    226             }
    227         }
    228     }
    229 }
    230