/* * 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. *
* 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 } } } }