blob: 19ae97070188a71c8c10df55dfd311600d52208d [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
Po-Chien Hsueh4e908c22019-03-07 11:57:17 +080017package com.android.dynsystem;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080018
Po-Chien Hsuehc51cf0f2019-03-21 17:16:06 +080019import android.content.Context;
Po-Chien Hsueh9c1500f2019-03-04 14:47:46 +080020import android.gsi.GsiProgress;
Po-Chien Hsuehc51cf0f2019-03-21 17:16:06 +080021import android.net.Uri;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080022import android.os.AsyncTask;
Howard Chenae615b32019-08-05 16:56:12 +080023import android.os.MemoryFile;
24import android.os.ParcelFileDescriptor;
Po-Chien Hsueh4e908c22019-03-07 11:57:17 +080025import android.os.image.DynamicSystemManager;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080026import android.util.Log;
27import android.webkit.URLUtil;
28
29import java.io.BufferedInputStream;
30import java.io.IOException;
31import java.io.InputStream;
32import java.net.URL;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080033import java.util.Locale;
34import java.util.zip.GZIPInputStream;
35
Po-Chien Hsueha5bd0842019-03-19 12:56:19 +080036class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> {
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080037
38 private static final String TAG = "InstallationAsyncTask";
39
Po-Chien Hsueh22c1f9a2019-05-17 15:08:20 +080040 private static final int READ_BUFFER_SIZE = 1 << 13;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080041
42 private class InvalidImageUrlException extends RuntimeException {
43 private InvalidImageUrlException(String message) {
44 super(message);
45 }
46 }
47
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080048 /** Not completed, including being cancelled */
49 static final int NO_RESULT = 0;
50 static final int RESULT_OK = 1;
51 static final int RESULT_ERROR_IO = 2;
52 static final int RESULT_ERROR_INVALID_URL = 3;
53 static final int RESULT_ERROR_EXCEPTION = 6;
54
55 interface InstallStatusListener {
56 void onProgressUpdate(long installedSize);
Po-Chien Hsueha5bd0842019-03-19 12:56:19 +080057 void onResult(int resultCode, Throwable detail);
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080058 void onCancelled();
59 }
60
61 private final String mUrl;
62 private final long mSystemSize;
63 private final long mUserdataSize;
Po-Chien Hsuehc51cf0f2019-03-21 17:16:06 +080064 private final Context mContext;
Po-Chien Hsueh4e908c22019-03-07 11:57:17 +080065 private final DynamicSystemManager mDynSystem;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080066 private final InstallStatusListener mListener;
Po-Chien Hsueh4e908c22019-03-07 11:57:17 +080067 private DynamicSystemManager.Session mInstallationSession;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080068
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080069 private int mResult = NO_RESULT;
70
71 private InputStream mStream;
72
73
Po-Chien Hsuehc51cf0f2019-03-21 17:16:06 +080074 InstallationAsyncTask(String url, long systemSize, long userdataSize, Context context,
Po-Chien Hsueh4e908c22019-03-07 11:57:17 +080075 DynamicSystemManager dynSystem, InstallStatusListener listener) {
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080076 mUrl = url;
77 mSystemSize = systemSize;
78 mUserdataSize = userdataSize;
Po-Chien Hsuehc51cf0f2019-03-21 17:16:06 +080079 mContext = context;
Po-Chien Hsueh4e908c22019-03-07 11:57:17 +080080 mDynSystem = dynSystem;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080081 mListener = listener;
82 }
83
84 @Override
85 protected void onPreExecute() {
86 mListener.onProgressUpdate(0);
87 }
88
89 @Override
Po-Chien Hsueha5bd0842019-03-19 12:56:19 +080090 protected Throwable doInBackground(String... voids) {
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +080091 Log.d(TAG, "Start doInBackground(), URL: " + mUrl);
92
93 try {
Po-Chien Hsueh9c1500f2019-03-04 14:47:46 +080094 long installedSize = 0;
95 long reportedInstalledSize = 0;
96
97 long minStepToReport = (mSystemSize + mUserdataSize) / 100;
98
99 // init input stream before calling startInstallation(), which takes 90 seconds.
100 initInputStream();
101
Howard Chene176c562019-08-13 17:01:53 +0800102 Thread thread =
103 new Thread(
104 () -> {
Howard Chen6ea5bed2019-10-04 18:15:14 +0800105 mDynSystem.startInstallation();
106 mDynSystem.createPartition("userdata", mUserdataSize, false);
Howard Chene176c562019-08-13 17:01:53 +0800107 mInstallationSession =
Howard Chen6ea5bed2019-10-04 18:15:14 +0800108 mDynSystem.createPartition("system", mSystemSize, true);
Howard Chene176c562019-08-13 17:01:53 +0800109 });
Po-Chien Hsueh9c1500f2019-03-04 14:47:46 +0800110
111 thread.start();
112
113 while (thread.isAlive()) {
114 if (isCancelled()) {
Po-Chien Hsueh4e908c22019-03-07 11:57:17 +0800115 boolean aborted = mDynSystem.abort();
116 Log.d(TAG, "Called DynamicSystemManager.abort(), result = " + aborted);
Po-Chien Hsueha5bd0842019-03-19 12:56:19 +0800117 return null;
Po-Chien Hsueh9c1500f2019-03-04 14:47:46 +0800118 }
119
Po-Chien Hsueh4e908c22019-03-07 11:57:17 +0800120 GsiProgress progress = mDynSystem.getInstallationProgress();
Po-Chien Hsueh9c1500f2019-03-04 14:47:46 +0800121 installedSize = progress.bytes_processed;
122
123 if (installedSize > reportedInstalledSize + minStepToReport) {
124 publishProgress(installedSize);
125 reportedInstalledSize = installedSize;
126 }
127
128 Thread.sleep(10);
129 }
130
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800131 if (mInstallationSession == null) {
Howard Chenae615b32019-08-05 16:56:12 +0800132 throw new IOException(
133 "Failed to start installation with requested size: "
134 + (mSystemSize + mUserdataSize));
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800135 }
136
Po-Chien Hsueh9c1500f2019-03-04 14:47:46 +0800137 installedSize = mUserdataSize;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800138
Howard Chenae615b32019-08-05 16:56:12 +0800139 MemoryFile memoryFile = new MemoryFile("dsu", READ_BUFFER_SIZE);
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800140 byte[] bytes = new byte[READ_BUFFER_SIZE];
Howard Chenae615b32019-08-05 16:56:12 +0800141 mInstallationSession.setAshmem(
142 new ParcelFileDescriptor(memoryFile.getFileDescriptor()), READ_BUFFER_SIZE);
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800143 int numBytesRead;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800144 Log.d(TAG, "Start installation loop");
145 while ((numBytesRead = mStream.read(bytes, 0, READ_BUFFER_SIZE)) != -1) {
Howard Chenae615b32019-08-05 16:56:12 +0800146 memoryFile.writeBytes(bytes, 0, 0, numBytesRead);
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800147 if (isCancelled()) {
148 break;
149 }
Howard Chenae615b32019-08-05 16:56:12 +0800150 if (!mInstallationSession.submitFromAshmem(numBytesRead)) {
Po-Chien Hsueh4e908c22019-03-07 11:57:17 +0800151 throw new IOException("Failed write() to DynamicSystem");
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800152 }
153
Po-Chien Hsueh9c1500f2019-03-04 14:47:46 +0800154 installedSize += numBytesRead;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800155
Po-Chien Hsueh9c1500f2019-03-04 14:47:46 +0800156 if (installedSize > reportedInstalledSize + minStepToReport) {
157 publishProgress(installedSize);
158 reportedInstalledSize = installedSize;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800159 }
160 }
Howard Chen6ea5bed2019-10-04 18:15:14 +0800161 mDynSystem.finishInstallation();
Po-Chien Hsueha5bd0842019-03-19 12:56:19 +0800162 return null;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800163
164 } catch (Exception e) {
165 e.printStackTrace();
Po-Chien Hsueha5bd0842019-03-19 12:56:19 +0800166 return e;
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800167 } finally {
168 close();
169 }
170 }
171
172 @Override
173 protected void onCancelled() {
174 Log.d(TAG, "onCancelled(), URL: " + mUrl);
175
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800176 mListener.onCancelled();
177 }
178
179 @Override
Po-Chien Hsueha5bd0842019-03-19 12:56:19 +0800180 protected void onPostExecute(Throwable detail) {
181 if (detail == null) {
182 mResult = RESULT_OK;
183 } else if (detail instanceof IOException) {
184 mResult = RESULT_ERROR_IO;
185 } else if (detail instanceof InvalidImageUrlException) {
186 mResult = RESULT_ERROR_INVALID_URL;
187 } else {
188 mResult = RESULT_ERROR_EXCEPTION;
189 }
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800190
Po-Chien Hsueha5bd0842019-03-19 12:56:19 +0800191 Log.d(TAG, "onPostExecute(), URL: " + mUrl + ", result: " + mResult);
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800192
Po-Chien Hsueha5bd0842019-03-19 12:56:19 +0800193 mListener.onResult(mResult, detail);
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800194 }
195
196 @Override
197 protected void onProgressUpdate(Long... values) {
198 long progress = values[0];
199 mListener.onProgressUpdate(progress);
200 }
201
202 private void initInputStream() throws IOException, InvalidImageUrlException {
203 if (URLUtil.isNetworkUrl(mUrl) || URLUtil.isFileUrl(mUrl)) {
204 mStream = new BufferedInputStream(new GZIPInputStream(new URL(mUrl).openStream()));
Po-Chien Hsuehc51cf0f2019-03-21 17:16:06 +0800205 } else if (URLUtil.isContentUrl(mUrl)) {
206 Uri uri = Uri.parse(mUrl);
207 mStream = new BufferedInputStream(new GZIPInputStream(
208 mContext.getContentResolver().openInputStream(uri)));
Po-Chien Hsueh64aa7822019-01-12 00:40:02 +0800209 } else {
210 throw new InvalidImageUrlException(
211 String.format(Locale.US, "Unsupported file source: %s", mUrl));
212 }
213 }
214
215 private void close() {
216 try {
217 if (mStream != null) {
218 mStream.close();
219 mStream = null;
220 }
221 } catch (IOException e) {
222 // ignore
223 }
224 }
225
226 int getResult() {
227 return mResult;
228 }
229
230 boolean commit() {
231 if (mInstallationSession == null) {
232 return false;
233 }
234
235 return mInstallationSession.commit();
236 }
237}