• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }