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 InstallTask(Context context, String packageName, ParcelFileDescriptor parcelFileDescriptor, PackageInstallerImpl.InstallListener callback, PackageInstaller.Session session, IntentSender commitCallback)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 isError()64 public boolean isError() { 65 return mErrorCode != InstallerConstants.STATUS_SUCCESS || !TextUtils.isEmpty(mErrorDesc); 66 } 67 execute()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 */ writeToOutputStreamFromAsset(OutputStream outputStream)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 */ safeClose(Closeable resource)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 }