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