summaryrefslogtreecommitdiff
path: root/packages/DynamicSystemInstallationService/src
diff options
context:
space:
mode:
authorPo-Chien Hsueh <pchsueh@google.com>2019-03-07 11:57:17 +0800
committerPo-Chien Hsueh <pchsueh@google.com>2019-03-21 17:25:59 +0800
commit4e908c24ca4a8b751546125548686069c045ffd7 (patch)
tree9ffbe8e79d082feca9f953cd187bf3d0d4620183 /packages/DynamicSystemInstallationService/src
parent16da0e59ff1ca5b4d499de139ec4284097fc21e5 (diff)
API review followup: DynamicAndroidClient
Some API changes per API reivew: - Move DynamicAndroidClient and ~Manager to android.os.image. - Rename them to DynamicSystemClient and ~Manager. - Rename permission MANAGE_DYNAMIC_ANDROID to MANAGE_DYNAMIC_SYSTEM - Corresponding changes in the installation service. - Corresponding changes in privapp-permissions-platform.xml. - Add missing annotations. - Change setOnStatusChangedListener's parameters order. - Improve documentations. - Re-generate api/system-current.txt. Bug: 126613281 Test: adb shell am Change-Id: Ia920e9ccf6de1dbbd38c52910cb72cb81b9b5b32
Diffstat (limited to 'packages/DynamicSystemInstallationService/src')
-rw-r--r--packages/DynamicSystemInstallationService/src/com/android/dynandroid/BootCompletedReceiver.java50
-rw-r--r--packages/DynamicSystemInstallationService/src/com/android/dynandroid/DynamicSystemInstallationService.java515
-rw-r--r--packages/DynamicSystemInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java235
-rw-r--r--packages/DynamicSystemInstallationService/src/com/android/dynandroid/VerificationActivity.java104
4 files changed, 904 insertions, 0 deletions
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynandroid/BootCompletedReceiver.java b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/BootCompletedReceiver.java
new file mode 100644
index 000000000000..38576ee47283
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/BootCompletedReceiver.java
@@ -0,0 +1,50 @@
+/*
+ * 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+import android.os.image.DynamicSystemClient;
+import android.util.Log;
+
+
+/**
+ * A BoardcastReceiver waiting for ACTION_BOOT_COMPLETED and ask
+ * the service to display a notification if we are currently running
+ * in DynamicSystem.
+ */
+public class BootCompletedReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "BootCompletedReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ Log.d(TAG, "Broadcast received: " + action);
+
+ if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+ Intent startServiceIntent = new Intent(
+ context, DynamicSystemInstallationService.class);
+
+ startServiceIntent.setAction(DynamicSystemClient.ACTION_NOTIFY_IF_IN_USE);
+ context.startServiceAsUser(startServiceIntent, UserHandle.SYSTEM);
+ }
+ }
+}
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynandroid/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/DynamicSystemInstallationService.java
new file mode 100644
index 000000000000..df2c57181904
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/DynamicSystemInstallationService.java
@@ -0,0 +1,515 @@
+/*
+ * 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 static android.os.AsyncTask.Status.FINISHED;
+import static android.os.AsyncTask.Status.PENDING;
+import static android.os.AsyncTask.Status.RUNNING;
+import static android.os.image.DynamicSystemClient.ACTION_NOTIFY_IF_IN_USE;
+import static android.os.image.DynamicSystemClient.ACTION_START_INSTALL;
+import static android.os.image.DynamicSystemClient.CAUSE_ERROR_EXCEPTION;
+import static android.os.image.DynamicSystemClient.CAUSE_ERROR_INVALID_URL;
+import static android.os.image.DynamicSystemClient.CAUSE_ERROR_IO;
+import static android.os.image.DynamicSystemClient.CAUSE_INSTALL_CANCELLED;
+import static android.os.image.DynamicSystemClient.CAUSE_INSTALL_COMPLETED;
+import static android.os.image.DynamicSystemClient.CAUSE_NOT_SPECIFIED;
+import static android.os.image.DynamicSystemClient.STATUS_IN_PROGRESS;
+import static android.os.image.DynamicSystemClient.STATUS_IN_USE;
+import static android.os.image.DynamicSystemClient.STATUS_NOT_STARTED;
+import static android.os.image.DynamicSystemClient.STATUS_READY;
+
+import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_EXCEPTION;
+import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_INVALID_URL;
+import static com.android.dynsystem.InstallationAsyncTask.RESULT_ERROR_IO;
+import static com.android.dynsystem.InstallationAsyncTask.RESULT_OK;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.image.DynamicSystemClient;
+import android.os.image.DynamicSystemManager;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * This class is the service in charge of DynamicSystem installation.
+ * It also posts status to notification bar and wait for user's
+ * cancel and confirm commnands.
+ */
+public class DynamicSystemInstallationService extends Service
+ implements InstallationAsyncTask.InstallStatusListener {
+
+ private static final String TAG = "DynSystemInstallationService";
+
+ /*
+ * Intent actions
+ */
+ private static final String ACTION_CANCEL_INSTALL =
+ "com.android.dynsystem.ACTION_CANCEL_INSTALL";
+ private static final String ACTION_DISCARD_INSTALL =
+ "com.android.dynsystem.ACTION_DISCARD_INSTALL";
+ private static final String ACTION_REBOOT_TO_DYN_SYSTEM =
+ "com.android.dynsystem.ACTION_REBOOT_TO_DYN_SYSTEM";
+ private static final String ACTION_REBOOT_TO_NORMAL =
+ "com.android.dynsystem.ACTION_REBOOT_TO_NORMAL";
+
+ /*
+ * For notification
+ */
+ private static final String NOTIFICATION_CHANNEL_ID = "com.android.dynsystem";
+ private static final int NOTIFICATION_ID = 1;
+
+ /*
+ * IPC
+ */
+ /** Keeps track of all current registered clients. */
+ ArrayList<Messenger> mClients = new ArrayList<>();
+
+ /** Handler of incoming messages from clients. */
+ final Messenger mMessenger = new Messenger(new IncomingHandler(this));
+
+ static class IncomingHandler extends Handler {
+ private final WeakReference<DynamicSystemInstallationService> mWeakService;
+
+ IncomingHandler(DynamicSystemInstallationService service) {
+ mWeakService = new WeakReference<>(service);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ DynamicSystemInstallationService service = mWeakService.get();
+
+ if (service != null) {
+ service.handleMessage(msg);
+ }
+ }
+ }
+
+ private DynamicSystemManager mDynSystem;
+ private NotificationManager mNM;
+
+ private long mSystemSize;
+ private long mUserdataSize;
+ private long mInstalledSize;
+ private boolean mJustCancelledByUser;
+
+ private InstallationAsyncTask mInstallTask;
+
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ prepareNotification();
+
+ mDynSystem = (DynamicSystemManager) getSystemService(Context.DYNAMIC_SYSTEM_SERVICE);
+ }
+
+ @Override
+ public void onDestroy() {
+ // Cancel the persistent notification.
+ mNM.cancel(NOTIFICATION_ID);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mMessenger.getBinder();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ String action = intent.getAction();
+
+ Log.d(TAG, "onStartCommand(): action=" + action);
+
+ if (ACTION_START_INSTALL.equals(action)) {
+ executeInstallCommand(intent);
+ } else if (ACTION_CANCEL_INSTALL.equals(action)) {
+ executeCancelCommand();
+ } else if (ACTION_DISCARD_INSTALL.equals(action)) {
+ executeDiscardCommand();
+ } else if (ACTION_REBOOT_TO_DYN_SYSTEM.equals(action)) {
+ executeRebootToDynSystemCommand();
+ } else if (ACTION_REBOOT_TO_NORMAL.equals(action)) {
+ executeRebootToNormalCommand();
+ } else if (ACTION_NOTIFY_IF_IN_USE.equals(action)) {
+ executeNotifyIfInUseCommand();
+ }
+
+ return Service.START_NOT_STICKY;
+ }
+
+ @Override
+ public void onProgressUpdate(long installedSize) {
+ mInstalledSize = installedSize;
+ postStatus(STATUS_IN_PROGRESS, CAUSE_NOT_SPECIFIED);
+ }
+
+ @Override
+ public void onResult(int result) {
+ if (result == RESULT_OK) {
+ postStatus(STATUS_READY, CAUSE_INSTALL_COMPLETED);
+ return;
+ }
+
+ // if it's not successful, reset the task and stop self.
+ resetTaskAndStop();
+
+ switch (result) {
+ case RESULT_ERROR_IO:
+ postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_IO);
+ break;
+
+ case RESULT_ERROR_INVALID_URL:
+ postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_INVALID_URL);
+ break;
+
+ case RESULT_ERROR_EXCEPTION:
+ postStatus(STATUS_NOT_STARTED, CAUSE_ERROR_EXCEPTION);
+ break;
+ }
+ }
+
+ @Override
+ public void onCancelled() {
+ resetTaskAndStop();
+ postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED);
+ }
+
+ private void executeInstallCommand(Intent intent) {
+ if (!verifyRequest(intent)) {
+ Log.e(TAG, "Verification failed. Did you use VerificationActivity?");
+ return;
+ }
+
+ if (mInstallTask != null) {
+ Log.e(TAG, "There is already an installation task running");
+ return;
+ }
+
+ if (isInDynamicSystem()) {
+ Log.e(TAG, "We are already running in DynamicSystem");
+ return;
+ }
+
+ String url = intent.getStringExtra(DynamicSystemClient.KEY_SYSTEM_URL);
+ mSystemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0);
+ mUserdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0);
+
+ mInstallTask = new InstallationAsyncTask(
+ url, mSystemSize, mUserdataSize, mDynSystem, this);
+
+ mInstallTask.execute();
+
+ // start fore ground
+ startForeground(NOTIFICATION_ID,
+ buildNotification(STATUS_IN_PROGRESS, CAUSE_NOT_SPECIFIED));
+ }
+
+ private void executeCancelCommand() {
+ if (mInstallTask == null || mInstallTask.getStatus() != RUNNING) {
+ Log.e(TAG, "Cancel command triggered, but there is no task running");
+ return;
+ }
+
+ mJustCancelledByUser = true;
+
+ if (mInstallTask.cancel(false)) {
+ // Will cleanup and post status in onCancelled()
+ Log.d(TAG, "Cancel request filed successfully");
+ } else {
+ Log.e(TAG, "Trying to cancel installation while it's already completed.");
+ }
+ }
+
+ private void executeDiscardCommand() {
+ if (isInDynamicSystem()) {
+ Log.e(TAG, "We are now running in AOT, please reboot to normal system first");
+ return;
+ }
+
+ if (getStatus() != STATUS_READY) {
+ Log.e(TAG, "Trying to discard AOT while there is no complete installation");
+ return;
+ }
+
+ Toast.makeText(this,
+ getString(R.string.toast_dynsystem_discarded),
+ Toast.LENGTH_LONG).show();
+
+ resetTaskAndStop();
+ postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED);
+
+ mDynSystem.remove();
+ }
+
+ private void executeRebootToDynSystemCommand() {
+ if (mInstallTask == null || mInstallTask.getStatus() != FINISHED) {
+ Log.e(TAG, "Trying to reboot to AOT while there is no complete installation");
+ return;
+ }
+
+ if (!mInstallTask.commit()) {
+ Log.e(TAG, "Failed to commit installation because of native runtime error.");
+ mNM.cancel(NOTIFICATION_ID);
+
+ Toast.makeText(this,
+ getString(R.string.toast_failed_to_reboot_to_dynsystem),
+ Toast.LENGTH_LONG).show();
+
+ mDynSystem.remove();
+
+ return;
+ }
+
+ PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+
+ if (powerManager != null) {
+ powerManager.reboot("dynsystem");
+ }
+ }
+
+ private void executeRebootToNormalCommand() {
+ if (!isInDynamicSystem()) {
+ Log.e(TAG, "It's already running in normal system.");
+ return;
+ }
+
+ // Per current design, we don't have disable() API. AOT is disabled on next reboot.
+ // TODO: Use better status query when b/125079548 is done.
+ PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+
+ if (powerManager != null) {
+ powerManager.reboot(null);
+ }
+ }
+
+ private void executeNotifyIfInUseCommand() {
+ int status = getStatus();
+
+ if (status == STATUS_IN_USE) {
+ startForeground(NOTIFICATION_ID,
+ buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED));
+ } else if (status == STATUS_READY) {
+ startForeground(NOTIFICATION_ID,
+ buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED));
+ }
+ }
+
+ private void resetTaskAndStop() {
+ mInstallTask = null;
+
+ stopForeground(true);
+
+ // stop self, but this service is not destroyed yet if it's still bound
+ stopSelf();
+ }
+
+ private void prepareNotification() {
+ NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+ getString(R.string.notification_channel_name),
+ NotificationManager.IMPORTANCE_LOW);
+
+ mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+
+ if (mNM != null) {
+ mNM.createNotificationChannel(chan);
+ }
+ }
+
+ private PendingIntent createPendingIntent(String action) {
+ Intent intent = new Intent(this, DynamicSystemInstallationService.class);
+ intent.setAction(action);
+ return PendingIntent.getService(this, 0, intent, 0);
+ }
+
+ private Notification buildNotification(int status, int cause) {
+ Notification.Builder builder = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_system_update_googblue_24dp)
+ .setProgress(0, 0, false);
+
+ switch (status) {
+ case STATUS_IN_PROGRESS:
+ builder.setContentText(getString(R.string.notification_install_inprogress));
+
+ int max = (int) Math.max((mSystemSize + mUserdataSize) >> 20, 1);
+ int progress = (int) (mInstalledSize >> 20);
+
+ builder.setProgress(max, progress, false);
+
+ builder.addAction(new Notification.Action.Builder(
+ null, getString(R.string.notification_action_cancel),
+ createPendingIntent(ACTION_CANCEL_INSTALL)).build());
+
+ break;
+
+ case STATUS_READY:
+ builder.setContentText(getString(R.string.notification_install_completed));
+
+ builder.addAction(new Notification.Action.Builder(
+ null, getString(R.string.notification_action_reboot_to_dynsystem),
+ createPendingIntent(ACTION_REBOOT_TO_DYN_SYSTEM)).build());
+
+ builder.addAction(new Notification.Action.Builder(
+ null, getString(R.string.notification_action_discard),
+ createPendingIntent(ACTION_DISCARD_INSTALL)).build());
+
+ break;
+
+ case STATUS_IN_USE:
+ builder.setContentText(getString(R.string.notification_dynsystem_in_use));
+
+ builder.addAction(new Notification.Action.Builder(
+ null, getString(R.string.notification_action_uninstall),
+ createPendingIntent(ACTION_REBOOT_TO_NORMAL)).build());
+
+ break;
+
+ case STATUS_NOT_STARTED:
+ if (cause != CAUSE_NOT_SPECIFIED && cause != CAUSE_INSTALL_CANCELLED) {
+ builder.setContentText(getString(R.string.notification_install_failed));
+ } else {
+ // no need to notify the user if the task is not started, or cancelled.
+ }
+ break;
+
+ default:
+ throw new IllegalStateException("status is invalid");
+ }
+
+ return builder.build();
+ }
+
+ private boolean verifyRequest(Intent intent) {
+ String url = intent.getStringExtra(DynamicSystemClient.KEY_SYSTEM_URL);
+
+ return VerificationActivity.isVerified(url);
+ }
+
+ private void postStatus(int status, int cause) {
+ Log.d(TAG, "postStatus(): statusCode=" + status + ", causeCode=" + cause);
+
+ boolean notifyOnNotificationBar = true;
+
+ if (status == STATUS_NOT_STARTED
+ && cause == CAUSE_INSTALL_CANCELLED
+ && mJustCancelledByUser) {
+ // if task is cancelled by user, do not notify them
+ notifyOnNotificationBar = false;
+ mJustCancelledByUser = false;
+ }
+
+ if (notifyOnNotificationBar) {
+ mNM.notify(NOTIFICATION_ID, buildNotification(status, cause));
+ }
+
+ for (int i = mClients.size() - 1; i >= 0; i--) {
+ try {
+ notifyOneClient(mClients.get(i), status, cause);
+ } catch (RemoteException e) {
+ mClients.remove(i);
+ }
+ }
+ }
+
+ private void notifyOneClient(Messenger client, int status, int cause) throws RemoteException {
+ Bundle bundle = new Bundle();
+
+ bundle.putLong(DynamicSystemClient.KEY_INSTALLED_SIZE, mInstalledSize);
+
+ client.send(Message.obtain(null,
+ DynamicSystemClient.MSG_POST_STATUS, status, cause, bundle));
+ }
+
+ private int getStatus() {
+ if (isInDynamicSystem()) {
+ return STATUS_IN_USE;
+ } else if (isDynamicSystemInstalled()) {
+ return STATUS_READY;
+ } else if (mInstallTask == null) {
+ return STATUS_NOT_STARTED;
+ }
+
+ switch (mInstallTask.getStatus()) {
+ case PENDING:
+ return STATUS_NOT_STARTED;
+
+ case RUNNING:
+ return STATUS_IN_PROGRESS;
+
+ case FINISHED:
+ int result = mInstallTask.getResult();
+
+ if (result == RESULT_OK) {
+ return STATUS_READY;
+ } else {
+ throw new IllegalStateException("A failed InstallationTask is not reset");
+ }
+
+ default:
+ return STATUS_NOT_STARTED;
+ }
+ }
+
+ private boolean isInDynamicSystem() {
+ return mDynSystem.isInUse();
+ }
+
+ private boolean isDynamicSystemInstalled() {
+ return mDynSystem.isInstalled();
+ }
+
+ void handleMessage(Message msg) {
+ switch (msg.what) {
+ case DynamicSystemClient.MSG_REGISTER_LISTENER:
+ try {
+ Messenger client = msg.replyTo;
+
+ int status = getStatus();
+
+ // tell just registered client my status, but do not specify cause
+ notifyOneClient(client, status, CAUSE_NOT_SPECIFIED);
+
+ mClients.add(client);
+ } catch (RemoteException e) {
+ // do nothing if we cannot send update to the client
+ e.printStackTrace();
+ }
+
+ break;
+ case DynamicSystemClient.MSG_UNREGISTER_LISTENER:
+ mClients.remove(msg.replyTo);
+ break;
+ default:
+ // do nothing
+ }
+ }
+}
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java
new file mode 100644
index 000000000000..052fc0a109b3
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/InstallationAsyncTask.java
@@ -0,0 +1,235 @@
+/*
+ * 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.gsi.GsiProgress;
+import android.os.AsyncTask;
+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.Arrays;
+import java.util.Locale;
+import java.util.zip.GZIPInputStream;
+
+
+class InstallationAsyncTask extends AsyncTask<String, Long, Integer> {
+
+ private static final String TAG = "InstallationAsyncTask";
+
+ private static final int READ_BUFFER_SIZE = 1 << 19;
+
+ 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);
+ void onCancelled();
+ }
+
+ private final String mUrl;
+ private final long mSystemSize;
+ private final long mUserdataSize;
+ 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,
+ DynamicSystemManager dynSystem, InstallStatusListener listener) {
+ mUrl = url;
+ mSystemSize = systemSize;
+ mUserdataSize = userdataSize;
+ mDynSystem = dynSystem;
+ mListener = listener;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ mListener.onProgressUpdate(0);
+ }
+
+ @Override
+ protected Integer 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 RESULT_OK;
+ }
+
+ GsiProgress progress = mDynSystem.getInstallationProgress();
+ installedSize = progress.bytes_processed;
+
+ if (installedSize > reportedInstalledSize + minStepToReport) {
+ publishProgress(installedSize);
+ reportedInstalledSize = installedSize;
+ }
+
+ Thread.sleep(10);
+ }
+
+
+ if (mInstallationSession == null) {
+ Log.e(TAG, "Failed to start installation with requested size: "
+ + (mSystemSize + mUserdataSize));
+
+ return RESULT_ERROR_IO;
+ }
+
+ installedSize = mUserdataSize;
+
+ byte[] bytes = new byte[READ_BUFFER_SIZE];
+
+ int numBytesRead;
+
+ Log.d(TAG, "Start installation loop");
+ while ((numBytesRead = mStream.read(bytes, 0, READ_BUFFER_SIZE)) != -1) {
+ if (isCancelled()) {
+ break;
+ }
+
+ byte[] writeBuffer = numBytesRead == READ_BUFFER_SIZE
+ ? bytes : Arrays.copyOf(bytes, numBytesRead);
+
+ if (!mInstallationSession.write(writeBuffer)) {
+ throw new IOException("Failed write() to DynamicSystem");
+ }
+
+ installedSize += numBytesRead;
+
+ if (installedSize > reportedInstalledSize + minStepToReport) {
+ publishProgress(installedSize);
+ reportedInstalledSize = installedSize;
+ }
+ }
+
+ return RESULT_OK;
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ return RESULT_ERROR_IO;
+
+ } catch (InvalidImageUrlException e) {
+ e.printStackTrace();
+ return RESULT_ERROR_INVALID_URL;
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ return RESULT_ERROR_EXCEPTION;
+
+ } finally {
+ close();
+ }
+ }
+
+ @Override
+ protected void onCancelled() {
+ Log.d(TAG, "onCancelled(), URL: " + mUrl);
+
+ close();
+
+ mListener.onCancelled();
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ Log.d(TAG, "onPostExecute(), URL: " + mUrl + ", result: " + result);
+
+ close();
+
+ mResult = result;
+ mListener.onResult(mResult);
+ }
+
+ @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 {
+ 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();
+ }
+}
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynandroid/VerificationActivity.java b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/VerificationActivity.java
new file mode 100644
index 000000000000..f05930f8ec07
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/VerificationActivity.java
@@ -0,0 +1,104 @@
+/*
+ * 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 static android.os.image.DynamicSystemClient.KEY_SYSTEM_SIZE;
+import static android.os.image.DynamicSystemClient.KEY_SYSTEM_URL;
+import static android.os.image.DynamicSystemClient.KEY_USERDATA_SIZE;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.image.DynamicSystemClient;
+import android.util.Log;
+
+
+/**
+ * This Activity starts KeyguardManager and ask the user to confirm
+ * before any installation request. If the device is not protected by
+ * a password, it approves the request by default.
+ */
+public class VerificationActivity extends Activity {
+
+ private static final String TAG = "VerificationActivity";
+
+ private static final int REQUEST_CODE = 1;
+
+ // For install request verification
+ private static String sVerifiedUrl;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
+
+ if (km != null) {
+ String title = getString(R.string.keyguard_title);
+ String description = getString(R.string.keyguard_description);
+ Intent intent = km.createConfirmDeviceCredentialIntent(title, description);
+
+ if (intent == null) {
+ Log.d(TAG, "This device is not protected by a password/pin");
+ startInstallationService();
+ finish();
+ } else {
+ startActivityForResult(intent, REQUEST_CODE);
+ }
+ } else {
+ finish();
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
+ startInstallationService();
+ }
+
+ finish();
+ }
+
+ private void startInstallationService() {
+ // retrieve data from calling intent
+ Intent callingIntent = getIntent();
+
+ String url = callingIntent.getStringExtra(KEY_SYSTEM_URL);
+ long systemSize = callingIntent.getLongExtra(KEY_SYSTEM_SIZE, 0);
+ long userdataSize = callingIntent.getLongExtra(KEY_USERDATA_SIZE, 0);
+
+ sVerifiedUrl = url;
+
+ // start service
+ Intent intent = new Intent(this, DynamicSystemInstallationService.class);
+ intent.setAction(DynamicSystemClient.ACTION_START_INSTALL);
+ intent.putExtra(KEY_SYSTEM_URL, url);
+ intent.putExtra(KEY_SYSTEM_SIZE, systemSize);
+ intent.putExtra(KEY_USERDATA_SIZE, userdataSize);
+
+ Log.d(TAG, "Starting Installation Service");
+ startServiceAsUser(intent, UserHandle.SYSTEM);
+ }
+
+ static boolean isVerified(String url) {
+ return sVerifiedUrl != null && sVerifiedUrl.equals(url);
+ }
+}