diff options
58 files changed, 2578 insertions, 315 deletions
diff --git a/Android.bp b/Android.bp index 80469d649649..900fba03daa7 100644 --- a/Android.bp +++ b/Android.bp @@ -386,7 +386,6 @@ java_defaults { // TODO(b/120066492): remove gps_debug and protolog.conf.json when the build // system propagates "required" properly. "gps_debug.conf", - "protolog.conf.json.gz", "framework-res", // any install dependencies should go into framework-minus-apex-install-dependencies // rather than here to avoid bloating incremental build time diff --git a/TEST_MAPPING b/TEST_MAPPING index d59775f4060b..6d9756dddd4e 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -234,30 +234,5 @@ } ] } - ], - "auto-features-postsubmit": [ - // Test tag for automotive feature targets. These are only running in postsubmit. - // This tag is used in targeted test features testing to limit resource use. - // TODO(b/256932212): this tag to be removed once the above is no longer in use. - { - "name": "FrameworksMockingServicesTests", - "options": [ - { - "include-filter": "com.android.server.pm.UserVisibilityMediatorSUSDTest" - }, - { - "include-filter": "com.android.server.pm.UserVisibilityMediatorMUMDTest" - }, - { - "include-filter": "com.android.server.pm.UserVisibilityMediatorMUPANDTest" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] - } ] } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 4a9fa9e63bf9..9182716a60cb 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -793,7 +793,7 @@ public class Activity extends ContextThemeWrapper private static final String SAVED_DIALOGS_TAG = "android:savedDialogs"; private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_"; private static final String SAVED_DIALOG_ARGS_KEY_PREFIX = "android:dialog_args_"; - private static final String HAS_CURENT_PERMISSIONS_REQUEST_KEY = + private static final String HAS_CURRENT_PERMISSIONS_REQUEST_KEY = "android:hasCurrentPermissionsRequest"; private static final String REQUEST_PERMISSIONS_WHO_PREFIX = "@android:requestPermissions:"; @@ -9095,14 +9095,14 @@ public class Activity extends ContextThemeWrapper private void storeHasCurrentPermissionRequest(Bundle bundle) { if (bundle != null && mHasCurrentPermissionsRequest) { - bundle.putBoolean(HAS_CURENT_PERMISSIONS_REQUEST_KEY, true); + bundle.putBoolean(HAS_CURRENT_PERMISSIONS_REQUEST_KEY, true); } } private void restoreHasCurrentPermissionRequest(Bundle bundle) { if (bundle != null) { mHasCurrentPermissionsRequest = bundle.getBoolean( - HAS_CURENT_PERMISSIONS_REQUEST_KEY, false); + HAS_CURRENT_PERMISSIONS_REQUEST_KEY, false); } } diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index db7055b1756d..7f9087baf668 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -1742,7 +1742,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { mCallbacks, result.getSequenceId()); } if ((!mSingleCapture) && (mPreviewProcessorType == - IPreviewExtenderImpl.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY)) { + IPreviewExtenderImpl.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY) + && mInitialized) { CaptureStageImpl captureStage = null; try { captureStage = mPreviewRequestUpdateProcessor.process( @@ -1765,8 +1766,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } else { mRequestUpdatedNeeded = false; } - } else if (mPreviewProcessorType == - IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR) { + } else if ((mPreviewProcessorType == + IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR) && mInitialized) { int idx = mPendingResultMap.indexOfKey(timestamp); if ((idx >= 0) && (mPendingResultMap.get(timestamp).first == null)) { @@ -1813,7 +1814,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } else { // No special handling for PROCESSOR_TYPE_NONE } - if (notifyClient) { + if (notifyClient && mInitialized) { final long ident = Binder.clearCallingIdentity(); try { if (processStatus) { diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index 83b7edaec72d..6246dd77fd6d 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -20,10 +20,12 @@ import static java.util.Objects.requireNonNull; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; +import android.content.pm.PackageManager; import android.net.LinkProperties; import android.net.NetworkCapabilities; import android.os.Binder; @@ -69,8 +71,13 @@ import java.util.concurrent.Executor; * tasks. In Safe Mode, the system will allow underlying cellular networks to be used as default. * Additionally, during Safe Mode, the VCN will continue to retry the connections, and will * automatically exit Safe Mode if all active tunnels connect successfully. + * + * <p>Apps targeting Android 15 or newer should check the existence of {@link + * PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION} before querying the service. If the feature is + * absent, {@link Context#getSystemService} may return null. */ @SystemService(Context.VCN_MANAGEMENT_SERVICE) +@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) public class VcnManager { @NonNull private static final String TAG = VcnManager.class.getSimpleName(); diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig index 97b773ee12ec..e64823af84cb 100644 --- a/core/java/android/net/vcn/flags.aconfig +++ b/core/java/android/net/vcn/flags.aconfig @@ -20,4 +20,18 @@ flag{ namespace: "vcn" description: "Feature flag for enabling network metric monitor" bug: "282996138" +} + +flag{ + name: "validate_network_on_ipsec_loss" + namespace: "vcn" + description: "Trigger network validation when IPsec packet loss exceeds the threshold" + bug: "329139898" +} + +flag{ + name: "evaluate_ipsec_loss_on_lp_nc_change" + namespace: "vcn" + description: "Re-evaluate IPsec packet loss on LinkProperties or NetworkCapabilities change" + bug: "323238888" }
\ No newline at end of file diff --git a/core/java/android/service/voice/OWNERS b/core/java/android/service/voice/OWNERS index 763c79e20846..5f9f6bde3129 100644 --- a/core/java/android/service/voice/OWNERS +++ b/core/java/android/service/voice/OWNERS @@ -3,5 +3,4 @@ include /core/java/android/app/assist/OWNERS # The owner here should not be assist owner -liangyuchen@google.com adudani@google.com diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS index a2f767d002f4..07d05a4ff1ea 100644 --- a/core/java/android/view/OWNERS +++ b/core/java/android/view/OWNERS @@ -75,12 +75,14 @@ per-file View.java = file:/graphics/java/android/graphics/OWNERS per-file View.java = file:/services/core/java/com/android/server/input/OWNERS per-file View.java = file:/services/core/java/com/android/server/wm/OWNERS per-file View.java = file:/core/java/android/view/inputmethod/OWNERS +per-file View.java = file:/core/java/android/text/OWNERS per-file ViewRootImpl.java = file:/services/accessibility/OWNERS per-file ViewRootImpl.java = file:/core/java/android/service/autofill/OWNERS per-file ViewRootImpl.java = file:/graphics/java/android/graphics/OWNERS per-file ViewRootImpl.java = file:/services/core/java/com/android/server/input/OWNERS per-file ViewRootImpl.java = file:/services/core/java/com/android/server/wm/OWNERS per-file ViewRootImpl.java = file:/core/java/android/view/inputmethod/OWNERS +per-file ViewRootImpl.java = file:/core/java/android/text/OWNERS per-file AccessibilityInteractionController.java = file:/services/accessibility/OWNERS per-file OnReceiveContentListener.java = file:/core/java/android/service/autofill/OWNERS per-file OnReceiveContentListener.java = file:/core/java/android/widget/OWNERS diff --git a/core/java/com/android/internal/net/ConnectivityBlobStore.java b/core/java/com/android/internal/net/ConnectivityBlobStore.java new file mode 100644 index 000000000000..1b18485e35fa --- /dev/null +++ b/core/java/com/android/internal/net/ConnectivityBlobStore.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net; + +import android.annotation.NonNull; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.os.Binder; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * Database for storing blobs with a key of name strings. + * @hide + */ +public class ConnectivityBlobStore { + private static final String TAG = ConnectivityBlobStore.class.getSimpleName(); + private static final String TABLENAME = "blob_table"; + private static final String ROOT_DIR = "/data/misc/connectivityblobdb/"; + + private static final String CREATE_TABLE = + "CREATE TABLE IF NOT EXISTS " + TABLENAME + " (" + + "owner INTEGER," + + "name BLOB," + + "blob BLOB," + + "UNIQUE(owner, name));"; + + private final SQLiteDatabase mDb; + + /** + * Construct a ConnectivityBlobStore object. + * + * @param dbName the filename of the database to create/access. + */ + public ConnectivityBlobStore(String dbName) { + this(new File(ROOT_DIR + dbName)); + } + + @VisibleForTesting + public ConnectivityBlobStore(File file) { + final SQLiteDatabase.OpenParams params = new SQLiteDatabase.OpenParams.Builder() + .addOpenFlags(SQLiteDatabase.CREATE_IF_NECESSARY) + .addOpenFlags(SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) + .build(); + mDb = SQLiteDatabase.openDatabase(file, params); + mDb.execSQL(CREATE_TABLE); + } + + /** + * Stores the blob under the name in the database. Existing blobs by the same name will be + * replaced. + * + * @param name The name of the blob + * @param blob The blob. + * @return true if the blob was successfully added. False otherwise. + * @hide + */ + public boolean put(@NonNull String name, @NonNull byte[] blob) { + final int ownerUid = Binder.getCallingUid(); + final ContentValues values = new ContentValues(); + values.put("owner", ownerUid); + values.put("name", name); + values.put("blob", blob); + + // No need for try-catch since it is done within db.replace + // nullColumnHack is for the case where values may be empty since SQL does not allow + // inserting a completely empty row. Since values is never empty, set this to null. + final long res = mDb.replace(TABLENAME, null /* nullColumnHack */, values); + return res > 0; + } + + /** + * Retrieves a blob by the name from the database. + * + * @param name Name of the blob to retrieve. + * @return The unstructured blob, that is the blob that was stored using + * {@link com.android.internal.net.ConnectivityBlobStore#put}. + * Returns null if no blob was found. + * @hide + */ + public byte[] get(@NonNull String name) { + final int ownerUid = Binder.getCallingUid(); + try (Cursor cursor = mDb.query(TABLENAME, + new String[] {"blob"} /* columns */, + "owner=? AND name=?" /* selection */, + new String[] {Integer.toString(ownerUid), name} /* selectionArgs */, + null /* groupBy */, + null /* having */, + null /* orderBy */)) { + if (cursor.moveToFirst()) { + return cursor.getBlob(0); + } + } catch (SQLException e) { + Log.e(TAG, "Error in getting " + name + ": " + e); + } + + return null; + } + + /** + * Removes a blob by the name from the database. + * + * @param name Name of the blob to be removed. + * @return True if a blob was removed. False if no such name was found. + * @hide + */ + public boolean remove(@NonNull String name) { + final int ownerUid = Binder.getCallingUid(); + try { + final int res = mDb.delete(TABLENAME, + "owner=? AND name=?" /* whereClause */, + new String[] {Integer.toString(ownerUid), name} /* whereArgs */); + return res > 0; + } catch (SQLException e) { + Log.e(TAG, "Error in removing " + name + ": " + e); + return false; + } + } + + /** + * Lists the name suffixes stored in the database matching the given prefix, sorted in + * ascending order. + * + * @param prefix String of prefix to list from the stored names. + * @return An array of strings representing the name suffixes stored in the database + * matching the given prefix, sorted in ascending order. + * The return value may be empty but never null. + * @hide + */ + public String[] list(@NonNull String prefix) { + final int ownerUid = Binder.getCallingUid(); + final List<String> names = new ArrayList<String>(); + try (Cursor cursor = mDb.query(TABLENAME, + new String[] {"name"} /* columns */, + "owner=? AND name LIKE ?" /* selection */, + new String[] {Integer.toString(ownerUid), prefix + "%"} /* selectionArgs */, + null /* groupBy */, + null /* having */, + "name ASC" /* orderBy */)) { + if (cursor.moveToFirst()) { + do { + final String name = cursor.getString(0); + names.add(name.substring(prefix.length())); + } while (cursor.moveToNext()); + } + } catch (SQLException e) { + Log.e(TAG, "Error in listing " + prefix + ": " + e); + } + + return names.toArray(new String[names.size()]); + } +} diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 7d740ef76daf..c8625b9114da 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -42,8 +42,8 @@ <!-- Argentina: 5 digits, known short codes listed --> <shortcode country="ar" pattern="\\d{5}" free="11711|28291|44077|78887" /> - <!-- Armenia: 3-4 digits, emergency numbers 10[123] --> - <shortcode country="am" pattern="\\d{3,4}" premium="11[2456]1|3024" free="10[123]" /> + <!-- Armenia: 3-5 digits, emergency numbers 10[123] --> + <shortcode country="am" pattern="\\d{3,5}" premium="11[2456]1|3024" free="10[123]|71522|71512|71502" /> <!-- Austria: 10 digits, premium prefix 09xx, plus EU --> <shortcode country="at" pattern="11\\d{4}" premium="09.*" free="116\\d{3}" /> @@ -111,7 +111,7 @@ <shortcode country="do" pattern="\\d{1,6}" free="912892" /> <!-- Ecuador: 1-6 digits (standard system default, not country specific) --> - <shortcode country="ec" pattern="\\d{1,6}" free="466453" /> + <shortcode country="ec" pattern="\\d{1,6}" free="466453|18512" /> <!-- Estonia: short codes 3-5 digits starting with 1, plus premium 7 digit numbers starting with 90, plus EU. http://www.tja.ee/public/documents/Elektrooniline_side/Oigusaktid/ENG/Estonian_Numbering_Plan_annex_06_09_2010.mht --> @@ -137,11 +137,11 @@ visual voicemail code for EE: 887 --> <shortcode country="gb" pattern="\\d{4,6}" premium="[5-8]\\d{4}" free="116\\d{3}|2020|35890|61002|61202|887|83669|34664|40406|60174|7726|37726|88555|9017|9018" /> - <!-- Georgia: 4 digits, known premium codes listed --> - <shortcode country="ge" pattern="\\d{4}" premium="801[234]|888[239]" /> + <!-- Georgia: 1-5 digits, known premium codes listed --> + <shortcode country="ge" pattern="\\d{1,5}" premium="801[234]|888[239]" free="95201|95202|95203" /> <!-- Ghana: 4 digits, known premium codes listed --> - <shortcode country="gh" pattern="\\d{4}" free="5041" /> + <shortcode country="gh" pattern="\\d{4}" free="5041|3777" /> <!-- Greece: 5 digits (54xxx, 19yxx, x=0-9, y=0-5): http://www.cmtelecom.com/premium-sms/greece --> <shortcode country="gr" pattern="\\d{5}" premium="54\\d{3}|19[0-5]\\d{2}" free="116\\d{3}|12115" /> @@ -210,6 +210,9 @@ <!-- Macedonia: 1-6 digits (not confirmed), known premium codes listed --> <shortcode country="mk" pattern="\\d{1,6}" free="129005|122" /> + <!-- Mongolia : 1-6 digits (standard system default, not country specific) --> + <shortcode country="mn" pattern="\\d{1,6}" free="44444|45678|445566" /> + <!-- Malawi: 1-5 digits (standard system default, not country specific) --> <shortcode country="mw" pattern="\\d{1,5}" free="4276" /> @@ -247,7 +250,7 @@ <shortcode country="ph" pattern="\\d{1,5}" free="2147|5495|5496" /> <!-- Pakistan --> - <shortcode country="pk" pattern="\\d{1,5}" free="2057|9092" /> + <shortcode country="pk" pattern="\\d{1,6}" free="2057|9092|909203" /> <!-- Palestine: 5 digits, known premium codes listed --> <shortcode country="ps" pattern="\\d{1,5}" free="37477|6681" /> @@ -291,7 +294,7 @@ <shortcode country="sk" premium="\\d{4}" free="116\\d{3}|8000" /> <!-- Senegal(SN): 1-5 digits (standard system default, not country specific) --> - <shortcode country="sn" pattern="\\d{1,5}" free="21215" /> + <shortcode country="sn" pattern="\\d{1,5}" free="21215|21098" /> <!-- El Salvador(SV): 1-5 digits (standard system default, not country specific) --> <shortcode country="sv" pattern="\\d{4,6}" free="466453" /> @@ -321,14 +324,17 @@ visual voicemail code for T-Mobile: 122 --> <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567|244444" free="122|87902|21696|24614|28003|30356|33669|40196|41064|41270|43753|44034|46645|52413|56139|57969|61785|66975|75136|76227|81398|83952|85140|86566|86799|95737|96684|99245|611611|96831" /> + <!--Uruguay : 1-5 digits (standard system default, not country specific) --> + <shortcode country="uy" pattern="\\d{1,5}" free="55002" /> + <!-- Vietnam: 1-5 digits (standard system default, not country specific) --> - <shortcode country="vn" pattern="\\d{1,5}" free="5001|9055" /> + <shortcode country="vn" pattern="\\d{1,5}" free="5001|9055|8079" /> <!-- Mayotte (French Territory): 1-5 digits (not confirmed) --> <shortcode country="yt" pattern="\\d{1,5}" free="38600,36300,36303,959" /> <!-- South Africa --> - <shortcode country="za" pattern="\\d{1,5}" free="44136|30791|36056" /> + <shortcode country="za" pattern="\\d{1,5}" free="44136|30791|36056|33009" /> <!-- Zimbabwe --> <shortcode country="zw" pattern="\\d{1,5}" free="33679" /> diff --git a/core/tests/bugreports/OWNERS b/core/tests/bugreports/OWNERS new file mode 100644 index 000000000000..dbd767c78853 --- /dev/null +++ b/core/tests/bugreports/OWNERS @@ -0,0 +1,2 @@ +# Bug component: 153446 +file:/platform/frameworks/native:/cmds/dumpstate/OWNERS diff --git a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java index de7244d49834..71c068d7ad46 100644 --- a/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java +++ b/core/tests/coretests/src/android/text/format/DateIntervalFormatTest.java @@ -149,7 +149,7 @@ public class DateIntervalFormatTest { FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); assertEquals("19.–22.01.2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); - assertEquals("19.01. – 22.04.2009", + assertEquals("19.01.\u2009–\u200922.04.2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * MONTH, FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE)); assertEquals("19.01.2009\u2009\u2013\u200909.02.2012", @@ -220,10 +220,10 @@ public class DateIntervalFormatTest { formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, 0)); assertEquals("19.–22. Jan. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL)); - assertEquals("Mo., 19. – Do., 22. Jan. 2009", + assertEquals("Mo., 19.\u2009–\u2009Do., 22. Jan. 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)); - assertEquals("Montag, 19. – Donnerstag, 22. Januar 2009", + assertEquals("Montag, 19.\u2009–\u2009Donnerstag, 22. Januar 2009", formatDateRange(de_DE, tz, fixedTime, fixedTime + 3 * DAY, FORMAT_SHOW_WEEKDAY)); assertEquals("19. Januar\u2009\u2013\u200922. April 2009", diff --git a/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java b/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java new file mode 100644 index 000000000000..68545cfe889c --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.content.Context; + +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ConnectivityBlobStoreTest { + private static final String DATABASE_FILENAME = "ConnectivityBlobStore.db"; + private static final String TEST_NAME = "TEST_NAME"; + private static final byte[] TEST_BLOB = new byte[] {(byte) 10, (byte) 90, (byte) 45, (byte) 12}; + + private Context mContext; + private File mFile; + + private ConnectivityBlobStore createConnectivityBlobStore() { + return new ConnectivityBlobStore(mFile); + } + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getContext(); + mFile = mContext.getDatabasePath(DATABASE_FILENAME); + } + + @After + public void tearDown() throws Exception { + mContext.deleteDatabase(DATABASE_FILENAME); + } + + @Test + public void testFileCreateDelete() { + assertFalse(mFile.exists()); + createConnectivityBlobStore(); + assertTrue(mFile.exists()); + + assertTrue(mContext.deleteDatabase(DATABASE_FILENAME)); + assertFalse(mFile.exists()); + } + + @Test + public void testPutAndGet() throws Exception { + final ConnectivityBlobStore connectivityBlobStore = createConnectivityBlobStore(); + assertNull(connectivityBlobStore.get(TEST_NAME)); + + assertTrue(connectivityBlobStore.put(TEST_NAME, TEST_BLOB)); + assertArrayEquals(TEST_BLOB, connectivityBlobStore.get(TEST_NAME)); + + // Test replacement + final byte[] newBlob = new byte[] {(byte) 15, (byte) 20}; + assertTrue(connectivityBlobStore.put(TEST_NAME, newBlob)); + assertArrayEquals(newBlob, connectivityBlobStore.get(TEST_NAME)); + } + + @Test + public void testRemove() throws Exception { + final ConnectivityBlobStore connectivityBlobStore = createConnectivityBlobStore(); + assertNull(connectivityBlobStore.get(TEST_NAME)); + assertFalse(connectivityBlobStore.remove(TEST_NAME)); + + assertTrue(connectivityBlobStore.put(TEST_NAME, TEST_BLOB)); + assertArrayEquals(TEST_BLOB, connectivityBlobStore.get(TEST_NAME)); + + assertTrue(connectivityBlobStore.remove(TEST_NAME)); + assertNull(connectivityBlobStore.get(TEST_NAME)); + + // Removing again returns false + assertFalse(connectivityBlobStore.remove(TEST_NAME)); + } + + @Test + public void testMultipleNames() throws Exception { + final String name1 = TEST_NAME + "1"; + final String name2 = TEST_NAME + "2"; + final ConnectivityBlobStore connectivityBlobStore = createConnectivityBlobStore(); + + assertNull(connectivityBlobStore.get(name1)); + assertNull(connectivityBlobStore.get(name2)); + assertFalse(connectivityBlobStore.remove(name1)); + assertFalse(connectivityBlobStore.remove(name2)); + + assertTrue(connectivityBlobStore.put(name1, TEST_BLOB)); + assertTrue(connectivityBlobStore.put(name2, TEST_BLOB)); + assertArrayEquals(TEST_BLOB, connectivityBlobStore.get(name1)); + assertArrayEquals(TEST_BLOB, connectivityBlobStore.get(name2)); + + // Replace the blob for name1 only. + final byte[] newBlob = new byte[] {(byte) 16, (byte) 21}; + assertTrue(connectivityBlobStore.put(name1, newBlob)); + assertArrayEquals(newBlob, connectivityBlobStore.get(name1)); + + assertTrue(connectivityBlobStore.remove(name1)); + assertNull(connectivityBlobStore.get(name1)); + assertArrayEquals(TEST_BLOB, connectivityBlobStore.get(name2)); + + assertFalse(connectivityBlobStore.remove(name1)); + assertTrue(connectivityBlobStore.remove(name2)); + assertNull(connectivityBlobStore.get(name2)); + assertFalse(connectivityBlobStore.remove(name2)); + } + + @Test + public void testList() throws Exception { + final String[] unsortedNames = new String[] { + TEST_NAME + "1", + TEST_NAME + "2", + TEST_NAME + "0", + "NON_MATCHING_PREFIX", + "MATCHING_SUFFIX_" + TEST_NAME + }; + // Expected to match and discard the prefix and be in increasing sorted order. + final String[] expected = new String[] { + "0", + "1", + "2" + }; + final ConnectivityBlobStore connectivityBlobStore = createConnectivityBlobStore(); + + for (int i = 0; i < unsortedNames.length; i++) { + assertTrue(connectivityBlobStore.put(unsortedNames[i], TEST_BLOB)); + } + final String[] actual = connectivityBlobStore.list(TEST_NAME /* prefix */); + assertArrayEquals(expected, actual); + } +} diff --git a/core/tests/coretests/src/com/android/internal/net/OWNERS b/core/tests/coretests/src/com/android/internal/net/OWNERS new file mode 100644 index 000000000000..f51ba475ab63 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/net/OWNERS @@ -0,0 +1 @@ +include /core/java/com/android/internal/net/OWNERS diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index 9320c144d2ec..d1aa8e9734c2 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -1432,6 +1432,16 @@ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc </font> </family> + <family lang="ja"> + <font weight="400" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight"> + NotoSerifHentaigana.ttf + <axis tag="wght" stylevalue="400"/> + </font> + <font weight="700" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight"> + NotoSerifHentaigana.ttf + <axis tag="wght" stylevalue="700"/> + </font> + </family> <family lang="ko"> <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Regular"> NotoSansCJK-Regular.ttc diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java index 2430e8d8e662..efbbfc23736f 100644 --- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java +++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java @@ -175,20 +175,6 @@ public class AndroidKeyStoreMaintenance { } /** - * Informs Keystore 2.0 that an off body event was detected. - */ - public static void onDeviceOffBody() { - StrictMode.noteDiskWrite(); - try { - getService().onDeviceOffBody(); - } catch (Exception e) { - // TODO This fails open. This is not a regression with respect to keystore1 but it - // should get fixed. - Log.e(TAG, "Error while reporting device off body event.", e); - } - } - - /** * Migrates a key given by the source descriptor to the location designated by the destination * descriptor. * diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index bd9abec22325..2cac2e150919 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -17,7 +17,6 @@ package android.security; import android.compat.annotation.UnsupportedAppUsage; -import android.os.Build; import android.os.StrictMode; /** @@ -30,10 +29,6 @@ import android.os.StrictMode; */ public class KeyStore { - // ResponseCodes - see system/security/keystore/include/keystore/keystore.h - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int NO_ERROR = 1; - // Used for UID field to indicate the calling UID. public static final int UID_SELF = -1; @@ -48,19 +43,12 @@ public class KeyStore { * Add an authentication record to the keystore authorization table. * * @param authToken The packed bytes of a hw_auth_token_t to be provided to keymaster. - * @return {@code KeyStore.NO_ERROR} on success, otherwise an error value corresponding to - * a {@code KeymasterDefs.KM_ERROR_} value or {@code KeyStore} ResponseCode. + * @return 0 on success, otherwise an error value corresponding to a + * {@code KeymasterDefs.KM_ERROR_} value or {@code KeyStore} ResponseCode. */ public int addAuthToken(byte[] authToken) { StrictMode.noteDiskWrite(); return Authorization.addAuthToken(authToken); } - - /** - * Notify keystore that the device went off-body. - */ - public void onDeviceOffBody() { - AndroidKeyStoreMaintenance.onDeviceOffBody(); - } } diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 9ba5a81dbb71..d359a9050a0f 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -1670,16 +1670,16 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * {@link #setUserAuthenticationValidityDurationSeconds} and * {@link #setUserAuthenticationRequired}). Once the device has been removed from the * user's body, the key will be considered unauthorized and the user will need to - * re-authenticate to use it. For keys without an authentication validity period this - * parameter has no effect. - * - * <p>Similarly, on devices that do not have an on-body sensor, this parameter will have no - * effect; the device will always be considered to be "on-body" and the key will therefore - * remain authorized until the validity period ends. + * re-authenticate to use it. If the device does not have an on-body sensor or the key does + * not have an authentication validity period, this parameter has no effect. + * <p> + * Since Android 12 (API level 31), this parameter has no effect even on devices that have + * an on-body sensor. A future version of Android may restore enforcement of this parameter. + * Meanwhile, it is recommended to not use it. * - * @param remainsValid if {@code true}, and if the device supports on-body detection, key - * will be invalidated when the device is removed from the user's body or when the - * authentication validity expires, whichever occurs first. + * @param remainsValid if {@code true}, and if the device supports enforcement of this + * parameter, the key will be invalidated when the device is removed from the user's body or + * when the authentication validity expires, whichever occurs first. */ @NonNull public Builder setUserAuthenticationValidWhileOnBody(boolean remainsValid) { diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index 9b455f05b99c..8e5ac45d394d 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -1037,16 +1037,16 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * {@link #setUserAuthenticationValidityDurationSeconds} and * {@link #setUserAuthenticationRequired}). Once the device has been removed from the * user's body, the key will be considered unauthorized and the user will need to - * re-authenticate to use it. For keys without an authentication validity period this - * parameter has no effect. - * - * <p>Similarly, on devices that do not have an on-body sensor, this parameter will have no - * effect; the device will always be considered to be "on-body" and the key will therefore - * remain authorized until the validity period ends. + * re-authenticate to use it. If the device does not have an on-body sensor or the key does + * not have an authentication validity period, this parameter has no effect. + * <p> + * Since Android 12 (API level 31), this parameter has no effect even on devices that have + * an on-body sensor. A future version of Android may restore enforcement of this parameter. + * Meanwhile, it is recommended to not use it. * - * @param remainsValid if {@code true}, and if the device supports on-body detection, key - * will be invalidated when the device is removed from the user's body or when the - * authentication validity expires, whichever occurs first. + * @param remainsValid if {@code true}, and if the device supports enforcement of this + * parameter, the key will be invalidated when the device is removed from the user's body or + * when the authentication validity expires, whichever occurs first. */ @NonNull public Builder setUserAuthenticationValidWhileOnBody(boolean remainsValid) { diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java index 101a10e3d312..3f39eeb0cc6b 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java @@ -359,14 +359,12 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor } catch (KeyStoreException keyStoreException) { GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit( mKey, keyStoreException); - if (e != null) { - if (e instanceof InvalidKeyException) { - throw (InvalidKeyException) e; - } else if (e instanceof InvalidAlgorithmParameterException) { - throw (InvalidAlgorithmParameterException) e; - } else { - throw new ProviderException("Unexpected exception type", e); - } + if (e instanceof InvalidKeyException) { + throw (InvalidKeyException) e; + } else if (e instanceof InvalidAlgorithmParameterException) { + throw (InvalidAlgorithmParameterException) e; + } else { + throw new ProviderException("Unexpected exception type", e); } } diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java index 372e4cb3d72e..9b82206e5709 100644 --- a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java +++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java @@ -20,7 +20,6 @@ import android.app.ActivityThread; import android.hardware.biometrics.BiometricManager; import android.hardware.security.keymint.ErrorCode; import android.security.GateKeeper; -import android.security.KeyStore; import android.security.KeyStoreException; import android.security.KeyStoreOperation; import android.security.keymaster.KeymasterDefs; @@ -131,15 +130,10 @@ abstract class KeyStoreCryptoOperationUtils { /** * Returns the exception to be thrown by the {@code Cipher.init} method of the crypto operation - * in response to {@code KeyStore.begin} operation or {@code null} if the {@code init} method - * should succeed. + * in response to a failed {code IKeystoreSecurityLevel#createOperation()}. */ public static GeneralSecurityException getExceptionForCipherInit( AndroidKeyStoreKey key, KeyStoreException e) { - if (e.getErrorCode() == KeyStore.NO_ERROR) { - return null; - } - // Cipher-specific cases switch (e.getErrorCode()) { case KeymasterDefs.KM_ERROR_INVALID_NONCE: diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java index ab7c27f70e05..2d7db5e6ed94 100644 --- a/media/java/android/media/MediaCas.java +++ b/media/java/android/media/MediaCas.java @@ -35,6 +35,7 @@ import android.media.tv.tunerresourcemanager.TunerResourceManager; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; +import android.os.IBinder; import android.os.IHwBinder; import android.os.Looper; import android.os.Message; @@ -43,7 +44,6 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceSpecificException; import android.util.Log; -import android.util.Singleton; import com.android.internal.util.FrameworkStatsLog; @@ -264,71 +264,107 @@ public final class MediaCas implements AutoCloseable { public static final int PLUGIN_STATUS_SESSION_NUMBER_CHANGED = android.hardware.cas.StatusEvent.PLUGIN_SESSION_NUMBER_CHANGED; - private static final Singleton<IMediaCasService> sService = - new Singleton<IMediaCasService>() { + private static IMediaCasService sService = null; + private static Object sAidlLock = new Object(); + + /** DeathListener for AIDL service */ + private static IBinder.DeathRecipient sDeathListener = + new IBinder.DeathRecipient() { @Override - protected IMediaCasService create() { - try { - Log.d(TAG, "Trying to get AIDL service"); - IMediaCasService serviceAidl = - IMediaCasService.Stub.asInterface( - ServiceManager.waitForDeclaredService( - IMediaCasService.DESCRIPTOR + "/default")); - if (serviceAidl != null) { - return serviceAidl; - } - } catch (Exception eAidl) { - Log.d(TAG, "Failed to get cas AIDL service"); + public void binderDied() { + synchronized (sAidlLock) { + Log.d(TAG, "The service is dead"); + sService.asBinder().unlinkToDeath(sDeathListener, 0); + sService = null; } - return null; } }; - private static final Singleton<android.hardware.cas.V1_0.IMediaCasService> sServiceHidl = - new Singleton<android.hardware.cas.V1_0.IMediaCasService>() { - @Override - protected android.hardware.cas.V1_0.IMediaCasService create() { - try { - Log.d(TAG, "Trying to get cas@1.2 service"); - android.hardware.cas.V1_2.IMediaCasService serviceV12 = - android.hardware.cas.V1_2.IMediaCasService.getService( - true /*wait*/); - if (serviceV12 != null) { - return serviceV12; - } - } catch (Exception eV1_2) { - Log.d(TAG, "Failed to get cas@1.2 service"); + static IMediaCasService getService() { + synchronized (sAidlLock) { + if (sService == null || !sService.asBinder().isBinderAlive()) { + try { + Log.d(TAG, "Trying to get AIDL service"); + sService = + IMediaCasService.Stub.asInterface( + ServiceManager.waitForDeclaredService( + IMediaCasService.DESCRIPTOR + "/default")); + if (sService != null) { + sService.asBinder().linkToDeath(sDeathListener, 0); } + } catch (Exception eAidl) { + Log.d(TAG, "Failed to get cas AIDL service"); + } + } + return sService; + } + } - try { - Log.d(TAG, "Trying to get cas@1.1 service"); - android.hardware.cas.V1_1.IMediaCasService serviceV11 = - android.hardware.cas.V1_1.IMediaCasService.getService( - true /*wait*/); - if (serviceV11 != null) { - return serviceV11; + private static android.hardware.cas.V1_0.IMediaCasService sServiceHidl = null; + private static Object sHidlLock = new Object(); + + /** Used to indicate the right end-point to handle the serviceDied method */ + private static final long MEDIA_CAS_HIDL_COOKIE = 394; + + /** DeathListener for HIDL service */ + private static IHwBinder.DeathRecipient sDeathListenerHidl = + new IHwBinder.DeathRecipient() { + @Override + public void serviceDied(long cookie) { + if (cookie == MEDIA_CAS_HIDL_COOKIE) { + synchronized (sHidlLock) { + sServiceHidl = null; } - } catch (Exception eV1_1) { - Log.d(TAG, "Failed to get cas@1.1 service"); } + } + }; - try { - Log.d(TAG, "Trying to get cas@1.0 service"); - return android.hardware.cas.V1_0.IMediaCasService.getService(true /*wait*/); - } catch (Exception eV1_0) { - Log.d(TAG, "Failed to get cas@1.0 service"); + static android.hardware.cas.V1_0.IMediaCasService getServiceHidl() { + synchronized (sHidlLock) { + if (sServiceHidl != null) { + return sServiceHidl; + } else { + try { + Log.d(TAG, "Trying to get cas@1.2 service"); + android.hardware.cas.V1_2.IMediaCasService serviceV12 = + android.hardware.cas.V1_2.IMediaCasService.getService(true /*wait*/); + if (serviceV12 != null) { + sServiceHidl = serviceV12; + sServiceHidl.linkToDeath(sDeathListenerHidl, MEDIA_CAS_HIDL_COOKIE); + return sServiceHidl; } - - return null; + } catch (Exception eV1_2) { + Log.d(TAG, "Failed to get cas@1.2 service"); } - }; - static IMediaCasService getService() { - return sService.get(); - } + try { + Log.d(TAG, "Trying to get cas@1.1 service"); + android.hardware.cas.V1_1.IMediaCasService serviceV11 = + android.hardware.cas.V1_1.IMediaCasService.getService(true /*wait*/); + if (serviceV11 != null) { + sServiceHidl = serviceV11; + sServiceHidl.linkToDeath(sDeathListenerHidl, MEDIA_CAS_HIDL_COOKIE); + return sServiceHidl; + } + } catch (Exception eV1_1) { + Log.d(TAG, "Failed to get cas@1.1 service"); + } - static android.hardware.cas.V1_0.IMediaCasService getServiceHidl() { - return sServiceHidl.get(); + try { + Log.d(TAG, "Trying to get cas@1.0 service"); + sServiceHidl = + android.hardware.cas.V1_0.IMediaCasService.getService(true /*wait*/); + if (sServiceHidl != null) { + sServiceHidl.linkToDeath(sDeathListenerHidl, MEDIA_CAS_HIDL_COOKIE); + } + return sServiceHidl; + } catch (Exception eV1_0) { + Log.d(TAG, "Failed to get cas@1.0 service"); + } + } + } + // Couldn't find an HIDL service, returning null. + return null; } private void validateInternalStates() { @@ -756,7 +792,7 @@ public final class MediaCas implements AutoCloseable { * @return Whether the specified CA system is supported on this device. */ public static boolean isSystemIdSupported(int CA_system_id) { - IMediaCasService service = sService.get(); + IMediaCasService service = getService(); if (service != null) { try { return service.isSystemIdSupported(CA_system_id); @@ -765,7 +801,7 @@ public final class MediaCas implements AutoCloseable { } } - android.hardware.cas.V1_0.IMediaCasService serviceHidl = sServiceHidl.get(); + android.hardware.cas.V1_0.IMediaCasService serviceHidl = getServiceHidl(); if (serviceHidl != null) { try { return serviceHidl.isSystemIdSupported(CA_system_id); @@ -781,7 +817,7 @@ public final class MediaCas implements AutoCloseable { * @return an array of descriptors for the available CA plugins. */ public static PluginDescriptor[] enumeratePlugins() { - IMediaCasService service = sService.get(); + IMediaCasService service = getService(); if (service != null) { try { AidlCasPluginDescriptor[] descriptors = service.enumeratePlugins(); @@ -794,10 +830,11 @@ public final class MediaCas implements AutoCloseable { } return results; } catch (RemoteException e) { + Log.e(TAG, "Some exception while enumerating plugins"); } } - android.hardware.cas.V1_0.IMediaCasService serviceHidl = sServiceHidl.get(); + android.hardware.cas.V1_0.IMediaCasService serviceHidl = getServiceHidl(); if (serviceHidl != null) { try { ArrayList<HidlCasPluginDescriptor> descriptors = serviceHidl.enumeratePlugins(); diff --git a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java index 37b5d408a508..a8d8f9a1a55d 100644 --- a/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java +++ b/packages/CrashRecovery/services/java/com/android/server/PackageWatchdog.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; +import android.crashrecovery.flags.Flags; import android.net.ConnectivityModuleConnector; import android.os.Environment; import android.os.Handler; @@ -57,16 +58,20 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -130,8 +135,25 @@ public class PackageWatchdog { @VisibleForTesting static final int DEFAULT_BOOT_LOOP_TRIGGER_COUNT = 5; - @VisibleForTesting + static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10); + // Boot loop at which packageWatchdog starts first mitigation + private static final String BOOT_LOOP_THRESHOLD = + "persist.device_config.configuration.boot_loop_threshold"; + @VisibleForTesting + static final int DEFAULT_BOOT_LOOP_THRESHOLD = 15; + // Once boot_loop_threshold is surpassed next mitigation would be triggered after + // specified number of reboots. + private static final String BOOT_LOOP_MITIGATION_INCREMENT = + "persist.device_config.configuration..boot_loop_mitigation_increment"; + @VisibleForTesting + static final int DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT = 2; + + // Threshold level at which or above user might experience significant disruption. + private static final String MAJOR_USER_IMPACT_LEVEL_THRESHOLD = + "persist.device_config.configuration.major_user_impact_level_threshold"; + private static final int DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD = + PackageHealthObserverImpact.USER_IMPACT_LEVEL_71; private long mNumberOfNativeCrashPollsRemaining; @@ -145,6 +167,7 @@ public class PackageWatchdog { private static final String ATTR_EXPLICIT_HEALTH_CHECK_DURATION = "health-check-duration"; private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check"; private static final String ATTR_MITIGATION_CALLS = "mitigation-calls"; + private static final String ATTR_MITIGATION_COUNT = "mitigation-count"; // A file containing information about the current mitigation count in the case of a boot loop. // This allows boot loop information to persist in the case of an fs-checkpoint being @@ -230,8 +253,16 @@ public class PackageWatchdog { mConnectivityModuleConnector = connectivityModuleConnector; mSystemClock = clock; mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS; - mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT, - DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS); + if (Flags.recoverabilityDetection()) { + mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT, + DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS, + SystemProperties.getInt(BOOT_LOOP_MITIGATION_INCREMENT, + DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT)); + } else { + mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT, + DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS); + } + loadFromFile(); sPackageWatchdog = this; } @@ -436,8 +467,13 @@ public class PackageWatchdog { mitigationCount = currentMonitoredPackage.getMitigationCountLocked(); } - currentObserverToNotify.execute(versionedPackage, - failureReason, mitigationCount); + if (Flags.recoverabilityDetection()) { + maybeExecute(currentObserverToNotify, versionedPackage, + failureReason, currentObserverImpact, mitigationCount); + } else { + currentObserverToNotify.execute(versionedPackage, + failureReason, mitigationCount); + } } } } @@ -467,37 +503,76 @@ public class PackageWatchdog { } } if (currentObserverToNotify != null) { - currentObserverToNotify.execute(failingPackage, failureReason, 1); + if (Flags.recoverabilityDetection()) { + maybeExecute(currentObserverToNotify, failingPackage, failureReason, + currentObserverImpact, /*mitigationCount=*/ 1); + } else { + currentObserverToNotify.execute(failingPackage, failureReason, 1); + } + } + } + + private void maybeExecute(PackageHealthObserver currentObserverToNotify, + VersionedPackage versionedPackage, + @FailureReasons int failureReason, + int currentObserverImpact, + int mitigationCount) { + if (currentObserverImpact < getUserImpactLevelLimit()) { + currentObserverToNotify.execute(versionedPackage, failureReason, mitigationCount); } } + /** * Called when the system server boots. If the system server is detected to be in a boot loop, * query each observer and perform the mitigation action with the lowest user impact. */ + @SuppressWarnings("GuardedBy") public void noteBoot() { synchronized (mLock) { - if (mBootThreshold.incrementAndTest()) { - mBootThreshold.reset(); + boolean mitigate = mBootThreshold.incrementAndTest(); + if (mitigate) { + if (!Flags.recoverabilityDetection()) { + mBootThreshold.reset(); + } int mitigationCount = mBootThreshold.getMitigationCount() + 1; PackageHealthObserver currentObserverToNotify = null; + ObserverInternal currentObserverInternal = null; int currentObserverImpact = Integer.MAX_VALUE; for (int i = 0; i < mAllObservers.size(); i++) { final ObserverInternal observer = mAllObservers.valueAt(i); PackageHealthObserver registeredObserver = observer.registeredObserver; if (registeredObserver != null) { - int impact = registeredObserver.onBootLoop(mitigationCount); + int impact = Flags.recoverabilityDetection() + ? registeredObserver.onBootLoop( + observer.getBootMitigationCount() + 1) + : registeredObserver.onBootLoop(mitigationCount); if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0 && impact < currentObserverImpact) { currentObserverToNotify = registeredObserver; + currentObserverInternal = observer; currentObserverImpact = impact; } } } if (currentObserverToNotify != null) { - mBootThreshold.setMitigationCount(mitigationCount); - mBootThreshold.saveMitigationCountToMetadata(); - currentObserverToNotify.executeBootLoopMitigation(mitigationCount); + if (Flags.recoverabilityDetection()) { + if (currentObserverImpact < getUserImpactLevelLimit() + || (currentObserverImpact >= getUserImpactLevelLimit() + && mBootThreshold.getCount() >= getBootLoopThreshold())) { + int currentObserverMitigationCount = + currentObserverInternal.getBootMitigationCount() + 1; + currentObserverInternal.setBootMitigationCount( + currentObserverMitigationCount); + saveAllObserversBootMitigationCountToMetadata(METADATA_FILE); + currentObserverToNotify.executeBootLoopMitigation( + currentObserverMitigationCount); + } + } else { + mBootThreshold.setMitigationCount(mitigationCount); + mBootThreshold.saveMitigationCountToMetadata(); + currentObserverToNotify.executeBootLoopMitigation(mitigationCount); + } } } } @@ -567,13 +642,27 @@ public class PackageWatchdog { mShortTaskHandler.post(()->checkAndMitigateNativeCrashes()); } + private int getUserImpactLevelLimit() { + return SystemProperties.getInt(MAJOR_USER_IMPACT_LEVEL_THRESHOLD, + DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD); + } + + private int getBootLoopThreshold() { + return SystemProperties.getInt(BOOT_LOOP_THRESHOLD, + DEFAULT_BOOT_LOOP_THRESHOLD); + } + /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */ @Retention(SOURCE) @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0, PackageHealthObserverImpact.USER_IMPACT_LEVEL_10, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_20, PackageHealthObserverImpact.USER_IMPACT_LEVEL_30, PackageHealthObserverImpact.USER_IMPACT_LEVEL_50, PackageHealthObserverImpact.USER_IMPACT_LEVEL_70, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_71, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_75, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_80, PackageHealthObserverImpact.USER_IMPACT_LEVEL_90, PackageHealthObserverImpact.USER_IMPACT_LEVEL_100}) public @interface PackageHealthObserverImpact { @@ -582,11 +671,15 @@ public class PackageWatchdog { /* Action has low user impact, user of a device will barely notice. */ int USER_IMPACT_LEVEL_10 = 10; /* Actions having medium user impact, user of a device will likely notice. */ + int USER_IMPACT_LEVEL_20 = 20; int USER_IMPACT_LEVEL_30 = 30; int USER_IMPACT_LEVEL_50 = 50; int USER_IMPACT_LEVEL_70 = 70; - int USER_IMPACT_LEVEL_90 = 90; /* Action has high user impact, a last resort, user of a device will be very frustrated. */ + int USER_IMPACT_LEVEL_71 = 71; + int USER_IMPACT_LEVEL_75 = 75; + int USER_IMPACT_LEVEL_80 = 80; + int USER_IMPACT_LEVEL_90 = 90; int USER_IMPACT_LEVEL_100 = 100; } @@ -1144,6 +1237,12 @@ public class PackageWatchdog { } } + @VisibleForTesting + @GuardedBy("mLock") + void registerObserverInternal(ObserverInternal observerInternal) { + mAllObservers.put(observerInternal.name, observerInternal); + } + /** * Represents an observer monitoring a set of packages along with the failure thresholds for * each package. @@ -1151,17 +1250,23 @@ public class PackageWatchdog { * <p> Note, the PackageWatchdog#mLock must always be held when reading or writing * instances of this class. */ - private static class ObserverInternal { + static class ObserverInternal { public final String name; @GuardedBy("mLock") private final ArrayMap<String, MonitoredPackage> mPackages = new ArrayMap<>(); @Nullable @GuardedBy("mLock") public PackageHealthObserver registeredObserver; + private int mMitigationCount; ObserverInternal(String name, List<MonitoredPackage> packages) { + this(name, packages, /*mitigationCount=*/ 0); + } + + ObserverInternal(String name, List<MonitoredPackage> packages, int mitigationCount) { this.name = name; updatePackagesLocked(packages); + this.mMitigationCount = mitigationCount; } /** @@ -1173,6 +1278,9 @@ public class PackageWatchdog { try { out.startTag(null, TAG_OBSERVER); out.attribute(null, ATTR_NAME, name); + if (Flags.recoverabilityDetection()) { + out.attributeInt(null, ATTR_MITIGATION_COUNT, mMitigationCount); + } for (int i = 0; i < mPackages.size(); i++) { MonitoredPackage p = mPackages.valueAt(i); p.writeLocked(out); @@ -1185,6 +1293,14 @@ public class PackageWatchdog { } } + public int getBootMitigationCount() { + return mMitigationCount; + } + + public void setBootMitigationCount(int mitigationCount) { + mMitigationCount = mitigationCount; + } + @GuardedBy("mLock") public void updatePackagesLocked(List<MonitoredPackage> packages) { for (int pIndex = 0; pIndex < packages.size(); pIndex++) { @@ -1289,6 +1405,7 @@ public class PackageWatchdog { **/ public static ObserverInternal read(TypedXmlPullParser parser, PackageWatchdog watchdog) { String observerName = null; + int observerMitigationCount = 0; if (TAG_OBSERVER.equals(parser.getName())) { observerName = parser.getAttributeValue(null, ATTR_NAME); if (TextUtils.isEmpty(observerName)) { @@ -1299,6 +1416,9 @@ public class PackageWatchdog { List<MonitoredPackage> packages = new ArrayList<>(); int innerDepth = parser.getDepth(); try { + if (Flags.recoverabilityDetection()) { + observerMitigationCount = parser.getAttributeInt(null, ATTR_MITIGATION_COUNT); + } while (XmlUtils.nextElementWithin(parser, innerDepth)) { if (TAG_PACKAGE.equals(parser.getName())) { try { @@ -1319,7 +1439,7 @@ public class PackageWatchdog { if (packages.isEmpty()) { return null; } - return new ObserverInternal(observerName, packages); + return new ObserverInternal(observerName, packages, observerMitigationCount); } /** Dumps information about this observer and the packages it watches. */ @@ -1679,6 +1799,27 @@ public class PackageWatchdog { } } + @GuardedBy("mLock") + @SuppressWarnings("GuardedBy") + void saveAllObserversBootMitigationCountToMetadata(String filePath) { + HashMap<String, Integer> bootMitigationCounts = new HashMap<>(); + for (int i = 0; i < mAllObservers.size(); i++) { + final ObserverInternal observer = mAllObservers.valueAt(i); + bootMitigationCounts.put(observer.name, observer.getBootMitigationCount()); + } + + try { + FileOutputStream fileStream = new FileOutputStream(new File(filePath)); + ObjectOutputStream objectStream = new ObjectOutputStream(fileStream); + objectStream.writeObject(bootMitigationCounts); + objectStream.flush(); + objectStream.close(); + fileStream.close(); + } catch (Exception e) { + Slog.i(TAG, "Could not save observers metadata to file: " + e); + } + } + /** * Handles the thresholding logic for system server boots. */ @@ -1686,10 +1827,16 @@ public class PackageWatchdog { private final int mBootTriggerCount; private final long mTriggerWindow; + private final int mBootMitigationIncrement; BootThreshold(int bootTriggerCount, long triggerWindow) { + this(bootTriggerCount, triggerWindow, /*bootMitigationIncrement=*/ 1); + } + + BootThreshold(int bootTriggerCount, long triggerWindow, int bootMitigationIncrement) { this.mBootTriggerCount = bootTriggerCount; this.mTriggerWindow = triggerWindow; + this.mBootMitigationIncrement = bootMitigationIncrement; } public void reset() { @@ -1761,8 +1908,13 @@ public class PackageWatchdog { /** Increments the boot counter, and returns whether the device is bootlooping. */ + @GuardedBy("mLock") public boolean incrementAndTest() { - readMitigationCountFromMetadataIfNecessary(); + if (Flags.recoverabilityDetection()) { + readAllObserversBootMitigationCountIfNecessary(METADATA_FILE); + } else { + readMitigationCountFromMetadataIfNecessary(); + } final long now = mSystemClock.uptimeMillis(); if (now - getStart() < 0) { Slog.e(TAG, "Window was less than zero. Resetting start to current time."); @@ -1770,8 +1922,12 @@ public class PackageWatchdog { setMitigationStart(now); } if (now - getMitigationStart() > DEFAULT_DEESCALATION_WINDOW_MS) { - setMitigationCount(0); setMitigationStart(now); + if (Flags.recoverabilityDetection()) { + resetAllObserversBootMitigationCount(); + } else { + setMitigationCount(0); + } } final long window = now - getStart(); if (window >= mTriggerWindow) { @@ -1782,9 +1938,48 @@ public class PackageWatchdog { int count = getCount() + 1; setCount(count); EventLogTags.writeRescueNote(Process.ROOT_UID, count, window); + if (Flags.recoverabilityDetection()) { + boolean mitigate = (count >= mBootTriggerCount) + && (count - mBootTriggerCount) % mBootMitigationIncrement == 0; + return mitigate; + } return count >= mBootTriggerCount; } } + @GuardedBy("mLock") + private void resetAllObserversBootMitigationCount() { + for (int i = 0; i < mAllObservers.size(); i++) { + final ObserverInternal observer = mAllObservers.valueAt(i); + observer.setBootMitigationCount(0); + } + } + + @GuardedBy("mLock") + @SuppressWarnings("GuardedBy") + void readAllObserversBootMitigationCountIfNecessary(String filePath) { + File metadataFile = new File(filePath); + if (metadataFile.exists()) { + try { + FileInputStream fileStream = new FileInputStream(metadataFile); + ObjectInputStream objectStream = new ObjectInputStream(fileStream); + HashMap<String, Integer> bootMitigationCounts = + (HashMap<String, Integer>) objectStream.readObject(); + objectStream.close(); + fileStream.close(); + + for (int i = 0; i < mAllObservers.size(); i++) { + final ObserverInternal observer = mAllObservers.valueAt(i); + if (bootMitigationCounts.containsKey(observer.name)) { + observer.setBootMitigationCount( + bootMitigationCounts.get(observer.name)); + } + } + } catch (Exception e) { + Slog.i(TAG, "Could not read observer metadata file: " + e); + } + } + } + } } diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java index 7bdc1a0e3ac7..7093ba42f40d 100644 --- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java +++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java @@ -20,6 +20,7 @@ import static android.provider.DeviceConfig.Properties; import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentResolver; @@ -27,6 +28,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; +import android.crashrecovery.flags.Flags; import android.os.Build; import android.os.Environment; import android.os.PowerManager; @@ -53,6 +55,8 @@ import com.android.server.am.SettingsToPropertiesMapper; import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog; import java.io.File; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -89,6 +93,40 @@ public class RescueParty { @VisibleForTesting static final int LEVEL_FACTORY_RESET = 5; @VisibleForTesting + static final int RESCUE_LEVEL_NONE = 0; + @VisibleForTesting + static final int RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET = 1; + @VisibleForTesting + static final int RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET = 2; + @VisibleForTesting + static final int RESCUE_LEVEL_WARM_REBOOT = 3; + @VisibleForTesting + static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 4; + @VisibleForTesting + static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 5; + @VisibleForTesting + static final int RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 6; + @VisibleForTesting + static final int RESCUE_LEVEL_FACTORY_RESET = 7; + + @IntDef(prefix = { "RESCUE_LEVEL_" }, value = { + RESCUE_LEVEL_NONE, + RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET, + RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET, + RESCUE_LEVEL_WARM_REBOOT, + RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS, + RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES, + RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS, + RESCUE_LEVEL_FACTORY_RESET + }) + @Retention(RetentionPolicy.SOURCE) + @interface RescueLevels {} + + @VisibleForTesting + static final String RESCUE_NON_REBOOT_LEVEL_LIMIT = "persist.sys.rescue_non_reboot_level_limit"; + @VisibleForTesting + static final int DEFAULT_RESCUE_NON_REBOOT_LEVEL_LIMIT = RESCUE_LEVEL_WARM_REBOOT - 1; + @VisibleForTesting static final String TAG = "RescueParty"; @VisibleForTesting static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2); @@ -347,11 +385,20 @@ public class RescueParty { } private static int getMaxRescueLevel(boolean mayPerformReboot) { - if (!mayPerformReboot - || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) { - return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS; + if (Flags.recoverabilityDetection()) { + if (!mayPerformReboot + || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) { + return SystemProperties.getInt(RESCUE_NON_REBOOT_LEVEL_LIMIT, + DEFAULT_RESCUE_NON_REBOOT_LEVEL_LIMIT); + } + return RESCUE_LEVEL_FACTORY_RESET; + } else { + if (!mayPerformReboot + || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) { + return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS; + } + return LEVEL_FACTORY_RESET; } - return LEVEL_FACTORY_RESET; } /** @@ -379,6 +426,46 @@ public class RescueParty { } } + /** + * Get the rescue level to perform if this is the n-th attempt at mitigating failure. + * When failedPackage is null then 1st and 2nd mitigation counts are redundant (scoped and + * all device config reset). Behaves as if one mitigation attempt was already done. + * + * @param mitigationCount the mitigation attempt number (1 = first attempt etc.). + * @param mayPerformReboot whether or not a reboot and factory reset may be performed + * for the given failure. + * @param failedPackage in case of bootloop this is null. + * @return the rescue level for the n-th mitigation attempt. + */ + private static @RescueLevels int getRescueLevel(int mitigationCount, boolean mayPerformReboot, + @Nullable VersionedPackage failedPackage) { + // Skipping RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET since it's not defined without a failed + // package. + if (failedPackage == null && mitigationCount > 0) { + mitigationCount += 1; + } + if (mitigationCount == 1) { + return RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET; + } else if (mitigationCount == 2) { + return RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET; + } else if (mitigationCount == 3) { + return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_WARM_REBOOT); + } else if (mitigationCount == 4) { + return Math.min(getMaxRescueLevel(mayPerformReboot), + RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS); + } else if (mitigationCount == 5) { + return Math.min(getMaxRescueLevel(mayPerformReboot), + RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES); + } else if (mitigationCount == 6) { + return Math.min(getMaxRescueLevel(mayPerformReboot), + RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS); + } else if (mitigationCount >= 7) { + return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_FACTORY_RESET); + } else { + return RESCUE_LEVEL_NONE; + } + } + private static void executeRescueLevel(Context context, @Nullable String failedPackage, int level) { Slog.w(TAG, "Attempting rescue level " + levelToString(level)); @@ -397,6 +484,15 @@ public class RescueParty { private static void executeRescueLevelInternal(Context context, int level, @Nullable String failedPackage) throws Exception { + if (Flags.recoverabilityDetection()) { + executeRescueLevelInternalNew(context, level, failedPackage); + } else { + executeRescueLevelInternalOld(context, level, failedPackage); + } + } + + private static void executeRescueLevelInternalOld(Context context, int level, @Nullable + String failedPackage) throws Exception { if (level <= LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS) { // Disabling flag resets on master branch for trunk stable launch. @@ -410,8 +506,6 @@ public class RescueParty { // Try our best to reset all settings possible, and once finished // rethrow any exception that we encountered Exception res = null; - Runnable runnable; - Thread thread; switch (level) { case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: try { @@ -453,21 +547,7 @@ public class RescueParty { } break; case LEVEL_WARM_REBOOT: - // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog - // when device shutting down. - setRebootProperty(true); - runnable = () -> { - try { - PowerManager pm = context.getSystemService(PowerManager.class); - if (pm != null) { - pm.reboot(TAG); - } - } catch (Throwable t) { - logRescueException(level, failedPackage, t); - } - }; - thread = new Thread(runnable); - thread.start(); + executeWarmReboot(context, level, failedPackage); break; case LEVEL_FACTORY_RESET: // Before the completion of Reboot, if any crash happens then PackageWatchdog @@ -475,23 +555,9 @@ public class RescueParty { // Adding a check to prevent factory reset to execute before above reboot completes. // Note: this reboot property is not persistent resets after reboot is completed. if (isRebootPropertySet()) { - break; + return; } - setFactoryResetProperty(true); - long now = System.currentTimeMillis(); - setLastFactoryResetTimeMs(now); - runnable = new Runnable() { - @Override - public void run() { - try { - RecoverySystem.rebootPromptAndWipeUserData(context, TAG); - } catch (Throwable t) { - logRescueException(level, failedPackage, t); - } - } - }; - thread = new Thread(runnable); - thread.start(); + executeFactoryReset(context, level, failedPackage); break; } @@ -500,6 +566,83 @@ public class RescueParty { } } + private static void executeRescueLevelInternalNew(Context context, @RescueLevels int level, + @Nullable String failedPackage) throws Exception { + CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED, + level, levelToString(level)); + switch (level) { + case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET: + // Temporary disable deviceConfig reset + // resetDeviceConfig(context, /*isScoped=*/true, failedPackage); + break; + case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET: + // Temporary disable deviceConfig reset + // resetDeviceConfig(context, /*isScoped=*/false, failedPackage); + break; + case RESCUE_LEVEL_WARM_REBOOT: + executeWarmReboot(context, level, failedPackage); + break; + case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: + resetAllSettingsIfNecessary(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS, level); + break; + case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: + resetAllSettingsIfNecessary(context, Settings.RESET_MODE_UNTRUSTED_CHANGES, level); + break; + case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: + resetAllSettingsIfNecessary(context, Settings.RESET_MODE_TRUSTED_DEFAULTS, level); + break; + case RESCUE_LEVEL_FACTORY_RESET: + // Before the completion of Reboot, if any crash happens then PackageWatchdog + // escalates to next level i.e. factory reset, as they happen in separate threads. + // Adding a check to prevent factory reset to execute before above reboot completes. + // Note: this reboot property is not persistent resets after reboot is completed. + if (isRebootPropertySet()) { + return; + } + executeFactoryReset(context, level, failedPackage); + break; + } + } + + private static void executeWarmReboot(Context context, int level, + @Nullable String failedPackage) { + // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog + // when device shutting down. + setRebootProperty(true); + Runnable runnable = () -> { + try { + PowerManager pm = context.getSystemService(PowerManager.class); + if (pm != null) { + pm.reboot(TAG); + } + } catch (Throwable t) { + logRescueException(level, failedPackage, t); + } + }; + Thread thread = new Thread(runnable); + thread.start(); + } + + private static void executeFactoryReset(Context context, int level, + @Nullable String failedPackage) { + setFactoryResetProperty(true); + long now = System.currentTimeMillis(); + setLastFactoryResetTimeMs(now); + Runnable runnable = new Runnable() { + @Override + public void run() { + try { + RecoverySystem.rebootPromptAndWipeUserData(context, TAG); + } catch (Throwable t) { + logRescueException(level, failedPackage, t); + } + } + }; + Thread thread = new Thread(runnable); + thread.start(); + } + + private static String getCompleteMessage(Throwable t) { final StringBuilder builder = new StringBuilder(); builder.append(t.getMessage()); @@ -521,17 +664,38 @@ public class RescueParty { } private static int mapRescueLevelToUserImpact(int rescueLevel) { - switch(rescueLevel) { - case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: - case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10; - case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: - case LEVEL_WARM_REBOOT: - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50; - case LEVEL_FACTORY_RESET: - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100; - default: - return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; + if (Flags.recoverabilityDetection()) { + switch (rescueLevel) { + case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET: + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10; + case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET: + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_20; + case RESCUE_LEVEL_WARM_REBOOT: + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50; + case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_71; + case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_75; + case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_80; + case RESCUE_LEVEL_FACTORY_RESET: + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100; + default: + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; + } + } else { + switch (rescueLevel) { + case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: + case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10; + case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: + case LEVEL_WARM_REBOOT: + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50; + case LEVEL_FACTORY_RESET: + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100; + default: + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; + } } } @@ -548,7 +712,7 @@ public class RescueParty { final ContentResolver resolver = context.getContentResolver(); try { Settings.Global.resetToDefaultsAsUser(resolver, null, mode, - UserHandle.SYSTEM.getIdentifier()); + UserHandle.SYSTEM.getIdentifier()); } catch (Exception e) { res = new RuntimeException("Failed to reset global settings", e); } @@ -667,8 +831,13 @@ public class RescueParty { @FailureReasons int failureReason, int mitigationCount) { if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) { - return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, + if (Flags.recoverabilityDetection()) { + return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, + mayPerformReboot(failedPackage), failedPackage)); + } else { + return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, mayPerformReboot(failedPackage))); + } } else { return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; } @@ -682,8 +851,10 @@ public class RescueParty { } if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) { - final int level = getRescueLevel(mitigationCount, - mayPerformReboot(failedPackage)); + final int level = Flags.recoverabilityDetection() ? getRescueLevel(mitigationCount, + mayPerformReboot(failedPackage), failedPackage) + : getRescueLevel(mitigationCount, + mayPerformReboot(failedPackage)); executeRescueLevel(mContext, failedPackage == null ? null : failedPackage.getPackageName(), level); return true; @@ -716,7 +887,12 @@ public class RescueParty { if (isDisabled()) { return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; } - return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true)); + if (Flags.recoverabilityDetection()) { + return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, + true, /*failedPackage=*/ null)); + } else { + return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true)); + } } @Override @@ -725,8 +901,10 @@ public class RescueParty { return false; } boolean mayPerformReboot = !shouldThrottleReboot(); - executeRescueLevel(mContext, /*failedPackage=*/ null, - getRescueLevel(mitigationCount, mayPerformReboot)); + final int level = Flags.recoverabilityDetection() ? getRescueLevel(mitigationCount, + mayPerformReboot, /*failedPackage=*/ null) + : getRescueLevel(mitigationCount, mayPerformReboot); + executeRescueLevel(mContext, /*failedPackage=*/ null, level); return true; } @@ -843,14 +1021,44 @@ public class RescueParty { } private static String levelToString(int level) { - switch (level) { - case LEVEL_NONE: return "NONE"; - case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: return "RESET_SETTINGS_UNTRUSTED_DEFAULTS"; - case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: return "RESET_SETTINGS_UNTRUSTED_CHANGES"; - case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: return "RESET_SETTINGS_TRUSTED_DEFAULTS"; - case LEVEL_WARM_REBOOT: return "WARM_REBOOT"; - case LEVEL_FACTORY_RESET: return "FACTORY_RESET"; - default: return Integer.toString(level); + if (Flags.recoverabilityDetection()) { + switch (level) { + case RESCUE_LEVEL_NONE: + return "NONE"; + case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET: + return "SCOPED_DEVICE_CONFIG_RESET"; + case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET: + return "ALL_DEVICE_CONFIG_RESET"; + case RESCUE_LEVEL_WARM_REBOOT: + return "WARM_REBOOT"; + case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: + return "RESET_SETTINGS_UNTRUSTED_DEFAULTS"; + case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: + return "RESET_SETTINGS_UNTRUSTED_CHANGES"; + case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: + return "RESET_SETTINGS_TRUSTED_DEFAULTS"; + case RESCUE_LEVEL_FACTORY_RESET: + return "FACTORY_RESET"; + default: + return Integer.toString(level); + } + } else { + switch (level) { + case LEVEL_NONE: + return "NONE"; + case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: + return "RESET_SETTINGS_UNTRUSTED_DEFAULTS"; + case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: + return "RESET_SETTINGS_UNTRUSTED_CHANGES"; + case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: + return "RESET_SETTINGS_TRUSTED_DEFAULTS"; + case LEVEL_WARM_REBOOT: + return "WARM_REBOOT"; + case LEVEL_FACTORY_RESET: + return "FACTORY_RESET"; + default: + return Integer.toString(level); + } } } } diff --git a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 0fb932735ab4..93f26aefb692 100644 --- a/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/packages/CrashRecovery/services/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -69,7 +69,7 @@ import java.util.function.Consumer; * * @hide */ -final class RollbackPackageHealthObserver implements PackageHealthObserver { +public final class RollbackPackageHealthObserver implements PackageHealthObserver { private static final String TAG = "RollbackPackageHealthObserver"; private static final String NAME = "rollback-observer"; private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT @@ -89,7 +89,7 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { private boolean mTwoPhaseRollbackEnabled; @VisibleForTesting - RollbackPackageHealthObserver(Context context, ApexManager apexManager) { + public RollbackPackageHealthObserver(Context context, ApexManager apexManager) { mContext = context; HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver"); handlerThread.start(); diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt index f063074b39b4..c6fd92c6e9b7 100644 --- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt +++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt @@ -127,10 +127,10 @@ private fun getCredentialOptionInfoList( userName = credentialEntry.username.toString(), displayName = credentialEntry.displayName?.toString(), icon = credentialEntry.icon.loadDrawable(context), - shouldTintIcon = credentialEntry.isDefaultIcon, + shouldTintIcon = credentialEntry.hasDefaultIcon, lastUsedTimeMillis = credentialEntry.lastUsedTime, isAutoSelectable = credentialEntry.isAutoSelectAllowed && - credentialEntry.autoSelectAllowedFromOption, + credentialEntry.isAutoSelectAllowedFromOption, ) ) } @@ -148,10 +148,10 @@ private fun getCredentialOptionInfoList( userName = credentialEntry.username.toString(), displayName = credentialEntry.displayName?.toString(), icon = credentialEntry.icon.loadDrawable(context), - shouldTintIcon = credentialEntry.isDefaultIcon, + shouldTintIcon = credentialEntry.hasDefaultIcon, lastUsedTimeMillis = credentialEntry.lastUsedTime, isAutoSelectable = credentialEntry.isAutoSelectAllowed && - credentialEntry.autoSelectAllowedFromOption, + credentialEntry.isAutoSelectAllowedFromOption, ) ) } @@ -170,10 +170,10 @@ private fun getCredentialOptionInfoList( userName = credentialEntry.title.toString(), displayName = credentialEntry.subtitle?.toString(), icon = credentialEntry.icon.loadDrawable(context), - shouldTintIcon = credentialEntry.isDefaultIcon, + shouldTintIcon = credentialEntry.hasDefaultIcon, lastUsedTimeMillis = credentialEntry.lastUsedTime, isAutoSelectable = credentialEntry.isAutoSelectAllowed && - credentialEntry.autoSelectAllowedFromOption, + credentialEntry.isAutoSelectAllowedFromOption, ) ) } diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 3a33f92bf7d1..71e084ee547e 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -35,6 +35,9 @@ java_library { srcs: [ ":statslog-SystemUI-java-gen", ], + libs: [ + "androidx.annotation_annotation", + ], lint: { baseline_filename: "lint-baseline.xml", }, diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java index 206babf9ec44..09675e28f5da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationViewModelTransformerTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.when; import android.testing.AndroidTestingRunner; +import androidx.lifecycle.ViewModel; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -56,7 +57,8 @@ public class ComplicationViewModelTransformerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); when(mFactory.create(Mockito.any(), Mockito.any())).thenReturn(mComponent); when(mComponent.getViewModelProvider()).thenReturn(mViewModelProvider); - when(mViewModelProvider.get(Mockito.any(), Mockito.any())).thenReturn(mViewModel); + when(mViewModelProvider.get(Mockito.any(), Mockito.<Class<ViewModel>>any())) + .thenReturn(mViewModel); } /** diff --git a/services/Android.bp b/services/Android.bp index 5cb8ec628c38..474d501bca7a 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -219,6 +219,7 @@ java_library { required: [ "libukey2_jni_shared", + "protolog.conf.json.gz", ], // Uncomment to enable output of certain warnings (deprecated, unchecked) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 2ee39c577977..09c1dea2f65a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -19423,7 +19423,7 @@ public class ActivityManagerService extends IActivityManager.Stub record.procStateSeqWaitingForNetwork = 0; final long totalTime = SystemClock.uptimeMillis() - startTime; if (totalTime >= mConstants.mNetworkAccessTimeoutMs || DEBUG_NETWORK) { - Slog.w(TAG_NETWORK, "Total time waited for network rules to get updated: " + Slog.wtf(TAG_NETWORK, "Total time waited for network rules to get updated: " + totalTime + ". Uid: " + callingUid + " procStateSeq: " + procStateSeq + " UidRec: " + record + " validateUidRec: " diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 5a078dbfa7b8..0bfbee694366 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4595,6 +4595,7 @@ public class AudioService extends IAudioService.Stub case AudioSystem.MODE_CALL_SCREENING: case AudioSystem.MODE_CALL_REDIRECT: case AudioSystem.MODE_COMMUNICATION_REDIRECT: + case AudioSystem.MODE_RINGTONE: break; default: // no-op is enough for all other values diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index f9568ea9d72b..6eba23f45fdf 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -256,10 +256,10 @@ public abstract class AuthenticationClient<T, O extends AuthenticateOptions> // For BP, BiometricService will add the authToken to Keystore. if (!isBiometricPrompt() && mIsStrongBiometric) { final int result = KeyStore.getInstance().addAuthToken(byteToken); - if (result != KeyStore.NO_ERROR) { + if (result != 0) { Slog.d(TAG, "Error adding auth token : " + result); } else { - Slog.d(TAG, "addAuthToken: " + result); + Slog.d(TAG, "addAuthToken succeeded"); } } else { Slog.d(TAG, "Skipping addAuthToken"); diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index b7ece2ea65b1..5905b7de5b6e 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -366,7 +366,6 @@ public class Vpn { private PendingIntent mStatusIntent; private volatile boolean mEnableTeardown = true; - private final INetworkManagementService mNms; private final INetd mNetd; @VisibleForTesting @GuardedBy("this") @@ -626,7 +625,6 @@ public class Vpn { mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); mDeps = deps; - mNms = netService; mNetd = netd; mUserId = userId; mLooper = looper; diff --git a/services/core/java/com/android/server/inputmethod/LocaleUtils.java b/services/core/java/com/android/server/inputmethod/LocaleUtils.java index 7d090dbcc5d8..b1348b3c785f 100644 --- a/services/core/java/com/android/server/inputmethod/LocaleUtils.java +++ b/services/core/java/com/android/server/inputmethod/LocaleUtils.java @@ -46,29 +46,44 @@ final class LocaleUtils { * @param desired The locale preferred by user. * @return A score based on the locale matching for the default subtype enabling. */ - @IntRange(from = 1, to = 3) + @IntRange(from = 1, to = 4) private static byte calculateMatchingSubScore(@NonNull final ULocale supported, @NonNull final ULocale desired) { // Assuming supported/desired is fully expanded. if (supported.equals(desired)) { - return 3; // Exact match. + return 4; // Exact match. } + // addLikelySubtags is a maximization process as per + // https://www.unicode.org/reports/tr35/#Likely_Subtags + ULocale maxDesired = ULocale.addLikelySubtags(desired); + // Skip language matching since it was already done in calculateMatchingScore. final String supportedScript = supported.getScript(); - if (supportedScript.isEmpty() || !supportedScript.equals(desired.getScript())) { + if (supportedScript.isEmpty() || !supportedScript.equals(maxDesired.getScript())) { // TODO: Need subscript matching. For example, Hanb should match with Bopo. return 1; } final String supportedCountry = supported.getCountry(); - if (supportedCountry.isEmpty() || !supportedCountry.equals(desired.getCountry())) { + if (supportedCountry.isEmpty() || !supportedCountry.equals(maxDesired.getCountry())) { return 2; } // Ignore others e.g. variants, extensions. - return 3; + + // Since addLikelySubtags can canonicalize subtags, e.g. the deprecated country codes + // an locale with an identical script and country before addLikelySubtags is in favour, + // and a score of 4 is returned. + String desiredScript = desired.getScript(); + String desiredCountry = desired.getCountry(); + if ((desiredScript.isEmpty() || desiredScript.equals(maxDesired.getScript())) + && (desiredCountry.isEmpty() || desiredCountry.equals(maxDesired.getCountry()))) { + return 4; + } else { + return 3; + } } private static final class ScoreEntry implements Comparable<ScoreEntry> { @@ -180,8 +195,7 @@ final class LocaleUtils { ULocale.forLocale(preferredLocale)); } score[j] = calculateMatchingSubScore( - preferredULocaleCache[j], - ULocale.addLikelySubtags(ULocale.forLocale(locale))); + preferredULocaleCache[j], ULocale.forLocale(locale)); if (canSkip && score[j] != 0) { canSkip = false; } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 71ff52376b46..c6fca9b76f8e 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -1214,16 +1214,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return false; } final int previousProcState = previousInfo.procState; - if (mBackgroundNetworkRestricted && (previousProcState >= BACKGROUND_THRESHOLD_STATE) - != (newProcState >= BACKGROUND_THRESHOLD_STATE)) { - // Proc-state change crossed BACKGROUND_THRESHOLD_STATE: Network rules for the - // BACKGROUND chain may change. - return true; - } if ((previousProcState <= TOP_THRESHOLD_STATE) - != (newProcState <= TOP_THRESHOLD_STATE)) { - // Proc-state change crossed TOP_THRESHOLD_STATE: Network rules for the - // LOW_POWER_STANDBY chain may change. + || (newProcState <= TOP_THRESHOLD_STATE)) { + // If the proc-state change crossed TOP_THRESHOLD_STATE, network rules for the + // LOW_POWER_STANDBY chain may change, so we need to evaluate the transition. + // In addition, we always process changes when the new process state is + // TOP_THRESHOLD_STATE or below, to avoid situations where the TOP app ends up + // waiting for NPMS to finish processing newProcStateSeq, even when it was + // redundant (b/327303931). return true; } if ((previousProcState <= FOREGROUND_THRESHOLD_STATE) @@ -1232,6 +1230,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // different chains may change. return true; } + if (mBackgroundNetworkRestricted && (previousProcState >= BACKGROUND_THRESHOLD_STATE) + != (newProcState >= BACKGROUND_THRESHOLD_STATE)) { + // Proc-state change crossed BACKGROUND_THRESHOLD_STATE: Network rules for the + // BACKGROUND chain may change. + return true; + } final int networkCapabilities = PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK; if ((previousInfo.capability & networkCapabilities) @@ -4322,7 +4326,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @GuardedBy("mUidRulesFirstLock") private boolean updateUidStateUL(int uid, int procState, long procStateSeq, @ProcessCapability int capability) { - Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateUidStateUL"); + Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateUidStateUL: " + uid + "/" + + ActivityManager.procStateToString(procState) + "/" + procStateSeq + "/" + + ActivityManager.getCapabilitiesSummary(capability)); try { final UidState oldUidState = mUidState.get(uid); if (oldUidState != null && procStateSeq < oldUidState.procStateSeq) { diff --git a/services/core/java/com/android/server/net/OWNERS b/services/core/java/com/android/server/net/OWNERS index d0e95dd55b6c..669cdaaf3ab5 100644 --- a/services/core/java/com/android/server/net/OWNERS +++ b/services/core/java/com/android/server/net/OWNERS @@ -4,3 +4,4 @@ file:platform/packages/modules/Connectivity:main:/OWNERS_core_networking jsharkey@android.com sudheersai@google.com yamasani@google.com +suprabh@google.com diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 600aff290193..a5c323ab287c 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -112,7 +112,6 @@ import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.ArchivedPackageParcel; import android.content.pm.DataLoaderType; -import android.content.pm.Flags; import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageInstaller; @@ -2556,9 +2555,7 @@ final class InstallPackageHelper { & PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE) != 0; /*@DexoptFlags*/ int extraFlags = - ignoreDexoptProfile && Flags.useArtServiceV2() - ? ArtFlags.FLAG_IGNORE_PROFILE - : 0; + ignoreDexoptProfile ? ArtFlags.FLAG_IGNORE_PROFILE : 0; DexoptParams params = dexoptOptions.convertToDexoptParams(extraFlags); DexoptResult dexOptResult = DexOptHelper.getArtManagerLocal().dexoptPackage( snapshot, packageName, params); diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index ee780d99b6b6..dcb9f1aad814 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -35,7 +35,6 @@ import android.apex.ApexInfo; import android.app.AppOpsManager; import android.content.pm.ArchivedPackageParcel; import android.content.pm.DataLoaderType; -import android.content.pm.Flags; import android.content.pm.IPackageInstallObserver2; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; @@ -931,7 +930,7 @@ final class InstallRequest { // Only report external profile warnings when installing from adb. The goal is to warn app // developers if they have provided bad external profiles, so it's not beneficial to report // those warnings in the normal app install workflow. - if (isInstallFromAdb() && Flags.useArtServiceV2()) { + if (isInstallFromAdb()) { var externalProfileErrors = new LinkedHashSet<String>(); for (PackageDexoptResult packageResult : dexoptResult.getPackageDexoptResults()) { for (DexContainerFileDexoptResult fileResult : diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java index 099c9ae33bfb..17f5e9585c22 100644 --- a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java +++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java @@ -44,6 +44,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.internal.telephony.flags.Flags; import com.android.internal.util.IndentingPrintWriter; import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; @@ -318,8 +319,12 @@ public class TelephonySubscriptionTracker extends BroadcastReceiver { if (SubscriptionManager.isValidSubscriptionId(subId)) { // Get only configs as needed to save memory. - final PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(subId, - VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS); + final PersistableBundle carrierConfig = + Flags.fixCrashOnGettingConfigWhenPhoneIsGone() + ? CarrierConfigManager.getCarrierConfigSubset(mContext, subId, + VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS) + : mCarrierConfigManager.getConfigForSubId(subId, + VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS); if (mDeps.isConfigForIdentifiedCarrier(carrierConfig)) { mReadySubIdsBySlotId.put(slotId, subId); diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java index a25d67ab66af..ed9fa65dee15 100644 --- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java +++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java @@ -24,8 +24,10 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.net.ConnectivityManager; import android.net.IpSecTransformState; import android.net.Network; +import android.net.vcn.Flags; import android.net.vcn.VcnManager; import android.os.Handler; import android.os.HandlerExecutor; @@ -71,6 +73,7 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { @NonNull private final Handler mHandler; @NonNull private final PowerManager mPowerManager; + @NonNull private final ConnectivityManager mConnectivityManager; @NonNull private final Object mCancellationToken = new Object(); @NonNull private final PacketLossCalculator mPacketLossCalculator; @@ -98,6 +101,8 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { mHandler = new Handler(getVcnContext().getLooper()); mPowerManager = getVcnContext().getContext().getSystemService(PowerManager.class); + mConnectivityManager = + getVcnContext().getContext().getSystemService(ConnectivityManager.class); mPacketLossCalculator = deps.getPacketLossCalculator(); @@ -205,6 +210,18 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { } @Override + public void onLinkPropertiesOrCapabilitiesChanged() { + if (!isStarted()) return; + + reschedulePolling(); + } + + private void reschedulePolling() { + mHandler.removeCallbacksAndEqualMessages(mCancellationToken); + mHandler.postDelayed(new PollIpSecStateRunnable(), mCancellationToken, 0L); + } + + @Override protected void start() { super.start(); clearTransformStateAndPollingEvents(); @@ -313,6 +330,13 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { } else { logInfo(logMsg); onValidationResultReceivedInternal(true /* isFailed */); + + if (Flags.validateNetworkOnIpsecLoss()) { + // Trigger re-validation of the underlying network; if it fails, the VCN will + // attempt to migrate away. + mConnectivityManager.reportNetworkConnectivity( + getNetwork(), false /* hasConnectivity */); + } } } diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java index 1704aa117a2b..a1b212f8d3d7 100644 --- a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java +++ b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java @@ -186,6 +186,11 @@ public abstract class NetworkMetricMonitor implements AutoCloseable { // Subclasses MUST override it if they care } + /** Called when LinkProperties or NetworkCapabilities have changed */ + public void onLinkPropertiesOrCapabilitiesChanged() { + // Subclasses MUST override it if they care + } + public boolean isValidationFailed() { return mIsValidationFailed; } @@ -203,6 +208,11 @@ public abstract class NetworkMetricMonitor implements AutoCloseable { return mVcnContext; } + @NonNull + public Network getNetwork() { + return mNetwork; + } + // Override methods for AutoCloseable. Subclasses MUST call super when overriding this method @Override public void close() { diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java index 2f4cf5e5d8c7..78e06d46c74c 100644 --- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java +++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java @@ -25,6 +25,7 @@ import android.net.IpSecTransform; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; +import android.net.vcn.Flags; import android.net.vcn.VcnManager; import android.net.vcn.VcnUnderlyingNetworkTemplate; import android.os.Handler; @@ -295,6 +296,12 @@ public class UnderlyingNetworkEvaluator { updatePriorityClass( underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); + + if (Flags.evaluateIpsecLossOnLpNcChange()) { + for (NetworkMetricMonitor monitor : mMetricMonitors) { + monitor.onLinkPropertiesOrCapabilitiesChanged(); + } + } } /** Set the LinkProperties */ @@ -308,6 +315,12 @@ public class UnderlyingNetworkEvaluator { updatePriorityClass( underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig); + + if (Flags.evaluateIpsecLossOnLpNcChange()) { + for (NetworkMetricMonitor monitor : mMetricMonitors) { + monitor.onLinkPropertiesOrCapabilitiesChanged(); + } + } } /** Set whether the network is blocked */ diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS index e06f2158dc14..79eb0dc620a5 100644 --- a/services/core/java/com/android/server/wm/OWNERS +++ b/services/core/java/com/android/server/wm/OWNERS @@ -24,3 +24,5 @@ per-file Background*Start* = set noparent per-file Background*Start* = file:/BAL_OWNERS per-file Background*Start* = ogunwale@google.com, louischang@google.com +# File related to activity callers +per-file ActivityCallerState.java = file:/core/java/android/app/COMPONENT_CALLER_OWNERS diff --git a/services/core/jni/com_android_server_am_OomConnection.cpp b/services/core/jni/com_android_server_am_OomConnection.cpp index 49a3ad35649b..054937fc683e 100644 --- a/services/core/jni/com_android_server_am_OomConnection.cpp +++ b/services/core/jni/com_android_server_am_OomConnection.cpp @@ -44,6 +44,12 @@ static MemEventListener memevent_listener(MemEventClient::AMS); * @throws java.lang.RuntimeException */ static jobjectArray android_server_am_OomConnection_waitOom(JNIEnv* env, jobject) { + if (!memevent_listener.ok()) { + memevent_listener.deregisterAllEvents(); + jniThrowRuntimeException(env, "Failed to initialize memevents listener"); + return nullptr; + } + if (!memevent_listener.registerEvent(MEM_EVENT_OOM_KILL)) { memevent_listener.deregisterAllEvents(); jniThrowRuntimeException(env, "listener failed to register to OOM events"); diff --git a/services/proguard.flags b/services/proguard.flags index 88561b460b05..a01e7dc16147 100644 --- a/services/proguard.flags +++ b/services/proguard.flags @@ -54,7 +54,10 @@ -keep,allowoptimization,allowaccessmodification class android.app.admin.flags.FeatureFlagsImpl { *; } -keep,allowoptimization,allowaccessmodification class com.android.server.input.NativeInputManagerService$NativeImpl { *; } -keep,allowoptimization,allowaccessmodification class com.android.server.ThreadPriorityBooster { *; } --keep,allowaccessmodification class android.app.admin.flags.Flags { *; } + +# Keep all aconfig Flag class as they might be statically referenced by other packages +# An merge or inlining could lead to missing dependencies that cause run time errors +-keepclassmembernames class android.**.Flags, com.android.**.Flags { public *; } # Referenced via CarServiceHelperService in car-frameworks-service (avoid removing) -keep public class com.android.server.utils.Slogf { *; } diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index c30ac2d6c248..682569f1d9ab 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -26,6 +26,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.RescueParty.LEVEL_FACTORY_RESET; +import static com.android.server.RescueParty.RESCUE_LEVEL_FACTORY_RESET; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -41,9 +42,11 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; +import android.crashrecovery.flags.Flags; import android.os.RecoverySystem; import android.os.SystemProperties; import android.os.UserHandle; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.provider.Settings; import android.util.ArraySet; @@ -55,6 +58,7 @@ import com.android.server.am.SettingsToPropertiesMapper; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.Answers; import org.mockito.ArgumentCaptor; @@ -69,6 +73,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -100,6 +105,9 @@ public class RescuePartyTest { private static final int THROTTLING_DURATION_MIN = 10; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private MockitoSession mSession; private HashMap<String, String> mSystemSettingsMap; private HashMap<String, String> mCrashRecoveryPropertiesMap; @@ -267,6 +275,42 @@ public class RescuePartyTest { } @Test + public void testBootLoopDetectionWithExecutionForAllRescueLevelsRecoverabilityDetection() { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + RescueParty.onSettingsProviderPublished(mMockContext); + verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver), + any(Executor.class), + mMonitorCallbackCaptor.capture())); + HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>(); + + // Record DeviceConfig accesses + DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue(); + monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1); + monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2); + + final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2}; + + noteBoot(1); + verifyDeviceConfigReset(expectedAllResetNamespaces, verifiedTimesMap); + + noteBoot(2); + assertTrue(RescueParty.isRebootPropertySet()); + + noteBoot(3); + verifyOnlySettingsReset(Settings.RESET_MODE_UNTRUSTED_DEFAULTS); + + noteBoot(4); + verifyOnlySettingsReset(Settings.RESET_MODE_UNTRUSTED_CHANGES); + + noteBoot(5); + verifyOnlySettingsReset(Settings.RESET_MODE_TRUSTED_DEFAULTS); + + setCrashRecoveryPropAttemptingReboot(false); + noteBoot(6); + assertTrue(RescueParty.isFactoryResetPropertySet()); + } + + @Test public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() { noteAppCrash(1, true); @@ -292,6 +336,47 @@ public class RescuePartyTest { } @Test + public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevelsRecoverability() { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + RescueParty.onSettingsProviderPublished(mMockContext); + verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver), + any(Executor.class), + mMonitorCallbackCaptor.capture())); + HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>(); + + // Record DeviceConfig accesses + DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue(); + monitorCallback.onDeviceConfigAccess(PERSISTENT_PACKAGE, NAMESPACE1); + monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1); + monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2); + + final String[] expectedResetNamespaces = new String[]{NAMESPACE1}; + final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2}; + + noteAppCrash(1, true); + verifyDeviceConfigReset(expectedResetNamespaces, verifiedTimesMap); + + noteAppCrash(2, true); + verifyDeviceConfigReset(expectedAllResetNamespaces, verifiedTimesMap); + + noteAppCrash(3, true); + assertTrue(RescueParty.isRebootPropertySet()); + + noteAppCrash(4, true); + verifyOnlySettingsReset(Settings.RESET_MODE_UNTRUSTED_DEFAULTS); + + noteAppCrash(5, true); + verifyOnlySettingsReset(Settings.RESET_MODE_UNTRUSTED_CHANGES); + + noteAppCrash(6, true); + verifyOnlySettingsReset(Settings.RESET_MODE_TRUSTED_DEFAULTS); + + setCrashRecoveryPropAttemptingReboot(false); + noteAppCrash(7, true); + assertTrue(RescueParty.isFactoryResetPropertySet()); + } + + @Test public void testNonPersistentAppOnlyPerformsFlagResets() { noteAppCrash(1, false); @@ -316,6 +401,45 @@ public class RescuePartyTest { } @Test + public void testNonPersistentAppOnlyPerformsFlagResetsRecoverabilityDetection() { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + RescueParty.onSettingsProviderPublished(mMockContext); + verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver), + any(Executor.class), + mMonitorCallbackCaptor.capture())); + HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>(); + + // Record DeviceConfig accesses + DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue(); + monitorCallback.onDeviceConfigAccess(NON_PERSISTENT_PACKAGE, NAMESPACE1); + monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1); + monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2); + + final String[] expectedResetNamespaces = new String[]{NAMESPACE1}; + final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2}; + + noteAppCrash(1, false); + verifyDeviceConfigReset(expectedResetNamespaces, verifiedTimesMap); + + noteAppCrash(2, false); + verifyDeviceConfigReset(expectedAllResetNamespaces, verifiedTimesMap); + + noteAppCrash(3, false); + assertFalse(RescueParty.isRebootPropertySet()); + + noteAppCrash(4, false); + verifyNoSettingsReset(Settings.RESET_MODE_UNTRUSTED_DEFAULTS); + noteAppCrash(5, false); + verifyNoSettingsReset(Settings.RESET_MODE_UNTRUSTED_CHANGES); + noteAppCrash(6, false); + verifyNoSettingsReset(Settings.RESET_MODE_TRUSTED_DEFAULTS); + + setCrashRecoveryPropAttemptingReboot(false); + noteAppCrash(7, false); + assertFalse(RescueParty.isFactoryResetPropertySet()); + } + + @Test public void testNonPersistentAppCrashDetectionWithScopedResets() { RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver), @@ -451,6 +575,19 @@ public class RescuePartyTest { } @Test + public void testIsRecoveryTriggeredRebootRecoverabilityDetection() { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + for (int i = 0; i < RESCUE_LEVEL_FACTORY_RESET; i++) { + noteBoot(i + 1); + } + assertFalse(RescueParty.isFactoryResetPropertySet()); + setCrashRecoveryPropAttemptingReboot(false); + noteBoot(RESCUE_LEVEL_FACTORY_RESET + 1); + assertTrue(RescueParty.isRecoveryTriggeredReboot()); + assertTrue(RescueParty.isFactoryResetPropertySet()); + } + + @Test public void testIsRecoveryTriggeredRebootOnlyAfterRebootCompleted() { for (int i = 0; i < LEVEL_FACTORY_RESET; i++) { noteBoot(i + 1); @@ -469,6 +606,25 @@ public class RescuePartyTest { } @Test + public void testIsRecoveryTriggeredRebootOnlyAfterRebootCompletedRecoverabilityDetection() { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + for (int i = 0; i < RESCUE_LEVEL_FACTORY_RESET; i++) { + noteBoot(i + 1); + } + int mitigationCount = RESCUE_LEVEL_FACTORY_RESET + 1; + assertFalse(RescueParty.isFactoryResetPropertySet()); + noteBoot(mitigationCount++); + assertFalse(RescueParty.isFactoryResetPropertySet()); + noteBoot(mitigationCount++); + assertFalse(RescueParty.isFactoryResetPropertySet()); + noteBoot(mitigationCount++); + setCrashRecoveryPropAttemptingReboot(false); + noteBoot(mitigationCount + 1); + assertTrue(RescueParty.isRecoveryTriggeredReboot()); + assertTrue(RescueParty.isFactoryResetPropertySet()); + } + + @Test public void testThrottlingOnBootFailures() { setCrashRecoveryPropAttemptingReboot(false); long now = System.currentTimeMillis(); @@ -481,6 +637,19 @@ public class RescuePartyTest { } @Test + public void testThrottlingOnBootFailuresRecoverabilityDetection() { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + setCrashRecoveryPropAttemptingReboot(false); + long now = System.currentTimeMillis(); + long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1); + setCrashRecoveryPropLastFactoryReset(beforeTimeout); + for (int i = 1; i <= RESCUE_LEVEL_FACTORY_RESET; i++) { + noteBoot(i); + } + assertFalse(RescueParty.isRecoveryTriggeredReboot()); + } + + @Test public void testThrottlingOnAppCrash() { setCrashRecoveryPropAttemptingReboot(false); long now = System.currentTimeMillis(); @@ -493,6 +662,19 @@ public class RescuePartyTest { } @Test + public void testThrottlingOnAppCrashRecoverabilityDetection() { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + setCrashRecoveryPropAttemptingReboot(false); + long now = System.currentTimeMillis(); + long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1); + setCrashRecoveryPropLastFactoryReset(beforeTimeout); + for (int i = 0; i <= RESCUE_LEVEL_FACTORY_RESET; i++) { + noteAppCrash(i + 1, true); + } + assertFalse(RescueParty.isRecoveryTriggeredReboot()); + } + + @Test public void testNotThrottlingAfterTimeoutOnBootFailures() { setCrashRecoveryPropAttemptingReboot(false); long now = System.currentTimeMillis(); @@ -503,6 +685,20 @@ public class RescuePartyTest { } assertTrue(RescueParty.isRecoveryTriggeredReboot()); } + + @Test + public void testNotThrottlingAfterTimeoutOnBootFailuresRecoverabilityDetection() { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + setCrashRecoveryPropAttemptingReboot(false); + long now = System.currentTimeMillis(); + long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1); + setCrashRecoveryPropLastFactoryReset(afterTimeout); + for (int i = 1; i <= RESCUE_LEVEL_FACTORY_RESET; i++) { + noteBoot(i); + } + assertTrue(RescueParty.isRecoveryTriggeredReboot()); + } + @Test public void testNotThrottlingAfterTimeoutOnAppCrash() { setCrashRecoveryPropAttemptingReboot(false); @@ -516,6 +712,19 @@ public class RescuePartyTest { } @Test + public void testNotThrottlingAfterTimeoutOnAppCrashRecoverabilityDetection() { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + setCrashRecoveryPropAttemptingReboot(false); + long now = System.currentTimeMillis(); + long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1); + setCrashRecoveryPropLastFactoryReset(afterTimeout); + for (int i = 0; i <= RESCUE_LEVEL_FACTORY_RESET; i++) { + noteAppCrash(i + 1, true); + } + assertTrue(RescueParty.isRecoveryTriggeredReboot()); + } + + @Test public void testNativeRescuePartyResets() { doReturn(true).when(() -> SettingsToPropertiesMapper.isNativeFlagsResetPerformed()); doReturn(FAKE_RESET_NATIVE_NAMESPACES).when( @@ -531,6 +740,7 @@ public class RescuePartyTest { @Test public void testExplicitlyEnablingAndDisablingRescue() { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false)); SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true)); assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage, @@ -543,6 +753,7 @@ public class RescuePartyTest { @Test public void testDisablingRescueByDeviceConfigFlag() { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false)); SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true)); @@ -568,6 +779,20 @@ public class RescuePartyTest { } @Test + public void testDisablingFactoryResetByDeviceConfigFlagRecoverabilityDetection() { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, Boolean.toString(true)); + + for (int i = 0; i < RESCUE_LEVEL_FACTORY_RESET; i++) { + noteBoot(i + 1); + } + assertFalse(RescueParty.isFactoryResetPropertySet()); + + // Restore the property value initialized in SetUp() + SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, ""); + } + + @Test public void testHealthCheckLevels() { RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); @@ -594,6 +819,46 @@ public class RescuePartyTest { } @Test + public void testHealthCheckLevelsRecoverabilityDetection() { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); + + // Ensure that no action is taken for cases where the failure reason is unknown + assertEquals(observer.onHealthCheckFailed(sFailingPackage, + PackageWatchdog.FAILURE_REASON_UNKNOWN, 1), + PackageHealthObserverImpact.USER_IMPACT_LEVEL_0); + + // Ensure the correct user impact is returned for each mitigation count. + assertEquals(observer.onHealthCheckFailed(sFailingPackage, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), + PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); + + assertEquals(observer.onHealthCheckFailed(sFailingPackage, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2), + PackageHealthObserverImpact.USER_IMPACT_LEVEL_20); + + assertEquals(observer.onHealthCheckFailed(sFailingPackage, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3), + PackageHealthObserverImpact.USER_IMPACT_LEVEL_20); + + assertEquals(observer.onHealthCheckFailed(sFailingPackage, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4), + PackageHealthObserverImpact.USER_IMPACT_LEVEL_20); + + assertEquals(observer.onHealthCheckFailed(sFailingPackage, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5), + PackageHealthObserverImpact.USER_IMPACT_LEVEL_20); + + assertEquals(observer.onHealthCheckFailed(sFailingPackage, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 6), + PackageHealthObserverImpact.USER_IMPACT_LEVEL_20); + + assertEquals(observer.onHealthCheckFailed(sFailingPackage, + PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 7), + PackageHealthObserverImpact.USER_IMPACT_LEVEL_20); + } + + @Test public void testBootLoopLevels() { RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); @@ -606,6 +871,19 @@ public class RescuePartyTest { } @Test + public void testBootLoopLevelsRecoverabilityDetection() { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); + + assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_20); + assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50); + assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_LEVEL_71); + assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_LEVEL_75); + assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_LEVEL_80); + assertEquals(observer.onBootLoop(6), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); + } + + @Test public void testResetDeviceConfigForPackagesOnlyRuntimeMap() { RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver), @@ -727,11 +1005,26 @@ public class RescuePartyTest { private void verifySettingsResets(int resetMode, String[] resetNamespaces, HashMap<String, Integer> configResetVerifiedTimesMap) { + verifyOnlySettingsReset(resetMode); + verifyDeviceConfigReset(resetNamespaces, configResetVerifiedTimesMap); + } + + private void verifyOnlySettingsReset(int resetMode) { verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null, resetMode, UserHandle.USER_SYSTEM)); verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(), eq(resetMode), anyInt())); - // Verify DeviceConfig resets + } + + private void verifyNoSettingsReset(int resetMode) { + verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null, + resetMode, UserHandle.USER_SYSTEM), never()); + verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(), + eq(resetMode), anyInt()), never()); + } + + private void verifyDeviceConfigReset(String[] resetNamespaces, + Map<String, Integer> configResetVerifiedTimesMap) { if (resetNamespaces == null) { verify(() -> DeviceConfig.resetToDefaults(anyInt(), anyString()), never()); } else { @@ -818,9 +1111,16 @@ public class RescuePartyTest { // mock properties in BootThreshold try { - mSpyBootThreshold = spy(watchdog.new BootThreshold( - PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, - PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS)); + if (Flags.recoverabilityDetection()) { + mSpyBootThreshold = spy(watchdog.new BootThreshold( + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS, + PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT)); + } else { + mSpyBootThreshold = spy(watchdog.new BootThreshold( + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS)); + } mCrashRecoveryPropertiesMap = new HashMap<>(); doAnswer((Answer<Integer>) invocationOnMock -> { diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java index 255cb6499d8a..26a2bee8cf68 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/inputmethod/LocaleUtilsTest.java @@ -357,6 +357,29 @@ public class LocaleUtilsTest { assertEquals(1, dest.size()); assertEquals(availableLocales.get(0), dest.get(0)); // "sr-Latn-RS" } + // Locale with deprecated subtag, e.g. CS for Serbia and Montenegro, should not win + // even if the other available locale doesn't have explicit script / country. + // On Android, users don't normally use deprecated subtags unless the application requests. + { + final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-RS"); + final ArrayList<Locale> availableLocales = new ArrayList<>(); + availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS")); + availableLocales.add(Locale.forLanguageTag("sr-RS")); + final ArrayList<Locale> dest = new ArrayList<>(); + LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); + assertEquals(1, dest.size()); + assertEquals(availableLocales.get(1), dest.get(0)); // "sr-RS" + } + { + final LocaleList preferredLocales = LocaleList.forLanguageTags("sr-RS"); + final ArrayList<Locale> availableLocales = new ArrayList<>(); + availableLocales.add(Locale.forLanguageTag("sr-Cyrl-CS")); + availableLocales.add(Locale.forLanguageTag("sr")); + final ArrayList<Locale> dest = new ArrayList<>(); + LocaleUtils.filterByLanguage(availableLocales, sIdentityMapper, preferredLocales, dest); + assertEquals(1, dest.size()); + assertEquals(availableLocales.get(1), dest.get(0)); // "sr" + } } @Test diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index a5f7963b9c96..bd30ef583903 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -2315,10 +2315,11 @@ public class NetworkPolicyManagerServiceTest { } waitForUidEventHandlerIdle(); try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { - // Doesn't cross any other threshold. + // Doesn't cross any threshold, but changes below TOP_THRESHOLD_STATE should always + // be processed callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE - 1, testProcStateSeq++, PROCESS_CAPABILITY_NONE); - assertFalse(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); } waitForUidEventHandlerIdle(); } @@ -2349,21 +2350,21 @@ public class NetworkPolicyManagerServiceTest { int testProcStateSeq = 0; try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { // First callback for uid. - callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE, testProcStateSeq++, + callOnUidStatechanged(UID_A, FOREGROUND_THRESHOLD_STATE, testProcStateSeq++, PROCESS_CAPABILITY_NONE); assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); } waitForUidEventHandlerIdle(); try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { // The same process-state with one network capability added. - callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE, testProcStateSeq++, + callOnUidStatechanged(UID_A, FOREGROUND_THRESHOLD_STATE, testProcStateSeq++, PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK); assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); } waitForUidEventHandlerIdle(); try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { // The same process-state with another network capability added. - callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE, testProcStateSeq++, + callOnUidStatechanged(UID_A, FOREGROUND_THRESHOLD_STATE, testProcStateSeq++, PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK); assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); @@ -2371,11 +2372,21 @@ public class NetworkPolicyManagerServiceTest { waitForUidEventHandlerIdle(); try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { // The same process-state with all capabilities, but no change in network capabilities. - callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE, testProcStateSeq++, + callOnUidStatechanged(UID_A, FOREGROUND_THRESHOLD_STATE, testProcStateSeq++, PROCESS_CAPABILITY_ALL); assertFalse(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); } waitForUidEventHandlerIdle(); + + callAndWaitOnUidStateChanged(UID_A, TOP_THRESHOLD_STATE, testProcStateSeq++, + PROCESS_CAPABILITY_ALL); + try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) { + // No change in capabilities, but TOP_THRESHOLD_STATE change should always be processed. + callOnUidStatechanged(UID_A, TOP_THRESHOLD_STATE, testProcStateSeq++, + PROCESS_CAPABILITY_ALL); + assertTrue(mService.mUidEventHandler.hasMessages(UID_MSG_STATE_CHANGED)); + } + waitForUidEventHandlerIdle(); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java index 887e5ee0c58a..e4436966ae03 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java @@ -304,8 +304,7 @@ public class ContentRecorderTests extends WindowTestsBase { mContentRecorder.updateRecording(); // Resize the output surface. - final Point newSurfaceSize = new Point(Math.round(mSurfaceSize.x / 2f), - Math.round(mSurfaceSize.y * 2)); + final Point newSurfaceSize = new Point(Math.round(mSurfaceSize.x / 2f), mSurfaceSize.y * 2); doReturn(newSurfaceSize).when(mWm.mDisplayManagerInternal).getDisplaySurfaceDefaultSize( anyInt()); mContentRecorder.onConfigurationChanged( diff --git a/tests/PackageWatchdog/Android.bp b/tests/PackageWatchdog/Android.bp index e0e6c4c43b16..2c5fdd3228ed 100644 --- a/tests/PackageWatchdog/Android.bp +++ b/tests/PackageWatchdog/Android.bp @@ -28,8 +28,10 @@ android_test { static_libs: [ "junit", "mockito-target-extended-minus-junit4", + "flag-junit", "frameworks-base-testutils", "androidx.test.rules", + "PlatformProperties", "services.core", "services.net", "truth", diff --git a/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java new file mode 100644 index 000000000000..081da11f2aa8 --- /dev/null +++ b/tests/PackageWatchdog/src/com/android/server/CrashRecoveryTest.java @@ -0,0 +1,644 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.VersionedPackage; +import android.content.rollback.PackageRollbackInfo; +import android.content.rollback.RollbackInfo; +import android.content.rollback.RollbackManager; +import android.crashrecovery.flags.Flags; +import android.net.ConnectivityModuleConnector; +import android.net.ConnectivityModuleConnector.ConnectivityModuleHealthListener; +import android.os.Handler; +import android.os.SystemProperties; +import android.os.test.TestLooper; +import android.platform.test.flag.junit.SetFlagsRule; +import android.provider.DeviceConfig; +import android.util.AtomicFile; + +import androidx.test.InstrumentationRegistry; + +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.server.RescueParty.RescuePartyObserver; +import com.android.server.pm.ApexManager; +import com.android.server.rollback.RollbackPackageHealthObserver; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Answers; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import org.mockito.stubbing.Answer; + +import java.io.File; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Test CrashRecovery, integration tests that include PackageWatchdog, RescueParty and + * RollbackPackageHealthObserver + */ +public class CrashRecoveryTest { + private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG = + "persist.device_config.configuration.disable_rescue_party"; + + private static final String APP_A = "com.package.a"; + private static final String APP_B = "com.package.b"; + private static final String APP_C = "com.package.c"; + private static final long VERSION_CODE = 1L; + private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1); + + private static final RollbackInfo ROLLBACK_INFO_LOW = getRollbackInfo(APP_A, VERSION_CODE, 1, + PackageManager.ROLLBACK_USER_IMPACT_LOW); + private static final RollbackInfo ROLLBACK_INFO_HIGH = getRollbackInfo(APP_B, VERSION_CODE, 2, + PackageManager.ROLLBACK_USER_IMPACT_HIGH); + private static final RollbackInfo ROLLBACK_INFO_MANUAL = getRollbackInfo(APP_C, VERSION_CODE, 3, + PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL); + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + private final TestClock mTestClock = new TestClock(); + private TestLooper mTestLooper; + private Context mSpyContext; + // Keep track of all created watchdogs to apply device config changes + private List<PackageWatchdog> mAllocatedWatchdogs; + @Mock + private ConnectivityModuleConnector mConnectivityModuleConnector; + @Mock + private PackageManager mMockPackageManager; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private ApexManager mApexManager; + @Mock + RollbackManager mRollbackManager; + // Mock only sysprop apis + private PackageWatchdog.BootThreshold mSpyBootThreshold; + @Captor + private ArgumentCaptor<ConnectivityModuleHealthListener> mConnectivityModuleCallbackCaptor; + private MockitoSession mSession; + private HashMap<String, String> mSystemSettingsMap; + private HashMap<String, String> mCrashRecoveryPropertiesMap; + + @Before + public void setUp() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + MockitoAnnotations.initMocks(this); + new File(InstrumentationRegistry.getContext().getFilesDir(), + "package-watchdog.xml").delete(); + adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG, + Manifest.permission.WRITE_DEVICE_CONFIG); + mTestLooper = new TestLooper(); + mSpyContext = spy(InstrumentationRegistry.getContext()); + when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> { + final PackageInfo res = new PackageInfo(); + res.packageName = inv.getArgument(0); + res.setLongVersionCode(VERSION_CODE); + return res; + }); + mSession = ExtendedMockito.mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .spyStatic(SystemProperties.class) + .spyStatic(RescueParty.class) + .startMocking(); + mSystemSettingsMap = new HashMap<>(); + + // Mock SystemProperties setter and various getters + doAnswer((Answer<Void>) invocationOnMock -> { + String key = invocationOnMock.getArgument(0); + String value = invocationOnMock.getArgument(1); + + mSystemSettingsMap.put(key, value); + return null; + } + ).when(() -> SystemProperties.set(anyString(), anyString())); + + doAnswer((Answer<Integer>) invocationOnMock -> { + String key = invocationOnMock.getArgument(0); + int defaultValue = invocationOnMock.getArgument(1); + + String storedValue = mSystemSettingsMap.get(key); + return storedValue == null ? defaultValue : Integer.parseInt(storedValue); + } + ).when(() -> SystemProperties.getInt(anyString(), anyInt())); + + doAnswer((Answer<Long>) invocationOnMock -> { + String key = invocationOnMock.getArgument(0); + long defaultValue = invocationOnMock.getArgument(1); + + String storedValue = mSystemSettingsMap.get(key); + return storedValue == null ? defaultValue : Long.parseLong(storedValue); + } + ).when(() -> SystemProperties.getLong(anyString(), anyLong())); + + doAnswer((Answer<Boolean>) invocationOnMock -> { + String key = invocationOnMock.getArgument(0); + boolean defaultValue = invocationOnMock.getArgument(1); + + String storedValue = mSystemSettingsMap.get(key); + return storedValue == null ? defaultValue : Boolean.parseBoolean(storedValue); + } + ).when(() -> SystemProperties.getBoolean(anyString(), anyBoolean())); + + SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true)); + SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false)); + + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PackageWatchdog.PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED, + Boolean.toString(true), false); + + DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, + PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT, + Integer.toString(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT), false); + + mAllocatedWatchdogs = new ArrayList<>(); + RescuePartyObserver.reset(); + } + + @After + public void tearDown() throws Exception { + dropShellPermissions(); + mSession.finishMocking(); + // Clean up listeners since too many listeners will delay notifications significantly + for (PackageWatchdog watchdog : mAllocatedWatchdogs) { + watchdog.removePropertyChangedListener(); + } + mAllocatedWatchdogs.clear(); + } + + @Test + public void testBootLoopWithRescueParty() throws Exception { + PackageWatchdog watchdog = createWatchdog(); + RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog); + + verify(rescuePartyObserver, never()).executeBootLoopMitigation(1); + int bootCounter = 0; + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { + watchdog.noteBoot(); + bootCounter += 1; + } + verify(rescuePartyObserver).executeBootLoopMitigation(1); + verify(rescuePartyObserver, never()).executeBootLoopMitigation(2); + + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { + watchdog.noteBoot(); + bootCounter += 1; + } + verify(rescuePartyObserver).executeBootLoopMitigation(2); + verify(rescuePartyObserver, never()).executeBootLoopMitigation(3); + + int bootLoopThreshold = PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - bootCounter; + for (int i = 0; i < bootLoopThreshold; i++) { + watchdog.noteBoot(); + } + verify(rescuePartyObserver).executeBootLoopMitigation(3); + verify(rescuePartyObserver, never()).executeBootLoopMitigation(4); + + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { + watchdog.noteBoot(); + } + verify(rescuePartyObserver).executeBootLoopMitigation(4); + verify(rescuePartyObserver, never()).executeBootLoopMitigation(5); + + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { + watchdog.noteBoot(); + } + verify(rescuePartyObserver).executeBootLoopMitigation(5); + verify(rescuePartyObserver, never()).executeBootLoopMitigation(6); + + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { + watchdog.noteBoot(); + } + verify(rescuePartyObserver).executeBootLoopMitigation(6); + verify(rescuePartyObserver, never()).executeBootLoopMitigation(7); + } + + @Test + public void testBootLoopWithRollbackPackageHealthObserver() throws Exception { + PackageWatchdog watchdog = createWatchdog(); + RollbackPackageHealthObserver rollbackObserver = + setUpRollbackPackageHealthObserver(watchdog); + + verify(rollbackObserver, never()).executeBootLoopMitigation(1); + int bootCounter = 0; + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { + watchdog.noteBoot(); + bootCounter += 1; + } + verify(rollbackObserver).executeBootLoopMitigation(1); + verify(rollbackObserver, never()).executeBootLoopMitigation(2); + + // Update the list of available rollbacks after executing bootloop mitigation once + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH, + ROLLBACK_INFO_MANUAL)); + + int bootLoopThreshold = PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - bootCounter; + for (int i = 0; i < bootLoopThreshold; i++) { + watchdog.noteBoot(); + } + verify(rollbackObserver).executeBootLoopMitigation(2); + verify(rollbackObserver, never()).executeBootLoopMitigation(3); + + // Update the list of available rollbacks after executing bootloop mitigation once + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL)); + + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { + watchdog.noteBoot(); + } + verify(rollbackObserver, never()).executeBootLoopMitigation(3); + } + + @Test + public void testBootLoopWithRescuePartyAndRollbackPackageHealthObserver() throws Exception { + PackageWatchdog watchdog = createWatchdog(); + RescuePartyObserver rescuePartyObserver = setUpRescuePartyObserver(watchdog); + RollbackPackageHealthObserver rollbackObserver = + setUpRollbackPackageHealthObserver(watchdog); + + verify(rescuePartyObserver, never()).executeBootLoopMitigation(1); + verify(rollbackObserver, never()).executeBootLoopMitigation(1); + int bootCounter = 0; + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { + watchdog.noteBoot(); + bootCounter += 1; + } + verify(rescuePartyObserver).executeBootLoopMitigation(1); + verify(rescuePartyObserver, never()).executeBootLoopMitigation(2); + verify(rollbackObserver, never()).executeBootLoopMitigation(1); + + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { + watchdog.noteBoot(); + bootCounter += 1; + } + verify(rescuePartyObserver).executeBootLoopMitigation(2); + verify(rescuePartyObserver, never()).executeBootLoopMitigation(3); + verify(rollbackObserver, never()).executeBootLoopMitigation(2); + + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { + watchdog.noteBoot(); + bootCounter += 1; + } + verify(rescuePartyObserver, never()).executeBootLoopMitigation(3); + verify(rollbackObserver).executeBootLoopMitigation(1); + verify(rollbackObserver, never()).executeBootLoopMitigation(2); + // Update the list of available rollbacks after executing bootloop mitigation once + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_HIGH, + ROLLBACK_INFO_MANUAL)); + + int bootLoopThreshold = PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - bootCounter; + for (int i = 0; i < bootLoopThreshold; i++) { + watchdog.noteBoot(); + } + verify(rescuePartyObserver).executeBootLoopMitigation(3); + verify(rescuePartyObserver, never()).executeBootLoopMitigation(4); + verify(rollbackObserver, never()).executeBootLoopMitigation(2); + + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { + watchdog.noteBoot(); + } + verify(rescuePartyObserver).executeBootLoopMitigation(4); + verify(rescuePartyObserver, never()).executeBootLoopMitigation(5); + verify(rollbackObserver, never()).executeBootLoopMitigation(2); + + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { + watchdog.noteBoot(); + } + verify(rescuePartyObserver).executeBootLoopMitigation(5); + verify(rescuePartyObserver, never()).executeBootLoopMitigation(6); + verify(rollbackObserver, never()).executeBootLoopMitigation(2); + + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { + watchdog.noteBoot(); + } + verify(rescuePartyObserver, never()).executeBootLoopMitigation(6); + verify(rollbackObserver).executeBootLoopMitigation(2); + verify(rollbackObserver, never()).executeBootLoopMitigation(3); + // Update the list of available rollbacks after executing bootloop mitigation + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_MANUAL)); + + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; i++) { + watchdog.noteBoot(); + } + verify(rescuePartyObserver).executeBootLoopMitigation(6); + verify(rescuePartyObserver, never()).executeBootLoopMitigation(7); + verify(rollbackObserver, never()).executeBootLoopMitigation(3); + } + + RollbackPackageHealthObserver setUpRollbackPackageHealthObserver(PackageWatchdog watchdog) { + RollbackPackageHealthObserver rollbackObserver = + spy(new RollbackPackageHealthObserver(mSpyContext, mApexManager)); + when(mSpyContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(ROLLBACK_INFO_LOW, + ROLLBACK_INFO_HIGH, ROLLBACK_INFO_MANUAL)); + when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager); + + watchdog.registerHealthObserver(rollbackObserver); + return rollbackObserver; + } + + RescuePartyObserver setUpRescuePartyObserver(PackageWatchdog watchdog) { + setCrashRecoveryPropRescueBootCount(0); + RescuePartyObserver rescuePartyObserver = spy(RescuePartyObserver.getInstance(mSpyContext)); + assertFalse(RescueParty.isRebootPropertySet()); + watchdog.registerHealthObserver(rescuePartyObserver); + return rescuePartyObserver; + } + + private static RollbackInfo getRollbackInfo(String packageName, long versionCode, + int rollbackId, int rollbackUserImpact) { + VersionedPackage appFrom = new VersionedPackage(packageName, versionCode + 1); + VersionedPackage appTo = new VersionedPackage(packageName, versionCode); + PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appFrom, appTo, null, + null, false, false, null); + RollbackInfo rollbackInfo = new RollbackInfo(rollbackId, List.of(packageRollbackInfo), + false, null, 111, rollbackUserImpact); + return rollbackInfo; + } + + private void adoptShellPermissions(String... permissions) { + androidx.test.platform.app.InstrumentationRegistry + .getInstrumentation() + .getUiAutomation() + .adoptShellPermissionIdentity(permissions); + } + + private void dropShellPermissions() { + androidx.test.platform.app.InstrumentationRegistry + .getInstrumentation() + .getUiAutomation() + .dropShellPermissionIdentity(); + } + + + private PackageWatchdog createWatchdog() { + return createWatchdog(new TestController(), true /* withPackagesReady */); + } + + private PackageWatchdog createWatchdog(TestController controller, boolean withPackagesReady) { + AtomicFile policyFile = + new AtomicFile(new File(mSpyContext.getFilesDir(), "package-watchdog.xml")); + Handler handler = new Handler(mTestLooper.getLooper()); + PackageWatchdog watchdog = + new PackageWatchdog(mSpyContext, policyFile, handler, handler, controller, + mConnectivityModuleConnector, mTestClock); + mockCrashRecoveryProperties(watchdog); + + // Verify controller is not automatically started + assertThat(controller.mIsEnabled).isFalse(); + if (withPackagesReady) { + // Only capture the NetworkStack callback for the latest registered watchdog + reset(mConnectivityModuleConnector); + watchdog.onPackagesReady(); + // Verify controller by default is started when packages are ready + assertThat(controller.mIsEnabled).isTrue(); + + verify(mConnectivityModuleConnector).registerHealthListener( + mConnectivityModuleCallbackCaptor.capture()); + } + mAllocatedWatchdogs.add(watchdog); + return watchdog; + } + + // Mock CrashRecoveryProperties as they cannot be accessed due to SEPolicy restrictions + private void mockCrashRecoveryProperties(PackageWatchdog watchdog) { + mCrashRecoveryPropertiesMap = new HashMap<>(); + + // mock properties in RescueParty + try { + + doAnswer((Answer<Boolean>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.attempting_factory_reset", "false"); + return Boolean.parseBoolean(storedValue); + }).when(() -> RescueParty.isFactoryResetPropertySet()); + doAnswer((Answer<Void>) invocationOnMock -> { + boolean value = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.attempting_factory_reset", + Boolean.toString(value)); + return null; + }).when(() -> RescueParty.setFactoryResetProperty(anyBoolean())); + + doAnswer((Answer<Boolean>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.attempting_reboot", "false"); + return Boolean.parseBoolean(storedValue); + }).when(() -> RescueParty.isRebootPropertySet()); + doAnswer((Answer<Void>) invocationOnMock -> { + boolean value = invocationOnMock.getArgument(0); + setCrashRecoveryPropAttemptingReboot(value); + return null; + }).when(() -> RescueParty.setRebootProperty(anyBoolean())); + + doAnswer((Answer<Long>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("persist.crashrecovery.last_factory_reset", "0"); + return Long.parseLong(storedValue); + }).when(() -> RescueParty.getLastFactoryResetTimeMs()); + doAnswer((Answer<Void>) invocationOnMock -> { + long value = invocationOnMock.getArgument(0); + setCrashRecoveryPropLastFactoryReset(value); + return null; + }).when(() -> RescueParty.setLastFactoryResetTimeMs(anyLong())); + + doAnswer((Answer<Integer>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.max_rescue_level_attempted", "0"); + return Integer.parseInt(storedValue); + }).when(() -> RescueParty.getMaxRescueLevelAttempted()); + doAnswer((Answer<Void>) invocationOnMock -> { + int value = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.max_rescue_level_attempted", + Integer.toString(value)); + return null; + }).when(() -> RescueParty.setMaxRescueLevelAttempted(anyInt())); + + } catch (Exception e) { + // tests will fail, just printing the error + System.out.println("Error while mocking crashrecovery properties " + e.getMessage()); + } + + try { + if (Flags.recoverabilityDetection()) { + mSpyBootThreshold = spy(watchdog.new BootThreshold( + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS, + PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT)); + } else { + mSpyBootThreshold = spy(watchdog.new BootThreshold( + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS)); + } + + doAnswer((Answer<Integer>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.rescue_boot_count", "0"); + return Integer.parseInt(storedValue); + }).when(mSpyBootThreshold).getCount(); + doAnswer((Answer<Void>) invocationOnMock -> { + int count = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.rescue_boot_count", + Integer.toString(count)); + return null; + }).when(mSpyBootThreshold).setCount(anyInt()); + + doAnswer((Answer<Integer>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.boot_mitigation_count", "0"); + return Integer.parseInt(storedValue); + }).when(mSpyBootThreshold).getMitigationCount(); + doAnswer((Answer<Void>) invocationOnMock -> { + int count = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.boot_mitigation_count", + Integer.toString(count)); + return null; + }).when(mSpyBootThreshold).setMitigationCount(anyInt()); + + doAnswer((Answer<Long>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.rescue_boot_start", "0"); + return Long.parseLong(storedValue); + }).when(mSpyBootThreshold).getStart(); + doAnswer((Answer<Void>) invocationOnMock -> { + long count = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.rescue_boot_start", + Long.toString(count)); + return null; + }).when(mSpyBootThreshold).setStart(anyLong()); + + doAnswer((Answer<Long>) invocationOnMock -> { + String storedValue = mCrashRecoveryPropertiesMap + .getOrDefault("crashrecovery.boot_mitigation_start", "0"); + return Long.parseLong(storedValue); + }).when(mSpyBootThreshold).getMitigationStart(); + doAnswer((Answer<Void>) invocationOnMock -> { + long count = invocationOnMock.getArgument(0); + mCrashRecoveryPropertiesMap.put("crashrecovery.boot_mitigation_start", + Long.toString(count)); + return null; + }).when(mSpyBootThreshold).setMitigationStart(anyLong()); + + Field mBootThresholdField = watchdog.getClass().getDeclaredField("mBootThreshold"); + mBootThresholdField.setAccessible(true); + mBootThresholdField.set(watchdog, mSpyBootThreshold); + } catch (Exception e) { + // tests will fail, just printing the error + System.out.println("Error detected while spying BootThreshold" + e.getMessage()); + } + } + + private void setCrashRecoveryPropRescueBootCount(int count) { + mCrashRecoveryPropertiesMap.put("crashrecovery.rescue_boot_count", + Integer.toString(count)); + } + + private void setCrashRecoveryPropAttemptingReboot(boolean value) { + mCrashRecoveryPropertiesMap.put("crashrecovery.attempting_reboot", + Boolean.toString(value)); + } + + private void setCrashRecoveryPropLastFactoryReset(long value) { + mCrashRecoveryPropertiesMap.put("persist.crashrecovery.last_factory_reset", + Long.toString(value)); + } + + private static class TestController extends ExplicitHealthCheckController { + TestController() { + super(null /* controller */); + } + + private boolean mIsEnabled; + private List<String> mSupportedPackages = new ArrayList<>(); + private List<String> mRequestedPackages = new ArrayList<>(); + private Consumer<List<PackageConfig>> mSupportedConsumer; + private List<Set> mSyncRequests = new ArrayList<>(); + + @Override + public void setEnabled(boolean enabled) { + mIsEnabled = enabled; + if (!mIsEnabled) { + mSupportedPackages.clear(); + } + } + + @Override + public void setCallbacks(Consumer<String> passedConsumer, + Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable) { + mSupportedConsumer = supportedConsumer; + } + + @Override + public void syncRequests(Set<String> packages) { + mSyncRequests.add(packages); + mRequestedPackages.clear(); + if (mIsEnabled) { + packages.retainAll(mSupportedPackages); + mRequestedPackages.addAll(packages); + List<PackageConfig> packageConfigs = new ArrayList<>(); + for (String packageName: packages) { + packageConfigs.add(new PackageConfig(packageName, SHORT_DURATION)); + } + mSupportedConsumer.accept(packageConfigs); + } else { + mSupportedConsumer.accept(Collections.emptyList()); + } + } + } + + private static class TestClock implements PackageWatchdog.SystemClock { + // Note 0 is special to the internal clock of PackageWatchdog. We need to start from + // a non-zero value in order not to disrupt the logic of PackageWatchdog. + private long mUpTimeMillis = 1; + @Override + public long uptimeMillis() { + return mUpTimeMillis; + } + } +} diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index 75284c712bd2..4f27e06083ba 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -36,11 +36,13 @@ import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.VersionedPackage; +import android.crashrecovery.flags.Flags; import android.net.ConnectivityModuleConnector; import android.net.ConnectivityModuleConnector.ConnectivityModuleHealthListener; import android.os.Handler; import android.os.SystemProperties; import android.os.test.TestLooper; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.util.AtomicFile; import android.util.Xml; @@ -54,11 +56,13 @@ import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.PackageWatchdog.HealthCheckState; import com.android.server.PackageWatchdog.MonitoredPackage; +import com.android.server.PackageWatchdog.ObserverInternal; import com.android.server.PackageWatchdog.PackageHealthObserver; import com.android.server.PackageWatchdog.PackageHealthObserverImpact; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; @@ -99,6 +103,10 @@ public class PackageWatchdogTest { private static final String OBSERVER_NAME_4 = "observer4"; private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1); private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5); + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private final TestClock mTestClock = new TestClock(); private TestLooper mTestLooper; private Context mSpyContext; @@ -128,6 +136,7 @@ public class PackageWatchdogTest { @Before public void setUp() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); MockitoAnnotations.initMocks(this); new File(InstrumentationRegistry.getContext().getFilesDir(), "package-watchdog.xml").delete(); @@ -444,6 +453,7 @@ public class PackageWatchdogTest { */ @Test public void testPackageFailureNotifyAllDifferentImpacts() throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); PackageWatchdog watchdog = createWatchdog(); TestObserver observerNone = new TestObserver(OBSERVER_NAME_1, PackageHealthObserverImpact.USER_IMPACT_LEVEL_0); @@ -488,6 +498,52 @@ public class PackageWatchdogTest { assertThat(observerLowPackages).containsExactly(APP_A); } + @Test + public void testPackageFailureNotifyAllDifferentImpactsRecoverability() throws Exception { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observerNone = new TestObserver(OBSERVER_NAME_1, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_0); + TestObserver observerHigh = new TestObserver(OBSERVER_NAME_2, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_50); + TestObserver observerMid = new TestObserver(OBSERVER_NAME_3, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); + TestObserver observerLow = new TestObserver(OBSERVER_NAME_4, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); + + // Start observing for all impact observers + watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D), + SHORT_DURATION); + watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C), + SHORT_DURATION); + watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B), + SHORT_DURATION); + watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A), + SHORT_DURATION); + + // Then fail all apps above the threshold + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE), + new VersionedPackage(APP_B, VERSION_CODE), + new VersionedPackage(APP_C, VERSION_CODE), + new VersionedPackage(APP_D, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + + // Verify least impact observers are notifed of package failures + List<String> observerNonePackages = observerNone.mMitigatedPackages; + List<String> observerHighPackages = observerHigh.mMitigatedPackages; + List<String> observerMidPackages = observerMid.mMitigatedPackages; + List<String> observerLowPackages = observerLow.mMitigatedPackages; + + // APP_D failure observed by only observerNone is not caught cos its impact is none + assertThat(observerNonePackages).isEmpty(); + // APP_C failure is caught by observerHigh cos it's the lowest impact observer + assertThat(observerHighPackages).containsExactly(APP_C); + // APP_B failure is caught by observerMid cos it's the lowest impact observer + assertThat(observerMidPackages).containsExactly(APP_B); + // APP_A failure is caught by observerLow cos it's the lowest impact observer + assertThat(observerLowPackages).containsExactly(APP_A); + } + /** * Test package failure and least impact observers are notified successively. * State transistions: @@ -501,6 +557,7 @@ public class PackageWatchdogTest { */ @Test public void testPackageFailureNotifyLeastImpactSuccessively() throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); PackageWatchdog watchdog = createWatchdog(); TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1, PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); @@ -563,11 +620,76 @@ public class PackageWatchdogTest { assertThat(observerSecond.mMitigatedPackages).isEmpty(); } + @Test + public void testPackageFailureNotifyLeastImpactSuccessivelyRecoverability() throws Exception { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); + TestObserver observerSecond = new TestObserver(OBSERVER_NAME_2, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); + + // Start observing for observerFirst and observerSecond with failure handling + watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION); + watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION); + + // Then fail APP_A above the threshold + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + + // Verify only observerFirst is notifed + assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A); + assertThat(observerSecond.mMitigatedPackages).isEmpty(); + + // After observerFirst handles failure, next action it has is high impact + observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_50; + observerFirst.mMitigatedPackages.clear(); + observerSecond.mMitigatedPackages.clear(); + + // Then fail APP_A again above the threshold + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + + // Verify only observerSecond is notifed cos it has least impact + assertThat(observerSecond.mMitigatedPackages).containsExactly(APP_A); + assertThat(observerFirst.mMitigatedPackages).isEmpty(); + + // After observerSecond handles failure, it has no further actions + observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; + observerFirst.mMitigatedPackages.clear(); + observerSecond.mMitigatedPackages.clear(); + + // Then fail APP_A again above the threshold + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + + // Verify only observerFirst is notifed cos it has the only action + assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A); + assertThat(observerSecond.mMitigatedPackages).isEmpty(); + + // After observerFirst handles failure, it too has no further actions + observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; + observerFirst.mMitigatedPackages.clear(); + observerSecond.mMitigatedPackages.clear(); + + // Then fail APP_A again above the threshold + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + + // Verify no observer is notified cos no actions left + assertThat(observerFirst.mMitigatedPackages).isEmpty(); + assertThat(observerSecond.mMitigatedPackages).isEmpty(); + } + /** * Test package failure and notifies only one observer even with observer impact tie. */ @Test public void testPackageFailureNotifyOneSameImpact() throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); PackageWatchdog watchdog = createWatchdog(); TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); @@ -588,6 +710,28 @@ public class PackageWatchdogTest { assertThat(observer2.mMitigatedPackages).isEmpty(); } + @Test + public void testPackageFailureNotifyOneSameImpactRecoverabilityDetection() throws Exception { + PackageWatchdog watchdog = createWatchdog(); + TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_50); + TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_50); + + // Start observing for observer1 and observer2 with failure handling + watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); + watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); + + // Then fail APP_A above the threshold + raiseFatalFailureAndDispatch(watchdog, + Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), + PackageWatchdog.FAILURE_REASON_UNKNOWN); + + // Verify only one observer is notifed + assertThat(observer1.mMitigatedPackages).containsExactly(APP_A); + assertThat(observer2.mMitigatedPackages).isEmpty(); + } + /** * Test package passing explicit health checks does not fail and vice versa. */ @@ -818,6 +962,7 @@ public class PackageWatchdogTest { @Test public void testNetworkStackFailure() { + mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); final PackageWatchdog wd = createWatchdog(); // Start observing with failure handling @@ -835,6 +980,25 @@ public class PackageWatchdogTest { assertThat(observer.mMitigatedPackages).containsExactly(APP_A); } + @Test + public void testNetworkStackFailureRecoverabilityDetection() { + final PackageWatchdog wd = createWatchdog(); + + // Start observing with failure handling + TestObserver observer = new TestObserver(OBSERVER_NAME_1, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); + wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION); + + // Notify of NetworkStack failure + mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A); + + // Run handler so package failures are dispatched to observers + mTestLooper.dispatchAll(); + + // Verify the NetworkStack observer is notified + assertThat(observer.mMitigatedPackages).isEmpty(); + } + /** Test default values are used when device property is invalid. */ @Test public void testInvalidConfig_watchdogTriggerFailureCount() { @@ -1045,6 +1209,7 @@ public class PackageWatchdogTest { /** Ensure that boot loop mitigation is done when the number of boots meets the threshold. */ @Test public void testBootLoopDetection_meetsThreshold() { + mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); PackageWatchdog watchdog = createWatchdog(); TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1); watchdog.registerHealthObserver(bootObserver); @@ -1054,6 +1219,16 @@ public class PackageWatchdogTest { assertThat(bootObserver.mitigatedBootLoop()).isTrue(); } + @Test + public void testBootLoopDetection_meetsThresholdRecoverability() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1); + watchdog.registerHealthObserver(bootObserver); + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD; i++) { + watchdog.noteBoot(); + } + assertThat(bootObserver.mitigatedBootLoop()).isTrue(); + } /** * Ensure that boot loop mitigation is not done when the number of boots does not meet the @@ -1071,10 +1246,43 @@ public class PackageWatchdogTest { } /** + * Ensure that boot loop mitigation is not done when the number of boots does not meet the + * threshold. + */ + @Test + public void testBootLoopDetection_doesNotMeetThresholdRecoverabilityLowImpact() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); + watchdog.registerHealthObserver(bootObserver); + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) { + watchdog.noteBoot(); + } + assertThat(bootObserver.mitigatedBootLoop()).isFalse(); + } + + /** + * Ensure that boot loop mitigation is not done when the number of boots does not meet the + * threshold. + */ + @Test + public void testBootLoopDetection_doesNotMeetThresholdRecoverabilityHighImpact() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_80); + watchdog.registerHealthObserver(bootObserver); + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - 1; i++) { + watchdog.noteBoot(); + } + assertThat(bootObserver.mitigatedBootLoop()).isFalse(); + } + + /** * Ensure that boot loop mitigation is done for the observer with the lowest user impact */ @Test public void testBootLoopMitigationDoneForLowestUserImpact() { + mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); PackageWatchdog watchdog = createWatchdog(); TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1); bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); @@ -1089,11 +1297,28 @@ public class PackageWatchdogTest { assertThat(bootObserver2.mitigatedBootLoop()).isFalse(); } + @Test + public void testBootLoopMitigationDoneForLowestUserImpactRecoverability() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1); + bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); + TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2); + bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); + watchdog.registerHealthObserver(bootObserver1); + watchdog.registerHealthObserver(bootObserver2); + for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD; i++) { + watchdog.noteBoot(); + } + assertThat(bootObserver1.mitigatedBootLoop()).isTrue(); + assertThat(bootObserver2.mitigatedBootLoop()).isFalse(); + } + /** * Ensure that the correct mitigation counts are sent to the boot loop observer. */ @Test public void testMultipleBootLoopMitigation() { + mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); PackageWatchdog watchdog = createWatchdog(); TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1); watchdog.registerHealthObserver(bootObserver); @@ -1114,6 +1339,64 @@ public class PackageWatchdogTest { assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4)); } + @Test + public void testMultipleBootLoopMitigationRecoverabilityLowImpact() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); + watchdog.registerHealthObserver(bootObserver); + for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; j++) { + watchdog.noteBoot(); + } + for (int i = 0; i < 4; i++) { + for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; j++) { + watchdog.noteBoot(); + } + } + + moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1); + + for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; j++) { + watchdog.noteBoot(); + } + for (int i = 0; i < 4; i++) { + for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; j++) { + watchdog.noteBoot(); + } + } + + assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4)); + } + + @Test + public void testMultipleBootLoopMitigationRecoverabilityHighImpact() { + PackageWatchdog watchdog = createWatchdog(); + TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_80); + watchdog.registerHealthObserver(bootObserver); + for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - 1; j++) { + watchdog.noteBoot(); + } + for (int i = 0; i < 4; i++) { + for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; j++) { + watchdog.noteBoot(); + } + } + + moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS + 1); + + for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_THRESHOLD - 1; j++) { + watchdog.noteBoot(); + } + for (int i = 0; i < 4; i++) { + for (int j = 0; j < PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT; j++) { + watchdog.noteBoot(); + } + } + + assertThat(bootObserver.mBootMitigationCounts).isEqualTo(List.of(1, 2, 3, 4, 1, 2, 3, 4)); + } + /** * Ensure that passing a null list of failed packages does not cause any mitigation logic to * execute. @@ -1304,6 +1587,78 @@ public class PackageWatchdogTest { } /** + * Ensure that a {@link ObserverInternal} may be correctly written and read in order to persist + * across reboots. + */ + @Test + @SuppressWarnings("GuardedBy") + public void testWritingAndReadingObserverInternalRecoverability() throws Exception { + PackageWatchdog watchdog = createWatchdog(); + + LongArrayQueue mitigationCalls = new LongArrayQueue(); + mitigationCalls.addLast(1000); + mitigationCalls.addLast(2000); + mitigationCalls.addLast(3000); + MonitoredPackage writePkg = watchdog.newMonitoredPackage( + "test.package", 1000, 2000, true, mitigationCalls); + final int bootMitigationCount = 4; + ObserverInternal writeObserver = new ObserverInternal("test", List.of(writePkg), + bootMitigationCount); + + // Write the observer + File tmpFile = File.createTempFile("observer-watchdog-test", ".xml"); + AtomicFile testFile = new AtomicFile(tmpFile); + FileOutputStream stream = testFile.startWrite(); + TypedXmlSerializer outputSerializer = Xml.resolveSerializer(stream); + outputSerializer.startDocument(null, true); + writeObserver.writeLocked(outputSerializer); + outputSerializer.endDocument(); + testFile.finishWrite(stream); + + // Read the observer + TypedXmlPullParser parser = Xml.resolvePullParser(testFile.openRead()); + XmlUtils.beginDocument(parser, "observer"); + ObserverInternal readObserver = ObserverInternal.read(parser, watchdog); + + assertThat(readObserver.name).isEqualTo(writeObserver.name); + assertThat(readObserver.getBootMitigationCount()).isEqualTo(bootMitigationCount); + } + + /** + * Ensure that boot mitigation counts may be correctly written and read as metadata + * in order to persist across reboots. + */ + @Test + @SuppressWarnings("GuardedBy") + public void testWritingAndReadingMetadataBootMitigationCountRecoverability() throws Exception { + PackageWatchdog watchdog = createWatchdog(); + String filePath = InstrumentationRegistry.getContext().getFilesDir().toString() + + "metadata_file.txt"; + + ObserverInternal observer1 = new ObserverInternal("test1", List.of(), 1); + ObserverInternal observer2 = new ObserverInternal("test2", List.of(), 2); + watchdog.registerObserverInternal(observer1); + watchdog.registerObserverInternal(observer2); + + mSpyBootThreshold = spy(watchdog.new BootThreshold( + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS, + PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT)); + + watchdog.saveAllObserversBootMitigationCountToMetadata(filePath); + + observer1.setBootMitigationCount(0); + observer2.setBootMitigationCount(0); + assertThat(observer1.getBootMitigationCount()).isEqualTo(0); + assertThat(observer2.getBootMitigationCount()).isEqualTo(0); + + mSpyBootThreshold.readAllObserversBootMitigationCountIfNecessary(filePath); + + assertThat(observer1.getBootMitigationCount()).isEqualTo(1); + assertThat(observer2.getBootMitigationCount()).isEqualTo(2); + } + + /** * Tests device config changes are propagated correctly. */ @Test @@ -1440,11 +1795,19 @@ public class PackageWatchdogTest { // Mock CrashRecoveryProperties as they cannot be accessed due to SEPolicy restrictions private void mockCrashRecoveryProperties(PackageWatchdog watchdog) { + mCrashRecoveryPropertiesMap = new HashMap<>(); + try { - mSpyBootThreshold = spy(watchdog.new BootThreshold( - PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, - PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS)); - mCrashRecoveryPropertiesMap = new HashMap<>(); + if (Flags.recoverabilityDetection()) { + mSpyBootThreshold = spy(watchdog.new BootThreshold( + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS, + PackageWatchdog.DEFAULT_BOOT_LOOP_MITIGATION_INCREMENT)); + } else { + mSpyBootThreshold = spy(watchdog.new BootThreshold( + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT, + PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS)); + } doAnswer((Answer<Integer>) invocationOnMock -> { String storedValue = mCrashRecoveryPropertiesMap diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java index ba9e4a831789..f82d9ca13938 100644 --- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java +++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java @@ -130,14 +130,13 @@ public class UpdatableSystemFontTest { private static final Pattern PATTERN_SYSTEM_FONT_FILES = Pattern.compile("^/(system|product)/fonts/"); - private String mKeyId; private FontManager mFontManager; private UiDevice mUiDevice; @Before public void setUp() throws Exception { Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); - mKeyId = insertCert(CERT_PATH); + insertCert(CERT_PATH); mFontManager = context.getSystemService(FontManager.class); expectCommandToSucceed("cmd font clear"); mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); @@ -147,9 +146,6 @@ public class UpdatableSystemFontTest { public void tearDown() throws Exception { // Ignore errors because this may fail if updatable system font is not enabled. runShellCommand("cmd font clear", null); - if (mKeyId != null) { - expectCommandToSucceed("mini-keyctl unlink " + mKeyId + " .fs-verity"); - } } @Test @@ -369,20 +365,11 @@ public class UpdatableSystemFontTest { assertThat(isFileOpenedBy(fontPath, EMOJI_RENDERING_TEST_APP_ID)).isFalse(); } - private static String insertCert(String certPath) throws Exception { - Pair<String, String> result; - try (InputStream is = new FileInputStream(certPath)) { - result = runShellCommand("mini-keyctl padd asymmetric fsv_test .fs-verity", is); - } + private static void insertCert(String certPath) throws Exception { // /data/local/tmp is not readable by system server. Copy a cert file to /data/fonts final String copiedCert = "/data/fonts/debug_cert.der"; runShellCommand("cp " + certPath + " " + copiedCert, null); runShellCommand("cmd font install-debug-cert " + copiedCert, null); - // Assert that there are no errors. - assertThat(result.second).isEmpty(); - String keyId = result.first.trim(); - assertThat(keyId).matches("^\\d+$"); - return keyId; } private int updateFontFile(String fontPath, String signaturePath) throws IOException { diff --git a/tests/vcn/Android.bp b/tests/vcn/Android.bp index bc1df75bb142..ee2e7cfcd480 100644 --- a/tests/vcn/Android.bp +++ b/tests/vcn/Android.bp @@ -31,6 +31,7 @@ android_test { "platform-test-annotations", "services.core", "service-connectivity-tiramisu-pre-jarjar", + "flag-junit", ], libs: [ "android.test.runner", diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java index 34f884b94296..887630b03a8c 100644 --- a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java @@ -38,6 +38,7 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; @@ -58,6 +59,7 @@ import android.os.HandlerExecutor; import android.os.ParcelUuid; import android.os.PersistableBundle; import android.os.test.TestLooper; +import android.platform.test.flag.junit.SetFlagsRule; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -71,7 +73,10 @@ import android.util.ArraySet; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.telephony.flags.Flags; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -128,6 +133,9 @@ public class TelephonySubscriptionTrackerTest { TEST_SUBID_TO_CARRIER_CONFIG_MAP = Collections.unmodifiableMap(subIdToCarrierConfigMap); } + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @NonNull private final Context mContext; @NonNull private final TestLooper mTestLooper; @NonNull private final Handler mHandler; @@ -185,6 +193,7 @@ public class TelephonySubscriptionTrackerTest { @Before public void setUp() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_FIX_CRASH_ON_GETTING_CONFIG_WHEN_PHONE_IS_GONE); doReturn(2).when(mTelephonyManager).getActiveModemCount(); mCallback = mock(TelephonySubscriptionTrackerCallback.class); @@ -594,4 +603,14 @@ public class TelephonySubscriptionTrackerTest { new ArraySet<>(Arrays.asList(TEST_SUBSCRIPTION_ID_1, TEST_SUBSCRIPTION_ID_2)), snapshot.getAllSubIdsInGroup(TEST_PARCEL_UUID)); } + + @Test + public void testCarrierConfigChangeWhenPhoneIsGoneShouldNotCrash() throws Exception { + doThrow(new IllegalStateException("Carrier config loader is not available.")) + .when(mCarrierConfigManager) + .getConfigForSubId(eq(TEST_SUBSCRIPTION_ID_1), any()); + + sendCarrierConfigChange(true /* hasValidSubscription */); + mTestLooper.dispatchAll(); + } } diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java index 1d7be2f4f039..fdf8fb8d3c41 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java @@ -34,6 +34,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -333,6 +334,7 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { public void testHandleLossRate_validationFail() throws Exception { checkHandleLossRate( 22, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */); + verify(mConnectivityManager).reportNetworkConnectivity(mNetwork, false); } @Test @@ -416,4 +418,31 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { checkGetPacketLossRate(oldState, 20000, 14000, 4096, 19); checkGetPacketLossRate(oldState, 20000, 14000, 3000, 10); } + + // Verify the polling event is scheduled with expected delays + private void verifyPollEventDelayAndScheduleNext(long expectedDelayMs) { + if (expectedDelayMs > 0) { + mTestLooper.dispatchAll(); + verify(mIpSecTransform, never()).requestIpSecTransformState(any(), any()); + mTestLooper.moveTimeForward(expectedDelayMs); + } + + mTestLooper.dispatchAll(); + verify(mIpSecTransform).requestIpSecTransformState(any(), any()); + reset(mIpSecTransform); + } + + @Test + public void testOnLinkPropertiesOrCapabilitiesChange() throws Exception { + // Start the monitor; verify the 1st poll is scheduled without delay + startMonitorAndCaptureStateReceiver(); + verifyPollEventDelayAndScheduleNext(0 /* expectedDelayMs */); + + // Verify the 2nd poll is rescheduled without delay + mIpSecPacketLossDetector.onLinkPropertiesOrCapabilitiesChanged(); + verifyPollEventDelayAndScheduleNext(0 /* expectedDelayMs */); + + // Verify the 3rd poll is scheduled with configured delay + verifyPollEventDelayAndScheduleNext(POLL_IPSEC_STATE_INTERVAL_MS); + } } diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java index 381c57496878..af6daa17e223 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; +import android.net.ConnectivityManager; import android.net.IpSecConfig; import android.net.IpSecTransform; import android.net.LinkProperties; @@ -33,12 +34,14 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.TelephonyNetworkSpecifier; import android.net.vcn.FeatureFlags; +import android.net.vcn.Flags; import android.os.Handler; import android.os.IPowerManager; import android.os.IThermalService; import android.os.ParcelUuid; import android.os.PowerManager; import android.os.test.TestLooper; +import android.platform.test.flag.junit.SetFlagsRule; import android.telephony.TelephonyManager; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; @@ -46,6 +49,7 @@ import com.android.server.vcn.VcnContext; import com.android.server.vcn.VcnNetworkProvider; import org.junit.Before; +import org.junit.Rule; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -53,6 +57,8 @@ import java.util.Set; import java.util.UUID; public abstract class NetworkEvaluationTestBase { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + protected static final String SSID = "TestWifi"; protected static final String SSID_OTHER = "TestWifiOther"; protected static final String PLMN_ID = "123456"; @@ -103,6 +109,7 @@ public abstract class NetworkEvaluationTestBase { @Mock protected FeatureFlags mFeatureFlags; @Mock protected android.net.platform.flags.FeatureFlags mCoreNetFeatureFlags; @Mock protected TelephonySubscriptionSnapshot mSubscriptionSnapshot; + @Mock protected ConnectivityManager mConnectivityManager; @Mock protected TelephonyManager mTelephonyManager; @Mock protected IPowerManager mPowerManagerService; @@ -114,6 +121,9 @@ public abstract class NetworkEvaluationTestBase { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mSetFlagsRule.enableFlags(Flags.FLAG_VALIDATE_NETWORK_ON_IPSEC_LOSS); + mSetFlagsRule.enableFlags(Flags.FLAG_EVALUATE_IPSEC_LOSS_ON_LP_NC_CHANGE); + when(mNetwork.getNetId()).thenReturn(-1); mTestLooper = new TestLooper(); @@ -130,6 +140,12 @@ public abstract class NetworkEvaluationTestBase { doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled(); setupSystemService( + mContext, + mConnectivityManager, + Context.CONNECTIVITY_SERVICE, + ConnectivityManager.class); + + setupSystemService( mContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class); when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager); when(mTelephonyManager.getNetworkOperator()).thenReturn(PLMN_ID); diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java index aa81efe9a1ce..1d6872195e81 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluatorTest.java @@ -31,6 +31,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyObject; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -333,4 +334,36 @@ public class UnderlyingNetworkEvaluatorTest extends NetworkEvaluationTestBase { .compare(penalized, notPenalized); assertEquals(1, result); } + + @Test + public void testNotifyNetworkMetricMonitorOnLpChange() throws Exception { + // Clear calls invoked when initializing mNetworkEvaluator + reset(mIpSecPacketLossDetector); + + final UnderlyingNetworkEvaluator evaluator = newUnderlyingNetworkEvaluator(); + evaluator.setNetworkCapabilities( + CELL_NETWORK_CAPABILITIES, + VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES, + SUB_GROUP, + mSubscriptionSnapshot, + mCarrierConfig); + + verify(mIpSecPacketLossDetector).onLinkPropertiesOrCapabilitiesChanged(); + } + + @Test + public void testNotifyNetworkMetricMonitorOnNcChange() throws Exception { + // Clear calls invoked when initializing mNetworkEvaluator + reset(mIpSecPacketLossDetector); + + final UnderlyingNetworkEvaluator evaluator = newUnderlyingNetworkEvaluator(); + evaluator.setLinkProperties( + LINK_PROPERTIES, + VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_TEMPLATES, + SUB_GROUP, + mSubscriptionSnapshot, + mCarrierConfig); + + verify(mIpSecPacketLossDetector).onLinkPropertiesOrCapabilitiesChanged(); + } } |