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