blob: cf286bdbde968d7a552e65907899fb44b6ffd788 [file] [log] [blame]
/*
* Copyright (C) 2019 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.dynsystem;
import android.content.Context;
import android.gsi.GsiProgress;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.MemoryFile;
import android.os.ParcelFileDescriptor;
import android.os.image.DynamicSystemManager;
import android.util.Log;
import android.webkit.URLUtil;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Locale;
import java.util.zip.GZIPInputStream;
class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> {
private static final String TAG = "InstallationAsyncTask";
private static final int READ_BUFFER_SIZE = 1 << 13;
private class InvalidImageUrlException extends RuntimeException {
private InvalidImageUrlException(String message) {
super(message);
}
}
/** Not completed, including being cancelled */
static final int NO_RESULT = 0;
static final int RESULT_OK = 1;
static final int RESULT_ERROR_IO = 2;
static final int RESULT_ERROR_INVALID_URL = 3;
static final int RESULT_ERROR_EXCEPTION = 6;
interface InstallStatusListener {
void onProgressUpdate(long installedSize);
void onResult(int resultCode, Throwable detail);
void onCancelled();
}
private final String mUrl;
private final long mSystemSize;
private final long mUserdataSize;
private final Context mContext;
private final DynamicSystemManager mDynSystem;
private final InstallStatusListener mListener;
private DynamicSystemManager.Session mInstallationSession;
private int mResult = NO_RESULT;
private InputStream mStream;
InstallationAsyncTask(String url, long systemSize, long userdataSize, Context context,
DynamicSystemManager dynSystem, InstallStatusListener listener) {
mUrl = url;
mSystemSize = systemSize;
mUserdataSize = userdataSize;
mContext = context;
mDynSystem = dynSystem;
mListener = listener;
}
@Override
protected void onPreExecute() {
mListener.onProgressUpdate(0);
}
@Override
protected Throwable doInBackground(String... voids) {
Log.d(TAG, "Start doInBackground(), URL: " + mUrl);
try {
long installedSize = 0;
long reportedInstalledSize = 0;
long minStepToReport = (mSystemSize + mUserdataSize) / 100;
// init input stream before calling startInstallation(), which takes 90 seconds.
initInputStream();
Thread thread = new Thread(() -> {
mInstallationSession =
mDynSystem.startInstallation(mSystemSize, mUserdataSize);
});
thread.start();
while (thread.isAlive()) {
if (isCancelled()) {
boolean aborted = mDynSystem.abort();
Log.d(TAG, "Called DynamicSystemManager.abort(), result = " + aborted);
return null;
}
GsiProgress progress = mDynSystem.getInstallationProgress();
installedSize = progress.bytes_processed;
if (installedSize > reportedInstalledSize + minStepToReport) {
publishProgress(installedSize);
reportedInstalledSize = installedSize;
}
Thread.sleep(10);
}
if (mInstallationSession == null) {
throw new IOException(
"Failed to start installation with requested size: "
+ (mSystemSize + mUserdataSize));
}
installedSize = mUserdataSize;
MemoryFile memoryFile = new MemoryFile("dsu", READ_BUFFER_SIZE);
byte[] bytes = new byte[READ_BUFFER_SIZE];
mInstallationSession.setAshmem(
new ParcelFileDescriptor(memoryFile.getFileDescriptor()), READ_BUFFER_SIZE);
int numBytesRead;
Log.d(TAG, "Start installation loop");
while ((numBytesRead = mStream.read(bytes, 0, READ_BUFFER_SIZE)) != -1) {
memoryFile.writeBytes(bytes, 0, 0, numBytesRead);
if (isCancelled()) {
break;
}
if (!mInstallationSession.submitFromAshmem(numBytesRead)) {
throw new IOException("Failed write() to DynamicSystem");
}
installedSize += numBytesRead;
if (installedSize > reportedInstalledSize + minStepToReport) {
publishProgress(installedSize);
reportedInstalledSize = installedSize;
}
}
return null;
} catch (Exception e) {
e.printStackTrace();
return e;
} finally {
close();
}
}
@Override
protected void onCancelled() {
Log.d(TAG, "onCancelled(), URL: " + mUrl);
mListener.onCancelled();
}
@Override
protected void onPostExecute(Throwable detail) {
if (detail == null) {
mResult = RESULT_OK;
} else if (detail instanceof IOException) {
mResult = RESULT_ERROR_IO;
} else if (detail instanceof InvalidImageUrlException) {
mResult = RESULT_ERROR_INVALID_URL;
} else {
mResult = RESULT_ERROR_EXCEPTION;
}
Log.d(TAG, "onPostExecute(), URL: " + mUrl + ", result: " + mResult);
mListener.onResult(mResult, detail);
}
@Override
protected void onProgressUpdate(Long... values) {
long progress = values[0];
mListener.onProgressUpdate(progress);
}
private void initInputStream() throws IOException, InvalidImageUrlException {
if (URLUtil.isNetworkUrl(mUrl) || URLUtil.isFileUrl(mUrl)) {
mStream = new BufferedInputStream(new GZIPInputStream(new URL(mUrl).openStream()));
} else if (URLUtil.isContentUrl(mUrl)) {
Uri uri = Uri.parse(mUrl);
mStream = new BufferedInputStream(new GZIPInputStream(
mContext.getContentResolver().openInputStream(uri)));
} else {
throw new InvalidImageUrlException(
String.format(Locale.US, "Unsupported file source: %s", mUrl));
}
}
private void close() {
try {
if (mStream != null) {
mStream.close();
mStream = null;
}
} catch (IOException e) {
// ignore
}
}
int getResult() {
return mResult;
}
boolean commit() {
if (mInstallationSession == null) {
return false;
}
return mInstallationSession.commit();
}
}