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 android.app.PendingIntent.FLAG_MUTABLE; 19 import static android.app.PendingIntent.FLAG_ONE_SHOT; 20 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; 21 import static android.content.pm.PackageManager.INSTALL_REPLACE_EXISTING; 22 23 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_INSTALL_PACKAGE_TASK_MS; 24 25 import static java.util.Objects.requireNonNull; 26 27 import android.annotation.NonNull; 28 import android.app.PendingIntent; 29 import android.app.admin.DevicePolicyManager; 30 import android.content.BroadcastReceiver; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.content.pm.PackageInstaller; 35 import android.content.pm.PackageManager; 36 37 import com.android.internal.annotations.VisibleForTesting; 38 import com.android.managedprovisioning.analytics.MetricsWriterFactory; 39 import com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker; 40 import com.android.managedprovisioning.common.ManagedProvisioningSharedPreferences; 41 import com.android.managedprovisioning.common.ProvisionLogger; 42 import com.android.managedprovisioning.common.SettingsFacade; 43 import com.android.managedprovisioning.common.Utils; 44 import com.android.managedprovisioning.model.ProvisioningParams; 45 46 import java.io.File; 47 import java.io.FileInputStream; 48 import java.io.IOException; 49 import java.io.InputStream; 50 import java.io.OutputStream; 51 import java.util.HashSet; 52 import java.util.Set; 53 54 55 /** 56 * Installs the management app apk from a download location provided by 57 * {@link PackageLocationProvider#getPackageLocation()}. 58 */ 59 public class InstallPackageTask extends AbstractProvisioningTask { 60 private static final String ACTION_INSTALL_DONE = InstallPackageTask.class.getName() + ".DONE."; 61 62 public static final int ERROR_PACKAGE_INVALID = 0; 63 public static final int ERROR_INSTALLATION_FAILED = 1; 64 65 private final PackageLocationProvider mPackageLocationProvider; 66 67 private final PackageManager mPm; 68 private final DevicePolicyManager mDpm; 69 private final PackageInstaller.SessionCallback mSessionCallback = new SessionCallback(); 70 private final String mPackageName; 71 private final Utils mUtils; 72 73 private static final int SUCCESS_INSTALLED_BROADCAST = 1; 74 private static final int SUCCESS_INSTALLED_CALLBACK = 2; 75 private final Set<Integer> mSuccessCodes = new HashSet<>(); 76 77 /** 78 * Create an InstallPackageTask. When run, this will attempt to install the device admin package 79 * if it is non-null. 80 * 81 * {@see #run(String, String)} for more detail on package installation. 82 */ InstallPackageTask( PackageLocationProvider packageLocationProvider, Context context, ProvisioningParams params, Callback callback)83 public InstallPackageTask( 84 PackageLocationProvider packageLocationProvider, 85 Context context, 86 ProvisioningParams params, 87 Callback callback) { 88 this(packageLocationProvider, context, params, callback, 89 new ProvisioningAnalyticsTracker( 90 MetricsWriterFactory.getMetricsWriter(context, new SettingsFacade()), 91 new ManagedProvisioningSharedPreferences(context)), 92 new Utils()); 93 } 94 95 @VisibleForTesting InstallPackageTask( PackageLocationProvider packageLocationProvider, Context context, ProvisioningParams params, Callback callback, ProvisioningAnalyticsTracker provisioningAnalyticsTracker, Utils utils)96 InstallPackageTask( 97 PackageLocationProvider packageLocationProvider, 98 Context context, 99 ProvisioningParams params, 100 Callback callback, 101 ProvisioningAnalyticsTracker provisioningAnalyticsTracker, 102 Utils utils) { 103 super(context, params, callback, provisioningAnalyticsTracker); 104 105 mPm = context.getPackageManager(); 106 mDpm = context.getSystemService(DevicePolicyManager.class); 107 mPackageLocationProvider = requireNonNull(packageLocationProvider); 108 mPackageName = requireNonNull(mProvisioningParams.inferDeviceAdminPackageName()); 109 mUtils = requireNonNull(utils); 110 } 111 copyStream(@onNull InputStream in, @NonNull OutputStream out)112 private static void copyStream(@NonNull InputStream in, @NonNull OutputStream out) 113 throws IOException { 114 byte[] buffer = new byte[16 * 1024]; 115 int numRead; 116 while ((numRead = in.read(buffer)) != -1) { 117 out.write(buffer, 0, numRead); 118 } 119 } 120 121 /** 122 * Installs a package. The package will be installed from the given location if one is provided. 123 * If a null or empty location is provided, and the package is installed for a different user, 124 * it will be enabled for the calling user. If the package location is not provided and the 125 * package is not installed for any other users, this task will produce an error. 126 * 127 * Errors will be indicated if a downloaded package is invalid, or installation fails. 128 */ 129 @Override run(int userId)130 public void run(int userId) { 131 startTaskTimer(); 132 133 File packageLocation = mPackageLocationProvider.getPackageLocation(); 134 ProvisionLogger.logi("Installing package " + mPackageName + " on user " + userId + " from " 135 + packageLocation); 136 if (packageLocation == null) { 137 success(); 138 return; 139 } 140 141 int installFlags = INSTALL_REPLACE_EXISTING; 142 // Current device owner (if exists) must be test-only, so it is fine to replace it with a 143 // test-only package of same package name. No need to further verify signature as 144 // installation will fail if signatures don't match. 145 if (mDpm.isDeviceOwnerApp(mPackageName)) { 146 installFlags |= PackageManager.INSTALL_ALLOW_TEST; 147 } 148 149 PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( 150 PackageInstaller.SessionParams.MODE_FULL_INSTALL); 151 params.installFlags |= installFlags; 152 153 try { 154 installPackage(packageLocation, mPackageName, params, mContext, mSessionCallback); 155 } catch (IOException e) { 156 ProvisionLogger.loge("Installing package " + mPackageName + " failed.", e); 157 error(ERROR_INSTALLATION_FAILED); 158 } finally { 159 packageLocation.delete(); 160 } 161 } 162 installPackage( File source, String packageName, PackageInstaller.SessionParams params, Context context, PackageInstaller.SessionCallback sessionCallback)163 private void installPackage( 164 File source, 165 String packageName, 166 PackageInstaller.SessionParams params, 167 Context context, 168 PackageInstaller.SessionCallback sessionCallback) 169 throws IOException { 170 PackageInstaller pi = context.getPackageManager().getPackageInstaller(); 171 context.registerReceiver( 172 new PackageAddedReceiver(packageName), 173 createPackageAddedIntentFilter()); 174 pi.registerSessionCallback(sessionCallback); 175 int sessionId = pi.createSession(params); 176 try (PackageInstaller.Session session = pi.openSession(sessionId)) { 177 try (FileInputStream in = new FileInputStream(source); 178 OutputStream out = session.openWrite(source.getName(), 0, -1)) { 179 copyStream(in, out); 180 } catch (IOException e) { 181 session.abandon(); 182 throw e; 183 } 184 185 String action = ACTION_INSTALL_DONE + sessionId; 186 PendingIntent pendingIntent = PendingIntent.getBroadcast( 187 context, 188 sessionId, 189 new Intent(action), 190 FLAG_ONE_SHOT | FLAG_UPDATE_CURRENT | FLAG_MUTABLE); 191 session.commit(pendingIntent.getIntentSender()); 192 } 193 } 194 createPackageAddedIntentFilter()195 private IntentFilter createPackageAddedIntentFilter() { 196 IntentFilter intentFilter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 197 intentFilter.addDataScheme("package"); 198 return intentFilter; 199 } 200 201 @Override getMetricsCategory()202 protected int getMetricsCategory() { 203 return PROVISIONING_INSTALL_PACKAGE_TASK_MS; 204 } 205 addSuccessStatus(int successStatus)206 private void addSuccessStatus(int successStatus) { 207 mSuccessCodes.add(successStatus); 208 if (mSuccessCodes.contains(SUCCESS_INSTALLED_BROADCAST) 209 && mSuccessCodes.contains(SUCCESS_INSTALLED_CALLBACK)) { 210 ProvisionLogger.logd("Package " + mPackageName + " is successfully installed."); 211 stopTaskTimer(); 212 success(); 213 } 214 } 215 216 private class PackageAddedReceiver extends BroadcastReceiver { 217 218 private final String mPackageName; 219 PackageAddedReceiver(String packageName)220 PackageAddedReceiver(String packageName) { 221 mPackageName = requireNonNull(packageName); 222 } 223 224 @Override onReceive(Context context, Intent intent)225 public void onReceive(Context context, Intent intent) { 226 ProvisionLogger.logd("PACKAGE_ADDED broadcast received with intent data " 227 + intent.getDataString()); 228 if (!mPackageName.equals(extractPackageNameFromDataString(intent.getDataString()))) { 229 ProvisionLogger.logd("The package name provided in the intent data does not equal " 230 + mPackageName); 231 return; 232 } 233 addSuccessStatus(SUCCESS_INSTALLED_BROADCAST); 234 context.unregisterReceiver(this); 235 } 236 extractPackageNameFromDataString(String dataString)237 private String extractPackageNameFromDataString(String dataString) { 238 return dataString.substring("package:".length()); 239 } 240 } 241 242 private class SessionCallback extends PackageInstaller.SessionCallback { 243 244 @Override onCreated(int sessionId)245 public void onCreated(int sessionId) {} 246 247 @Override onBadgingChanged(int sessionId)248 public void onBadgingChanged(int sessionId) {} 249 250 @Override onActiveChanged(int sessionId, boolean active)251 public void onActiveChanged(int sessionId, boolean active) {} 252 253 @Override onProgressChanged(int sessionId, float progress)254 public void onProgressChanged(int sessionId, float progress) {} 255 256 @Override onFinished(int sessionId, boolean success)257 public void onFinished(int sessionId, boolean success) { 258 PackageInstaller packageInstaller = mPm.getPackageInstaller(); 259 packageInstaller.unregisterSessionCallback(mSessionCallback); 260 if (!success) { 261 boolean packageInstalled = 262 mUtils.isPackageInstalled(mPackageName, mContext.getPackageManager()); 263 if (packageInstalled) { 264 ProvisionLogger.logd("Current version of " + mPackageName 265 + " higher than the version to be installed. It was not reinstalled."); 266 // If the package is already at a higher version: success. 267 // Do not log time if package is already at a higher version, as that isn't 268 // useful. 269 success(); 270 return; 271 } else { 272 ProvisionLogger.logd("Installing package " + mPackageName + " failed."); 273 error(ERROR_INSTALLATION_FAILED); 274 return; 275 } 276 } 277 ProvisionLogger.logd("Install package callback received for " + mPackageName); 278 addSuccessStatus(SUCCESS_INSTALLED_CALLBACK); 279 } 280 } 281 } 282