diff options
author | Po-Chien Hsueh <pchsueh@google.com> | 2019-03-07 11:57:17 +0800 |
---|---|---|
committer | Po-Chien Hsueh <pchsueh@google.com> | 2019-03-21 17:25:59 +0800 |
commit | 4e908c24ca4a8b751546125548686069c045ffd7 (patch) | |
tree | 9ffbe8e79d082feca9f953cd187bf3d0d4620183 /packages/DynamicSystemInstallationService/src | |
parent | 16da0e59ff1ca5b4d499de139ec4284097fc21e5 (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')
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); + } +} |