summaryrefslogtreecommitdiffstats
path: root/src/com/android/packageinstaller/wear/InstallTask.java
blob: 53a460dc18ca87f4021f6cc07013c5b95d6155b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
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
            }
        }
    }
}