1 /* 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 package android.os.image; 17 18 import android.annotation.BytesLong; 19 import android.annotation.CallbackExecutor; 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.RequiresPermission; 24 import android.annotation.SystemApi; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.ServiceConnection; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.IBinder; 33 import android.os.Looper; 34 import android.os.Message; 35 import android.os.Messenger; 36 import android.os.ParcelableException; 37 import android.os.RemoteException; 38 import android.util.Slog; 39 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 import java.lang.ref.WeakReference; 43 import java.util.concurrent.Executor; 44 45 /** 46 * <p>This class contains methods and constants used to start a {@code DynamicSystem} installation, 47 * and a listener for status updates.</p> 48 * 49 * <p>{@code DynamicSystem} allows users to run certified system images in a non destructive manner 50 * without needing to prior OEM unlock. It creates a temporary system partition to install the new 51 * system image, and a temporary data partition for the newly installed system to run with.</p> 52 * 53 * After the installation is completed, the device will be running in the new system on next the 54 * reboot. Then, when the user reboots the device again, it will leave {@code DynamicSystem} and go 55 * back to the original system. While running in {@code DynamicSystem}, persitent storage for 56 * factory reset protection (FRP) remains unchanged. Since the user is running the new system with 57 * a temporarily created data partition, their original user data are kept unchanged.</p> 58 * 59 * <p>With {@link #setOnStatusChangedListener}, API users can register an 60 * {@link #OnStatusChangedListener} to get status updates and their causes when the installation is 61 * started, stopped, or cancelled. It also sends progress updates during the installation. With 62 * {@link #start}, API users can start an installation with the {@link Uri} to a unsparsed and 63 * gzipped system image. The {@link Uri} can be a web URL or a content Uri to a local path.</p> 64 * 65 * @hide 66 */ 67 @SystemApi 68 public class DynamicSystemClient { 69 private static final String TAG = "DynamicSystemClient"; 70 71 /** @hide */ 72 @IntDef(prefix = { "STATUS_" }, value = { 73 STATUS_UNKNOWN, 74 STATUS_NOT_STARTED, 75 STATUS_IN_PROGRESS, 76 STATUS_READY, 77 STATUS_IN_USE, 78 }) 79 @Retention(RetentionPolicy.SOURCE) 80 public @interface InstallationStatus {} 81 82 /** @hide */ 83 @IntDef(prefix = { "CAUSE_" }, value = { 84 CAUSE_NOT_SPECIFIED, 85 CAUSE_INSTALL_COMPLETED, 86 CAUSE_INSTALL_CANCELLED, 87 CAUSE_ERROR_IO, 88 CAUSE_ERROR_INVALID_URL, 89 CAUSE_ERROR_IPC, 90 CAUSE_ERROR_EXCEPTION, 91 }) 92 @Retention(RetentionPolicy.SOURCE) 93 public @interface StatusChangedCause {} 94 95 /** Listener for installation status updates. */ 96 public interface OnStatusChangedListener { 97 /** 98 * This callback is called when installation status is changed, and when the 99 * client is {@link #bind} to {@code DynamicSystem} installation service. 100 * 101 * @param status status code, also defined in {@code DynamicSystemClient}. 102 * @param cause cause code, also defined in {@code DynamicSystemClient}. 103 * @param progress number of bytes installed. 104 * @param detail additional detail about the error if available, otherwise null. 105 */ onStatusChanged(@nstallationStatus int status, @StatusChangedCause int cause, @BytesLong long progress, @Nullable Throwable detail)106 void onStatusChanged(@InstallationStatus int status, @StatusChangedCause int cause, 107 @BytesLong long progress, @Nullable Throwable detail); 108 } 109 110 /* 111 * Status codes 112 */ 113 /** We are bound to installation service, but failed to get its status */ 114 public static final int STATUS_UNKNOWN = 0; 115 116 /** Installation is not started yet. */ 117 public static final int STATUS_NOT_STARTED = 1; 118 119 /** Installation is in progress. */ 120 public static final int STATUS_IN_PROGRESS = 2; 121 122 /** Installation is finished but the user has not launched it. */ 123 public static final int STATUS_READY = 3; 124 125 /** Device is running in {@code DynamicSystem}. */ 126 public static final int STATUS_IN_USE = 4; 127 128 /* 129 * Causes 130 */ 131 /** Cause is not specified. This means the status is not changed. */ 132 public static final int CAUSE_NOT_SPECIFIED = 0; 133 134 /** Status changed because installation is completed. */ 135 public static final int CAUSE_INSTALL_COMPLETED = 1; 136 137 /** Status changed because installation is cancelled. */ 138 public static final int CAUSE_INSTALL_CANCELLED = 2; 139 140 /** Installation failed due to {@code IOException}. */ 141 public static final int CAUSE_ERROR_IO = 3; 142 143 /** Installation failed because the image URL source is not supported. */ 144 public static final int CAUSE_ERROR_INVALID_URL = 4; 145 146 /** Installation failed due to IPC error. */ 147 public static final int CAUSE_ERROR_IPC = 5; 148 149 /** Installation failed due to unhandled exception. */ 150 public static final int CAUSE_ERROR_EXCEPTION = 6; 151 152 /* 153 * IPC Messages 154 */ 155 /** 156 * Message to register listener. 157 * @hide 158 */ 159 public static final int MSG_REGISTER_LISTENER = 1; 160 161 /** 162 * Message to unregister listener. 163 * @hide 164 */ 165 public static final int MSG_UNREGISTER_LISTENER = 2; 166 167 /** 168 * Message for status updates. 169 * @hide 170 */ 171 public static final int MSG_POST_STATUS = 3; 172 173 /* 174 * Messages keys 175 */ 176 /** 177 * Message key, for progress updates. 178 * @hide 179 */ 180 public static final String KEY_INSTALLED_SIZE = "KEY_INSTALLED_SIZE"; 181 182 /** 183 * Message key, used when the service is sending exception detail to the client. 184 * @hide 185 */ 186 public static final String KEY_EXCEPTION_DETAIL = "KEY_EXCEPTION_DETAIL"; 187 188 /* 189 * Intent Actions 190 */ 191 /** 192 * Intent action: start installation. 193 * @hide 194 */ 195 public static final String ACTION_START_INSTALL = 196 "android.os.image.action.START_INSTALL"; 197 198 /** 199 * Intent action: notify user if we are currently running in {@code DynamicSystem}. 200 * @hide 201 */ 202 public static final String ACTION_NOTIFY_IF_IN_USE = 203 "android.os.image.action.NOTIFY_IF_IN_USE"; 204 205 /* 206 * Intent Keys 207 */ 208 /** 209 * Intent key: Size of the system image, in bytes. 210 * @hide 211 */ 212 public static final String KEY_SYSTEM_SIZE = "KEY_SYSTEM_SIZE"; 213 214 /** 215 * Intent key: Number of bytes to reserve for userdata. 216 * @hide 217 */ 218 public static final String KEY_USERDATA_SIZE = "KEY_USERDATA_SIZE"; 219 220 221 private static class IncomingHandler extends Handler { 222 private final WeakReference<DynamicSystemClient> mWeakClient; 223 IncomingHandler(DynamicSystemClient service)224 IncomingHandler(DynamicSystemClient service) { 225 super(Looper.getMainLooper()); 226 mWeakClient = new WeakReference<>(service); 227 } 228 229 @Override handleMessage(Message msg)230 public void handleMessage(Message msg) { 231 DynamicSystemClient service = mWeakClient.get(); 232 233 if (service != null) { 234 service.handleMessage(msg); 235 } 236 } 237 } 238 239 private class DynSystemServiceConnection implements ServiceConnection { onServiceConnected(ComponentName className, IBinder service)240 public void onServiceConnected(ComponentName className, IBinder service) { 241 Slog.v(TAG, "onServiceConnected: " + className); 242 243 mService = new Messenger(service); 244 245 try { 246 Message msg = Message.obtain(null, MSG_REGISTER_LISTENER); 247 msg.replyTo = mMessenger; 248 249 mService.send(msg); 250 } catch (RemoteException e) { 251 Slog.e(TAG, "Unable to get status from installation service"); 252 notifyOnStatusChangedListener(STATUS_UNKNOWN, CAUSE_ERROR_IPC, 0, e); 253 } 254 } 255 onServiceDisconnected(ComponentName className)256 public void onServiceDisconnected(ComponentName className) { 257 Slog.v(TAG, "onServiceDisconnected: " + className); 258 mService = null; 259 } 260 } 261 262 private final Context mContext; 263 private final DynSystemServiceConnection mConnection; 264 private final Messenger mMessenger; 265 266 private boolean mBound; 267 private Executor mExecutor; 268 private OnStatusChangedListener mListener; 269 private Messenger mService; 270 271 /** 272 * Create a new {@code DynamicSystem} client. 273 * 274 * @param context a {@link Context} will be used to bind the installation service. 275 * 276 * @hide 277 */ 278 @SystemApi DynamicSystemClient(@onNull Context context)279 public DynamicSystemClient(@NonNull Context context) { 280 mContext = context; 281 mConnection = new DynSystemServiceConnection(); 282 mMessenger = new Messenger(new IncomingHandler(this)); 283 } 284 285 /** 286 * This method register a listener for status change. The listener is called using 287 * the executor. 288 */ setOnStatusChangedListener( @onNull @allbackExecutor Executor executor, @NonNull OnStatusChangedListener listener)289 public void setOnStatusChangedListener( 290 @NonNull @CallbackExecutor Executor executor, 291 @NonNull OnStatusChangedListener listener) { 292 mListener = listener; 293 mExecutor = executor; 294 } 295 296 /** 297 * This method register a listener for status change. The listener is called in main 298 * thread. 299 */ setOnStatusChangedListener( @onNull OnStatusChangedListener listener)300 public void setOnStatusChangedListener( 301 @NonNull OnStatusChangedListener listener) { 302 mListener = listener; 303 mExecutor = null; 304 } 305 notifyOnStatusChangedListener( int status, int cause, long progress, Throwable detail)306 private void notifyOnStatusChangedListener( 307 int status, int cause, long progress, Throwable detail) { 308 if (mListener != null) { 309 if (mExecutor != null) { 310 mExecutor.execute( 311 () -> { 312 mListener.onStatusChanged(status, cause, progress, detail); 313 }); 314 } else { 315 mListener.onStatusChanged(status, cause, progress, detail); 316 } 317 } 318 } 319 320 /** 321 * Bind to {@code DynamicSystem} installation service. Binding to the installation service 322 * allows it to send status updates to {@link #OnStatusChangedListener}. It is recommanded 323 * to bind before calling {@link #start} and get status updates. 324 * @hide 325 */ 326 @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM) 327 @SystemApi bind()328 public void bind() { 329 Intent intent = new Intent(); 330 intent.setClassName("com.android.dynsystem", 331 "com.android.dynsystem.DynamicSystemInstallationService"); 332 333 mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 334 335 mBound = true; 336 } 337 338 /** 339 * Unbind from {@code DynamicSystem} installation service. Unbinding from the installation 340 * service stops it from sending following status updates. 341 * @hide 342 */ 343 @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM) 344 @SystemApi unbind()345 public void unbind() { 346 if (!mBound) { 347 return; 348 } 349 350 if (mService != null) { 351 try { 352 Message msg = Message.obtain(null, MSG_UNREGISTER_LISTENER); 353 msg.replyTo = mMessenger; 354 mService.send(msg); 355 } catch (RemoteException e) { 356 Slog.e(TAG, "Unable to unregister from installation service"); 357 } 358 } 359 360 // Detach our existing connection. 361 mContext.unbindService(mConnection); 362 363 mBound = false; 364 } 365 366 /** 367 * Start installing {@code DynamicSystem} from URL with default userdata size. 368 * 369 * Calling this function will first start an Activity to confirm device credential, using 370 * {@link KeyguardManager}. If it's confirmed, the installation service will be started. 371 * 372 * This function doesn't require prior calling {@link #bind}. 373 * 374 * @param systemUrl a network Uri, a file Uri or a content Uri pointing to a system image file. 375 * @param systemSize size of system image. 376 * @hide 377 */ 378 @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM) 379 @SystemApi start(@onNull Uri systemUrl, @BytesLong long systemSize)380 public void start(@NonNull Uri systemUrl, @BytesLong long systemSize) { 381 start(systemUrl, systemSize, 0 /* Use the default userdata size */); 382 } 383 384 /** 385 * Start installing {@code DynamicSystem} from URL. 386 * 387 * Calling this function will first start an Activity to confirm device credential, using 388 * {@link KeyguardManager}. If it's confirmed, the installation service will be started. 389 * 390 * This function doesn't require prior calling {@link #bind}. 391 * 392 * @param systemUrl a network Uri, a file Uri or a content Uri pointing to a system image file. 393 * @param systemSize size of system image. 394 * @param userdataSize bytes reserved for userdata. 395 */ 396 @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM) start(@onNull Uri systemUrl, @BytesLong long systemSize, @BytesLong long userdataSize)397 public void start(@NonNull Uri systemUrl, @BytesLong long systemSize, 398 @BytesLong long userdataSize) { 399 Intent intent = new Intent(); 400 401 intent.setClassName("com.android.dynsystem", 402 "com.android.dynsystem.VerificationActivity"); 403 404 intent.setData(systemUrl); 405 intent.setAction(ACTION_START_INSTALL); 406 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 407 408 intent.putExtra(KEY_SYSTEM_SIZE, systemSize); 409 intent.putExtra(KEY_USERDATA_SIZE, userdataSize); 410 411 mContext.startActivity(intent); 412 } 413 handleMessage(Message msg)414 private void handleMessage(Message msg) { 415 switch (msg.what) { 416 case MSG_POST_STATUS: 417 int status = msg.arg1; 418 int cause = msg.arg2; 419 // obj is non-null 420 Bundle bundle = (Bundle) msg.obj; 421 long progress = bundle.getLong(KEY_INSTALLED_SIZE); 422 ParcelableException t = (ParcelableException) bundle.getSerializable( 423 KEY_EXCEPTION_DETAIL); 424 425 Throwable detail = t == null ? null : t.getCause(); 426 427 notifyOnStatusChangedListener(status, cause, progress, detail); 428 break; 429 default: 430 // do nothing 431 432 } 433 } 434 } 435