blob: 3c759e948b4ed662941174b6ec1aa1177e611bc6 [file] [log] [blame]
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +08001/*
2 * Copyright (C) 2019 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
17package com.android.dynandroid;
18
19import android.os.AsyncTask;
20import android.os.DynamicAndroidManager;
21import android.util.Log;
22import android.webkit.URLUtil;
23
24import java.io.BufferedInputStream;
25import java.io.IOException;
26import java.io.InputStream;
27import java.net.URL;
28import java.util.Arrays;
29import java.util.Locale;
30import java.util.zip.GZIPInputStream;
31
32
33class InstallationAsyncTask extends AsyncTask<String, Long, Integer> {
34
35 private static final String TAG = "InstallationAsyncTask";
36
37 private static final int READ_BUFFER_SIZE = 1 << 19;
38
39 private class InvalidImageUrlException extends RuntimeException {
40 private InvalidImageUrlException(String message) {
41 super(message);
42 }
43 }
44
45
46 /** Not completed, including being cancelled */
47 static final int NO_RESULT = 0;
48 static final int RESULT_OK = 1;
49 static final int RESULT_ERROR_IO = 2;
50 static final int RESULT_ERROR_INVALID_URL = 3;
51 static final int RESULT_ERROR_EXCEPTION = 6;
52
53 interface InstallStatusListener {
54 void onProgressUpdate(long installedSize);
55 void onResult(int resultCode);
56 void onCancelled();
57 }
58
59 private final String mUrl;
60 private final long mSystemSize;
61 private final long mUserdataSize;
62 private final DynamicAndroidManager mDynamicAndroid;
63 private final InstallStatusListener mListener;
64 private DynamicAndroidManager.Session mInstallationSession;
65
66 private long mInstalledSize;
67 private long mReportedInstalledSize;
68 private int mResult = NO_RESULT;
69
70 private InputStream mStream;
71
72
73 InstallationAsyncTask(String url, long systemSize, long userdataSize,
74 DynamicAndroidManager dynAndroid, InstallStatusListener listener) {
75 mUrl = url;
76 mSystemSize = systemSize;
77 mUserdataSize = userdataSize;
78 mDynamicAndroid = dynAndroid;
79 mListener = listener;
80 }
81
82 @Override
83 protected void onPreExecute() {
84 mListener.onProgressUpdate(0);
85 }
86
87 @Override
88 protected Integer doInBackground(String... voids) {
89 Log.d(TAG, "Start doInBackground(), URL: " + mUrl);
90
91 try {
92 // call start in background
93 mInstallationSession = mDynamicAndroid.startInstallation(mSystemSize, mUserdataSize);
94
95 if (mInstallationSession == null) {
96 Log.e(TAG, "Failed to start installation with requested size: "
97 + (mSystemSize + mUserdataSize));
98
99 return RESULT_ERROR_IO;
100 }
101
102 initInputStream();
103
104 byte[] bytes = new byte[READ_BUFFER_SIZE];
105
106 int numBytesRead;
107 long minStepToReport = mSystemSize / 100;
108
109 Log.d(TAG, "Start installation loop");
110 while ((numBytesRead = mStream.read(bytes, 0, READ_BUFFER_SIZE)) != -1) {
111 if (isCancelled()) {
112 break;
113 }
114
115 byte[] writeBuffer = numBytesRead == READ_BUFFER_SIZE
116 ? bytes : Arrays.copyOf(bytes, numBytesRead);
117
118 if (!mInstallationSession.write(writeBuffer)) {
119 throw new IOException("Failed write() to DynamicAndroid");
120 }
121
122 mInstalledSize += numBytesRead;
123
124 if (mInstalledSize > mReportedInstalledSize + minStepToReport) {
125 publishProgress(mInstalledSize);
126 mReportedInstalledSize = mInstalledSize;
127 }
128 }
129
130 return RESULT_OK;
131
132 } catch (IOException e) {
133 e.printStackTrace();
134 return RESULT_ERROR_IO;
135
136 } catch (InvalidImageUrlException e) {
137 e.printStackTrace();
138 return RESULT_ERROR_INVALID_URL;
139
140 } catch (Exception e) {
141 e.printStackTrace();
142 return RESULT_ERROR_EXCEPTION;
143
144 } finally {
145 close();
146 }
147 }
148
149 @Override
150 protected void onCancelled() {
151 Log.d(TAG, "onCancelled(), URL: " + mUrl);
152
153 close();
154
155 mListener.onCancelled();
156 }
157
158 @Override
159 protected void onPostExecute(Integer result) {
160 Log.d(TAG, "onPostExecute(), URL: " + mUrl + ", result: " + result);
161
162 close();
163
164 mResult = result;
165 mListener.onResult(mResult);
166 }
167
168 @Override
169 protected void onProgressUpdate(Long... values) {
170 long progress = values[0];
171 mListener.onProgressUpdate(progress);
172 }
173
174 private void initInputStream() throws IOException, InvalidImageUrlException {
175 if (URLUtil.isNetworkUrl(mUrl) || URLUtil.isFileUrl(mUrl)) {
176 mStream = new BufferedInputStream(new GZIPInputStream(new URL(mUrl).openStream()));
177 } else {
178 throw new InvalidImageUrlException(
179 String.format(Locale.US, "Unsupported file source: %s", mUrl));
180 }
181 }
182
183 private void close() {
184 try {
185 if (mStream != null) {
186 mStream.close();
187 mStream = null;
188 }
189 } catch (IOException e) {
190 // ignore
191 }
192 }
193
194 int getResult() {
195 return mResult;
196 }
197
198 boolean commit() {
199 if (mInstallationSession == null) {
200 return false;
201 }
202
203 return mInstallationSession.commit();
204 }
205}