blob: 8492b0cf58e287f570767470a10db2cf547e0623 [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 Baoe01b5202017-04-11 00:28:34 -070019import static java.nio.charset.StandardCharsets.UTF_8;
20
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060021import android.annotation.RequiresPermission;
22import android.annotation.SuppressLint;
Tao Baoe8a403d2015-12-31 07:44:55 -080023import android.annotation.SystemApi;
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060024import android.annotation.SystemService;
yinxuf4f9cec2017-06-19 10:28:19 -070025import android.app.PendingIntent;
Jason parks4ca74dc2011-03-14 15:23:31 -050026import android.content.BroadcastReceiver;
Jeff Davidsone05f37e2017-07-19 16:55:45 -070027import android.content.ContentResolver;
Jason parks4ca74dc2011-03-14 15:23:31 -050028import android.content.Context;
29import android.content.Intent;
yinxuf4f9cec2017-06-19 10:28:19 -070030import android.content.IntentFilter;
Dmitri Plotnikov690c6bd2017-05-10 16:26:38 -070031import android.content.pm.PackageManager;
yinxuf4f9cec2017-06-19 10:28:19 -070032import android.provider.Settings;
33import android.telephony.euicc.EuiccManager;
Jeff Sharkey004a4b22014-09-24 11:45:24 -070034import android.text.TextUtils;
Jason parks4ca74dc2011-03-14 15:23:31 -050035import android.util.Log;
Dmitri Plotnikov690c6bd2017-05-10 16:26:38 -070036import android.view.Display;
37import android.view.WindowManager;
Jason parks4ca74dc2011-03-14 15:23:31 -050038
Tao Baoe01b5202017-04-11 00:28:34 -070039import libcore.io.Streams;
40
yinxuf4f9cec2017-06-19 10:28:19 -070041import java.io.ByteArrayInputStream;
Doug Zongker1af33d02010-01-05 11:28:55 -080042import java.io.File;
Tao Baoe01b5202017-04-11 00:28:34 -070043import java.io.FileInputStream;
Doug Zongker1af33d02010-01-05 11:28:55 -080044import java.io.FileNotFoundException;
45import java.io.FileWriter;
46import java.io.IOException;
Doug Zongkere2d58e92011-11-04 13:39:47 -070047import java.io.InputStream;
Doug Zongker1af33d02010-01-05 11:28:55 -080048import java.io.RandomAccessFile;
49import java.security.GeneralSecurityException;
50import java.security.PublicKey;
Doug Zongker1af33d02010-01-05 11:28:55 -080051import java.security.SignatureException;
Doug Zongker1af33d02010-01-05 11:28:55 -080052import java.security.cert.CertificateFactory;
53import java.security.cert.X509Certificate;
Tao Baoe01b5202017-04-11 00:28:34 -070054import java.util.ArrayList;
Doug Zongker1af33d02010-01-05 11:28:55 -080055import java.util.Enumeration;
56import java.util.HashSet;
Doug Zongkere33b4002012-08-23 09:52:14 -070057import java.util.Locale;
yinxuf4f9cec2017-06-19 10:28:19 -070058import java.util.concurrent.CountDownLatch;
59import java.util.concurrent.TimeUnit;
Qingxi Lic6097db2018-01-09 10:57:38 -080060import java.util.concurrent.atomic.AtomicBoolean;
Doug Zongker1af33d02010-01-05 11:28:55 -080061import java.util.zip.ZipEntry;
62import java.util.zip.ZipFile;
Tao Baoe01b5202017-04-11 00:28:34 -070063import java.util.zip.ZipInputStream;
Doug Zongker1af33d02010-01-05 11:28:55 -080064
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +010065import sun.security.pkcs.PKCS7;
66import sun.security.pkcs.SignerInfo;
Doug Zongker1af33d02010-01-05 11:28:55 -080067
68/**
69 * RecoverySystem contains methods for interacting with the Android
70 * recovery system (the separate partition that can be used to install
71 * system updates, wipe user data, etc.)
Doug Zongker1af33d02010-01-05 11:28:55 -080072 */
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060073@SystemService(Context.RECOVERY_SERVICE)
Doug Zongker1af33d02010-01-05 11:28:55 -080074public class RecoverySystem {
75 private static final String TAG = "RecoverySystem";
76
77 /**
78 * Default location of zip file containing public keys (X509
79 * certs) authorized to sign OTA updates.
80 */
81 private static final File DEFAULT_KEYSTORE =
82 new File("/system/etc/security/otacerts.zip");
83
84 /** Send progress to listeners no more often than this (in ms). */
85 private static final long PUBLISH_PROGRESS_INTERVAL_MS = 500;
86
qingxie060ffd2017-06-23 15:32:53 -070087 private static final long DEFAULT_EUICC_FACTORY_RESET_TIMEOUT_MILLIS = 30000L; // 30 s
yinxuf4f9cec2017-06-19 10:28:19 -070088
qingxie060ffd2017-06-23 15:32:53 -070089 private static final long MIN_EUICC_FACTORY_RESET_TIMEOUT_MILLIS = 5000L; // 5 s
yinxuf4f9cec2017-06-19 10:28:19 -070090
qingxie060ffd2017-06-23 15:32:53 -070091 private static final long MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS = 60000L; // 60 s
yinxuf4f9cec2017-06-19 10:28:19 -070092
Tao Bao5065e122015-08-18 12:37:02 -070093 /** Used to communicate with recovery. See bootable/recovery/recovery.cpp. */
Tao Baoe8a403d2015-12-31 07:44:55 -080094 private static final File RECOVERY_DIR = new File("/cache/recovery");
95 private static final File LOG_FILE = new File(RECOVERY_DIR, "log");
Tianjie Xud12e1502018-09-06 14:26:33 -070096 private static final String LAST_INSTALL_PATH = "last_install";
Tao Baoe8a403d2015-12-31 07:44:55 -080097 private static final String LAST_PREFIX = "last_";
qingxie060ffd2017-06-23 15:32:53 -070098 private static final String ACTION_EUICC_FACTORY_RESET =
99 "com.android.internal.action.EUICC_FACTORY_RESET";
Tao Baoe8a403d2015-12-31 07:44:55 -0800100
Qingxi Lic6097db2018-01-09 10:57:38 -0800101 /** used in {@link #wipeEuiccData} as package name of callback intent */
102 private static final String PACKAGE_NAME_WIPING_EUICC_DATA_CALLBACK = "android";
103
Tao Baoe8a403d2015-12-31 07:44:55 -0800104 /**
105 * The recovery image uses this file to identify the location (i.e. blocks)
106 * of an OTA package on the /data partition. The block map file is
107 * generated by uncrypt.
108 *
109 * @hide
110 */
111 public static final File BLOCK_MAP_FILE = new File(RECOVERY_DIR, "block.map");
112
113 /**
114 * UNCRYPT_PACKAGE_FILE stores the filename to be uncrypt'd, which will be
115 * read by uncrypt.
116 *
117 * @hide
118 */
119 public static final File UNCRYPT_PACKAGE_FILE = new File(RECOVERY_DIR, "uncrypt_file");
Doug Zongker1af33d02010-01-05 11:28:55 -0800120
Tianjie Xu036d0862016-09-24 15:39:56 -0700121 /**
122 * UNCRYPT_STATUS_FILE stores the time cost (and error code in the case of a failure)
123 * of uncrypt.
124 *
125 * @hide
126 */
127 public static final File UNCRYPT_STATUS_FILE = new File(RECOVERY_DIR, "uncrypt_status");
128
Doug Zongker1af33d02010-01-05 11:28:55 -0800129 // Length limits for reading files.
Tao Baoe8a403d2015-12-31 07:44:55 -0800130 private static final int LOG_FILE_MAX_LENGTH = 64 * 1024;
131
132 // Prevent concurrent execution of requests.
133 private static final Object sRequestLock = new Object();
134
135 private final IRecoverySystem mService;
Doug Zongker1af33d02010-01-05 11:28:55 -0800136
137 /**
138 * Interface definition for a callback to be invoked regularly as
139 * verification proceeds.
140 */
141 public interface ProgressListener {
142 /**
143 * Called periodically as the verification progresses.
144 *
145 * @param progress the approximate percentage of the
146 * verification that has been completed, ranging from 0
147 * to 100 (inclusive).
148 */
149 public void onProgress(int progress);
150 }
151
152 /** @return the set of certs that can be used to sign an OTA package. */
Kenny Root27e54942013-10-14 11:17:25 -0700153 private static HashSet<X509Certificate> getTrustedCerts(File keystore)
Doug Zongker1af33d02010-01-05 11:28:55 -0800154 throws IOException, GeneralSecurityException {
Kenny Root27e54942013-10-14 11:17:25 -0700155 HashSet<X509Certificate> trusted = new HashSet<X509Certificate>();
Doug Zongker1af33d02010-01-05 11:28:55 -0800156 if (keystore == null) {
157 keystore = DEFAULT_KEYSTORE;
158 }
159 ZipFile zip = new ZipFile(keystore);
160 try {
161 CertificateFactory cf = CertificateFactory.getInstance("X.509");
162 Enumeration<? extends ZipEntry> entries = zip.entries();
163 while (entries.hasMoreElements()) {
164 ZipEntry entry = entries.nextElement();
Doug Zongkere2d58e92011-11-04 13:39:47 -0700165 InputStream is = zip.getInputStream(entry);
166 try {
Kenny Root27e54942013-10-14 11:17:25 -0700167 trusted.add((X509Certificate) cf.generateCertificate(is));
Doug Zongkere2d58e92011-11-04 13:39:47 -0700168 } finally {
169 is.close();
170 }
Doug Zongker1af33d02010-01-05 11:28:55 -0800171 }
172 } finally {
173 zip.close();
174 }
175 return trusted;
176 }
177
178 /**
179 * Verify the cryptographic signature of a system update package
180 * before installing it. Note that the package is also verified
181 * separately by the installer once the device is rebooted into
182 * the recovery system. This function will return only if the
183 * package was successfully verified; otherwise it will throw an
184 * exception.
185 *
186 * Verification of a package can take significant time, so this
Doug Zongkercb956572010-03-11 10:27:21 -0800187 * function should not be called from a UI thread. Interrupting
188 * the thread while this function is in progress will result in a
189 * SecurityException being thrown (and the thread's interrupt flag
190 * will be cleared).
Doug Zongker1af33d02010-01-05 11:28:55 -0800191 *
192 * @param packageFile the package to be verified
193 * @param listener an object to receive periodic progress
194 * updates as verification proceeds. May be null.
195 * @param deviceCertsZipFile the zip file of certificates whose
196 * public keys we will accept. Verification succeeds if the
197 * package is signed by the private key corresponding to any
198 * public key in this file. May be null to use the system default
199 * file (currently "/system/etc/security/otacerts.zip").
200 *
201 * @throws IOException if there were any errors reading the
202 * package or certs files.
203 * @throws GeneralSecurityException if verification failed
204 */
205 public static void verifyPackage(File packageFile,
206 ProgressListener listener,
207 File deviceCertsZipFile)
208 throws IOException, GeneralSecurityException {
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100209 final long fileLen = packageFile.length();
Doug Zongker1af33d02010-01-05 11:28:55 -0800210
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100211 final RandomAccessFile raf = new RandomAccessFile(packageFile, "r");
Doug Zongker1af33d02010-01-05 11:28:55 -0800212 try {
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100213 final long startTimeMillis = System.currentTimeMillis();
Doug Zongker1af33d02010-01-05 11:28:55 -0800214 if (listener != null) {
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100215 listener.onProgress(0);
Doug Zongker1af33d02010-01-05 11:28:55 -0800216 }
217
218 raf.seek(fileLen - 6);
219 byte[] footer = new byte[6];
220 raf.readFully(footer);
221
222 if (footer[2] != (byte)0xff || footer[3] != (byte)0xff) {
223 throw new SignatureException("no signature in file (no footer)");
224 }
225
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100226 final int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8);
227 final int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8);
Doug Zongker1af33d02010-01-05 11:28:55 -0800228
229 byte[] eocd = new byte[commentSize + 22];
230 raf.seek(fileLen - (commentSize + 22));
231 raf.readFully(eocd);
232
233 // Check that we have found the start of the
234 // end-of-central-directory record.
235 if (eocd[0] != (byte)0x50 || eocd[1] != (byte)0x4b ||
236 eocd[2] != (byte)0x05 || eocd[3] != (byte)0x06) {
237 throw new SignatureException("no signature in file (bad footer)");
238 }
239
240 for (int i = 4; i < eocd.length-3; ++i) {
241 if (eocd[i ] == (byte)0x50 && eocd[i+1] == (byte)0x4b &&
242 eocd[i+2] == (byte)0x05 && eocd[i+3] == (byte)0x06) {
243 throw new SignatureException("EOCD marker found after start of EOCD");
244 }
245 }
246
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100247 // Parse the signature
248 PKCS7 block =
249 new PKCS7(new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart));
Doug Zongker1af33d02010-01-05 11:28:55 -0800250
Doug Zongker1af33d02010-01-05 11:28:55 -0800251 // Take the first certificate from the signature (packages
252 // should contain only one).
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100253 X509Certificate[] certificates = block.getCertificates();
254 if (certificates == null || certificates.length == 0) {
Doug Zongker1af33d02010-01-05 11:28:55 -0800255 throw new SignatureException("signature contains no certificates");
256 }
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100257 X509Certificate cert = certificates[0];
258 PublicKey signatureKey = cert.getPublicKey();
Doug Zongker1af33d02010-01-05 11:28:55 -0800259
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100260 SignerInfo[] signerInfos = block.getSignerInfos();
261 if (signerInfos == null || signerInfos.length == 0) {
262 throw new SignatureException("signature contains no signedData");
Doug Zongker1af33d02010-01-05 11:28:55 -0800263 }
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100264 SignerInfo signerInfo = signerInfos[0];
Doug Zongker1af33d02010-01-05 11:28:55 -0800265
266 // Check that the public key of the certificate contained
267 // in the package equals one of our trusted public keys.
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100268 boolean verified = false;
Kenny Root27e54942013-10-14 11:17:25 -0700269 HashSet<X509Certificate> trusted = getTrustedCerts(
Doug Zongker1af33d02010-01-05 11:28:55 -0800270 deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile);
Kenny Root27e54942013-10-14 11:17:25 -0700271 for (X509Certificate c : trusted) {
Doug Zongker1af33d02010-01-05 11:28:55 -0800272 if (c.getPublicKey().equals(signatureKey)) {
273 verified = true;
274 break;
275 }
276 }
277 if (!verified) {
278 throw new SignatureException("signature doesn't match any trusted key");
279 }
280
281 // The signature cert matches a trusted key. Now verify that
282 // the digest in the cert matches the actual file data.
Doug Zongker1af33d02010-01-05 11:28:55 -0800283 raf.seek(0);
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100284 final ProgressListener listenerForInner = listener;
285 SignerInfo verifyResult = block.verify(signerInfo, new InputStream() {
286 // The signature covers all of the OTA package except the
287 // archive comment and its 2-byte length.
288 long toRead = fileLen - commentSize - 2;
289 long soFar = 0;
Doug Zongker1af33d02010-01-05 11:28:55 -0800290
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100291 int lastPercent = 0;
292 long lastPublishTime = startTimeMillis;
293
294 @Override
295 public int read() throws IOException {
296 throw new UnsupportedOperationException();
Doug Zongker1af33d02010-01-05 11:28:55 -0800297 }
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100298
299 @Override
300 public int read(byte[] b, int off, int len) throws IOException {
301 if (soFar >= toRead) {
302 return -1;
303 }
304 if (Thread.currentThread().isInterrupted()) {
305 return -1;
306 }
307
308 int size = len;
309 if (soFar + size > toRead) {
310 size = (int)(toRead - soFar);
311 }
312 int read = raf.read(b, off, size);
313 soFar += read;
314
315 if (listenerForInner != null) {
316 long now = System.currentTimeMillis();
317 int p = (int)(soFar * 100 / toRead);
318 if (p > lastPercent &&
319 now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
320 lastPercent = p;
321 lastPublishTime = now;
322 listenerForInner.onProgress(lastPercent);
323 }
324 }
325
326 return read;
327 }
328 });
329
330 final boolean interrupted = Thread.interrupted();
Doug Zongker1af33d02010-01-05 11:28:55 -0800331 if (listener != null) {
332 listener.onProgress(100);
333 }
334
Doug Zongkercb956572010-03-11 10:27:21 -0800335 if (interrupted) {
336 throw new SignatureException("verification was interrupted");
337 }
338
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100339 if (verifyResult == null) {
Doug Zongker1af33d02010-01-05 11:28:55 -0800340 throw new SignatureException("signature digest verification failed");
341 }
342 } finally {
343 raf.close();
344 }
Tao Baoe01b5202017-04-11 00:28:34 -0700345
346 // Additionally verify the package compatibility.
347 if (!readAndVerifyPackageCompatibilityEntry(packageFile)) {
348 throw new SignatureException("package compatibility verification failed");
349 }
350 }
351
352 /**
353 * Verifies the compatibility entry from an {@link InputStream}.
354 *
355 * @return the verification result.
356 */
357 private static boolean verifyPackageCompatibility(InputStream inputStream) throws IOException {
358 ArrayList<String> list = new ArrayList<>();
359 ZipInputStream zis = new ZipInputStream(inputStream);
360 ZipEntry entry;
361 while ((entry = zis.getNextEntry()) != null) {
362 long entrySize = entry.getSize();
363 if (entrySize > Integer.MAX_VALUE || entrySize < 0) {
364 throw new IOException(
365 "invalid entry size (" + entrySize + ") in the compatibility file");
366 }
367 byte[] bytes = new byte[(int) entrySize];
368 Streams.readFully(zis, bytes);
369 list.add(new String(bytes, UTF_8));
370 }
371 if (list.isEmpty()) {
372 throw new IOException("no entries found in the compatibility file");
373 }
Tao Bao87daeb152017-04-18 15:53:40 -0700374 return (VintfObject.verify(list.toArray(new String[list.size()])) == 0);
Tao Baoe01b5202017-04-11 00:28:34 -0700375 }
376
377 /**
378 * Reads and verifies the compatibility entry in an OTA zip package. The compatibility entry is
379 * a zip file (inside the OTA package zip).
380 *
381 * @return {@code true} if the entry doesn't exist or verification passes.
382 */
383 private static boolean readAndVerifyPackageCompatibilityEntry(File packageFile)
384 throws IOException {
385 try (ZipFile zip = new ZipFile(packageFile)) {
386 ZipEntry entry = zip.getEntry("compatibility.zip");
387 if (entry == null) {
388 return true;
389 }
390 InputStream inputStream = zip.getInputStream(entry);
391 return verifyPackageCompatibility(inputStream);
392 }
393 }
394
395 /**
396 * Verifies the package compatibility info against the current system.
397 *
398 * @param compatibilityFile the {@link File} that contains the package compatibility info.
399 * @throws IOException if there were any errors reading the compatibility file.
400 * @return the compatibility verification result.
401 *
402 * {@hide}
403 */
404 @SystemApi
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600405 @SuppressLint("Doclava125")
Tao Baoe01b5202017-04-11 00:28:34 -0700406 public static boolean verifyPackageCompatibility(File compatibilityFile) throws IOException {
407 try (InputStream inputStream = new FileInputStream(compatibilityFile)) {
408 return verifyPackageCompatibility(inputStream);
409 }
Doug Zongker1af33d02010-01-05 11:28:55 -0800410 }
411
412 /**
Tao Baoe8a403d2015-12-31 07:44:55 -0800413 * Process a given package with uncrypt. No-op if the package is not on the
414 * /data partition.
415 *
416 * @param Context the Context to use
417 * @param packageFile the package to be processed
418 * @param listener an object to receive periodic progress updates as
419 * processing proceeds. May be null.
420 * @param handler the Handler upon which the callbacks will be
421 * executed.
422 *
423 * @throws IOException if there were any errors processing the package file.
424 *
425 * @hide
426 */
427 @SystemApi
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600428 @RequiresPermission(android.Manifest.permission.RECOVERY)
Tao Baoe8a403d2015-12-31 07:44:55 -0800429 public static void processPackage(Context context,
430 File packageFile,
431 final ProgressListener listener,
432 final Handler handler)
433 throws IOException {
434 String filename = packageFile.getCanonicalPath();
435 if (!filename.startsWith("/data/")) {
436 return;
437 }
438
439 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
440 IRecoverySystemProgressListener progressListener = null;
441 if (listener != null) {
442 final Handler progressHandler;
443 if (handler != null) {
444 progressHandler = handler;
445 } else {
446 progressHandler = new Handler(context.getMainLooper());
447 }
448 progressListener = new IRecoverySystemProgressListener.Stub() {
449 int lastProgress = 0;
450 long lastPublishTime = System.currentTimeMillis();
451
452 @Override
453 public void onProgress(final int progress) {
454 final long now = System.currentTimeMillis();
455 progressHandler.post(new Runnable() {
456 @Override
457 public void run() {
458 if (progress > lastProgress &&
459 now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
460 lastProgress = progress;
461 lastPublishTime = now;
462 listener.onProgress(progress);
463 }
464 }
465 });
466 }
467 };
468 }
469
470 if (!rs.uncrypt(filename, progressListener)) {
471 throw new IOException("process package failed");
472 }
473 }
474
475 /**
476 * Process a given package with uncrypt. No-op if the package is not on the
477 * /data partition.
478 *
479 * @param Context the Context to use
480 * @param packageFile the package to be processed
481 * @param listener an object to receive periodic progress updates as
482 * processing proceeds. May be null.
483 *
484 * @throws IOException if there were any errors processing the package file.
485 *
486 * @hide
487 */
488 @SystemApi
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600489 @RequiresPermission(android.Manifest.permission.RECOVERY)
Tao Baoe8a403d2015-12-31 07:44:55 -0800490 public static void processPackage(Context context,
491 File packageFile,
492 final ProgressListener listener)
493 throws IOException {
494 processPackage(context, packageFile, listener, null);
495 }
496
497 /**
Doug Zongker1af33d02010-01-05 11:28:55 -0800498 * Reboots the device in order to install the given update
499 * package.
Jeff Brown64010e82010-04-08 16:52:18 -0700500 * Requires the {@link android.Manifest.permission#REBOOT} permission.
Doug Zongker1af33d02010-01-05 11:28:55 -0800501 *
502 * @param context the Context to use
Doug Zongker4baf6412010-09-20 13:09:57 -0700503 * @param packageFile the update package to install. Must be on
504 * a partition mountable by recovery. (The set of partitions
505 * known to recovery may vary from device to device. Generally,
506 * /cache and /data are safe.)
Doug Zongker1af33d02010-01-05 11:28:55 -0800507 *
508 * @throws IOException if writing the recovery command file
509 * fails, or if the reboot itself fails.
510 */
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600511 @RequiresPermission(android.Manifest.permission.RECOVERY)
Doug Zongker1af33d02010-01-05 11:28:55 -0800512 public static void installPackage(Context context, File packageFile)
Tao Baoe8a403d2015-12-31 07:44:55 -0800513 throws IOException {
514 installPackage(context, packageFile, false);
515 }
516
517 /**
518 * If the package hasn't been processed (i.e. uncrypt'd), set up
519 * UNCRYPT_PACKAGE_FILE and delete BLOCK_MAP_FILE to trigger uncrypt during the
520 * reboot.
521 *
522 * @param context the Context to use
523 * @param packageFile the update package to install. Must be on a
524 * partition mountable by recovery.
525 * @param processed if the package has been processed (uncrypt'd).
526 *
527 * @throws IOException if writing the recovery command file fails, or if
528 * the reboot itself fails.
529 *
530 * @hide
531 */
532 @SystemApi
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600533 @RequiresPermission(android.Manifest.permission.RECOVERY)
Tao Baoe8a403d2015-12-31 07:44:55 -0800534 public static void installPackage(Context context, File packageFile, boolean processed)
535 throws IOException {
536 synchronized (sRequestLock) {
537 LOG_FILE.delete();
538 // Must delete the file in case it was created by system server.
539 UNCRYPT_PACKAGE_FILE.delete();
540
541 String filename = packageFile.getCanonicalPath();
542 Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
543
Tianjie Xuac75f1e2016-04-28 16:44:42 -0700544 // If the package name ends with "_s.zip", it's a security update.
545 boolean securityUpdate = filename.endsWith("_s.zip");
546
Tao Bao36baafe2016-03-14 17:11:05 -0700547 // If the package is on the /data partition, the package needs to
548 // be processed (i.e. uncrypt'd). The caller specifies if that has
549 // been done in 'processed' parameter.
Tao Baoe8a403d2015-12-31 07:44:55 -0800550 if (filename.startsWith("/data/")) {
Tao Bao36baafe2016-03-14 17:11:05 -0700551 if (processed) {
552 if (!BLOCK_MAP_FILE.exists()) {
553 Log.e(TAG, "Package claimed to have been processed but failed to find "
554 + "the block map file.");
555 throw new IOException("Failed to find block map file");
556 }
557 } else {
558 FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE);
559 try {
560 uncryptFile.write(filename + "\n");
561 } finally {
562 uncryptFile.close();
563 }
564 // UNCRYPT_PACKAGE_FILE needs to be readable and writable
565 // by system server.
566 if (!UNCRYPT_PACKAGE_FILE.setReadable(true, false)
567 || !UNCRYPT_PACKAGE_FILE.setWritable(true, false)) {
568 Log.e(TAG, "Error setting permission for " + UNCRYPT_PACKAGE_FILE);
569 }
570
571 BLOCK_MAP_FILE.delete();
572 }
573
574 // If the package is on the /data partition, use the block map
575 // file as the package name instead.
Tao Baoe8a403d2015-12-31 07:44:55 -0800576 filename = "@/cache/recovery/block.map";
577 }
578
579 final String filenameArg = "--update_package=" + filename + "\n";
Tianjie Xub1b38b32017-03-28 20:12:14 +0000580 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n";
Tianjie Xuac75f1e2016-04-28 16:44:42 -0700581 final String securityArg = "--security\n";
582
583 String command = filenameArg + localeArg;
584 if (securityUpdate) {
585 command += securityArg;
586 }
Tao Baoe8a403d2015-12-31 07:44:55 -0800587
588 RecoverySystem rs = (RecoverySystem) context.getSystemService(
589 Context.RECOVERY_SERVICE);
Tao Baocc769912017-01-17 12:42:43 -0800590 if (!rs.setupBcb(command)) {
591 throw new IOException("Setup BCB failed");
592 }
593
594 // Having set up the BCB (bootloader control block), go ahead and reboot
595 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
Dmitri Plotnikov690c6bd2017-05-10 16:26:38 -0700596 String reason = PowerManager.REBOOT_RECOVERY_UPDATE;
597
598 // On TV, reboot quiescently if the screen is off
599 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
600 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
601 if (wm.getDefaultDisplay().getState() != Display.STATE_ON) {
602 reason += ",quiescent";
603 }
604 }
605 pm.reboot(reason);
Tao Baoe8a403d2015-12-31 07:44:55 -0800606
607 throw new IOException("Reboot failed (no permissions?)");
608 }
609 }
610
611 /**
612 * Schedule to install the given package on next boot. The caller needs to
613 * ensure that the package must have been processed (uncrypt'd) if needed.
614 * It sets up the command in BCB (bootloader control block), which will
615 * be read by the bootloader and the recovery image.
616 *
617 * @param Context the Context to use.
618 * @param packageFile the package to be installed.
619 *
620 * @throws IOException if there were any errors setting up the BCB.
621 *
622 * @hide
623 */
624 @SystemApi
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600625 @RequiresPermission(android.Manifest.permission.RECOVERY)
Tao Baoe8a403d2015-12-31 07:44:55 -0800626 public static void scheduleUpdateOnBoot(Context context, File packageFile)
627 throws IOException {
Doug Zongker1af33d02010-01-05 11:28:55 -0800628 String filename = packageFile.getCanonicalPath();
Tianjie Xuac75f1e2016-04-28 16:44:42 -0700629 boolean securityUpdate = filename.endsWith("_s.zip");
Tao Bao90237f72015-05-21 16:25:19 -0700630
Tao Baoe8a403d2015-12-31 07:44:55 -0800631 // If the package is on the /data partition, use the block map file as
632 // the package name instead.
Tao Bao90237f72015-05-21 16:25:19 -0700633 if (filename.startsWith("/data/")) {
634 filename = "@/cache/recovery/block.map";
635 }
636
Tao Baoe8a403d2015-12-31 07:44:55 -0800637 final String filenameArg = "--update_package=" + filename + "\n";
Tianjie Xub1b38b32017-03-28 20:12:14 +0000638 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n";
Tianjie Xuac75f1e2016-04-28 16:44:42 -0700639 final String securityArg = "--security\n";
640
641 String command = filenameArg + localeArg;
642 if (securityUpdate) {
643 command += securityArg;
644 }
Tao Baoe8a403d2015-12-31 07:44:55 -0800645
646 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
647 if (!rs.setupBcb(command)) {
648 throw new IOException("schedule update on boot failed");
649 }
650 }
651
652 /**
653 * Cancel any scheduled update by clearing up the BCB (bootloader control
654 * block).
655 *
656 * @param Context the Context to use.
657 *
658 * @throws IOException if there were any errors clearing up the BCB.
659 *
660 * @hide
661 */
662 @SystemApi
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600663 @RequiresPermission(android.Manifest.permission.RECOVERY)
Tao Baoe8a403d2015-12-31 07:44:55 -0800664 public static void cancelScheduledUpdate(Context context)
665 throws IOException {
666 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
667 if (!rs.clearBcb()) {
668 throw new IOException("cancel scheduled update failed");
669 }
Doug Zongker1af33d02010-01-05 11:28:55 -0800670 }
671
672 /**
Doug Zongkercdf00882014-03-18 12:52:04 -0700673 * Reboots the device and wipes the user data and cache
674 * partitions. This is sometimes called a "factory reset", which
675 * is something of a misnomer because the system partition is not
676 * restored to its factory state. Requires the
677 * {@link android.Manifest.permission#REBOOT} permission.
Doug Zongker1af33d02010-01-05 11:28:55 -0800678 *
679 * @param context the Context to use
680 *
681 * @throws IOException if writing the recovery command file
682 * fails, or if the reboot itself fails.
Julia Reynoldsfe053802014-06-30 11:41:32 -0400683 * @throws SecurityException if the current user is not allowed to wipe data.
Doug Zongker1af33d02010-01-05 11:28:55 -0800684 */
Jason parks4ca74dc2011-03-14 15:23:31 -0500685 public static void rebootWipeUserData(Context context) throws IOException {
Benjamin Franzf9d5e6a2016-05-26 14:24:29 +0100686 rebootWipeUserData(context, false /* shutdown */, context.getPackageName(),
yinxuf4f9cec2017-06-19 10:28:19 -0700687 false /* force */, false /* wipeEuicc */);
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700688 }
689
690 /** {@hide} */
691 public static void rebootWipeUserData(Context context, String reason) throws IOException {
yinxuf4f9cec2017-06-19 10:28:19 -0700692 rebootWipeUserData(context, false /* shutdown */, reason, false /* force */,
693 false /* wipeEuicc */);
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700694 }
695
696 /** {@hide} */
697 public static void rebootWipeUserData(Context context, boolean shutdown)
698 throws IOException {
yinxuf4f9cec2017-06-19 10:28:19 -0700699 rebootWipeUserData(context, shutdown, context.getPackageName(), false /* force */,
700 false /* wipeEuicc */);
701 }
702
703 /** {@hide} */
704 public static void rebootWipeUserData(Context context, boolean shutdown, String reason,
705 boolean force) throws IOException {
706 rebootWipeUserData(context, shutdown, reason, force, false /* wipeEuicc */);
Doug Zongkercdf00882014-03-18 12:52:04 -0700707 }
708
709 /**
710 * Reboots the device and wipes the user data and cache
711 * partitions. This is sometimes called a "factory reset", which
712 * is something of a misnomer because the system partition is not
713 * restored to its factory state. Requires the
714 * {@link android.Manifest.permission#REBOOT} permission.
715 *
716 * @param context the Context to use
717 * @param shutdown if true, the device will be powered down after
718 * the wipe completes, rather than being rebooted
719 * back to the regular system.
Benjamin Franzf9d5e6a2016-05-26 14:24:29 +0100720 * @param reason the reason for the wipe that is visible in the logs
721 * @param force whether the {@link UserManager.DISALLOW_FACTORY_RESET} user restriction
722 * should be ignored
yinxuf4f9cec2017-06-19 10:28:19 -0700723 * @param wipeEuicc whether wipe the euicc data
Doug Zongkercdf00882014-03-18 12:52:04 -0700724 *
725 * @throws IOException if writing the recovery command file
726 * fails, or if the reboot itself fails.
Julia Reynoldsfe053802014-06-30 11:41:32 -0400727 * @throws SecurityException if the current user is not allowed to wipe data.
Doug Zongkercdf00882014-03-18 12:52:04 -0700728 *
729 * @hide
730 */
Benjamin Franzf9d5e6a2016-05-26 14:24:29 +0100731 public static void rebootWipeUserData(Context context, boolean shutdown, String reason,
yinxuf4f9cec2017-06-19 10:28:19 -0700732 boolean force, boolean wipeEuicc) throws IOException {
Julia Reynoldsfe053802014-06-30 11:41:32 -0400733 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
Benjamin Franzf9d5e6a2016-05-26 14:24:29 +0100734 if (!force && um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) {
Julia Reynoldsfe053802014-06-30 11:41:32 -0400735 throw new SecurityException("Wiping data is not allowed for this user.");
736 }
Jason parks4ca74dc2011-03-14 15:23:31 -0500737 final ConditionVariable condition = new ConditionVariable();
738
739 Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION");
Christopher Tate2d6c9452017-01-25 14:49:44 -0800740 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
741 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700742 context.sendOrderedBroadcastAsUser(intent, UserHandle.SYSTEM,
Dianne Hackborn5ac72a22012-08-29 18:32:08 -0700743 android.Manifest.permission.MASTER_CLEAR,
Jason parks4ca74dc2011-03-14 15:23:31 -0500744 new BroadcastReceiver() {
745 @Override
746 public void onReceive(Context context, Intent intent) {
747 condition.open();
748 }
749 }, null, 0, null, null);
750
751 // Block until the ordered broadcast has completed.
752 condition.block();
753
Qingxi Lif4577a52018-01-11 11:24:29 -0800754 if (wipeEuicc) {
755 wipeEuiccData(context, PACKAGE_NAME_WIPING_EUICC_DATA_CALLBACK);
756 }
yinxuf4f9cec2017-06-19 10:28:19 -0700757
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700758 String shutdownArg = null;
Doug Zongkercdf00882014-03-18 12:52:04 -0700759 if (shutdown) {
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700760 shutdownArg = "--shutdown_after";
Doug Zongkercdf00882014-03-18 12:52:04 -0700761 }
762
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700763 String reasonArg = null;
764 if (!TextUtils.isEmpty(reason)) {
765 reasonArg = "--reason=" + sanitizeArg(reason);
766 }
767
Tianjie Xub1b38b32017-03-28 20:12:14 +0000768 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700769 bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);
Doug Zongker1af33d02010-01-05 11:28:55 -0800770 }
771
Qingxi Lic6097db2018-01-09 10:57:38 -0800772 /**
773 * Returns whether wipe Euicc data successfully or not.
774 *
Qingxi Lic6097db2018-01-09 10:57:38 -0800775 * @param packageName the package name of the caller app.
776 *
777 * @hide
778 */
Qingxi Lif4577a52018-01-11 11:24:29 -0800779 public static boolean wipeEuiccData(Context context, final String packageName) {
Jeff Davidsone05f37e2017-07-19 16:55:45 -0700780 ContentResolver cr = context.getContentResolver();
781 if (Settings.Global.getInt(cr, Settings.Global.EUICC_PROVISIONED, 0) == 0) {
782 // If the eUICC isn't provisioned, there's no reason to either wipe or retain profiles,
783 // as there's nothing to wipe nor retain.
784 Log.d(TAG, "Skipping eUICC wipe/retain as it is not provisioned");
Qingxi Lic6097db2018-01-09 10:57:38 -0800785 return true;
Jeff Davidsone05f37e2017-07-19 16:55:45 -0700786 }
787
yinxuf4f9cec2017-06-19 10:28:19 -0700788 EuiccManager euiccManager = (EuiccManager) context.getSystemService(
789 Context.EUICC_SERVICE);
790 if (euiccManager != null && euiccManager.isEnabled()) {
791 CountDownLatch euiccFactoryResetLatch = new CountDownLatch(1);
Qingxi Lic6097db2018-01-09 10:57:38 -0800792 final AtomicBoolean wipingSucceeded = new AtomicBoolean(false);
yinxuf4f9cec2017-06-19 10:28:19 -0700793
794 BroadcastReceiver euiccWipeFinishReceiver = new BroadcastReceiver() {
795 @Override
796 public void onReceive(Context context, Intent intent) {
qingxie060ffd2017-06-23 15:32:53 -0700797 if (ACTION_EUICC_FACTORY_RESET.equals(intent.getAction())) {
yinxuf4f9cec2017-06-19 10:28:19 -0700798 if (getResultCode() != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) {
799 int detailedCode = intent.getIntExtra(
800 EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0);
Qingxi Lif4577a52018-01-11 11:24:29 -0800801 Log.e(TAG, "Error wiping euicc data, Detailed code = "
802 + detailedCode);
yinxuf4f9cec2017-06-19 10:28:19 -0700803 } else {
Qingxi Lif4577a52018-01-11 11:24:29 -0800804 Log.d(TAG, "Successfully wiped euicc data.");
Qingxi Lic6097db2018-01-09 10:57:38 -0800805 wipingSucceeded.set(true /* newValue */);
yinxuf4f9cec2017-06-19 10:28:19 -0700806 }
807 euiccFactoryResetLatch.countDown();
808 }
809 }
810 };
811
qingxie060ffd2017-06-23 15:32:53 -0700812 Intent intent = new Intent(ACTION_EUICC_FACTORY_RESET);
Qingxi Lic6097db2018-01-09 10:57:38 -0800813 intent.setPackage(packageName);
yinxuf4f9cec2017-06-19 10:28:19 -0700814 PendingIntent callbackIntent = PendingIntent.getBroadcastAsUser(
815 context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, UserHandle.SYSTEM);
816 IntentFilter filterConsent = new IntentFilter();
qingxie060ffd2017-06-23 15:32:53 -0700817 filterConsent.addAction(ACTION_EUICC_FACTORY_RESET);
yinxuf4f9cec2017-06-19 10:28:19 -0700818 HandlerThread euiccHandlerThread = new HandlerThread("euiccWipeFinishReceiverThread");
819 euiccHandlerThread.start();
820 Handler euiccHandler = new Handler(euiccHandlerThread.getLooper());
qingxiaee0ab92017-07-12 13:15:51 -0700821 context.getApplicationContext()
822 .registerReceiver(euiccWipeFinishReceiver, filterConsent, null, euiccHandler);
Qingxi Lif4577a52018-01-11 11:24:29 -0800823 euiccManager.eraseSubscriptions(callbackIntent);
yinxuf4f9cec2017-06-19 10:28:19 -0700824 try {
825 long waitingTimeMillis = Settings.Global.getLong(
826 context.getContentResolver(),
qingxie060ffd2017-06-23 15:32:53 -0700827 Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS,
828 DEFAULT_EUICC_FACTORY_RESET_TIMEOUT_MILLIS);
829 if (waitingTimeMillis < MIN_EUICC_FACTORY_RESET_TIMEOUT_MILLIS) {
830 waitingTimeMillis = MIN_EUICC_FACTORY_RESET_TIMEOUT_MILLIS;
831 } else if (waitingTimeMillis > MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS) {
832 waitingTimeMillis = MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS;
yinxuf4f9cec2017-06-19 10:28:19 -0700833 }
834 if (!euiccFactoryResetLatch.await(waitingTimeMillis, TimeUnit.MILLISECONDS)) {
Qingxi Lif4577a52018-01-11 11:24:29 -0800835 Log.e(TAG, "Timeout wiping eUICC data.");
Qingxi Lic6097db2018-01-09 10:57:38 -0800836 return false;
yinxuf4f9cec2017-06-19 10:28:19 -0700837 }
yinxuf4f9cec2017-06-19 10:28:19 -0700838 } catch (InterruptedException e) {
839 Thread.currentThread().interrupt();
Qingxi Lif4577a52018-01-11 11:24:29 -0800840 Log.e(TAG, "Wiping eUICC data interrupted", e);
Qingxi Lic6097db2018-01-09 10:57:38 -0800841 return false;
842 } finally {
843 context.getApplicationContext().unregisterReceiver(euiccWipeFinishReceiver);
yinxuf4f9cec2017-06-19 10:28:19 -0700844 }
Qingxi Lic6097db2018-01-09 10:57:38 -0800845 return wipingSucceeded.get();
yinxuf4f9cec2017-06-19 10:28:19 -0700846 }
Qingxi Lic6097db2018-01-09 10:57:38 -0800847 return false;
yinxuf4f9cec2017-06-19 10:28:19 -0700848 }
849
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700850 /** {@hide} */
851 public static void rebootPromptAndWipeUserData(Context context, String reason)
852 throws IOException {
853 String reasonArg = null;
854 if (!TextUtils.isEmpty(reason)) {
855 reasonArg = "--reason=" + sanitizeArg(reason);
856 }
857
858 final String localeArg = "--locale=" + Locale.getDefault().toString();
859 bootCommand(context, null, "--prompt_and_wipe_data", reasonArg, localeArg);
860 }
861
Doug Zongker1af33d02010-01-05 11:28:55 -0800862 /**
Doug Zongker33651202011-07-19 12:45:09 -0700863 * Reboot into the recovery system to wipe the /cache partition.
Doug Zongker1af33d02010-01-05 11:28:55 -0800864 * @throws IOException if something goes wrong.
Doug Zongker1af33d02010-01-05 11:28:55 -0800865 */
Doug Zongker33651202011-07-19 12:45:09 -0700866 public static void rebootWipeCache(Context context) throws IOException {
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700867 rebootWipeCache(context, context.getPackageName());
868 }
869
870 /** {@hide} */
871 public static void rebootWipeCache(Context context, String reason) throws IOException {
872 String reasonArg = null;
873 if (!TextUtils.isEmpty(reason)) {
874 reasonArg = "--reason=" + sanitizeArg(reason);
875 }
876
Tianjie Xub1b38b32017-03-28 20:12:14 +0000877 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700878 bootCommand(context, "--wipe_cache", reasonArg, localeArg);
Doug Zongker1af33d02010-01-05 11:28:55 -0800879 }
880
881 /**
Tao Bao1327a972016-06-02 08:47:56 -0700882 * Reboot into recovery and wipe the A/B device.
883 *
884 * @param Context the Context to use.
885 * @param packageFile the wipe package to be applied.
886 * @param reason the reason to wipe.
887 *
888 * @throws IOException if something goes wrong.
889 *
890 * @hide
891 */
892 @SystemApi
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600893 @RequiresPermission(allOf = {
894 android.Manifest.permission.RECOVERY,
895 android.Manifest.permission.REBOOT
896 })
Tao Bao1327a972016-06-02 08:47:56 -0700897 public static void rebootWipeAb(Context context, File packageFile, String reason)
898 throws IOException {
899 String reasonArg = null;
900 if (!TextUtils.isEmpty(reason)) {
901 reasonArg = "--reason=" + sanitizeArg(reason);
902 }
903
904 final String filename = packageFile.getCanonicalPath();
905 final String filenameArg = "--wipe_package=" + filename;
Tianjie Xub1b38b32017-03-28 20:12:14 +0000906 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
Tao Bao1327a972016-06-02 08:47:56 -0700907 bootCommand(context, "--wipe_ab", filenameArg, reasonArg, localeArg);
908 }
909
910 /**
Doug Zongker1af33d02010-01-05 11:28:55 -0800911 * Reboot into the recovery system with the supplied argument.
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700912 * @param args to pass to the recovery utility.
Doug Zongker1af33d02010-01-05 11:28:55 -0800913 * @throws IOException if something goes wrong.
914 */
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700915 private static void bootCommand(Context context, String... args) throws IOException {
Tao Bao794c8b02016-09-27 11:15:42 -0700916 LOG_FILE.delete();
Doug Zongker1af33d02010-01-05 11:28:55 -0800917
Tao Bao794c8b02016-09-27 11:15:42 -0700918 StringBuilder command = new StringBuilder();
919 for (String arg : args) {
920 if (!TextUtils.isEmpty(arg)) {
921 command.append(arg);
922 command.append("\n");
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700923 }
Doug Zongker1af33d02010-01-05 11:28:55 -0800924 }
Tao Bao794c8b02016-09-27 11:15:42 -0700925
926 // Write the command into BCB (bootloader control block) and boot from
927 // there. Will not return unless failed.
928 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
Tao Baocc769912017-01-17 12:42:43 -0800929 rs.rebootRecoveryWithCommand(command.toString());
Tao Bao794c8b02016-09-27 11:15:42 -0700930
931 throw new IOException("Reboot failed (no permissions?)");
Doug Zongker1af33d02010-01-05 11:28:55 -0800932 }
933
934 /**
935 * Called after booting to process and remove recovery-related files.
936 * @return the log file from recovery, or null if none was found.
937 *
938 * @hide
939 */
Tianjie Xudcd36442016-05-13 12:30:29 -0700940 public static String handleAftermath(Context context) {
Doug Zongker1af33d02010-01-05 11:28:55 -0800941 // Record the tail of the LOG_FILE
942 String log = null;
943 try {
944 log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n");
945 } catch (FileNotFoundException e) {
946 Log.i(TAG, "No recovery log file");
947 } catch (IOException e) {
948 Log.e(TAG, "Error reading recovery log", e);
949 }
950
Tianjie Xudcd36442016-05-13 12:30:29 -0700951
Tao Baoe8217ff2016-02-01 16:08:18 -0800952 // Only remove the OTA package if it's partially processed (uncrypt'd).
953 boolean reservePackage = BLOCK_MAP_FILE.exists();
Tao Baoe8a403d2015-12-31 07:44:55 -0800954 if (!reservePackage && UNCRYPT_PACKAGE_FILE.exists()) {
Tao Bao87212ad2015-10-19 14:48:36 -0700955 String filename = null;
956 try {
Tao Baoe8a403d2015-12-31 07:44:55 -0800957 filename = FileUtils.readTextFile(UNCRYPT_PACKAGE_FILE, 0, null);
Tao Bao87212ad2015-10-19 14:48:36 -0700958 } catch (IOException e) {
959 Log.e(TAG, "Error reading uncrypt file", e);
960 }
961
962 // Remove the OTA package on /data that has been (possibly
963 // partially) processed. (Bug: 24973532)
964 if (filename != null && filename.startsWith("/data")) {
Tao Baoe8a403d2015-12-31 07:44:55 -0800965 if (UNCRYPT_PACKAGE_FILE.delete()) {
Tao Bao87212ad2015-10-19 14:48:36 -0700966 Log.i(TAG, "Deleted: " + filename);
967 } else {
968 Log.e(TAG, "Can't delete: " + filename);
969 }
970 }
971 }
972
Tao Baoe8217ff2016-02-01 16:08:18 -0800973 // We keep the update logs (beginning with LAST_PREFIX), and optionally
974 // the block map file (BLOCK_MAP_FILE) for a package. BLOCK_MAP_FILE
975 // will be created at the end of a successful uncrypt. If seeing this
976 // file, we keep the block map file and the file that contains the
Tao Baoe8a403d2015-12-31 07:44:55 -0800977 // package name (UNCRYPT_PACKAGE_FILE). This is to reduce the work for
978 // GmsCore to avoid re-downloading everything again.
Doug Zongker1af33d02010-01-05 11:28:55 -0800979 String[] names = RECOVERY_DIR.list();
980 for (int i = 0; names != null && i < names.length; i++) {
Tianjie Xud12e1502018-09-06 14:26:33 -0700981 // Do not remove the last_install file since the recovery-persist takes care of it.
982 if (names[i].startsWith(LAST_PREFIX) || names[i].equals(LAST_INSTALL_PATH)) continue;
Tao Baoe8217ff2016-02-01 16:08:18 -0800983 if (reservePackage && names[i].equals(BLOCK_MAP_FILE.getName())) continue;
Tao Baoe8a403d2015-12-31 07:44:55 -0800984 if (reservePackage && names[i].equals(UNCRYPT_PACKAGE_FILE.getName())) continue;
Tao Baoe8217ff2016-02-01 16:08:18 -0800985
Tao Bao5065e122015-08-18 12:37:02 -0700986 recursiveDelete(new File(RECOVERY_DIR, names[i]));
Doug Zongker1af33d02010-01-05 11:28:55 -0800987 }
988
989 return log;
990 }
991
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700992 /**
Tao Bao5065e122015-08-18 12:37:02 -0700993 * Internally, delete a given file or directory recursively.
994 */
995 private static void recursiveDelete(File name) {
996 if (name.isDirectory()) {
997 String[] files = name.list();
998 for (int i = 0; files != null && i < files.length; i++) {
999 File f = new File(name, files[i]);
1000 recursiveDelete(f);
1001 }
1002 }
1003
1004 if (!name.delete()) {
1005 Log.e(TAG, "Can't delete: " + name);
1006 } else {
1007 Log.i(TAG, "Deleted: " + name);
1008 }
1009 }
1010
1011 /**
Tao Baoe8a403d2015-12-31 07:44:55 -08001012 * Talks to RecoverySystemService via Binder to trigger uncrypt.
1013 */
1014 private boolean uncrypt(String packageFile, IRecoverySystemProgressListener listener) {
1015 try {
1016 return mService.uncrypt(packageFile, listener);
1017 } catch (RemoteException unused) {
1018 }
1019 return false;
1020 }
1021
1022 /**
1023 * Talks to RecoverySystemService via Binder to set up the BCB.
1024 */
1025 private boolean setupBcb(String command) {
1026 try {
1027 return mService.setupBcb(command);
1028 } catch (RemoteException unused) {
1029 }
1030 return false;
1031 }
1032
1033 /**
1034 * Talks to RecoverySystemService via Binder to clear up the BCB.
1035 */
1036 private boolean clearBcb() {
1037 try {
1038 return mService.clearBcb();
1039 } catch (RemoteException unused) {
1040 }
1041 return false;
1042 }
1043
1044 /**
Tao Bao794c8b02016-09-27 11:15:42 -07001045 * Talks to RecoverySystemService via Binder to set up the BCB command and
1046 * reboot into recovery accordingly.
1047 */
Tao Baocc769912017-01-17 12:42:43 -08001048 private void rebootRecoveryWithCommand(String command) {
Tao Bao794c8b02016-09-27 11:15:42 -07001049 try {
Tao Baocc769912017-01-17 12:42:43 -08001050 mService.rebootRecoveryWithCommand(command);
Tao Bao794c8b02016-09-27 11:15:42 -07001051 } catch (RemoteException ignored) {
1052 }
1053 }
1054
1055 /**
Jeff Sharkey004a4b22014-09-24 11:45:24 -07001056 * Internally, recovery treats each line of the command file as a separate
1057 * argv, so we only need to protect against newlines and nulls.
1058 */
1059 private static String sanitizeArg(String arg) {
1060 arg = arg.replace('\0', '?');
1061 arg = arg.replace('\n', '?');
1062 return arg;
1063 }
1064
Andreas Gampe70e21e62015-03-18 16:13:33 -07001065
1066 /**
1067 * @removed Was previously made visible by accident.
1068 */
Tao Baoe8a403d2015-12-31 07:44:55 -08001069 public RecoverySystem() {
1070 mService = null;
1071 }
1072
1073 /**
1074 * @hide
1075 */
1076 public RecoverySystem(IRecoverySystem service) {
1077 mService = service;
1078 }
Doug Zongker1af33d02010-01-05 11:28:55 -08001079}