blob: 53a460dc18ca87f4021f6cc07013c5b95d6155b2 [file] [log] [blame]
/*
* 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
}
}
}
}