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