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