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.ManagedProvisioningSharedPreferences; 40 import com.android.managedprovisioning.common.ProvisionLogger; 41 import com.android.managedprovisioning.R; 42 import com.android.managedprovisioning.common.Globals; 43 import com.android.managedprovisioning.common.SettingsFacade; 44 import com.android.managedprovisioning.common.Utils; 45 import com.android.managedprovisioning.model.PackageDownloadInfo; 46 import com.android.managedprovisioning.model.ProvisioningParams; 47 48 import java.io.File; 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 #getDownloadedPackageLocation()}. 53 */ 54 public class DownloadPackageTask extends AbstractProvisioningTask { 55 public static final int ERROR_DOWNLOAD_FAILED = 0; 56 public static final int ERROR_OTHER = 1; 57 58 private BroadcastReceiver mReceiver; 59 private final DownloadManager mDownloadManager; 60 private final String mPackageName; 61 private final PackageDownloadInfo mPackageDownloadInfo; 62 private long mDownloadId; 63 64 private final Utils mUtils; 65 66 private String mDownloadLocationTo; //local file where the package is downloaded. 67 private boolean mDoneDownloading; 68 DownloadPackageTask( Context context, ProvisioningParams provisioningParams, Callback callback)69 public DownloadPackageTask( 70 Context context, 71 ProvisioningParams provisioningParams, 72 Callback callback) { 73 this(new Utils(), context, provisioningParams, callback, 74 new ProvisioningAnalyticsTracker( 75 MetricsWriterFactory.getMetricsWriter(context, new SettingsFacade()), 76 new ManagedProvisioningSharedPreferences(context))); 77 } 78 79 @VisibleForTesting DownloadPackageTask( Utils utils, Context context, ProvisioningParams provisioningParams, Callback callback, ProvisioningAnalyticsTracker provisioningAnalyticsTracker)80 DownloadPackageTask( 81 Utils utils, 82 Context context, 83 ProvisioningParams provisioningParams, 84 Callback callback, 85 ProvisioningAnalyticsTracker provisioningAnalyticsTracker) { 86 super(context, provisioningParams, callback, provisioningAnalyticsTracker); 87 88 mUtils = checkNotNull(utils); 89 mDownloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); 90 mDownloadManager.setAccessFilename(true); 91 mPackageName = provisioningParams.inferDeviceAdminPackageName(); 92 mPackageDownloadInfo = checkNotNull(provisioningParams.deviceAdminDownloadInfo); 93 } 94 95 @Override getStatusMsgId()96 public int getStatusMsgId() { 97 return R.string.progress_download; 98 } 99 100 @Override run(int userId)101 public void run(int userId) { 102 startTaskTimer(); 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 success(); 108 return; 109 } 110 if (!mUtils.isConnectedToNetwork(mContext)) { 111 ProvisionLogger.loge("DownloadPackageTask: not connected to the network, can't download" 112 + " the package"); 113 error(ERROR_OTHER); 114 return; 115 } 116 117 setDpcDownloadedSetting(mContext); 118 119 mReceiver = createDownloadReceiver(); 120 // register the receiver on the worker thread to avoid threading issues with respect to 121 // the location variable 122 mContext.registerReceiver(mReceiver, 123 new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), 124 null, 125 new Handler(Looper.myLooper())); 126 127 if (Globals.DEBUG) { 128 ProvisionLogger.logd("Starting download from " + mPackageDownloadInfo.location); 129 } 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 mDownloadId = mDownloadManager.enqueue(request); 149 } 150 151 /** 152 * Set MANAGED_PROVISIONING_DPC_DOWNLOADED to 1, which will prevent restarting setup-wizard. 153 * 154 * <p>See b/132261064. 155 */ setDpcDownloadedSetting(Context context)156 private static void setDpcDownloadedSetting(Context context) { 157 Settings.Secure.putInt( 158 context.getContentResolver(), MANAGED_PROVISIONING_DPC_DOWNLOADED, 1); 159 } 160 161 @Override getMetricsCategory()162 protected int getMetricsCategory() { 163 return PROVISIONING_DOWNLOAD_PACKAGE_TASK_MS; 164 } 165 createDownloadReceiver()166 private BroadcastReceiver createDownloadReceiver() { 167 return new BroadcastReceiver() { 168 @Override 169 public void onReceive(Context context, Intent intent) { 170 if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) { 171 Query q = new Query(); 172 q.setFilterById(mDownloadId); 173 Cursor c = mDownloadManager.query(q); 174 if (c.moveToFirst()) { 175 int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS); 176 if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) { 177 mDownloadLocationTo = c.getString( 178 c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME)); 179 c.close(); 180 onDownloadSuccess(); 181 } else if (DownloadManager.STATUS_FAILED == c.getInt(columnIndex)) { 182 int reason = 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 succesfully to: " + mDownloadLocationTo); 203 mDoneDownloading = true; 204 stopTaskTimer(); 205 success(); 206 } 207 208 public String getDownloadedPackageLocation() { 209 return mDownloadLocationTo; 210 } 211 212 private void onDownloadFail(int errorCode) { 213 ProvisionLogger.loge("Downloading package failed."); 214 ProvisionLogger.loge("COLUMN_REASON in DownloadManager response has value: " 215 + errorCode); 216 error(ERROR_DOWNLOAD_FAILED); 217 } 218 219 public void cleanUp() { 220 if (mReceiver != null) { 221 //Unregister receiver. 222 mContext.unregisterReceiver(mReceiver); 223 mReceiver = null; 224 } 225 226 boolean removeSuccess = mDownloadManager.remove(mDownloadId) == 1; 227 if (removeSuccess) { 228 ProvisionLogger.logd("Successfully removed installer file."); 229 } else { 230 ProvisionLogger.loge("Could not remove installer file."); 231 // Ignore this error. Failing cleanup should not stop provisioning flow. 232 } 233 } 234 } 235