blob: 48fc2a6bf449ab7b8be71af35d9cd40a10b94603 [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;
Andrei Onea24ec3212019-03-15 17:35:05 +000025import android.annotation.UnsupportedAppUsage;
yinxuf4f9cec2017-06-19 10:28:19 -070026import android.app.PendingIntent;
Jason parks4ca74dc2011-03-14 15:23:31 -050027import android.content.BroadcastReceiver;
Jeff Davidsone05f37e2017-07-19 16:55:45 -070028import android.content.ContentResolver;
Jason parks4ca74dc2011-03-14 15:23:31 -050029import android.content.Context;
30import android.content.Intent;
yinxuf4f9cec2017-06-19 10:28:19 -070031import android.content.IntentFilter;
Dmitri Plotnikov690c6bd2017-05-10 16:26:38 -070032import android.content.pm.PackageManager;
yinxuf4f9cec2017-06-19 10:28:19 -070033import android.provider.Settings;
Qingxi Lie0662472019-05-17 15:16:21 -070034import android.telephony.SubscriptionInfo;
35import android.telephony.SubscriptionManager;
yinxuf4f9cec2017-06-19 10:28:19 -070036import android.telephony.euicc.EuiccManager;
Jeff Sharkey004a4b22014-09-24 11:45:24 -070037import android.text.TextUtils;
Yutaro Maruyama3835f6a2018-03-30 12:36:37 +080038import android.text.format.DateFormat;
Jason parks4ca74dc2011-03-14 15:23:31 -050039import android.util.Log;
Dmitri Plotnikov690c6bd2017-05-10 16:26:38 -070040import android.view.Display;
41import android.view.WindowManager;
Jason parks4ca74dc2011-03-14 15:23:31 -050042
Tao Baoe01b5202017-04-11 00:28:34 -070043import libcore.io.Streams;
44
yinxuf4f9cec2017-06-19 10:28:19 -070045import java.io.ByteArrayInputStream;
Doug Zongker1af33d02010-01-05 11:28:55 -080046import java.io.File;
Tao Baoe01b5202017-04-11 00:28:34 -070047import java.io.FileInputStream;
Doug Zongker1af33d02010-01-05 11:28:55 -080048import java.io.FileNotFoundException;
49import java.io.FileWriter;
50import java.io.IOException;
Doug Zongkere2d58e92011-11-04 13:39:47 -070051import java.io.InputStream;
Doug Zongker1af33d02010-01-05 11:28:55 -080052import java.io.RandomAccessFile;
53import java.security.GeneralSecurityException;
54import java.security.PublicKey;
Doug Zongker1af33d02010-01-05 11:28:55 -080055import java.security.SignatureException;
Doug Zongker1af33d02010-01-05 11:28:55 -080056import java.security.cert.CertificateFactory;
57import java.security.cert.X509Certificate;
Tao Baoe01b5202017-04-11 00:28:34 -070058import java.util.ArrayList;
Doug Zongker1af33d02010-01-05 11:28:55 -080059import java.util.Enumeration;
60import java.util.HashSet;
Qingxi Lie0662472019-05-17 15:16:21 -070061import java.util.List;
Doug Zongkere33b4002012-08-23 09:52:14 -070062import java.util.Locale;
yinxuf4f9cec2017-06-19 10:28:19 -070063import java.util.concurrent.CountDownLatch;
64import java.util.concurrent.TimeUnit;
Qingxi Lic6097db2018-01-09 10:57:38 -080065import java.util.concurrent.atomic.AtomicBoolean;
Qingxi Lie0662472019-05-17 15:16:21 -070066import java.util.concurrent.atomic.AtomicInteger;
Doug Zongker1af33d02010-01-05 11:28:55 -080067import java.util.zip.ZipEntry;
68import java.util.zip.ZipFile;
Tao Baoe01b5202017-04-11 00:28:34 -070069import java.util.zip.ZipInputStream;
Doug Zongker1af33d02010-01-05 11:28:55 -080070
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +010071import sun.security.pkcs.PKCS7;
72import sun.security.pkcs.SignerInfo;
Doug Zongker1af33d02010-01-05 11:28:55 -080073
74/**
75 * RecoverySystem contains methods for interacting with the Android
76 * recovery system (the separate partition that can be used to install
77 * system updates, wipe user data, etc.)
Doug Zongker1af33d02010-01-05 11:28:55 -080078 */
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -060079@SystemService(Context.RECOVERY_SERVICE)
Doug Zongker1af33d02010-01-05 11:28:55 -080080public class RecoverySystem {
81 private static final String TAG = "RecoverySystem";
82
83 /**
84 * Default location of zip file containing public keys (X509
85 * certs) authorized to sign OTA updates.
86 */
87 private static final File DEFAULT_KEYSTORE =
88 new File("/system/etc/security/otacerts.zip");
89
90 /** Send progress to listeners no more often than this (in ms). */
91 private static final long PUBLISH_PROGRESS_INTERVAL_MS = 500;
92
qingxie060ffd2017-06-23 15:32:53 -070093 private static final long DEFAULT_EUICC_FACTORY_RESET_TIMEOUT_MILLIS = 30000L; // 30 s
qingxie060ffd2017-06-23 15:32:53 -070094 private static final long MIN_EUICC_FACTORY_RESET_TIMEOUT_MILLIS = 5000L; // 5 s
qingxie060ffd2017-06-23 15:32:53 -070095 private static final long MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS = 60000L; // 60 s
yinxuf4f9cec2017-06-19 10:28:19 -070096
Qingxi Lie0662472019-05-17 15:16:21 -070097 private static final long DEFAULT_EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS =
98 45000L; // 45 s
99 private static final long MIN_EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS = 15000L; // 15 s
100 private static final long MAX_EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS = 90000L; // 90 s
101
Tao Bao5065e122015-08-18 12:37:02 -0700102 /** Used to communicate with recovery. See bootable/recovery/recovery.cpp. */
Tao Baoe8a403d2015-12-31 07:44:55 -0800103 private static final File RECOVERY_DIR = new File("/cache/recovery");
104 private static final File LOG_FILE = new File(RECOVERY_DIR, "log");
Tianjie Xud12e1502018-09-06 14:26:33 -0700105 private static final String LAST_INSTALL_PATH = "last_install";
Tao Baoe8a403d2015-12-31 07:44:55 -0800106 private static final String LAST_PREFIX = "last_";
qingxie060ffd2017-06-23 15:32:53 -0700107 private static final String ACTION_EUICC_FACTORY_RESET =
108 "com.android.internal.action.EUICC_FACTORY_RESET";
Qingxi Lie0662472019-05-17 15:16:21 -0700109 private static final String ACTION_EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS =
110 "com.android.internal.action.EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS";
Tao Baoe8a403d2015-12-31 07:44:55 -0800111
Qingxi Lie0662472019-05-17 15:16:21 -0700112 /**
113 * Used in {@link #wipeEuiccData} & {@link #removeEuiccInvisibleSubs} as package name of
114 * callback intent.
115 */
116 private static final String PACKAGE_NAME_EUICC_DATA_MANAGEMENT_CALLBACK = "android";
Qingxi Lic6097db2018-01-09 10:57:38 -0800117
Tao Baoe8a403d2015-12-31 07:44:55 -0800118 /**
119 * The recovery image uses this file to identify the location (i.e. blocks)
120 * of an OTA package on the /data partition. The block map file is
121 * generated by uncrypt.
122 *
123 * @hide
124 */
125 public static final File BLOCK_MAP_FILE = new File(RECOVERY_DIR, "block.map");
126
127 /**
128 * UNCRYPT_PACKAGE_FILE stores the filename to be uncrypt'd, which will be
129 * read by uncrypt.
130 *
131 * @hide
132 */
133 public static final File UNCRYPT_PACKAGE_FILE = new File(RECOVERY_DIR, "uncrypt_file");
Doug Zongker1af33d02010-01-05 11:28:55 -0800134
Tianjie Xu036d0862016-09-24 15:39:56 -0700135 /**
136 * UNCRYPT_STATUS_FILE stores the time cost (and error code in the case of a failure)
137 * of uncrypt.
138 *
139 * @hide
140 */
141 public static final File UNCRYPT_STATUS_FILE = new File(RECOVERY_DIR, "uncrypt_status");
142
Doug Zongker1af33d02010-01-05 11:28:55 -0800143 // Length limits for reading files.
Tao Baoe8a403d2015-12-31 07:44:55 -0800144 private static final int LOG_FILE_MAX_LENGTH = 64 * 1024;
145
146 // Prevent concurrent execution of requests.
147 private static final Object sRequestLock = new Object();
148
149 private final IRecoverySystem mService;
Doug Zongker1af33d02010-01-05 11:28:55 -0800150
151 /**
152 * Interface definition for a callback to be invoked regularly as
153 * verification proceeds.
154 */
155 public interface ProgressListener {
156 /**
157 * Called periodically as the verification progresses.
158 *
159 * @param progress the approximate percentage of the
160 * verification that has been completed, ranging from 0
161 * to 100 (inclusive).
162 */
163 public void onProgress(int progress);
164 }
165
166 /** @return the set of certs that can be used to sign an OTA package. */
Kenny Root27e54942013-10-14 11:17:25 -0700167 private static HashSet<X509Certificate> getTrustedCerts(File keystore)
Doug Zongker1af33d02010-01-05 11:28:55 -0800168 throws IOException, GeneralSecurityException {
Kenny Root27e54942013-10-14 11:17:25 -0700169 HashSet<X509Certificate> trusted = new HashSet<X509Certificate>();
Doug Zongker1af33d02010-01-05 11:28:55 -0800170 if (keystore == null) {
171 keystore = DEFAULT_KEYSTORE;
172 }
173 ZipFile zip = new ZipFile(keystore);
174 try {
175 CertificateFactory cf = CertificateFactory.getInstance("X.509");
176 Enumeration<? extends ZipEntry> entries = zip.entries();
177 while (entries.hasMoreElements()) {
178 ZipEntry entry = entries.nextElement();
Doug Zongkere2d58e92011-11-04 13:39:47 -0700179 InputStream is = zip.getInputStream(entry);
180 try {
Kenny Root27e54942013-10-14 11:17:25 -0700181 trusted.add((X509Certificate) cf.generateCertificate(is));
Doug Zongkere2d58e92011-11-04 13:39:47 -0700182 } finally {
183 is.close();
184 }
Doug Zongker1af33d02010-01-05 11:28:55 -0800185 }
186 } finally {
187 zip.close();
188 }
189 return trusted;
190 }
191
192 /**
193 * Verify the cryptographic signature of a system update package
194 * before installing it. Note that the package is also verified
195 * separately by the installer once the device is rebooted into
196 * the recovery system. This function will return only if the
197 * package was successfully verified; otherwise it will throw an
198 * exception.
199 *
200 * Verification of a package can take significant time, so this
Doug Zongkercb956572010-03-11 10:27:21 -0800201 * function should not be called from a UI thread. Interrupting
202 * the thread while this function is in progress will result in a
203 * SecurityException being thrown (and the thread's interrupt flag
204 * will be cleared).
Doug Zongker1af33d02010-01-05 11:28:55 -0800205 *
206 * @param packageFile the package to be verified
207 * @param listener an object to receive periodic progress
208 * updates as verification proceeds. May be null.
209 * @param deviceCertsZipFile the zip file of certificates whose
210 * public keys we will accept. Verification succeeds if the
211 * package is signed by the private key corresponding to any
212 * public key in this file. May be null to use the system default
213 * file (currently "/system/etc/security/otacerts.zip").
214 *
215 * @throws IOException if there were any errors reading the
216 * package or certs files.
217 * @throws GeneralSecurityException if verification failed
218 */
219 public static void verifyPackage(File packageFile,
220 ProgressListener listener,
221 File deviceCertsZipFile)
222 throws IOException, GeneralSecurityException {
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100223 final long fileLen = packageFile.length();
Doug Zongker1af33d02010-01-05 11:28:55 -0800224
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100225 final RandomAccessFile raf = new RandomAccessFile(packageFile, "r");
Doug Zongker1af33d02010-01-05 11:28:55 -0800226 try {
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100227 final long startTimeMillis = System.currentTimeMillis();
Doug Zongker1af33d02010-01-05 11:28:55 -0800228 if (listener != null) {
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100229 listener.onProgress(0);
Doug Zongker1af33d02010-01-05 11:28:55 -0800230 }
231
232 raf.seek(fileLen - 6);
233 byte[] footer = new byte[6];
234 raf.readFully(footer);
235
236 if (footer[2] != (byte)0xff || footer[3] != (byte)0xff) {
237 throw new SignatureException("no signature in file (no footer)");
238 }
239
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100240 final int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8);
241 final int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8);
Doug Zongker1af33d02010-01-05 11:28:55 -0800242
243 byte[] eocd = new byte[commentSize + 22];
244 raf.seek(fileLen - (commentSize + 22));
245 raf.readFully(eocd);
246
247 // Check that we have found the start of the
248 // end-of-central-directory record.
249 if (eocd[0] != (byte)0x50 || eocd[1] != (byte)0x4b ||
250 eocd[2] != (byte)0x05 || eocd[3] != (byte)0x06) {
251 throw new SignatureException("no signature in file (bad footer)");
252 }
253
254 for (int i = 4; i < eocd.length-3; ++i) {
255 if (eocd[i ] == (byte)0x50 && eocd[i+1] == (byte)0x4b &&
256 eocd[i+2] == (byte)0x05 && eocd[i+3] == (byte)0x06) {
257 throw new SignatureException("EOCD marker found after start of EOCD");
258 }
259 }
260
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100261 // Parse the signature
262 PKCS7 block =
263 new PKCS7(new ByteArrayInputStream(eocd, commentSize+22-signatureStart, signatureStart));
Doug Zongker1af33d02010-01-05 11:28:55 -0800264
Doug Zongker1af33d02010-01-05 11:28:55 -0800265 // Take the first certificate from the signature (packages
266 // should contain only one).
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100267 X509Certificate[] certificates = block.getCertificates();
268 if (certificates == null || certificates.length == 0) {
Doug Zongker1af33d02010-01-05 11:28:55 -0800269 throw new SignatureException("signature contains no certificates");
270 }
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100271 X509Certificate cert = certificates[0];
272 PublicKey signatureKey = cert.getPublicKey();
Doug Zongker1af33d02010-01-05 11:28:55 -0800273
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100274 SignerInfo[] signerInfos = block.getSignerInfos();
275 if (signerInfos == null || signerInfos.length == 0) {
276 throw new SignatureException("signature contains no signedData");
Doug Zongker1af33d02010-01-05 11:28:55 -0800277 }
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100278 SignerInfo signerInfo = signerInfos[0];
Doug Zongker1af33d02010-01-05 11:28:55 -0800279
280 // Check that the public key of the certificate contained
281 // in the package equals one of our trusted public keys.
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100282 boolean verified = false;
Kenny Root27e54942013-10-14 11:17:25 -0700283 HashSet<X509Certificate> trusted = getTrustedCerts(
Doug Zongker1af33d02010-01-05 11:28:55 -0800284 deviceCertsZipFile == null ? DEFAULT_KEYSTORE : deviceCertsZipFile);
Kenny Root27e54942013-10-14 11:17:25 -0700285 for (X509Certificate c : trusted) {
Doug Zongker1af33d02010-01-05 11:28:55 -0800286 if (c.getPublicKey().equals(signatureKey)) {
287 verified = true;
288 break;
289 }
290 }
291 if (!verified) {
292 throw new SignatureException("signature doesn't match any trusted key");
293 }
294
295 // The signature cert matches a trusted key. Now verify that
296 // the digest in the cert matches the actual file data.
Doug Zongker1af33d02010-01-05 11:28:55 -0800297 raf.seek(0);
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100298 final ProgressListener listenerForInner = listener;
299 SignerInfo verifyResult = block.verify(signerInfo, new InputStream() {
300 // The signature covers all of the OTA package except the
301 // archive comment and its 2-byte length.
302 long toRead = fileLen - commentSize - 2;
303 long soFar = 0;
Doug Zongker1af33d02010-01-05 11:28:55 -0800304
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100305 int lastPercent = 0;
306 long lastPublishTime = startTimeMillis;
307
308 @Override
309 public int read() throws IOException {
310 throw new UnsupportedOperationException();
Doug Zongker1af33d02010-01-05 11:28:55 -0800311 }
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100312
313 @Override
314 public int read(byte[] b, int off, int len) throws IOException {
315 if (soFar >= toRead) {
316 return -1;
317 }
318 if (Thread.currentThread().isInterrupted()) {
319 return -1;
320 }
321
322 int size = len;
323 if (soFar + size > toRead) {
324 size = (int)(toRead - soFar);
325 }
326 int read = raf.read(b, off, size);
327 soFar += read;
328
329 if (listenerForInner != null) {
330 long now = System.currentTimeMillis();
331 int p = (int)(soFar * 100 / toRead);
332 if (p > lastPercent &&
333 now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
334 lastPercent = p;
335 lastPublishTime = now;
336 listenerForInner.onProgress(lastPercent);
337 }
338 }
339
340 return read;
341 }
342 });
343
344 final boolean interrupted = Thread.interrupted();
Doug Zongker1af33d02010-01-05 11:28:55 -0800345 if (listener != null) {
346 listener.onProgress(100);
347 }
348
Doug Zongkercb956572010-03-11 10:27:21 -0800349 if (interrupted) {
350 throw new SignatureException("verification was interrupted");
351 }
352
Przemyslaw Szczepaniak9ad08ec2015-07-09 13:28:30 +0100353 if (verifyResult == null) {
Doug Zongker1af33d02010-01-05 11:28:55 -0800354 throw new SignatureException("signature digest verification failed");
355 }
356 } finally {
357 raf.close();
358 }
Tao Baoe01b5202017-04-11 00:28:34 -0700359
360 // Additionally verify the package compatibility.
361 if (!readAndVerifyPackageCompatibilityEntry(packageFile)) {
362 throw new SignatureException("package compatibility verification failed");
363 }
364 }
365
366 /**
367 * Verifies the compatibility entry from an {@link InputStream}.
368 *
369 * @return the verification result.
370 */
Andrei Onea24ec3212019-03-15 17:35:05 +0000371 @UnsupportedAppUsage
Tao Baoe01b5202017-04-11 00:28:34 -0700372 private static boolean verifyPackageCompatibility(InputStream inputStream) throws IOException {
373 ArrayList<String> list = new ArrayList<>();
374 ZipInputStream zis = new ZipInputStream(inputStream);
375 ZipEntry entry;
376 while ((entry = zis.getNextEntry()) != null) {
377 long entrySize = entry.getSize();
378 if (entrySize > Integer.MAX_VALUE || entrySize < 0) {
379 throw new IOException(
380 "invalid entry size (" + entrySize + ") in the compatibility file");
381 }
382 byte[] bytes = new byte[(int) entrySize];
383 Streams.readFully(zis, bytes);
384 list.add(new String(bytes, UTF_8));
385 }
386 if (list.isEmpty()) {
387 throw new IOException("no entries found in the compatibility file");
388 }
Tao Bao87daeb152017-04-18 15:53:40 -0700389 return (VintfObject.verify(list.toArray(new String[list.size()])) == 0);
Tao Baoe01b5202017-04-11 00:28:34 -0700390 }
391
392 /**
393 * Reads and verifies the compatibility entry in an OTA zip package. The compatibility entry is
394 * a zip file (inside the OTA package zip).
395 *
396 * @return {@code true} if the entry doesn't exist or verification passes.
397 */
398 private static boolean readAndVerifyPackageCompatibilityEntry(File packageFile)
399 throws IOException {
400 try (ZipFile zip = new ZipFile(packageFile)) {
401 ZipEntry entry = zip.getEntry("compatibility.zip");
402 if (entry == null) {
403 return true;
404 }
405 InputStream inputStream = zip.getInputStream(entry);
406 return verifyPackageCompatibility(inputStream);
407 }
408 }
409
410 /**
411 * Verifies the package compatibility info against the current system.
412 *
413 * @param compatibilityFile the {@link File} that contains the package compatibility info.
414 * @throws IOException if there were any errors reading the compatibility file.
415 * @return the compatibility verification result.
416 *
417 * {@hide}
418 */
419 @SystemApi
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600420 @SuppressLint("Doclava125")
Tao Baoe01b5202017-04-11 00:28:34 -0700421 public static boolean verifyPackageCompatibility(File compatibilityFile) throws IOException {
422 try (InputStream inputStream = new FileInputStream(compatibilityFile)) {
423 return verifyPackageCompatibility(inputStream);
424 }
Doug Zongker1af33d02010-01-05 11:28:55 -0800425 }
426
427 /**
Tao Baoe8a403d2015-12-31 07:44:55 -0800428 * Process a given package with uncrypt. No-op if the package is not on the
429 * /data partition.
430 *
431 * @param Context the Context to use
432 * @param packageFile the package to be processed
433 * @param listener an object to receive periodic progress updates as
434 * processing proceeds. May be null.
435 * @param handler the Handler upon which the callbacks will be
436 * executed.
437 *
438 * @throws IOException if there were any errors processing the package file.
439 *
440 * @hide
441 */
442 @SystemApi
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600443 @RequiresPermission(android.Manifest.permission.RECOVERY)
Tao Baoe8a403d2015-12-31 07:44:55 -0800444 public static void processPackage(Context context,
445 File packageFile,
446 final ProgressListener listener,
447 final Handler handler)
448 throws IOException {
449 String filename = packageFile.getCanonicalPath();
450 if (!filename.startsWith("/data/")) {
451 return;
452 }
453
454 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
455 IRecoverySystemProgressListener progressListener = null;
456 if (listener != null) {
457 final Handler progressHandler;
458 if (handler != null) {
459 progressHandler = handler;
460 } else {
461 progressHandler = new Handler(context.getMainLooper());
462 }
463 progressListener = new IRecoverySystemProgressListener.Stub() {
464 int lastProgress = 0;
465 long lastPublishTime = System.currentTimeMillis();
466
467 @Override
468 public void onProgress(final int progress) {
469 final long now = System.currentTimeMillis();
470 progressHandler.post(new Runnable() {
471 @Override
472 public void run() {
473 if (progress > lastProgress &&
474 now - lastPublishTime > PUBLISH_PROGRESS_INTERVAL_MS) {
475 lastProgress = progress;
476 lastPublishTime = now;
477 listener.onProgress(progress);
478 }
479 }
480 });
481 }
482 };
483 }
484
485 if (!rs.uncrypt(filename, progressListener)) {
486 throw new IOException("process package failed");
487 }
488 }
489
490 /**
491 * Process a given package with uncrypt. No-op if the package is not on the
492 * /data partition.
493 *
494 * @param Context the Context to use
495 * @param packageFile the package to be processed
496 * @param listener an object to receive periodic progress updates as
497 * processing proceeds. May be null.
498 *
499 * @throws IOException if there were any errors processing the package file.
500 *
501 * @hide
502 */
503 @SystemApi
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600504 @RequiresPermission(android.Manifest.permission.RECOVERY)
Tao Baoe8a403d2015-12-31 07:44:55 -0800505 public static void processPackage(Context context,
506 File packageFile,
507 final ProgressListener listener)
508 throws IOException {
509 processPackage(context, packageFile, listener, null);
510 }
511
512 /**
Doug Zongker1af33d02010-01-05 11:28:55 -0800513 * Reboots the device in order to install the given update
514 * package.
Jeff Brown64010e82010-04-08 16:52:18 -0700515 * Requires the {@link android.Manifest.permission#REBOOT} permission.
Doug Zongker1af33d02010-01-05 11:28:55 -0800516 *
517 * @param context the Context to use
Doug Zongker4baf6412010-09-20 13:09:57 -0700518 * @param packageFile the update package to install. Must be on
519 * a partition mountable by recovery. (The set of partitions
520 * known to recovery may vary from device to device. Generally,
521 * /cache and /data are safe.)
Doug Zongker1af33d02010-01-05 11:28:55 -0800522 *
523 * @throws IOException if writing the recovery command file
524 * fails, or if the reboot itself fails.
525 */
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600526 @RequiresPermission(android.Manifest.permission.RECOVERY)
Doug Zongker1af33d02010-01-05 11:28:55 -0800527 public static void installPackage(Context context, File packageFile)
Tao Baoe8a403d2015-12-31 07:44:55 -0800528 throws IOException {
529 installPackage(context, packageFile, false);
530 }
531
532 /**
533 * If the package hasn't been processed (i.e. uncrypt'd), set up
534 * UNCRYPT_PACKAGE_FILE and delete BLOCK_MAP_FILE to trigger uncrypt during the
535 * reboot.
536 *
537 * @param context the Context to use
538 * @param packageFile the update package to install. Must be on a
539 * partition mountable by recovery.
540 * @param processed if the package has been processed (uncrypt'd).
541 *
542 * @throws IOException if writing the recovery command file fails, or if
543 * the reboot itself fails.
544 *
545 * @hide
546 */
547 @SystemApi
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600548 @RequiresPermission(android.Manifest.permission.RECOVERY)
Tao Baoe8a403d2015-12-31 07:44:55 -0800549 public static void installPackage(Context context, File packageFile, boolean processed)
550 throws IOException {
551 synchronized (sRequestLock) {
552 LOG_FILE.delete();
553 // Must delete the file in case it was created by system server.
554 UNCRYPT_PACKAGE_FILE.delete();
555
556 String filename = packageFile.getCanonicalPath();
557 Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
558
Tianjie Xuac75f1e2016-04-28 16:44:42 -0700559 // If the package name ends with "_s.zip", it's a security update.
560 boolean securityUpdate = filename.endsWith("_s.zip");
561
Tao Bao36baafe2016-03-14 17:11:05 -0700562 // If the package is on the /data partition, the package needs to
563 // be processed (i.e. uncrypt'd). The caller specifies if that has
564 // been done in 'processed' parameter.
Tao Baoe8a403d2015-12-31 07:44:55 -0800565 if (filename.startsWith("/data/")) {
Tao Bao36baafe2016-03-14 17:11:05 -0700566 if (processed) {
567 if (!BLOCK_MAP_FILE.exists()) {
568 Log.e(TAG, "Package claimed to have been processed but failed to find "
569 + "the block map file.");
570 throw new IOException("Failed to find block map file");
571 }
572 } else {
573 FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE);
574 try {
575 uncryptFile.write(filename + "\n");
576 } finally {
577 uncryptFile.close();
578 }
579 // UNCRYPT_PACKAGE_FILE needs to be readable and writable
580 // by system server.
581 if (!UNCRYPT_PACKAGE_FILE.setReadable(true, false)
582 || !UNCRYPT_PACKAGE_FILE.setWritable(true, false)) {
583 Log.e(TAG, "Error setting permission for " + UNCRYPT_PACKAGE_FILE);
584 }
585
586 BLOCK_MAP_FILE.delete();
587 }
588
589 // If the package is on the /data partition, use the block map
590 // file as the package name instead.
Tao Baoe8a403d2015-12-31 07:44:55 -0800591 filename = "@/cache/recovery/block.map";
592 }
593
594 final String filenameArg = "--update_package=" + filename + "\n";
Tianjie Xub1b38b32017-03-28 20:12:14 +0000595 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n";
Tianjie Xuac75f1e2016-04-28 16:44:42 -0700596 final String securityArg = "--security\n";
597
598 String command = filenameArg + localeArg;
599 if (securityUpdate) {
600 command += securityArg;
601 }
Tao Baoe8a403d2015-12-31 07:44:55 -0800602
603 RecoverySystem rs = (RecoverySystem) context.getSystemService(
604 Context.RECOVERY_SERVICE);
Tao Baocc769912017-01-17 12:42:43 -0800605 if (!rs.setupBcb(command)) {
606 throw new IOException("Setup BCB failed");
607 }
608
609 // Having set up the BCB (bootloader control block), go ahead and reboot
610 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
Dmitri Plotnikov690c6bd2017-05-10 16:26:38 -0700611 String reason = PowerManager.REBOOT_RECOVERY_UPDATE;
612
613 // On TV, reboot quiescently if the screen is off
614 if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
615 WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
616 if (wm.getDefaultDisplay().getState() != Display.STATE_ON) {
617 reason += ",quiescent";
618 }
619 }
620 pm.reboot(reason);
Tao Baoe8a403d2015-12-31 07:44:55 -0800621
622 throw new IOException("Reboot failed (no permissions?)");
623 }
624 }
625
626 /**
627 * Schedule to install the given package on next boot. The caller needs to
628 * ensure that the package must have been processed (uncrypt'd) if needed.
629 * It sets up the command in BCB (bootloader control block), which will
630 * be read by the bootloader and the recovery image.
631 *
632 * @param Context the Context to use.
633 * @param packageFile the package to be installed.
634 *
635 * @throws IOException if there were any errors setting up the BCB.
636 *
637 * @hide
638 */
639 @SystemApi
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600640 @RequiresPermission(android.Manifest.permission.RECOVERY)
Tao Baoe8a403d2015-12-31 07:44:55 -0800641 public static void scheduleUpdateOnBoot(Context context, File packageFile)
642 throws IOException {
Doug Zongker1af33d02010-01-05 11:28:55 -0800643 String filename = packageFile.getCanonicalPath();
Tianjie Xuac75f1e2016-04-28 16:44:42 -0700644 boolean securityUpdate = filename.endsWith("_s.zip");
Tao Bao90237f72015-05-21 16:25:19 -0700645
Tao Baoe8a403d2015-12-31 07:44:55 -0800646 // If the package is on the /data partition, use the block map file as
647 // the package name instead.
Tao Bao90237f72015-05-21 16:25:19 -0700648 if (filename.startsWith("/data/")) {
649 filename = "@/cache/recovery/block.map";
650 }
651
Tao Baoe8a403d2015-12-31 07:44:55 -0800652 final String filenameArg = "--update_package=" + filename + "\n";
Tianjie Xub1b38b32017-03-28 20:12:14 +0000653 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n";
Tianjie Xuac75f1e2016-04-28 16:44:42 -0700654 final String securityArg = "--security\n";
655
656 String command = filenameArg + localeArg;
657 if (securityUpdate) {
658 command += securityArg;
659 }
Tao Baoe8a403d2015-12-31 07:44:55 -0800660
661 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
662 if (!rs.setupBcb(command)) {
663 throw new IOException("schedule update on boot failed");
664 }
665 }
666
667 /**
668 * Cancel any scheduled update by clearing up the BCB (bootloader control
669 * block).
670 *
671 * @param Context the Context to use.
672 *
673 * @throws IOException if there were any errors clearing up the BCB.
674 *
675 * @hide
676 */
677 @SystemApi
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -0600678 @RequiresPermission(android.Manifest.permission.RECOVERY)
Tao Baoe8a403d2015-12-31 07:44:55 -0800679 public static void cancelScheduledUpdate(Context context)
680 throws IOException {
681 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
682 if (!rs.clearBcb()) {
683 throw new IOException("cancel scheduled update failed");
684 }
Doug Zongker1af33d02010-01-05 11:28:55 -0800685 }
686
687 /**
Doug Zongkercdf00882014-03-18 12:52:04 -0700688 * Reboots the device and wipes the user data and cache
689 * partitions. This is sometimes called a "factory reset", which
690 * is something of a misnomer because the system partition is not
691 * restored to its factory state. Requires the
692 * {@link android.Manifest.permission#REBOOT} permission.
Doug Zongker1af33d02010-01-05 11:28:55 -0800693 *
694 * @param context the Context to use
695 *
696 * @throws IOException if writing the recovery command file
697 * fails, or if the reboot itself fails.
Julia Reynoldsfe053802014-06-30 11:41:32 -0400698 * @throws SecurityException if the current user is not allowed to wipe data.
Doug Zongker1af33d02010-01-05 11:28:55 -0800699 */
Jason parks4ca74dc2011-03-14 15:23:31 -0500700 public static void rebootWipeUserData(Context context) throws IOException {
Benjamin Franzf9d5e6a2016-05-26 14:24:29 +0100701 rebootWipeUserData(context, false /* shutdown */, context.getPackageName(),
yinxuf4f9cec2017-06-19 10:28:19 -0700702 false /* force */, false /* wipeEuicc */);
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700703 }
704
705 /** {@hide} */
706 public static void rebootWipeUserData(Context context, String reason) throws IOException {
yinxuf4f9cec2017-06-19 10:28:19 -0700707 rebootWipeUserData(context, false /* shutdown */, reason, false /* force */,
708 false /* wipeEuicc */);
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700709 }
710
711 /** {@hide} */
712 public static void rebootWipeUserData(Context context, boolean shutdown)
713 throws IOException {
yinxuf4f9cec2017-06-19 10:28:19 -0700714 rebootWipeUserData(context, shutdown, context.getPackageName(), false /* force */,
715 false /* wipeEuicc */);
716 }
717
718 /** {@hide} */
719 public static void rebootWipeUserData(Context context, boolean shutdown, String reason,
720 boolean force) throws IOException {
721 rebootWipeUserData(context, shutdown, reason, force, false /* wipeEuicc */);
Doug Zongkercdf00882014-03-18 12:52:04 -0700722 }
723
724 /**
725 * Reboots the device and wipes the user data and cache
726 * partitions. This is sometimes called a "factory reset", which
727 * is something of a misnomer because the system partition is not
728 * restored to its factory state. Requires the
729 * {@link android.Manifest.permission#REBOOT} permission.
730 *
731 * @param context the Context to use
732 * @param shutdown if true, the device will be powered down after
733 * the wipe completes, rather than being rebooted
734 * back to the regular system.
Benjamin Franzf9d5e6a2016-05-26 14:24:29 +0100735 * @param reason the reason for the wipe that is visible in the logs
736 * @param force whether the {@link UserManager.DISALLOW_FACTORY_RESET} user restriction
737 * should be ignored
yinxuf4f9cec2017-06-19 10:28:19 -0700738 * @param wipeEuicc whether wipe the euicc data
Doug Zongkercdf00882014-03-18 12:52:04 -0700739 *
740 * @throws IOException if writing the recovery command file
741 * fails, or if the reboot itself fails.
Julia Reynoldsfe053802014-06-30 11:41:32 -0400742 * @throws SecurityException if the current user is not allowed to wipe data.
Doug Zongkercdf00882014-03-18 12:52:04 -0700743 *
744 * @hide
745 */
Benjamin Franzf9d5e6a2016-05-26 14:24:29 +0100746 public static void rebootWipeUserData(Context context, boolean shutdown, String reason,
yinxuf4f9cec2017-06-19 10:28:19 -0700747 boolean force, boolean wipeEuicc) throws IOException {
Julia Reynoldsfe053802014-06-30 11:41:32 -0400748 UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
Benjamin Franzf9d5e6a2016-05-26 14:24:29 +0100749 if (!force && um.hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET)) {
Julia Reynoldsfe053802014-06-30 11:41:32 -0400750 throw new SecurityException("Wiping data is not allowed for this user.");
751 }
Jason parks4ca74dc2011-03-14 15:23:31 -0500752 final ConditionVariable condition = new ConditionVariable();
753
754 Intent intent = new Intent("android.intent.action.MASTER_CLEAR_NOTIFICATION");
Christopher Tate2d6c9452017-01-25 14:49:44 -0800755 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
756 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
Xiaohui Chene4de5a02015-09-22 15:33:31 -0700757 context.sendOrderedBroadcastAsUser(intent, UserHandle.SYSTEM,
Dianne Hackborn5ac72a22012-08-29 18:32:08 -0700758 android.Manifest.permission.MASTER_CLEAR,
Jason parks4ca74dc2011-03-14 15:23:31 -0500759 new BroadcastReceiver() {
760 @Override
761 public void onReceive(Context context, Intent intent) {
762 condition.open();
763 }
764 }, null, 0, null, null);
765
766 // Block until the ordered broadcast has completed.
767 condition.block();
768
Qingxi Lie0662472019-05-17 15:16:21 -0700769 EuiccManager euiccManager = context.getSystemService(EuiccManager.class);
Qingxi Lif4577a52018-01-11 11:24:29 -0800770 if (wipeEuicc) {
Qingxi Lie0662472019-05-17 15:16:21 -0700771 wipeEuiccData(context, PACKAGE_NAME_EUICC_DATA_MANAGEMENT_CALLBACK);
772 } else {
773 removeEuiccInvisibleSubs(context, euiccManager);
Qingxi Lif4577a52018-01-11 11:24:29 -0800774 }
yinxuf4f9cec2017-06-19 10:28:19 -0700775
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700776 String shutdownArg = null;
Doug Zongkercdf00882014-03-18 12:52:04 -0700777 if (shutdown) {
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700778 shutdownArg = "--shutdown_after";
Doug Zongkercdf00882014-03-18 12:52:04 -0700779 }
780
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700781 String reasonArg = null;
782 if (!TextUtils.isEmpty(reason)) {
Yutaro Maruyama3835f6a2018-03-30 12:36:37 +0800783 String timeStamp = DateFormat.format("yyyy-MM-ddTHH:mm:ssZ", System.currentTimeMillis()).toString();
784 reasonArg = "--reason=" + sanitizeArg(reason + "," + timeStamp);
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700785 }
786
Tianjie Xub1b38b32017-03-28 20:12:14 +0000787 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
Jeff Sharkey004a4b22014-09-24 11:45:24 -0700788 bootCommand(context, shutdownArg, "--wipe_data", reasonArg, localeArg);
Doug Zongker1af33d02010-01-05 11:28:55 -0800789 }
790
Qingxi Lic6097db2018-01-09 10:57:38 -0800791 /**
792 * Returns whether wipe Euicc data successfully or not.
793 *
Qingxi Lic6097db2018-01-09 10:57:38 -0800794 * @param packageName the package name of the caller app.
795 *
796 * @hide
797 */
Qingxi Lif4577a52018-01-11 11:24:29 -0800798 public static boolean wipeEuiccData(Context context, final String packageName) {
Jeff Davidsone05f37e2017-07-19 16:55:45 -0700799 ContentResolver cr = context.getContentResolver();
800 if (Settings.Global.getInt(cr, Settings.Global.EUICC_PROVISIONED, 0) == 0) {
801 // If the eUICC isn't provisioned, there's no reason to either wipe or retain profiles,
802 // as there's nothing to wipe nor retain.
803 Log.d(TAG, "Skipping eUICC wipe/retain as it is not provisioned");
Qingxi Lic6097db2018-01-09 10:57:38 -0800804 return true;
Jeff Davidsone05f37e2017-07-19 16:55:45 -0700805 }
806
yinxuf4f9cec2017-06-19 10:28:19 -0700807 EuiccManager euiccManager = (EuiccManager) context.getSystemService(
808 Context.EUICC_SERVICE);
809 if (euiccManager != null && euiccManager.isEnabled()) {
810 CountDownLatch euiccFactoryResetLatch = new CountDownLatch(1);
Qingxi Lic6097db2018-01-09 10:57:38 -0800811 final AtomicBoolean wipingSucceeded = new AtomicBoolean(false);
yinxuf4f9cec2017-06-19 10:28:19 -0700812
813 BroadcastReceiver euiccWipeFinishReceiver = new BroadcastReceiver() {
814 @Override
815 public void onReceive(Context context, Intent intent) {
qingxie060ffd2017-06-23 15:32:53 -0700816 if (ACTION_EUICC_FACTORY_RESET.equals(intent.getAction())) {
yinxuf4f9cec2017-06-19 10:28:19 -0700817 if (getResultCode() != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) {
818 int detailedCode = intent.getIntExtra(
819 EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0);
Qingxi Lif4577a52018-01-11 11:24:29 -0800820 Log.e(TAG, "Error wiping euicc data, Detailed code = "
821 + detailedCode);
yinxuf4f9cec2017-06-19 10:28:19 -0700822 } else {
Qingxi Lif4577a52018-01-11 11:24:29 -0800823 Log.d(TAG, "Successfully wiped euicc data.");
Qingxi Lic6097db2018-01-09 10:57:38 -0800824 wipingSucceeded.set(true /* newValue */);
yinxuf4f9cec2017-06-19 10:28:19 -0700825 }
826 euiccFactoryResetLatch.countDown();
827 }
828 }
829 };
830
qingxie060ffd2017-06-23 15:32:53 -0700831 Intent intent = new Intent(ACTION_EUICC_FACTORY_RESET);
Qingxi Lic6097db2018-01-09 10:57:38 -0800832 intent.setPackage(packageName);
yinxuf4f9cec2017-06-19 10:28:19 -0700833 PendingIntent callbackIntent = PendingIntent.getBroadcastAsUser(
834 context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, UserHandle.SYSTEM);
835 IntentFilter filterConsent = new IntentFilter();
qingxie060ffd2017-06-23 15:32:53 -0700836 filterConsent.addAction(ACTION_EUICC_FACTORY_RESET);
yinxuf4f9cec2017-06-19 10:28:19 -0700837 HandlerThread euiccHandlerThread = new HandlerThread("euiccWipeFinishReceiverThread");
838 euiccHandlerThread.start();
839 Handler euiccHandler = new Handler(euiccHandlerThread.getLooper());
qingxiaee0ab92017-07-12 13:15:51 -0700840 context.getApplicationContext()
841 .registerReceiver(euiccWipeFinishReceiver, filterConsent, null, euiccHandler);
Qingxi Lif4577a52018-01-11 11:24:29 -0800842 euiccManager.eraseSubscriptions(callbackIntent);
yinxuf4f9cec2017-06-19 10:28:19 -0700843 try {
844 long waitingTimeMillis = Settings.Global.getLong(
845 context.getContentResolver(),
qingxie060ffd2017-06-23 15:32:53 -0700846 Settings.Global.EUICC_FACTORY_RESET_TIMEOUT_MILLIS,
847 DEFAULT_EUICC_FACTORY_RESET_TIMEOUT_MILLIS);
848 if (waitingTimeMillis < MIN_EUICC_FACTORY_RESET_TIMEOUT_MILLIS) {
849 waitingTimeMillis = MIN_EUICC_FACTORY_RESET_TIMEOUT_MILLIS;
850 } else if (waitingTimeMillis > MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS) {
851 waitingTimeMillis = MAX_EUICC_FACTORY_RESET_TIMEOUT_MILLIS;
yinxuf4f9cec2017-06-19 10:28:19 -0700852 }
853 if (!euiccFactoryResetLatch.await(waitingTimeMillis, TimeUnit.MILLISECONDS)) {
Qingxi Lif4577a52018-01-11 11:24:29 -0800854 Log.e(TAG, "Timeout wiping eUICC data.");
Qingxi Lic6097db2018-01-09 10:57:38 -0800855 return false;
yinxuf4f9cec2017-06-19 10:28:19 -0700856 }
yinxuf4f9cec2017-06-19 10:28:19 -0700857 } catch (InterruptedException e) {
858 Thread.currentThread().interrupt();
Qingxi Lif4577a52018-01-11 11:24:29 -0800859 Log.e(TAG, "Wiping eUICC data interrupted", e);
Qingxi Lic6097db2018-01-09 10:57:38 -0800860 return false;
861 } finally {
862 context.getApplicationContext().unregisterReceiver(euiccWipeFinishReceiver);
yinxuf4f9cec2017-06-19 10:28:19 -0700863 }
Qingxi Lic6097db2018-01-09 10:57:38 -0800864 return wipingSucceeded.get();
yinxuf4f9cec2017-06-19 10:28:19 -0700865 }
Qingxi Lic6097db2018-01-09 10:57:38 -0800866 return false;
yinxuf4f9cec2017-06-19 10:28:19 -0700867 }
868
Qingxi Lie0662472019-05-17 15:16:21 -0700869 private static void removeEuiccInvisibleSubs(
870 Context context, EuiccManager euiccManager) {
871 ContentResolver cr = context.getContentResolver();
872 if (Settings.Global.getInt(cr, Settings.Global.EUICC_PROVISIONED, 0) == 0) {
873 // If the eUICC isn't provisioned, there's no need to remove euicc invisible profiles,
874 // as there's nothing to be removed.
875 Log.i(TAG, "Skip removing eUICC invisible profiles as it is not provisioned.");
876 return;
877 } else if (euiccManager == null || !euiccManager.isEnabled()) {
878 Log.i(TAG, "Skip removing eUICC invisible profiles as eUICC manager is not available.");
879 return;
880 }
881 SubscriptionManager subscriptionManager =
882 context.getSystemService(SubscriptionManager.class);
883 List<SubscriptionInfo> availableSubs =
884 subscriptionManager.getAvailableSubscriptionInfoList();
885 if (availableSubs == null || availableSubs.isEmpty()) {
886 Log.i(TAG, "Skip removing eUICC invisible profiles as no available profiles found.");
887 return;
888 }
889 List<SubscriptionInfo> invisibleSubs = new ArrayList<>();
890 for (SubscriptionInfo sub : availableSubs) {
891 if (sub.isEmbedded() && !subscriptionManager.isSubscriptionVisible(sub)) {
892 invisibleSubs.add(sub);
893 }
894 }
895 removeEuiccInvisibleSubs(context, invisibleSubs, euiccManager);
896 }
897
898 private static boolean removeEuiccInvisibleSubs(
899 Context context, List<SubscriptionInfo> subscriptionInfos, EuiccManager euiccManager) {
900 if (subscriptionInfos == null || subscriptionInfos.isEmpty()) {
901 Log.i(TAG, "There are no eUICC invisible profiles needed to be removed.");
902 return true;
903 }
904 CountDownLatch removeSubsLatch = new CountDownLatch(subscriptionInfos.size());
905 final AtomicInteger removedSubsCount = new AtomicInteger(0);
906
907 BroadcastReceiver removeEuiccSubsReceiver = new BroadcastReceiver() {
908 @Override
909 public void onReceive(Context context, Intent intent) {
910 if (ACTION_EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS.equals(intent.getAction())) {
911 if (getResultCode() != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) {
912 int detailedCode = intent.getIntExtra(
913 EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0);
914 Log.e(TAG, "Error removing euicc opportunistic profile, Detailed code = "
915 + detailedCode);
916 } else {
917 Log.e(TAG, "Successfully remove euicc opportunistic profile.");
918 removedSubsCount.incrementAndGet();
919 }
920 removeSubsLatch.countDown();
921 }
922 }
923 };
924
925 Intent intent = new Intent(ACTION_EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS);
926 intent.setPackage(PACKAGE_NAME_EUICC_DATA_MANAGEMENT_CALLBACK);
927 PendingIntent callbackIntent = PendingIntent.getBroadcastAsUser(
928 context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, UserHandle.SYSTEM);
929 IntentFilter intentFilter = new IntentFilter();
930 intentFilter.addAction(ACTION_EUICC_REMOVE_INVISIBLE_SUBSCRIPTIONS);
931 HandlerThread euiccHandlerThread =
932 new HandlerThread("euiccRemovingSubsReceiverThread");
933 euiccHandlerThread.start();
934 Handler euiccHandler = new Handler(euiccHandlerThread.getLooper());
935 context.getApplicationContext()
936 .registerReceiver(
937 removeEuiccSubsReceiver, intentFilter, null, euiccHandler);
938 for (SubscriptionInfo subscriptionInfo : subscriptionInfos) {
939 Log.i(
940 TAG,
941 "Remove invisible subscription " + subscriptionInfo.getSubscriptionId()
942 + " from card " + subscriptionInfo.getCardId());
943 euiccManager.createForCardId(subscriptionInfo.getCardId())
944 .deleteSubscription(subscriptionInfo.getSubscriptionId(), callbackIntent);
945 }
946 try {
947 long waitingTimeMillis = Settings.Global.getLong(
948 context.getContentResolver(),
949 Settings.Global.EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS,
950 DEFAULT_EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS);
951 if (waitingTimeMillis < MIN_EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS) {
952 waitingTimeMillis = MIN_EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS;
953 } else if (waitingTimeMillis > MAX_EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS) {
954 waitingTimeMillis = MAX_EUICC_REMOVING_INVISIBLE_PROFILES_TIMEOUT_MILLIS;
955 }
956 if (!removeSubsLatch.await(waitingTimeMillis, TimeUnit.MILLISECONDS)) {
957 Log.e(TAG, "Timeout removing invisible euicc profiles.");
958 return false;
959 }
960 } catch (InterruptedException e) {
961 Thread.currentThread().interrupt();
962 Log.e(TAG, "Removing invisible euicc profiles interrupted", e);
963 return false;
964 } finally {
965 context.getApplicationContext().unregisterReceiver(removeEuiccSubsReceiver);
966 if (euiccHandlerThread != null) {
967 euiccHandlerThread.quit();
968 }
969 }
970 return removedSubsCount.get() == subscriptionInfos.size();
971 }
972
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -0700973 /** {@hide} */
974 public static void rebootPromptAndWipeUserData(Context context, String reason)
975 throws IOException {
Daniel Rosenberg8cbd24d2019-03-19 19:24:22 -0700976 boolean checkpointing = false;
Daniel Rosenbergd078d8b2019-06-20 15:15:58 -0700977 boolean needReboot = false;
978 IVold vold = null;
979 try {
980 vold = IVold.Stub.asInterface(ServiceManager.checkService("vold"));
981 if (vold != null) {
982 checkpointing = vold.needsCheckpoint();
983 } else {
984 Log.w(TAG, "Failed to get vold");
985 }
986 } catch (Exception e) {
987 Log.w(TAG, "Failed to check for checkpointing");
988 }
Daniel Rosenberg8cbd24d2019-03-19 19:24:22 -0700989
990 // If we are running in checkpointing mode, we should not prompt a wipe.
991 // Checkpointing may save us. If it doesn't, we will wind up here again.
Daniel Rosenbergd078d8b2019-06-20 15:15:58 -0700992 if (checkpointing) {
993 try {
994 vold.abortChanges("rescueparty", false);
995 Log.i(TAG, "Rescue Party requested wipe. Aborting update");
996 } catch (Exception e) {
997 Log.i(TAG, "Rescue Party requested wipe. Rebooting instead.");
998 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
999 pm.reboot("rescueparty");
Daniel Rosenberg8cbd24d2019-03-19 19:24:22 -07001000 }
Daniel Rosenbergd078d8b2019-06-20 15:15:58 -07001001 return;
Daniel Rosenberg8cbd24d2019-03-19 19:24:22 -07001002 }
1003
Jeff Sharkeyfe6f85c2017-01-20 10:42:57 -07001004 String reasonArg = null;
1005 if (!TextUtils.isEmpty(reason)) {
1006 reasonArg = "--reason=" + sanitizeArg(reason);
1007 }
1008
1009 final String localeArg = "--locale=" + Locale.getDefault().toString();
1010 bootCommand(context, null, "--prompt_and_wipe_data", reasonArg, localeArg);
1011 }
1012
Doug Zongker1af33d02010-01-05 11:28:55 -08001013 /**
Doug Zongker33651202011-07-19 12:45:09 -07001014 * Reboot into the recovery system to wipe the /cache partition.
Doug Zongker1af33d02010-01-05 11:28:55 -08001015 * @throws IOException if something goes wrong.
Doug Zongker1af33d02010-01-05 11:28:55 -08001016 */
Doug Zongker33651202011-07-19 12:45:09 -07001017 public static void rebootWipeCache(Context context) throws IOException {
Jeff Sharkey004a4b22014-09-24 11:45:24 -07001018 rebootWipeCache(context, context.getPackageName());
1019 }
1020
1021 /** {@hide} */
1022 public static void rebootWipeCache(Context context, String reason) throws IOException {
1023 String reasonArg = null;
1024 if (!TextUtils.isEmpty(reason)) {
1025 reasonArg = "--reason=" + sanitizeArg(reason);
1026 }
1027
Tianjie Xub1b38b32017-03-28 20:12:14 +00001028 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
Jeff Sharkey004a4b22014-09-24 11:45:24 -07001029 bootCommand(context, "--wipe_cache", reasonArg, localeArg);
Doug Zongker1af33d02010-01-05 11:28:55 -08001030 }
1031
1032 /**
Tao Bao1327a972016-06-02 08:47:56 -07001033 * Reboot into recovery and wipe the A/B device.
1034 *
1035 * @param Context the Context to use.
1036 * @param packageFile the wipe package to be applied.
1037 * @param reason the reason to wipe.
1038 *
1039 * @throws IOException if something goes wrong.
1040 *
1041 * @hide
1042 */
1043 @SystemApi
Jeff Sharkeyd86b8fe2017-06-02 17:36:26 -06001044 @RequiresPermission(allOf = {
1045 android.Manifest.permission.RECOVERY,
1046 android.Manifest.permission.REBOOT
1047 })
Tao Bao1327a972016-06-02 08:47:56 -07001048 public static void rebootWipeAb(Context context, File packageFile, String reason)
1049 throws IOException {
1050 String reasonArg = null;
1051 if (!TextUtils.isEmpty(reason)) {
1052 reasonArg = "--reason=" + sanitizeArg(reason);
1053 }
1054
1055 final String filename = packageFile.getCanonicalPath();
1056 final String filenameArg = "--wipe_package=" + filename;
Tianjie Xub1b38b32017-03-28 20:12:14 +00001057 final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() ;
Tao Bao1327a972016-06-02 08:47:56 -07001058 bootCommand(context, "--wipe_ab", filenameArg, reasonArg, localeArg);
1059 }
1060
1061 /**
Doug Zongker1af33d02010-01-05 11:28:55 -08001062 * Reboot into the recovery system with the supplied argument.
Jeff Sharkey004a4b22014-09-24 11:45:24 -07001063 * @param args to pass to the recovery utility.
Doug Zongker1af33d02010-01-05 11:28:55 -08001064 * @throws IOException if something goes wrong.
1065 */
Jeff Sharkey004a4b22014-09-24 11:45:24 -07001066 private static void bootCommand(Context context, String... args) throws IOException {
Tao Bao794c8b02016-09-27 11:15:42 -07001067 LOG_FILE.delete();
Doug Zongker1af33d02010-01-05 11:28:55 -08001068
Tao Bao794c8b02016-09-27 11:15:42 -07001069 StringBuilder command = new StringBuilder();
1070 for (String arg : args) {
1071 if (!TextUtils.isEmpty(arg)) {
1072 command.append(arg);
1073 command.append("\n");
Jeff Sharkey004a4b22014-09-24 11:45:24 -07001074 }
Doug Zongker1af33d02010-01-05 11:28:55 -08001075 }
Tao Bao794c8b02016-09-27 11:15:42 -07001076
1077 // Write the command into BCB (bootloader control block) and boot from
1078 // there. Will not return unless failed.
1079 RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
Tao Baocc769912017-01-17 12:42:43 -08001080 rs.rebootRecoveryWithCommand(command.toString());
Tao Bao794c8b02016-09-27 11:15:42 -07001081
1082 throw new IOException("Reboot failed (no permissions?)");
Doug Zongker1af33d02010-01-05 11:28:55 -08001083 }
1084
1085 /**
1086 * Called after booting to process and remove recovery-related files.
1087 * @return the log file from recovery, or null if none was found.
1088 *
1089 * @hide
1090 */
Tianjie Xudcd36442016-05-13 12:30:29 -07001091 public static String handleAftermath(Context context) {
Doug Zongker1af33d02010-01-05 11:28:55 -08001092 // Record the tail of the LOG_FILE
1093 String log = null;
1094 try {
1095 log = FileUtils.readTextFile(LOG_FILE, -LOG_FILE_MAX_LENGTH, "...\n");
1096 } catch (FileNotFoundException e) {
1097 Log.i(TAG, "No recovery log file");
1098 } catch (IOException e) {
1099 Log.e(TAG, "Error reading recovery log", e);
1100 }
1101
Tianjie Xudcd36442016-05-13 12:30:29 -07001102
Tao Baoe8217ff2016-02-01 16:08:18 -08001103 // Only remove the OTA package if it's partially processed (uncrypt'd).
1104 boolean reservePackage = BLOCK_MAP_FILE.exists();
Tao Baoe8a403d2015-12-31 07:44:55 -08001105 if (!reservePackage && UNCRYPT_PACKAGE_FILE.exists()) {
Tao Bao87212ad2015-10-19 14:48:36 -07001106 String filename = null;
1107 try {
Tao Baoe8a403d2015-12-31 07:44:55 -08001108 filename = FileUtils.readTextFile(UNCRYPT_PACKAGE_FILE, 0, null);
Tao Bao87212ad2015-10-19 14:48:36 -07001109 } catch (IOException e) {
1110 Log.e(TAG, "Error reading uncrypt file", e);
1111 }
1112
1113 // Remove the OTA package on /data that has been (possibly
1114 // partially) processed. (Bug: 24973532)
1115 if (filename != null && filename.startsWith("/data")) {
Tao Baoe8a403d2015-12-31 07:44:55 -08001116 if (UNCRYPT_PACKAGE_FILE.delete()) {
Tao Bao87212ad2015-10-19 14:48:36 -07001117 Log.i(TAG, "Deleted: " + filename);
1118 } else {
1119 Log.e(TAG, "Can't delete: " + filename);
1120 }
1121 }
1122 }
1123
Tao Baoe8217ff2016-02-01 16:08:18 -08001124 // We keep the update logs (beginning with LAST_PREFIX), and optionally
1125 // the block map file (BLOCK_MAP_FILE) for a package. BLOCK_MAP_FILE
1126 // will be created at the end of a successful uncrypt. If seeing this
1127 // file, we keep the block map file and the file that contains the
Tao Baoe8a403d2015-12-31 07:44:55 -08001128 // package name (UNCRYPT_PACKAGE_FILE). This is to reduce the work for
1129 // GmsCore to avoid re-downloading everything again.
Doug Zongker1af33d02010-01-05 11:28:55 -08001130 String[] names = RECOVERY_DIR.list();
1131 for (int i = 0; names != null && i < names.length; i++) {
Tianjie Xud12e1502018-09-06 14:26:33 -07001132 // Do not remove the last_install file since the recovery-persist takes care of it.
1133 if (names[i].startsWith(LAST_PREFIX) || names[i].equals(LAST_INSTALL_PATH)) continue;
Tao Baoe8217ff2016-02-01 16:08:18 -08001134 if (reservePackage && names[i].equals(BLOCK_MAP_FILE.getName())) continue;
Tao Baoe8a403d2015-12-31 07:44:55 -08001135 if (reservePackage && names[i].equals(UNCRYPT_PACKAGE_FILE.getName())) continue;
Tao Baoe8217ff2016-02-01 16:08:18 -08001136
Tao Bao5065e122015-08-18 12:37:02 -07001137 recursiveDelete(new File(RECOVERY_DIR, names[i]));
Doug Zongker1af33d02010-01-05 11:28:55 -08001138 }
1139
1140 return log;
1141 }
1142
Jeff Sharkey004a4b22014-09-24 11:45:24 -07001143 /**
Tao Bao5065e122015-08-18 12:37:02 -07001144 * Internally, delete a given file or directory recursively.
1145 */
1146 private static void recursiveDelete(File name) {
1147 if (name.isDirectory()) {
1148 String[] files = name.list();
1149 for (int i = 0; files != null && i < files.length; i++) {
1150 File f = new File(name, files[i]);
1151 recursiveDelete(f);
1152 }
1153 }
1154
1155 if (!name.delete()) {
1156 Log.e(TAG, "Can't delete: " + name);
1157 } else {
1158 Log.i(TAG, "Deleted: " + name);
1159 }
1160 }
1161
1162 /**
Tao Baoe8a403d2015-12-31 07:44:55 -08001163 * Talks to RecoverySystemService via Binder to trigger uncrypt.
1164 */
1165 private boolean uncrypt(String packageFile, IRecoverySystemProgressListener listener) {
1166 try {
1167 return mService.uncrypt(packageFile, listener);
1168 } catch (RemoteException unused) {
1169 }
1170 return false;
1171 }
1172
1173 /**
1174 * Talks to RecoverySystemService via Binder to set up the BCB.
1175 */
1176 private boolean setupBcb(String command) {
1177 try {
1178 return mService.setupBcb(command);
1179 } catch (RemoteException unused) {
1180 }
1181 return false;
1182 }
1183
1184 /**
1185 * Talks to RecoverySystemService via Binder to clear up the BCB.
1186 */
1187 private boolean clearBcb() {
1188 try {
1189 return mService.clearBcb();
1190 } catch (RemoteException unused) {
1191 }
1192 return false;
1193 }
1194
1195 /**
Tao Bao794c8b02016-09-27 11:15:42 -07001196 * Talks to RecoverySystemService via Binder to set up the BCB command and
1197 * reboot into recovery accordingly.
1198 */
Tao Baocc769912017-01-17 12:42:43 -08001199 private void rebootRecoveryWithCommand(String command) {
Tao Bao794c8b02016-09-27 11:15:42 -07001200 try {
Tao Baocc769912017-01-17 12:42:43 -08001201 mService.rebootRecoveryWithCommand(command);
Tao Bao794c8b02016-09-27 11:15:42 -07001202 } catch (RemoteException ignored) {
1203 }
1204 }
1205
1206 /**
Jeff Sharkey004a4b22014-09-24 11:45:24 -07001207 * Internally, recovery treats each line of the command file as a separate
1208 * argv, so we only need to protect against newlines and nulls.
1209 */
1210 private static String sanitizeArg(String arg) {
1211 arg = arg.replace('\0', '?');
1212 arg = arg.replace('\n', '?');
1213 return arg;
1214 }
1215
Andreas Gampe70e21e62015-03-18 16:13:33 -07001216
1217 /**
1218 * @removed Was previously made visible by accident.
1219 */
Tao Baoe8a403d2015-12-31 07:44:55 -08001220 public RecoverySystem() {
1221 mService = null;
1222 }
1223
1224 /**
1225 * @hide
1226 */
1227 public RecoverySystem(IRecoverySystem service) {
1228 mService = service;
1229 }
Doug Zongker1af33d02010-01-05 11:28:55 -08001230}