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 android.app.DownloadManager; 19 import android.app.DownloadManager.Query; 20 import android.app.DownloadManager.Request; 21 import android.content.BroadcastReceiver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.content.pm.ActivityInfo; 26 import android.content.pm.PackageInfo; 27 import android.content.pm.PackageManager; 28 import android.database.Cursor; 29 import android.net.Uri; 30 import android.text.TextUtils; 31 import android.util.Base64; 32 33 import com.android.managedprovisioning.ProvisionLogger; 34 35 import java.io.InputStream; 36 import java.io.IOException; 37 import java.io.FileInputStream; 38 import java.security.MessageDigest; 39 import java.security.NoSuchAlgorithmException; 40 import java.util.Arrays; 41 42 /** 43 * Downloads a given file and checks whether its hash matches a given hash to verify that the 44 * intended file was downloaded. 45 */ 46 public class DownloadPackageTask { 47 public static final int ERROR_HASH_MISMATCH = 0; 48 public static final int ERROR_DOWNLOAD_FAILED = 1; 49 public static final int ERROR_OTHER = 2; 50 51 private static final String HASH_TYPE = "SHA-1"; 52 53 private final Context mContext; 54 private final String mDownloadLocationFrom; 55 private final Callback mCallback; 56 private final byte[] mHash; 57 private final String mHttpCookieHeader; 58 59 private boolean mDoneDownloading; 60 private String mDownloadLocationTo; 61 private long mDownloadId; 62 private BroadcastReceiver mReceiver; 63 64 public DownloadPackageTask (Context context, String downloadLocation, byte[] hash, 65 String httpCookieHeader, Callback callback) { 66 mCallback = callback; 67 mContext = context; 68 mDownloadLocationFrom = downloadLocation; 69 mHash = hash; 70 mHttpCookieHeader = httpCookieHeader; 71 mDoneDownloading = false; 72 } 73 74 public boolean downloadLocationWasProvided() { 75 return !TextUtils.isEmpty(mDownloadLocationFrom); 76 } 77 78 public void run() { 79 mReceiver = createDownloadReceiver(); 80 mContext.registerReceiver(mReceiver, 81 new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); 82 83 ProvisionLogger.logd("Starting download from " + mDownloadLocationFrom); 84 DownloadManager dm = (DownloadManager) mContext 85 .getSystemService(Context.DOWNLOAD_SERVICE); 86 Request request = new Request(Uri.parse(mDownloadLocationFrom)); 87 if (mHttpCookieHeader != null) { 88 request.addRequestHeader("Cookie", mHttpCookieHeader); 89 ProvisionLogger.logd("Downloading with http cookie header: " + mHttpCookieHeader); 90 } 91 mDownloadId = dm.enqueue(request); 92 } 93 94 private BroadcastReceiver createDownloadReceiver() { 95 return new BroadcastReceiver() { 96 @Override 97 public void onReceive(Context context, Intent intent) { 98 if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) { 99 Query q = new Query(); 100 q.setFilterById(mDownloadId); 101 DownloadManager dm = (DownloadManager) mContext 102 .getSystemService(Context.DOWNLOAD_SERVICE); 103 Cursor c = dm.query(q); 104 if (c.moveToFirst()) { 105 int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS); 106 if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) { 107 String location = c.getString( 108 c.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME)); 109 c.close(); 110 onDownloadSuccess(location); 111 } else if (DownloadManager.STATUS_FAILED == c.getInt(columnIndex)){ 112 int reason = c.getColumnIndex(DownloadManager.COLUMN_REASON); 113 c.close(); 114 onDownloadFail(reason); 115 } 116 } 117 } 118 } 119 }; 120 } 121 122 private void onDownloadSuccess(String location) { 123 if (mDoneDownloading) { 124 // DownloadManager can send success more than once. Only act first time. 125 return; 126 } else { 127 mDoneDownloading = true; 128 } 129 130 ProvisionLogger.logd("Downloaded succesfully to: " + location); 131 132 // Check whether hash of downloaded file matches hash given in constructor. 133 byte[] hash = computeHash(location); 134 if (hash == null) { 135 136 // Error should have been reported in computeHash(). 137 return; 138 } 139 140 if (Arrays.equals(mHash, hash)) { 141 ProvisionLogger.logd(HASH_TYPE + "-hashes matched, both are " 142 + byteArrayToString(hash)); 143 mDownloadLocationTo = location; 144 mCallback.onSuccess(); 145 } else { 146 ProvisionLogger.loge(HASH_TYPE + "-hash of downloaded file does not match given hash."); 147 ProvisionLogger.loge(HASH_TYPE + "-hash of downloaded file: " 148 + byteArrayToString(hash)); 149 ProvisionLogger.loge(HASH_TYPE + "-hash provided by programmer: " 150 + byteArrayToString(mHash)); 151 152 mCallback.onError(ERROR_HASH_MISMATCH); 153 } 154 } 155 156 private void onDownloadFail(int errorCode) { 157 ProvisionLogger.loge("Downloading package failed."); 158 ProvisionLogger.loge("COLUMN_REASON in DownloadManager response has value: " 159 + errorCode); 160 mCallback.onError(ERROR_DOWNLOAD_FAILED); 161 } 162 163 private byte[] computeHash(String fileLocation) { 164 InputStream fis = null; 165 MessageDigest md; 166 byte hash[] = null; 167 try { 168 md = MessageDigest.getInstance(HASH_TYPE); 169 } catch (NoSuchAlgorithmException e) { 170 ProvisionLogger.loge("Hashing algorithm " + HASH_TYPE + " not supported.", e); 171 mCallback.onError(ERROR_OTHER); 172 return null; 173 } 174 try { 175 fis = new FileInputStream(fileLocation); 176 177 byte[] buffer = new byte[256]; 178 int n = 0; 179 while (n != -1) { 180 n = fis.read(buffer); 181 if (n > 0) { 182 md.update(buffer, 0, n); 183 } 184 } 185 hash = md.digest(); 186 } catch (IOException e) { 187 ProvisionLogger.loge("IO error.", e); 188 mCallback.onError(ERROR_OTHER); 189 } finally { 190 // Close input stream quietly. 191 try { 192 if (fis != null) { 193 fis.close(); 194 } 195 } catch (IOException e) { 196 // Ignore. 197 } 198 } 199 return hash; 200 } 201 202 public String getDownloadedPackageLocation() { 203 return mDownloadLocationTo; 204 } 205 206 public void cleanUp() { 207 if (mReceiver != null) { 208 //Unregister receiver. 209 mContext.unregisterReceiver(mReceiver); 210 mReceiver = null; 211 } 212 213 //Remove download. 214 DownloadManager dm = (DownloadManager) mContext 215 .getSystemService(Context.DOWNLOAD_SERVICE); 216 boolean removeSuccess = dm.remove(mDownloadId) == 1; 217 if (removeSuccess) { 218 ProvisionLogger.logd("Successfully removed the device owner installer file."); 219 } else { 220 ProvisionLogger.loge("Could not remove the device owner installer file."); 221 // Ignore this error. Failing cleanup should not stop provisioning flow. 222 } 223 } 224 225 // For logging purposes only. 226 String byteArrayToString(byte[] ba) { 227 return Base64.encodeToString(ba, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); 228 } 229 230 public abstract static class Callback { 231 public abstract void onSuccess(); 232 public abstract void onError(int errorCode); 233 } 234 } 235