blob: ddcd63520549e5e3c6e6453b224fd80274956243 [file] [log] [blame]
Doug Zongker1af33d02010-01-05 11:28:55 -08001/*
2 * Copyright (C) 2010 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
17package android.os;
18
Tao Baoe8a403d2015-12-31 07:44:55 -080019import android.annotation.SystemApi;
Jason parks4ca74dc2011-03-14 15:23:31 -050020import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
Julia Reynoldsfe053802014-06-30 11:41:32 -040023import android.os.UserManager;
Jeff Sharkey004a4b22014-09-24 11:45:24 -070024import android.text.TextUtils;
Jason parks4ca74dc2011-03-14 15:23:31 -050025import android.util.Log;
26
Doug Zongker1af33d02010-01-05 11:28:55 -080027import java.io.ByteArrayInputStream;
28import java.io.File;
29import java.io.FileNotFoundException;
30import java.io.FileWriter;
31import java.io.IOException;
Doug Zongkere2d58e92011-11-04 13:39:47 -070032import java.io.InputStream;
Doug Zongker1af33d02010-01-05 11:28:55 -080033import java.io.RandomAccessFile;
34import java.security.GeneralSecurityException;
35import java.security.PublicKey;
36import java.security.Signature;
37import java.security.SignatureException;
Doug Zongker1af33d02010-01-05 11:28:55 -080038import java.security.cert.CertificateFactory;
39import java.security.cert.X509Certificate;
Doug Zongker1af33d02010-01-05 11:28:55 -080040import java.util.Enumeration;
41import java.util.HashSet;
42import java.util.Iterator;
43import java.util.List;
Doug Zongkere33b4002012-08-23 09:52:14 -070044import java.util.Locale;
Doug Zongker1af33d02010-01-05 11:28:55 -080045import java.util.zip.ZipEntry;
46import java.util.zip.ZipFile;
47
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +010048import sun.security.pkcs.PKCS7;
49import sun.security.pkcs.SignerInfo;
Doug Zongker1af33d02010-01-05 11:28:55 -080050
51/**
52 * RecoverySystem contains methods for interacting with the Android
53 * recovery system (the separate partition that can be used to install
54 * system updates, wipe user data, etc.)
Doug Zongker1af33d02010-01-05 11:28:55 -080055 */
56public class RecoverySystem {
57 private static final String TAG = "RecoverySystem";
58
59 /**
60 * Default location of zip file containing public keys (X509
61 * certs) authorized to sign OTA updates.
62 */
63 private static final File DEFAULT_KEYSTORE =
64 new File("/system/etc/security/otacerts.zip");
65
66 /** Send progress to listeners no more often than this (in ms). */
67 private static final long PUBLISH_PROGRESS_INTERVAL_MS = 500;
68
Tao Bao5065e122015-08-18 12:37:02 -070069 /** Used to communicate with recovery. See bootable/recovery/recovery.cpp. */
Tao Baoe8a403d2015-12-31 07:44:55 -080070 private static final File RECOVERY_DIR = new File("/cache/recovery");
71 private static final File LOG_FILE = new File(RECOVERY_DIR, "log");
72 private static final String LAST_PREFIX = "last_";
73
74 /**
75 * The recovery image uses this file to identify the location (i.e. blocks)
76 * of an OTA package on the /data partition. The block map file is
77 * generated by uncrypt.
78 *
79 * @hide
80 */
81 public static final File BLOCK_MAP_FILE = new File(RECOVERY_DIR, "block.map");
82
83 /**
84 * UNCRYPT_PACKAGE_FILE stores the filename to be uncrypt'd, which will be
85 * read by uncrypt.
86 *
87 * @hide
88 */
89 public static final File UNCRYPT_PACKAGE_FILE = new File(RECOVERY_DIR, "uncrypt_file");
Doug Zongker1af33d02010-01-05 11:28:55 -080090
91 // Length limits for reading files.
Tao Baoe8a403d2015-12-31 07:44:55 -080092 private static final int LOG_FILE_MAX_LENGTH = 64 * 1024;
93
94 // Prevent concurrent execution of requests.
95 private static final Object sRequestLock = new Object();
96
97 private final IRecoverySystem mService;
Doug Zongker1af33d02010-01-05 11:28:55 -080098
99 /**
100 * Interface definition for a callback to be invoked regularly as
101 * verification proceeds.
102 */
103 public interface ProgressListener {
104 /**
105 * Called periodically as the verification progresses.
106 *
107 * @param progress the approximate percentage of the
108 * verification that has been completed, ranging from 0
109 * to 100 (inclusive).
110 */
111 public void onProgress(int progress);
112 }
113
114 /** @return the set of certs that can be used to sign an OTA package. */
Kenny Root27e54942013-10-14 11:17:25 -0700115 private static HashSet<X509Certificate> getTrustedCerts(File keystore)
Doug Zongker1af33d02010-01-05 11:28:55 -0800116 throws IOException, GeneralSecurityException {
Kenny Root27e54942013-10-14 11:17:25 -0700117 HashSet<X509Certificate> trusted = new HashSet<X509Certificate>();
Doug Zongker1af33d02010-01-05 11:28:55 -0800118 if (keystore == null) {
119 keystore = DEFAULT_KEYSTORE;
120 }
121 ZipFile zip = new ZipFile(keystore);
122 try {
123 CertificateFactory cf = CertificateFactory.getInstance("X.509");
124 Enumeration<? extends ZipEntry> entries = zip.entries();
125 while (entries.hasMoreElements()) {
126 ZipEntry entry = entries.nextElement();
Doug Zongkere2d58e92011-11-04 13:39:47 -0700127 InputStream is = zip.getInputStream(entry);
128 try {
Kenny Root27e54942013-10-14 11:17:25 -0700129 trusted.add((X509Certificate) cf.generateCertificate(is));
Doug Zongkere2d58e92011-11-04 13:39:47 -0700130 } finally {
131 is.close();
132 }
Doug Zongker1af33d02010-01-05 11:28:55 -0800133 }
134 } finally {
135 zip.close();
136 }
137 return trusted;
138 }
139
140 /**
141 * Verify the cryptographic signature of a system update package
142 * before installing it. Note that the package is also verified
143 * separately by the installer once the device is rebooted into
144 * the recovery system. This function will return only if the
145 * package was successfully verified; otherwise it will throw an
146 * exception.
147 *
148 * Verification of a package can take significant time, so this
Doug Zongkercb956572010-03-11 10:27:21 -0800149 * function should not be called from a UI thread. Interrupting
150 * the thread while this function is in progress will result in a
151 * SecurityException being thrown (and the thread's interrupt flag
152 * will be cleared).
Doug Zongker1af33d02010-01-05 11:28:55 -0800153 *
154 * @param packageFile the package to be verified
155 * @param listener an object to receive periodic progress
156 * updates as verification proceeds. May be null.
157 * @param deviceCertsZipFile the zip file of certificates whose
158 * public keys we will accept. Verification succeeds if the
159 * package is signed by the private key corresponding to any
160 * public key in this file. May be null to use the system default
161 * file (currently "/system/etc/security/otacerts.zip").
162 *
163 * @throws IOException if there were any errors reading the
164 * package or certs files.
165 * @throws GeneralSecurityException if verification failed
166 */
167 public static void verifyPackage(File packageFile,
168 ProgressListener listener,
169 File deviceCertsZipFile)
170 throws IOException, GeneralSecurityException {
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100171 final long fileLen = packageFile.length();
Doug Zongker1af33d02010-01-05 11:28:55 -0800172
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100173 final RandomAccessFile raf = new RandomAccessFile(packageFile, "r");
Doug Zongker1af33d02010-01-05 11:28:55 -0800174 try {
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100175 final long startTimeMillis = System.currentTimeMillis();
Doug Zongker1af33d02010-01-05 11:28:55 -0800176 if (listener != null) {
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100177 listener.onProgress(0);
Doug Zongker1af33d02010-01-05 11:28:55 -0800178 }
179
180 raf.seek(fileLen - 6);
181 byte[] footer = new byte[6];
182 raf.readFully(footer);
183
184 if (footer[2] != (byte)0xff || footer[3] != (byte)0xff) {
185 throw new SignatureException("no signature in file (no footer)");
186 }
187
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100188 final int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8);
189 final int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8);
Doug Zongker1af33d02010-01-05 11:28:55 -0800190
191 byte[] eocd = new byte[commentSize + 22];
192 raf.seek(fileLen - (commentSize + 22));
193 raf.readFully(eocd);
194
195 // Check that we have found the start of the
196 // end-of-central-directory record.
197 if (eocd[0] != (byte)0x50 || eocd[1] != (byte)0x4b ||
198 eocd[2] != (byte)0x05 || eocd[3] != (byte)0x06) {
199 throw new SignatureException("no signature in file (bad footer)");
200 }
201
202 for (int i = 4; i < eocd.length-3; ++i) {
203 if (eocd[i ] == (byte)0x50 && eocd[i+1] == (byte)0x4b &&
204 eocd[i+2] == (byte)0x05 && eocd[i+3] == (byte)0x06) {
205 throw new SignatureException("EOCD marker found after start of EOCD");
206 }
207 }
208
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100209 // Parse the signature
210 PKCS7 block =
211 new PKCS7(new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart));
Doug Zongker1af33d02010-01-05 11:28:55 -0800212
Doug Zongker1af33d02010-01-05 11:28:55 -0800213 // Take the first certificate from the signature (packages
214 // should contain only one).
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100215 X509Certificate[] certificates = block.getCertificates();
216 if (certificates == null || certificates.length == 0) {
Doug Zongker1af33d02010-01-05 11:28:55 -0800217 throw new SignatureException("signature contains no certificates");
218 }
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100219 X509Certificate cert = certificates[0];
220 PublicKey signatureKey = cert.getPublicKey();
Doug Zongker1af33d02010-01-05 11:28:55 -0800221
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100222 SignerInfo[] signerInfos = block.getSignerInfos();
223 if (signerInfos == null || signerInfos.length == 0) {
224 throw new SignatureException("signature contains no signedData");
Doug Zongker1af33d02010-01-05 11:28:55 -0800225 }
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100226 SignerInfo signerInfo = signerInfos[0];
Doug Zongker1af33d02010-01-05 11:28:55 -0800227
228 // Check that the public key of the certificate contained
229 // in the package equals one of our trusted public keys.
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100230 boolean verified = false;
Kenny Root27e54942013-10-14 11:17:25 -0700231 HashSet<X509Certificate> trusted = getTrustedCerts(
Doug Zongker1af33d02010-01-05 11:28:55 -0800232 deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile);
Kenny Root27e54942013-10-14 11:17:25 -0700233 for (X509Certificate c : trusted) {
Doug Zongker1af33d02010-01-05 11:28:55 -0800234 if (c.getPublicKey().equals(signatureKey)) {
235 verified = true;
236 break;
237 }
238 }
239 if (!verified) {
240 throw new SignatureException("signature doesn't match any trusted key");
241 }
242
243 // The signature cert matches a trusted key. Now verify that
244 // the digest in the cert matches the actual file data.
Doug Zongker1af33d02010-01-05 11:28:55 -0800245 raf.seek(0);
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100246 final ProgressListener listenerForInner = listener;
247 SignerInfo verifyResult = block.verify(signerInfo, new InputStream() {
248 // The signature covers all of the OTA package except the
249 // archive comment and its 2-byte length.
250 long toRead = fileLen - commentSize - 2;
251 long soFar = 0;
Doug Zongker1af33d02010-01-05 11:28:55 -0800252
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100253 int lastPercent = 0;
254 long lastPublishTime = startTimeMillis;
255
256 @Override
257 public int read() throws IOException {
258 throw new UnsupportedOperationException();
Doug Zongker1af33d02010-01-05 11:28:55 -0800259 }
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100260
261 @Override
262 public int read(byte[] b, int off, int len) throws IOException {
263 if (soFar >= toRead) {
264 return -1;
265 }
266 if (Thread.currentThread().isInterrupted()) {
267 return -1;
268 }
269
270 int size = len;
271 if (soFar + size > toRead) {
272 size = (int)(toRead - soFar);
273 }
274 int read = raf.read(b, off, size);
275 soFar += read;
276
277 if (listenerForInner != null) {
278 long now = System.currentTimeMillis();
279 int p = (int)(soFar * 100 / toRead);
280 if (p > lastPercent &&
281 now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
282 lastPercent = p;
283 lastPublishTime = now;
284 listenerForInner.onProgress(lastPercent);
285 }
286 }
287
288 return read;
289 }
290 });
291
292 final boolean interrupted = Thread.interrupted();
Doug Zongker1af33d02010-01-05 11:28:55 -0800293 if (listener != null) {
294 listener.onProgress(100);
295 }
296
Doug Zongkercb956572010-03-11 10:27:21 -0800297 if (interrupted) {
298 throw new SignatureException("verification was interrupted");
299 }
300
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100301 if (verifyResult == null) {
Doug Zongker1af33d02010-01-05 11:28:55 -0800302 throw new SignatureException("signature digest verification failed");
303 }
304 } finally {
305 raf.close();
306 }
307 }
308
309 /**
Tao Baoe8a403d2015-12-31 07:44:55 -0800310 * Process a given package with uncrypt. No-op if the package is not on the
311 * /data partition.
312 *
313 * @param Context the Context to use
314 * @param packageFile the package to be processed
315 * @param listener an object to receive periodic progress updates as
316 * processing proceeds. May be null.
317 * @param handler the Handler upon which the callbacks will be
318 * executed.
319 *
320 * @throws IOException if there were any errors processing the package file.
321 *
322 * @hide
323 */
324 @SystemApi
325 public static void processPackage(Context context,
326 File packageFile,
327 final ProgressListener listener,
328 final Handler handler)
329 throws IOException {
330 String filename = packageFile.getCanonicalPath();
331 if (!filename.startsWith("/data/")) {
332 return;
333 }
334
335 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
336 IRecoverySystemProgressListener progressListener = null;
337 if (listener != null) {
338 final Handler progressHandler;
339 if (handler != null) {
340 progressHandler = handler;
341 } else {
342 progressHandler = new Handler(context.getMainLooper());
343 }
344 progressListener = new IRecoverySystemProgressListener.Stub() {
345 int lastProgress = 0;
346 long lastPublishTime = System.currentTimeMillis();
347
348 @Override
349 public void onProgress(final int progress) {
350 final long now = System.currentTimeMillis();
351 progressHandler.post(new Runnable() {
352 @Override
353 public void run() {
354 if (progress > lastProgress &&
355 now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
356 lastProgress = progress;
357 lastPublishTime = now;
358 listener.onProgress(progress);
359 }
360 }
361 });
362 }
363 };
364 }
365
366 if (!rs.uncrypt(filename, progressListener)) {
367 throw new IOException("process package failed");
368 }
369 }
370
371 /**
372 * Process a given package with uncrypt. No-op if the package is not on the
373 * /data partition.
374 *
375 * @param Context the Context to use
376 * @param packageFile the package to be processed
377 * @param listener an object to receive periodic progress updates as
378 * processing proceeds. May be null.
379 *
380 * @throws IOException if there were any errors processing the package file.
381 *
382 * @hide
383 */
384 @SystemApi
385 public static void processPackage(Context context,
386 File packageFile,
387 final ProgressListener listener)
388 throws IOException {
389 processPackage(context, packageFile, listener, null);
390 }
391
392 /**
Doug Zongker1af33d02010-01-05 11:28:55 -0800393 * Reboots the device in order to install the given update
394 * package.
Jeff Brown64010e82010-04-08 16:52:18 -0700395 * Requires the {@link android.Manifest.permission#REBOOT} permission.
Doug Zongker1af33d02010-01-05 11:28:55 -0800396 *
397 * @param context the Context to use
Doug Zongker4baf6412010-09-20 13:09:57 -0700398 * @param packageFile the update package to install. Must be on
399 * a partition mountable by recovery. (The set of partitions
400 * known to recovery may vary from device to device. Generally,
401 * /cache and /data are safe.)
Doug Zongker1af33d02010-01-05 11:28:55 -0800402 *
403 * @throws IOException if writing the recovery command file
404 * fails, or if the reboot itself fails.
405 */
406 public static void installPackage(Context context, File packageFile)
Tao Baoe8a403d2015-12-31 07:44:55 -0800407 throws IOException {
408 installPackage(context, packageFile, false);
409 }
410
411 /**
412 * If the package hasn't been processed (i.e. uncrypt'd), set up
413 * UNCRYPT_PACKAGE_FILE and delete BLOCK_MAP_FILE to trigger uncrypt during the
414 * reboot.
415 *
416 * @param context the Context to use
417 * @param packageFile the update package to install. Must be on a
418 * partition mountable by recovery.
419 * @param processed if the package has been processed (uncrypt'd).
420 *
421 * @throws IOException if writing the recovery command file fails, or if
422 * the reboot itself fails.
423 *
424 * @hide
425 */
426 @SystemApi
427 public static void installPackage(Context context, File packageFile, boolean processed)
428 throws IOException {
429 synchronized (sRequestLock) {
430 LOG_FILE.delete();
431 // Must delete the file in case it was created by system server.
432 UNCRYPT_PACKAGE_FILE.delete();
433
434 String filename = packageFile.getCanonicalPath();
435 Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
436
437 if (!processed && filename.startsWith("/data/")) {
438 FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE);
439 try {
440 uncryptFile.write(filename + "\n");
441 } finally {
442 uncryptFile.close();
443 }
444 // UNCRYPT_PACKAGE_FILE needs to be readable and writable by system server.
445 if (!UNCRYPT_PACKAGE_FILE.setReadable(true, false)
446 || !UNCRYPT_PACKAGE_FILE.setWritable(true, false)) {
447 Log.e(TAG, "Error setting permission for " + UNCRYPT_PACKAGE_FILE);
448 }
449
450 BLOCK_MAP_FILE.delete();
451 }
452
453 // If the package is on the /data partition, use the block map file as
454 // the package name instead.
455 if (filename.startsWith("/data/")) {
456 filename = "@/cache/recovery/block.map";
457 }
458
459 final String filenameArg = "--update_package=" + filename + "\n";
460 final String localeArg = "--locale=" + Locale.getDefault().toString() + "\n";
461 final String command = filenameArg + localeArg;
462
463 RecoverySystem rs = (RecoverySystem) context.getSystemService(
464 Context.RECOVERY_SERVICE);
465 if (!rs.setupBcb(command)) {
466 throw new IOException("Setup BCB failed");
467 }
468
469 // Having set up the BCB (bootloader control block), go ahead and reboot
470 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
471 pm.reboot(PowerManager.REBOOT_RECOVERY_UPDATE);
472
473 throw new IOException("Reboot failed (no permissions?)");
474 }
475 }
476
477 /**
478 * Schedule to install the given package on next boot. The caller needs to
479 * ensure that the package must have been processed (uncrypt'd) if needed.
480 * It sets up the command in BCB (bootloader control block), which will
481 * be read by the bootloader and the recovery image.
482 *
483 * @param Context the Context to use.
484 * @param packageFile the package to be installed.
485 *
486 * @throws IOException if there were any errors setting up the BCB.
487 *
488 * @hide
489 */
490 @SystemApi
491 public static void scheduleUpdateOnBoot(Context context, File packageFile)
492 throws IOException {
Doug Zongker1af33d02010-01-05 11:28:55 -0800493 String filename = packageFile.getCanonicalPath();
Tao Bao90237f72015-05-21 16:25:19 -0700494
Tao Baoe8a403d2015-12-31 07:44:55 -0800495 // If the package is on the /data partition, use the block map file as
496 // the package name instead.
Tao Bao90237f72015-05-21 16:25:19 -0700497 if (filename.startsWith("/data/")) {
498 filename = "@/cache/recovery/block.map";
499 }
500
Tao Baoe8a403d2015-12-31 07:44:55 -0800501 final String filenameArg = "--update_package=" + filename + "\n";
502 final String localeArg = "--locale=" + Locale.getDefault().toString() + "\n";
503 final String command = filenameArg + localeArg;
504
505 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
506 if (!rs.setupBcb(command)) {
507 throw new IOException("schedule update on boot failed");
508 }
509 }
510
511 /**
512 * Cancel any scheduled update by clearing up the BCB (bootloader control
513 * block).
514 *
515 * @param Context the Context to use.
516 *
517 * @throws IOException if there were any errors clearing up the BCB.
518 *
519 * @hide
520 */
521 @SystemApi
522 public static void cancelScheduledUpdate(Context context)
523 throws IOException {
524 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
525 if (!rs.clearBcb()) {
526 throw new IOException("cancel scheduled update failed");
527 }
Doug Zongker1af33d02010-01-05 11:28:55 -0800528 }
529
530 /**
Doug Zongkercdf00882014-03-18 12:52:04 -0700531 * Reboots the device and wipes the user data and cache
532 * partitions. This is sometimes called a "factory reset", which
533 * is something of a misnomer because the system partition is not
534 * restored to its factory state. Requires the
535 * {@link android.Manifest.permission#REBOOT} permission.
Doug Zongker1af33d02010-01-05 11:28:55 -0800536 *
537 * @param context the Context to use
538 *
539 * @throws IOException if writing the recovery command file
540 * fails, or if the reboot itself fails.
Julia Reynoldsfe053802014-06-30 11:41:32 -0400541 * @throws SecurityException if the current user is not allowed to wipe data.
Doug Zongker1af33d02010-01-05 11:28:55 -0800542 */
Jason parks4ca74dc2011-03-14 15:23:31 -0500543 public static void rebootWipeUserData(Context context) throws IOException {
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700544 rebootWipeUserData(context, false, context.getPackageName());
545 }
546
547 /** {@hide} */
548 public static void rebootWipeUserData(Context context, String reason) throws IOException {
549 rebootWipeUserData(context, false, reason);
550 }
551
552 /** {@hide} */
553 public static void rebootWipeUserData(Context context, boolean shutdown)
554 throws IOException {
555 rebootWipeUserData(context, shutdown, context.getPackageName());
Doug Zongkercdf00882014-03-18 12:52:04 -0700556 }
557
558 /**
559 * Reboots the device and wipes the user data and cache
560 * partitions. This is sometimes called a "factory reset", which
561 * is something of a misnomer because the system partition is not
562 * restored to its factory state. Requires the
563 * {@link android.Manifest.permission#REBOOT} permission.
564 *
565 * @param context the Context to use
566 * @param shutdown if true, the device will be powered down after
567 * the wipe completes, rather than being rebooted
568 * back to the regular system.
569 *
570 * @throws IOException if writing the recovery command file
571 * fails, or if the reboot itself fails.
Julia Reynoldsfe053802014-06-30 11:41:32 -0400572 * @throws SecurityException if the current user is not allowed to wipe data.
Doug Zongkercdf00882014-03-18 12:52:04 -0700573 *
574 * @hide
575 */
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700576 public static void rebootWipeUserData(Context context, boolean shutdown, String reason)
577 throws IOException {
Julia Reynoldsfe053802014-06-30 11:41:32 -0400578 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
579 if (um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) {
580 throw new SecurityException("Wiping data is not allowed for this user.");
581 }
Jason parks4ca74dc2011-03-14 15:23:31 -0500582 final ConditionVariable condition = new ConditionVariable();
583
584 Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION");
Christopher Tatee27ae552014-04-24 15:13:13 -0700585 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700586 context.sendOrderedBroadcastAsUser(intent, UserHandle.SYSTEM,
Dianne Hackborn5ac72a22012-08-29 18:32:08 -0700587 android.Manifest.permission.MASTER_CLEAR,
Jason parks4ca74dc2011-03-14 15:23:31 -0500588 new BroadcastReceiver() {
589 @Override
590 public void onReceive(Context context, Intent intent) {
591 condition.open();
592 }
593 }, null, 0, null, null);
594
595 // Block until the ordered broadcast has completed.
596 condition.block();
597
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700598 String shutdownArg = null;
Doug Zongkercdf00882014-03-18 12:52:04 -0700599 if (shutdown) {
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700600 shutdownArg = "--shutdown_after";
Doug Zongkercdf00882014-03-18 12:52:04 -0700601 }
602
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700603 String reasonArg = null;
604 if (!TextUtils.isEmpty(reason)) {
605 reasonArg = "--reason=" + sanitizeArg(reason);
606 }
607
608 final String localeArg = "--locale=" + Locale.getDefault().toString();
609 bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);
Doug Zongker1af33d02010-01-05 11:28:55 -0800610 }
611
612 /**
Doug Zongker33651202011-07-19 12:45:09 -0700613 * Reboot into the recovery system to wipe the /cache partition.
Doug Zongker1af33d02010-01-05 11:28:55 -0800614 * @throws IOException if something goes wrong.
Doug Zongker1af33d02010-01-05 11:28:55 -0800615 */
Doug Zongker33651202011-07-19 12:45:09 -0700616 public static void rebootWipeCache(Context context) throws IOException {
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700617 rebootWipeCache(context, context.getPackageName());
618 }
619
620 /** {@hide} */
621 public static void rebootWipeCache(Context context, String reason) throws IOException {
622 String reasonArg = null;
623 if (!TextUtils.isEmpty(reason)) {
624 reasonArg = "--reason=" + sanitizeArg(reason);
625 }
626
627 final String localeArg = "--locale=" + Locale.getDefault().toString();
628 bootCommand(context, "--wipe_cache", reasonArg, localeArg);
Doug Zongker1af33d02010-01-05 11:28:55 -0800629 }
630
631 /**
632 * Reboot into the recovery system with the supplied argument.
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700633 * @param args to pass to the recovery utility.
Doug Zongker1af33d02010-01-05 11:28:55 -0800634 * @throws IOException if something goes wrong.
635 */
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700636 private static void bootCommand(Context context, String... args) throws IOException {
Tao Baoe8a403d2015-12-31 07:44:55 -0800637 synchronized (sRequestLock) {
638 LOG_FILE.delete();
Doug Zongker1af33d02010-01-05 11:28:55 -0800639
Tao Baoe8a403d2015-12-31 07:44:55 -0800640 StringBuilder command = new StringBuilder();
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700641 for (String arg : args) {
642 if (!TextUtils.isEmpty(arg)) {
Tao Baoe8a403d2015-12-31 07:44:55 -0800643 command.append(arg);
644 command.append("\n");
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700645 }
646 }
Tao Baoe8a403d2015-12-31 07:44:55 -0800647
648 // Write the command into BCB (bootloader control block).
649 RecoverySystem rs = (RecoverySystem) context.getSystemService(
650 Context.RECOVERY_SERVICE);
651 rs.setupBcb(command.toString());
652
653 // Having set up the BCB, go ahead and reboot.
654 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
655 pm.reboot(PowerManager.REBOOT_RECOVERY);
656
657 throw new IOException("Reboot failed (no permissions?)");
Doug Zongker1af33d02010-01-05 11:28:55 -0800658 }
Doug Zongker1af33d02010-01-05 11:28:55 -0800659 }
660
661 /**
662 * Called after booting to process and remove recovery-related files.
663 * @return the log file from recovery, or null if none was found.
664 *
665 * @hide
666 */
667 public static String handleAftermath() {
668 // Record the tail of the LOG_FILE
669 String log = null;
670 try {
671 log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n");
672 } catch (FileNotFoundException e) {
673 Log.i(TAG, "No recovery log file");
674 } catch (IOException e) {
675 Log.e(TAG, "Error reading recovery log", e);
676 }
677
Tao Baoe8217ff2016-02-01 16:08:18 -0800678 // Only remove the OTA package if it's partially processed (uncrypt'd).
679 boolean reservePackage = BLOCK_MAP_FILE.exists();
Tao Baoe8a403d2015-12-31 07:44:55 -0800680 if (!reservePackage && UNCRYPT_PACKAGE_FILE.exists()) {
Tao Bao87212ad2015-10-19 14:48:36 -0700681 String filename = null;
682 try {
Tao Baoe8a403d2015-12-31 07:44:55 -0800683 filename = FileUtils.readTextFile(UNCRYPT_PACKAGE_FILE, 0, null);
Tao Bao87212ad2015-10-19 14:48:36 -0700684 } catch (IOException e) {
685 Log.e(TAG, "Error reading uncrypt file", e);
686 }
687
688 // Remove the OTA package on /data that has been (possibly
689 // partially) processed. (Bug: 24973532)
690 if (filename != null && filename.startsWith("/data")) {
Tao Baoe8a403d2015-12-31 07:44:55 -0800691 if (UNCRYPT_PACKAGE_FILE.delete()) {
Tao Bao87212ad2015-10-19 14:48:36 -0700692 Log.i(TAG, "Deleted: " + filename);
693 } else {
694 Log.e(TAG, "Can't delete: " + filename);
695 }
696 }
697 }
698
Tao Baoe8217ff2016-02-01 16:08:18 -0800699 // We keep the update logs (beginning with LAST_PREFIX), and optionally
700 // the block map file (BLOCK_MAP_FILE) for a package. BLOCK_MAP_FILE
701 // will be created at the end of a successful uncrypt. If seeing this
702 // file, we keep the block map file and the file that contains the
Tao Baoe8a403d2015-12-31 07:44:55 -0800703 // package name (UNCRYPT_PACKAGE_FILE). This is to reduce the work for
704 // GmsCore to avoid re-downloading everything again.
Doug Zongker1af33d02010-01-05 11:28:55 -0800705 String[] names = RECOVERY_DIR.list();
706 for (int i = 0; names != null && i < names.length; i++) {
Doug Zongker3d5040f82011-04-12 09:23:51 -0700707 if (names[i].startsWith(LAST_PREFIX)) continue;
Tao Baoe8217ff2016-02-01 16:08:18 -0800708 if (reservePackage && names[i].equals(BLOCK_MAP_FILE.getName())) continue;
Tao Baoe8a403d2015-12-31 07:44:55 -0800709 if (reservePackage && names[i].equals(UNCRYPT_PACKAGE_FILE.getName())) continue;
Tao Baoe8217ff2016-02-01 16:08:18 -0800710
Tao Bao5065e122015-08-18 12:37:02 -0700711 recursiveDelete(new File(RECOVERY_DIR, names[i]));
Doug Zongker1af33d02010-01-05 11:28:55 -0800712 }
713
714 return log;
715 }
716
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700717 /**
Tao Bao5065e122015-08-18 12:37:02 -0700718 * Internally, delete a given file or directory recursively.
719 */
720 private static void recursiveDelete(File name) {
721 if (name.isDirectory()) {
722 String[] files = name.list();
723 for (int i = 0; files != null && i < files.length; i++) {
724 File f = new File(name, files[i]);
725 recursiveDelete(f);
726 }
727 }
728
729 if (!name.delete()) {
730 Log.e(TAG, "Can't delete: " + name);
731 } else {
732 Log.i(TAG, "Deleted: " + name);
733 }
734 }
735
736 /**
Tao Baoe8a403d2015-12-31 07:44:55 -0800737 * Talks to RecoverySystemService via Binder to trigger uncrypt.
738 */
739 private boolean uncrypt(String packageFile, IRecoverySystemProgressListener listener) {
740 try {
741 return mService.uncrypt(packageFile, listener);
742 } catch (RemoteException unused) {
743 }
744 return false;
745 }
746
747 /**
748 * Talks to RecoverySystemService via Binder to set up the BCB.
749 */
750 private boolean setupBcb(String command) {
751 try {
752 return mService.setupBcb(command);
753 } catch (RemoteException unused) {
754 }
755 return false;
756 }
757
758 /**
759 * Talks to RecoverySystemService via Binder to clear up the BCB.
760 */
761 private boolean clearBcb() {
762 try {
763 return mService.clearBcb();
764 } catch (RemoteException unused) {
765 }
766 return false;
767 }
768
769 /**
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700770 * Internally, recovery treats each line of the command file as a separate
771 * argv, so we only need to protect against newlines and nulls.
772 */
773 private static String sanitizeArg(String arg) {
774 arg = arg.replace('\0', '?');
775 arg = arg.replace('\n', '?');
776 return arg;
777 }
778
Andreas Gampe70e21e62015-03-18 16:13:33 -0700779
780 /**
781 * @removed Was previously made visible by accident.
782 */
Tao Baoe8a403d2015-12-31 07:44:55 -0800783 public RecoverySystem() {
784 mService = null;
785 }
786
787 /**
788 * @hide
789 */
790 public RecoverySystem(IRecoverySystem service) {
791 mService = service;
792 }
Doug Zongker1af33d02010-01-05 11:28:55 -0800793}