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.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED; 19 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_DOWNLOAD_PACKAGE_TASK_MS; 20 import static com.android.internal.util.Preconditions.checkNotNull; 21 import static java.lang.String.format; 22 23 import android.app.DownloadManager; 24 import android.app.DownloadManager.Query; 25 import android.app.DownloadManager.Request; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.database.Cursor; 31 import android.net.Uri; 32 import android.os.Handler; 33 import android.os.Looper; 34 import android.provider.Settings; 35 36 import com.android.internal.annotations.VisibleForTesting; 37 import com.android.managedprovisioning.analytics.MetricsWriterFactory; 38 import com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker; 39 import com.android.managedprovisioning.common.Globals; 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.PackageDownloadInfo; 45 import com.android.managedprovisioning.model.ProvisioningParams; 46 47 import java.io.File; 48 import java.net.URI; 49 50 /** 51 * Downloads the management app apk from the url provided by {@link PackageDownloadInfo#location}. 52 * The location of the downloaded file can be read via {@link PackageLocationProvider 53 * #getDownloadLocation()}}. 54 */ 55 public class DownloadPackageTask extends AbstractProvisioningTask 56 implements PackageLocationProvider { 57 public static final int ERROR_DOWNLOAD_FAILED = 0; 58 public static final int ERROR_OTHER = 1; 59 60 private BroadcastReceiver mReceiver; 61 private final DownloadManager mDownloadManager; 62 private final String mPackageName; 63 private final PackageDownloadInfo mPackageDownloadInfo; 64 private long mDownloadId; 65 66 private final Utils mUtils; 67 68 private File mDownloadLocationTo; //local file where the package is downloaded. 69 private boolean mDoneDownloading; 70 DownloadPackageTask( Context context, ProvisioningParams provisioningParams, PackageDownloadInfo packageDownloadInfo, Callback callback)71 public DownloadPackageTask( 72 Context context, 73 ProvisioningParams provisioningParams, 74 PackageDownloadInfo packageDownloadInfo, 75 Callback callback) { 76 this(new Utils(), context, provisioningParams, packageDownloadInfo, callback, 77 new ProvisioningAnalyticsTracker( 78 MetricsWriterFactory.getMetricsWriter(context, new SettingsFacade()), 79 new ManagedProvisioningSharedPreferences(context))); 80 } 81 82 @VisibleForTesting DownloadPackageTask( Utils utils, Context context, ProvisioningParams provisioningParams, PackageDownloadInfo packageDownloadInfo, Callback callback, ProvisioningAnalyticsTracker provisioningAnalyticsTracker)83 DownloadPackageTask( 84 Utils utils, 85 Context context, 86 ProvisioningParams provisioningParams, 87 PackageDownloadInfo packageDownloadInfo, 88 Callback callback, 89 ProvisioningAnalyticsTracker provisioningAnalyticsTracker) { 90 super(context, provisioningParams, callback, provisioningAnalyticsTracker); 91 92 mUtils = checkNotNull(utils); 93 mDownloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); 94 mDownloadManager.setAccessFilename(true); 95 mPackageName = provisioningParams.inferDeviceAdminPackageName(); 96 mPackageDownloadInfo = checkNotNull(packageDownloadInfo); 97 } 98 99 @Override run(int userId)100 public void run(int userId) { 101 startTaskTimer(); 102 ProvisionLogger.logd(format("Starting download of %s on user %s for user %s", mPackageName, mContext.getUserId(), userId)); 103 if (!mUtils.packageRequiresUpdate(mPackageName, mPackageDownloadInfo.minVersion, 104 mContext)) { 105 // Do not log time if package is already on device and does not require an update, as 106 // that isn't useful. 107 ProvisionLogger.logd(format("No update needed for %s", mPackageName)); 108 success(); 109 return; 110 } 111 if (!mUtils.isConnectedToNetwork(mContext)) { 112 ProvisionLogger.loge("DownloadPackageTask: not connected to the network, can't download" 113 + " the package"); 114 error(ERROR_OTHER); 115 return; 116 } 117 118 setDpcDownloadedSetting(mContext); 119 120 ProvisionLogger.logd(format("Creating download receiver for %s", mPackageName)); 121 mReceiver = createDownloadReceiver(); 122 // register the receiver on the worker thread to avoid threading issues with respect to 123 // the location variable 124 ProvisionLogger.logd(format("Registering download receiver for %s", mPackageName)); 125 mContext.registerReceiver(mReceiver, 126 new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), 127 null, 128 new Handler(Looper.myLooper()), 129 Context.RECEIVER_EXPORTED); 130 131 Request request = new Request(Uri.parse(mPackageDownloadInfo.location)); 132 133 // Note that the apk may not actually be downloaded to this path. This could happen if 134 // this file already exists. 135 String path = mContext.getExternalFilesDir(null) 136 + "/download_cache/managed_provisioning_downloaded_app.apk"; 137 File downloadedFile = new File(path); 138 downloadedFile.getParentFile().mkdirs(); // If the folder doesn't exists it is created 139 request.setDestinationUri(Uri.fromFile(downloadedFile)); 140 141 if (mPackageDownloadInfo.cookieHeader != null) { 142 request.addRequestHeader("Cookie", mPackageDownloadInfo.cookieHeader); 143 if (Globals.DEBUG) { 144 ProvisionLogger.logd("Downloading with http cookie header: " 145 + mPackageDownloadInfo.cookieHeader); 146 } 147 } 148 ProvisionLogger.logd(format("Starting download for %s from %s into %s", mPackageName, mPackageDownloadInfo.location, path)); 149 mDownloadId = mDownloadManager.enqueue(request); 150 } 151 152 /** 153 * Set MANAGED_PROVISIONING_DPC_DOWNLOADED to 1, which will prevent restarting setup-wizard. 154 * 155 * <p>See b/132261064. 156 */ setDpcDownloadedSetting(Context context)157 private static void setDpcDownloadedSetting(Context context) { 158 Settings.Secure.putInt( 159 context.getContentResolver(), MANAGED_PROVISIONING_DPC_DOWNLOADED, 1); 160 } 161 162 @Override getMetricsCategory()163 protected int getMetricsCategory() { 164 return PROVISIONING_DOWNLOAD_PACKAGE_TASK_MS; 165 } 166 createDownloadReceiver()167 private BroadcastReceiver createDownloadReceiver() { 168 return new BroadcastReceiver() { 169 @Override 170 public void onReceive(Context context, Intent intent) { 171 if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) { 172 ProvisionLogger.logd(format("Download receiver invoked for %s", mPackageName)); 173 Query q = new Query(); 174 q.setFilterById(mDownloadId); 175 Cursor c = mDownloadManager.query(q); 176 if (c.moveToFirst()) { 177 int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS); 178 if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) { 179 mDownloadLocationTo = new File(URI.create(c.getString( 180 c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)))); 181 c.close(); 182 onDownloadSuccess(); 183 } else if (DownloadManager.STATUS_FAILED == c.getInt(columnIndex)) { 184 final int reason = 185 c.getInt(c.getColumnIndex(DownloadManager.COLUMN_REASON)); 186 c.close(); 187 onDownloadFail(reason); 188 } 189 } 190 } 191 } 192 }; 193 } 194 195 /** 196 * For a successful download, check that the downloaded file is the expected file. 197 * If the package hash is provided then that is used, otherwise a signature hash is used. 198 */ 199 private void onDownloadSuccess() { 200 if (mDoneDownloading) { 201 // DownloadManager can send success more than once. Only act first time. 202 return; 203 } 204 205 ProvisionLogger.logd("Downloaded successfully to: " 206 + mDownloadLocationTo.getAbsolutePath()); 207 mDoneDownloading = true; 208 stopTaskTimer(); 209 success(); 210 } 211 212 @Override 213 public File getPackageLocation() { 214 return mDownloadLocationTo; 215 } 216 217 private void onDownloadFail(int errorCode) { 218 ProvisionLogger.loge("Downloading package failed (download id " + mDownloadId 219 + "). COLUMN_REASON in DownloadManager response has value: " + errorCode); 220 error(ERROR_DOWNLOAD_FAILED); 221 } 222 223 public void cleanUp() { 224 if (mReceiver != null) { 225 //Unregister receiver. 226 mContext.unregisterReceiver(mReceiver); 227 mReceiver = null; 228 } 229 230 boolean removeSuccess = mDownloadManager.remove(mDownloadId) == 1; 231 if (removeSuccess) { 232 ProvisionLogger.logd("Successfully removed installer file."); 233 } else { 234 ProvisionLogger.loge("Could not remove installer file."); 235 // Ignore this error. Failing cleanup should not stop provisioning flow. 236 } 237 } 238 } 239