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