Home | History | Annotate | Download | only in task
      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