Home | History | Annotate | Download | only in wear
      1 /*
      2  * Copyright (C) 2016 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 
     17 package com.android.packageinstaller.wear;
     18 
     19 import android.content.Context;
     20 import android.content.IntentSender;
     21 import android.content.pm.PackageInstaller;
     22 import android.os.Looper;
     23 import android.os.ParcelFileDescriptor;
     24 import android.text.TextUtils;
     25 import android.util.Log;
     26 
     27 import java.io.Closeable;
     28 import java.io.IOException;
     29 import java.io.InputStream;
     30 import java.io.OutputStream;
     31 
     32 /**
     33  * Task that installs an APK. This must not be called on the main thread.
     34  * This code is based off the Finsky/Wearsky implementation
     35  */
     36 public class InstallTask {
     37     private static final String TAG = "InstallTask";
     38 
     39     private static final int DEFAULT_BUFFER_SIZE = 8192;
     40 
     41     private final Context mContext;
     42     private String mPackageName;
     43     private ParcelFileDescriptor mParcelFileDescriptor;
     44     private PackageInstallerImpl.InstallListener mCallback;
     45     private PackageInstaller.Session mSession;
     46     private IntentSender mCommitCallback;
     47 
     48     private Exception mException = null;
     49     private int mErrorCode = 0;
     50     private String mErrorDesc = null;
     51 
     52     public InstallTask(Context context, String packageName,
     53             ParcelFileDescriptor parcelFileDescriptor,
     54             PackageInstallerImpl.InstallListener callback, PackageInstaller.Session session,
     55             IntentSender commitCallback) {
     56         mContext = context;
     57         mPackageName = packageName;
     58         mParcelFileDescriptor = parcelFileDescriptor;
     59         mCallback = callback;
     60         mSession = session;
     61         mCommitCallback = commitCallback;
     62     }
     63 
     64     public boolean isError() {
     65         return mErrorCode != InstallerConstants.STATUS_SUCCESS || !TextUtils.isEmpty(mErrorDesc);
     66     }
     67 
     68     public void execute() {
     69         if (Looper.myLooper() == Looper.getMainLooper()) {
     70             throw new IllegalStateException("This method cannot be called from the UI thread.");
     71         }
     72 
     73         OutputStream sessionStream = null;
     74         try {
     75             sessionStream = mSession.openWrite(mPackageName, 0, -1);
     76 
     77             // 2b: Stream the asset to the installer. Note:
     78             // Note: writeToOutputStreamFromAsset() always safely closes the input stream
     79             writeToOutputStreamFromAsset(sessionStream);
     80             mSession.fsync(sessionStream);
     81         } catch (Exception e) {
     82             mException = e;
     83             mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM;
     84             mErrorDesc = "Could not write to stream";
     85         } finally {
     86             if (sessionStream != null) {
     87                 // 2c: close output stream
     88                 try {
     89                     sessionStream.close();
     90                 } catch (Exception e) {
     91                     // Ignore otherwise
     92                     if (mException == null) {
     93                         mException = e;
     94                         mErrorCode = InstallerConstants.ERROR_INSTALL_CLOSE_STREAM;
     95                         mErrorDesc = "Could not close session stream";
     96                     }
     97                 }
     98             }
     99         }
    100 
    101         if (mErrorCode != InstallerConstants.STATUS_SUCCESS) {
    102             // An error occurred, we're done
    103             Log.e(TAG, "Exception while installing " + mPackageName + ": " + mErrorCode + ", "
    104                     + mErrorDesc + ", " + mException);
    105             mSession.close();
    106             mCallback.installFailed(mErrorCode, "[" + mPackageName + "]" + mErrorDesc);
    107         } else {
    108             // 3. Commit the session (this actually installs it.)  Session map
    109             // will be cleaned up in the callback.
    110             mCallback.installBeginning();
    111             mSession.commit(mCommitCallback);
    112             mSession.close();
    113         }
    114     }
    115 
    116     /**
    117      * {@code PackageInstaller} works with streams. Get the {@code FileDescriptor}
    118      * corresponding to the {@code Asset} and then write the contents into an
    119      * {@code OutputStream} that is passed in.
    120      * <br>
    121      * The {@code FileDescriptor} is closed but the {@code OutputStream} is not closed.
    122      */
    123     private boolean writeToOutputStreamFromAsset(OutputStream outputStream) {
    124         if (outputStream == null) {
    125             mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM_EXCEPTION;
    126             mErrorDesc = "Got a null OutputStream.";
    127             return false;
    128         }
    129 
    130         if (mParcelFileDescriptor == null || mParcelFileDescriptor.getFileDescriptor() == null)  {
    131             mErrorCode = InstallerConstants.ERROR_COULD_NOT_GET_FD;
    132             mErrorDesc = "Could not get FD";
    133             return false;
    134         }
    135 
    136         InputStream inputStream = null;
    137         try {
    138             byte[] inputBuf = new byte[DEFAULT_BUFFER_SIZE];
    139             int bytesRead;
    140             inputStream = new ParcelFileDescriptor.AutoCloseInputStream(mParcelFileDescriptor);
    141 
    142             while ((bytesRead = inputStream.read(inputBuf)) > -1) {
    143                 if (bytesRead > 0) {
    144                     outputStream.write(inputBuf, 0, bytesRead);
    145                 }
    146             }
    147 
    148             outputStream.flush();
    149         } catch (IOException e) {
    150             mErrorCode = InstallerConstants.ERROR_INSTALL_APK_COPY_FAILURE;
    151             mErrorDesc = "Reading from Asset FD or writing to temp file failed: " + e;
    152             return false;
    153         } finally {
    154             safeClose(inputStream);
    155         }
    156 
    157         return true;
    158     }
    159 
    160     /**
    161      * Quietly close a closeable resource (e.g. a stream or file). The input may already
    162      * be closed and it may even be null.
    163      */
    164     public static void safeClose(Closeable resource) {
    165         if (resource != null) {
    166             try {
    167                 resource.close();
    168             } catch (IOException ioe) {
    169                 // Catch and discard the error
    170             }
    171         }
    172     }
    173 }