diff options
Diffstat (limited to 'src/com/android/packageinstaller/wear/InstallTask.java')
-rw-r--r-- | src/com/android/packageinstaller/wear/InstallTask.java | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/src/com/android/packageinstaller/wear/InstallTask.java b/src/com/android/packageinstaller/wear/InstallTask.java new file mode 100644 index 00000000..53a460dc --- /dev/null +++ b/src/com/android/packageinstaller/wear/InstallTask.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.packageinstaller.wear; + +import android.content.Context; +import android.content.IntentSender; +import android.content.pm.PackageInstaller; +import android.os.Looper; +import android.os.ParcelFileDescriptor; +import android.text.TextUtils; +import android.util.Log; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Task that installs an APK. This must not be called on the main thread. + * This code is based off the Finsky/Wearsky implementation + */ +public class InstallTask { + private static final String TAG = "InstallTask"; + + private static final int DEFAULT_BUFFER_SIZE = 8192; + + private final Context mContext; + private String mPackageName; + private ParcelFileDescriptor mParcelFileDescriptor; + private PackageInstallerImpl.InstallListener mCallback; + private PackageInstaller.Session mSession; + private IntentSender mCommitCallback; + + private Exception mException = null; + private int mErrorCode = 0; + private String mErrorDesc = null; + + public InstallTask(Context context, String packageName, + ParcelFileDescriptor parcelFileDescriptor, + PackageInstallerImpl.InstallListener callback, PackageInstaller.Session session, + IntentSender commitCallback) { + mContext = context; + mPackageName = packageName; + mParcelFileDescriptor = parcelFileDescriptor; + mCallback = callback; + mSession = session; + mCommitCallback = commitCallback; + } + + public boolean isError() { + return mErrorCode != InstallerConstants.STATUS_SUCCESS || !TextUtils.isEmpty(mErrorDesc); + } + + public void execute() { + if (Looper.myLooper() == Looper.getMainLooper()) { + throw new IllegalStateException("This method cannot be called from the UI thread."); + } + + OutputStream sessionStream = null; + try { + sessionStream = mSession.openWrite(mPackageName, 0, -1); + + // 2b: Stream the asset to the installer. Note: + // Note: writeToOutputStreamFromAsset() always safely closes the input stream + writeToOutputStreamFromAsset(sessionStream); + mSession.fsync(sessionStream); + } catch (Exception e) { + mException = e; + mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM; + mErrorDesc = "Could not write to stream"; + } finally { + if (sessionStream != null) { + // 2c: close output stream + try { + sessionStream.close(); + } catch (Exception e) { + // Ignore otherwise + if (mException == null) { + mException = e; + mErrorCode = InstallerConstants.ERROR_INSTALL_CLOSE_STREAM; + mErrorDesc = "Could not close session stream"; + } + } + } + } + + if (mErrorCode != InstallerConstants.STATUS_SUCCESS) { + // An error occurred, we're done + Log.e(TAG, "Exception while installing " + mPackageName + ": " + mErrorCode + ", " + + mErrorDesc + ", " + mException); + mSession.close(); + mCallback.installFailed(mErrorCode, "[" + mPackageName + "]" + mErrorDesc); + } else { + // 3. Commit the session (this actually installs it.) Session map + // will be cleaned up in the callback. + mCallback.installBeginning(); + mSession.commit(mCommitCallback); + mSession.close(); + } + } + + /** + * {@code PackageInstaller} works with streams. Get the {@code FileDescriptor} + * corresponding to the {@code Asset} and then write the contents into an + * {@code OutputStream} that is passed in. + * <br> + * The {@code FileDescriptor} is closed but the {@code OutputStream} is not closed. + */ + private boolean writeToOutputStreamFromAsset(OutputStream outputStream) { + if (outputStream == null) { + mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM_EXCEPTION; + mErrorDesc = "Got a null OutputStream."; + return false; + } + + if (mParcelFileDescriptor == null || mParcelFileDescriptor.getFileDescriptor() == null) { + mErrorCode = InstallerConstants.ERROR_COULD_NOT_GET_FD; + mErrorDesc = "Could not get FD"; + return false; + } + + InputStream inputStream = null; + try { + byte[] inputBuf = new byte[DEFAULT_BUFFER_SIZE]; + int bytesRead; + inputStream = new ParcelFileDescriptor.AutoCloseInputStream(mParcelFileDescriptor); + + while ((bytesRead = inputStream.read(inputBuf)) > -1) { + if (bytesRead > 0) { + outputStream.write(inputBuf, 0, bytesRead); + } + } + + outputStream.flush(); + } catch (IOException e) { + mErrorCode = InstallerConstants.ERROR_INSTALL_APK_COPY_FAILURE; + mErrorDesc = "Reading from Asset FD or writing to temp file failed: " + e; + return false; + } finally { + safeClose(inputStream); + } + + return true; + } + + /** + * Quietly close a closeable resource (e.g. a stream or file). The input may already + * be closed and it may even be null. + */ + public static void safeClose(Closeable resource) { + if (resource != null) { + try { + resource.close(); + } catch (IOException ioe) { + // Catch and discard the error + } + } + } +}
\ No newline at end of file |