Merge "Add stub for VirtualDeviceManagerInternal"
diff --git a/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java b/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
index 973e996..762b16c 100644
--- a/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
+++ b/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
@@ -24,19 +24,17 @@
import android.database.sqlite.SQLiteDatabase;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
-
+import java.io.File;
+import java.util.Random;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.Random;
-
/**
* Performance tests for typical CRUD operations and loading rows into the Cursor
*
@@ -58,12 +56,9 @@
public void setUp() {
mContext = InstrumentationRegistry.getTargetContext();
mContext.deleteDatabase(DB_NAME);
- mDatabase = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
- mDatabase.execSQL("CREATE TABLE T1 "
- + "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)");
- mDatabase.execSQL("CREATE TABLE T2 ("
- + "_ID INTEGER PRIMARY KEY, COL_A VARCHAR(100), T1_ID INTEGER,"
- + "FOREIGN KEY(T1_ID) REFERENCES T1 (_ID))");
+
+ createOrOpenTestDatabase(
+ SQLiteDatabase.JOURNAL_MODE_TRUNCATE, SQLiteDatabase.SYNC_MODE_FULL);
}
@After
@@ -72,6 +67,25 @@
mContext.deleteDatabase(DB_NAME);
}
+ private void createOrOpenTestDatabase(String journalMode, String syncMode) {
+ SQLiteDatabase.OpenParams.Builder paramsBuilder = new SQLiteDatabase.OpenParams.Builder();
+ File dbFile = mContext.getDatabasePath(DB_NAME);
+ if (journalMode != null) {
+ paramsBuilder.setJournalMode(journalMode);
+ }
+ if (syncMode != null) {
+ paramsBuilder.setSynchronousMode(syncMode);
+ }
+ paramsBuilder.addOpenFlags(SQLiteDatabase.CREATE_IF_NECESSARY);
+
+ mDatabase = SQLiteDatabase.openDatabase(dbFile, paramsBuilder.build());
+ mDatabase.execSQL("CREATE TABLE T1 "
+ + "(_ID INTEGER PRIMARY KEY, COL_A INTEGER, COL_B VARCHAR(100), COL_C REAL)");
+ mDatabase.execSQL("CREATE TABLE T2 ("
+ + "_ID INTEGER PRIMARY KEY, COL_A VARCHAR(100), T1_ID INTEGER,"
+ + "FOREIGN KEY(T1_ID) REFERENCES T1 (_ID))");
+ }
+
@Test
public void testSelect() {
insertT1TestDataSet();
@@ -192,22 +206,114 @@
}
}
+ /**
+ * This test measures the insertion of a single row into a database using DELETE journal and
+ * synchronous modes.
+ */
@Test
public void testInsert() {
insertT1TestDataSet();
+ testInsertInternal("testInsert");
+ }
+
+ @Test
+ public void testInsertWithPersistFull() {
+ recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_PERSIST, SQLiteDatabase.SYNC_MODE_FULL);
+ insertT1TestDataSet();
+ testInsertInternal("testInsertWithPersistFull");
+ }
+
+ private void testInsertInternal(String traceTag) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
ContentValues cv = new ContentValues();
cv.put("_ID", DEFAULT_DATASET_SIZE);
cv.put("COL_B", "NewValue");
cv.put("COL_C", 1.1);
- String[] deleteArgs = new String[]{String.valueOf(DEFAULT_DATASET_SIZE)};
+ String[] deleteArgs = new String[] {String.valueOf(DEFAULT_DATASET_SIZE)};
+
while (state.keepRunning()) {
+ android.os.Trace.beginSection(traceTag);
assertEquals(DEFAULT_DATASET_SIZE, mDatabase.insert("T1", null, cv));
state.pauseTiming();
assertEquals(1, mDatabase.delete("T1", "_ID=?", deleteArgs));
state.resumeTiming();
+ android.os.Trace.endSection();
+ }
+ }
+
+ /**
+ * This test measures the insertion of a single row into a database using WAL journal mode and
+ * NORMAL synchronous mode.
+ */
+ @Test
+ public void testInsertWithWalNormalMode() {
+ recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_NORMAL);
+ insertT1TestDataSet();
+
+ testInsertInternal("testInsertWithWalNormalMode");
+ }
+
+ /**
+ * This test measures the insertion of a single row into a database using WAL journal mode and
+ * FULL synchronous mode. The goal is to see the difference between NORMAL vs FULL sync modes.
+ */
+ @Test
+ public void testInsertWithWalFullMode() {
+ recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_FULL);
+
+ insertT1TestDataSet();
+
+ testInsertInternal("testInsertWithWalFullMode");
+ }
+
+ /**
+ * This test measures the insertion of a multiple rows in a single transaction using WAL journal
+ * mode and NORMAL synchronous mode.
+ */
+ @Test
+ public void testBulkInsertWithWalNormalMode() {
+ recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_NORMAL);
+ testBulkInsertInternal("testBulkInsertWithWalNormalMode");
+ }
+
+ @Test
+ public void testBulkInsertWithPersistFull() {
+ recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_PERSIST, SQLiteDatabase.SYNC_MODE_FULL);
+ testBulkInsertInternal("testBulkInsertWithPersistFull");
+ }
+
+ /**
+ * This test measures the insertion of a multiple rows in a single transaction using TRUNCATE
+ * journal mode and FULL synchronous mode.
+ */
+ @Test
+ public void testBulkInsert() {
+ testBulkInsertInternal("testBulkInsert");
+ }
+
+ private void testBulkInsertInternal(String traceTag) {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ String[] statements = new String[DEFAULT_DATASET_SIZE];
+ for (int i = 0; i < DEFAULT_DATASET_SIZE; ++i) {
+ statements[i] = "INSERT INTO T1 VALUES (?,?,?,?)";
+ }
+
+ while (state.keepRunning()) {
+ android.os.Trace.beginSection(traceTag);
+ mDatabase.beginTransaction();
+ for (int i = 0; i < DEFAULT_DATASET_SIZE; ++i) {
+ mDatabase.execSQL(statements[i], new Object[] {i, i, "T1Value" + i, i * 1.1});
+ }
+ mDatabase.setTransactionSuccessful();
+ mDatabase.endTransaction();
+ android.os.Trace.endSection();
+
+ state.pauseTiming();
+ mDatabase.execSQL("DELETE FROM T1");
+ state.resumeTiming();
}
}
@@ -227,9 +333,30 @@
}
}
+ /**
+ * This test measures the update of a random row in a database.
+ */
+ @Test
+ public void testUpdateWithWalNormalMode() {
+ recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_NORMAL);
+ insertT1TestDataSet();
+ testUpdateInternal("testUpdateWithWalNormalMode");
+ }
+
+ @Test
+ public void testUpdateWithPersistFull() {
+ recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_PERSIST, SQLiteDatabase.SYNC_MODE_FULL);
+ insertT1TestDataSet();
+ testUpdateInternal("testUpdateWithPersistFull");
+ }
+
@Test
public void testUpdate() {
insertT1TestDataSet();
+ testUpdateInternal("testUpdate");
+ }
+
+ private void testUpdateInternal(String traceTag) {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
Random rnd = new Random(0);
@@ -237,6 +364,7 @@
ContentValues cv = new ContentValues();
String[] argArray = new String[1];
while (state.keepRunning()) {
+ android.os.Trace.beginSection(traceTag);
int id = rnd.nextInt(DEFAULT_DATASET_SIZE);
cv.put("COL_A", i);
cv.put("COL_B", "UpdatedValue");
@@ -244,6 +372,109 @@
argArray[0] = String.valueOf(id);
assertEquals(1, mDatabase.update("T1", cv, "_ID=?", argArray));
i++;
+ android.os.Trace.endSection();
+ }
+ }
+
+ /**
+ * This test measures a multi-threaded read-write environment where there are 2 readers and
+ * 1 writer in the database using TRUNCATE journal mode and FULL syncMode.
+ */
+ @Test
+ public void testMultithreadedReadWrite() {
+ insertT1TestDataSet();
+ performMultithreadedReadWriteTest();
+ }
+
+ private void doReadLoop(int totalIterations) {
+ Random rnd = new Random(0);
+ int currentIteration = 0;
+ while (currentIteration < totalIterations) {
+ android.os.Trace.beginSection("ReadDatabase");
+ int index = rnd.nextInt(DEFAULT_DATASET_SIZE);
+ try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 "
+ + "WHERE _ID=?",
+ new String[] {String.valueOf(index)})) {
+ cursor.moveToNext();
+ cursor.getInt(0);
+ cursor.getInt(1);
+ cursor.getString(2);
+ cursor.getDouble(3);
+ }
+ ++currentIteration;
+ android.os.Trace.endSection();
+ }
+ }
+
+ private void doReadLoop(BenchmarkState state) {
+ Random rnd = new Random(0);
+ while (state.keepRunning()) {
+ android.os.Trace.beginSection("ReadDatabase");
+ int index = rnd.nextInt(DEFAULT_DATASET_SIZE);
+ try (Cursor cursor = mDatabase.rawQuery("SELECT _ID, COL_A, COL_B, COL_C FROM T1 "
+ + "WHERE _ID=?",
+ new String[] {String.valueOf(index)})) {
+ cursor.moveToNext();
+ cursor.getInt(0);
+ cursor.getInt(1);
+ cursor.getString(2);
+ cursor.getDouble(3);
+ }
+ android.os.Trace.endSection();
+ }
+ }
+
+ private void doUpdateLoop(int totalIterations) {
+ SQLiteDatabase db = mContext.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);
+ Random rnd = new Random(0);
+ int i = 0;
+ ContentValues cv = new ContentValues();
+ String[] argArray = new String[1];
+
+ while (i < totalIterations) {
+ android.os.Trace.beginSection("UpdateDatabase");
+ int id = rnd.nextInt(DEFAULT_DATASET_SIZE);
+ cv.put("COL_A", i);
+ cv.put("COL_B", "UpdatedValue");
+ cv.put("COL_C", i);
+ argArray[0] = String.valueOf(id);
+ db.update("T1", cv, "_ID=?", argArray);
+ i++;
+ android.os.Trace.endSection();
+ }
+ }
+
+ /**
+ * This test measures a multi-threaded read-write environment where there are 2 readers and
+ * 1 writer in the database using WAL journal mode and NORMAL syncMode.
+ */
+ @Test
+ public void testMultithreadedReadWriteWithWalNormal() {
+ recreateTestDatabase(SQLiteDatabase.JOURNAL_MODE_WAL, SQLiteDatabase.SYNC_MODE_NORMAL);
+ insertT1TestDataSet();
+
+ performMultithreadedReadWriteTest();
+ }
+
+ private void performMultithreadedReadWriteTest() {
+ int totalBGIterations = 10000;
+ // Writer - Fixed iterations to avoid consuming cycles from mainloop benchmark iterations
+ Thread updateThread = new Thread(() -> { doUpdateLoop(totalBGIterations); });
+
+ // Reader 1 - Fixed iterations to avoid consuming cycles from mainloop benchmark iterations
+ Thread readerThread = new Thread(() -> { doReadLoop(totalBGIterations); });
+
+ updateThread.start();
+ readerThread.start();
+
+ // Reader 2
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ doReadLoop(state);
+
+ try {
+ updateThread.join();
+ readerThread.join();
+ } catch (Exception e) {
}
}
@@ -270,5 +501,11 @@
mDatabase.setTransactionSuccessful();
mDatabase.endTransaction();
}
+
+ private void recreateTestDatabase(String journalMode, String syncMode) {
+ mDatabase.close();
+ mContext.deleteDatabase(DB_NAME);
+ createOrOpenTestDatabase(journalMode, syncMode);
+ }
}
diff --git a/core/api/current.txt b/core/api/current.txt
index a8a252e..cc8b638 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -14376,11 +14376,21 @@
field public static final int CONFLICT_ROLLBACK = 1; // 0x1
field public static final int CREATE_IF_NECESSARY = 268435456; // 0x10000000
field public static final int ENABLE_WRITE_AHEAD_LOGGING = 536870912; // 0x20000000
+ field public static final String JOURNAL_MODE_DELETE = "DELETE";
+ field public static final String JOURNAL_MODE_MEMORY = "MEMORY";
+ field public static final String JOURNAL_MODE_OFF = "OFF";
+ field public static final String JOURNAL_MODE_PERSIST = "PERSIST";
+ field public static final String JOURNAL_MODE_TRUNCATE = "TRUNCATE";
+ field public static final String JOURNAL_MODE_WAL = "WAL";
field public static final int MAX_SQL_CACHE_SIZE = 100; // 0x64
field public static final int NO_LOCALIZED_COLLATORS = 16; // 0x10
field public static final int OPEN_READONLY = 1; // 0x1
field public static final int OPEN_READWRITE = 0; // 0x0
field public static final int SQLITE_MAX_LIKE_PATTERN_LENGTH = 50000; // 0xc350
+ field public static final String SYNC_MODE_EXTRA = "EXTRA";
+ field public static final String SYNC_MODE_FULL = "FULL";
+ field public static final String SYNC_MODE_NORMAL = "NORMAL";
+ field public static final String SYNC_MODE_OFF = "OFF";
}
public static interface SQLiteDatabase.CursorFactory {
@@ -32493,6 +32503,7 @@
field public static final String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking";
field @Deprecated public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
field public static final String DISALLOW_ADD_USER = "no_add_user";
+ field public static final String DISALLOW_ADD_WIFI_CONFIG = "no_add_wifi_config";
field public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
field public static final String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
field public static final String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display";
@@ -32548,6 +32559,7 @@
field public static final String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";
field public static final String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer";
field public static final String DISALLOW_USER_SWITCH = "no_user_switch";
+ field public static final String DISALLOW_WIFI_DIRECT = "no_wifi_direct";
field public static final String DISALLOW_WIFI_TETHERING = "no_wifi_tethering";
field public static final String ENSURE_VERIFY_APPS = "ensure_verify_apps";
field public static final String KEY_RESTRICTIONS_PENDING = "restrictions_pending";
@@ -42980,8 +42992,8 @@
method @NonNull public java.util.List<android.net.Uri> getDeviceToDeviceStatusSharingContacts(int);
method public int getDeviceToDeviceStatusSharingPreference(int);
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getOpportunisticSubscriptions();
- method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public String getPhoneNumber(int, int);
- method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public String getPhoneNumber(int);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public String getPhoneNumber(int, int);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public String getPhoneNumber(int);
method public static int getSlotIndex(int);
method @Nullable public int[] getSubscriptionIds(int);
method @NonNull public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int);
@@ -42993,7 +43005,7 @@
method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void removeSubscriptionsFromGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
- method public void setCarrierPhoneNumber(int, @NonNull String);
+ method @RequiresPermission("carrier privileges") public void setCarrierPhoneNumber(int, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingContacts(int, @NonNull java.util.List<android.net.Uri>);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingPreference(int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunistic(boolean, int);
@@ -44678,6 +44690,7 @@
field public static final int TYPE_TEXT_FLAG_CAP_CHARACTERS = 4096; // 0x1000
field public static final int TYPE_TEXT_FLAG_CAP_SENTENCES = 16384; // 0x4000
field public static final int TYPE_TEXT_FLAG_CAP_WORDS = 8192; // 0x2000
+ field public static final int TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS = 1048576; // 0x100000
field public static final int TYPE_TEXT_FLAG_IME_MULTI_LINE = 262144; // 0x40000
field public static final int TYPE_TEXT_FLAG_MULTI_LINE = 131072; // 0x20000
field public static final int TYPE_TEXT_FLAG_NO_SUGGESTIONS = 524288; // 0x80000
@@ -45807,8 +45820,10 @@
public class StyleSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan {
ctor public StyleSpan(int);
+ ctor public StyleSpan(int, int);
ctor public StyleSpan(@NonNull android.os.Parcel);
method public int describeContents();
+ method public int getFontWeightAdjustment();
method public int getSpanTypeId();
method public int getStyle();
method public void updateDrawState(android.text.TextPaint);
@@ -51331,6 +51346,7 @@
method public void addAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
method public boolean addAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
method public void addAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, @Nullable android.os.Handler);
+ method public void addAudioDescriptionRequestedChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
method public boolean addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
method public void addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, @Nullable android.os.Handler);
method @ColorInt public int getAccessibilityFocusColor();
@@ -51347,6 +51363,7 @@
method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
method public boolean removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
method public boolean removeAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
+ method public boolean removeAudioDescriptionRequestedChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
method public boolean removeTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
field public static final int FLAG_CONTENT_CONTROLS = 4; // 0x4
@@ -51362,6 +51379,10 @@
method public void onAccessibilityStateChanged(boolean);
}
+ public static interface AccessibilityManager.AudioDescriptionRequestedChangeListener {
+ method public void onAudioDescriptionRequestedChanged(boolean);
+ }
+
public static interface AccessibilityManager.TouchExplorationStateChangeListener {
method public void onTouchExplorationStateChanged(boolean);
}
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 0bbc80c..b8ce02e7 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -79,7 +79,7 @@
}
public abstract class ContentResolver {
- method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public final void registerContentObserverAsUser(@NonNull android.net.Uri, boolean, @NonNull android.database.ContentObserver, @NonNull android.os.UserHandle);
+ method @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public final void registerContentObserverAsUser(@NonNull android.net.Uri, boolean, @NonNull android.database.ContentObserver, @NonNull android.os.UserHandle);
}
public abstract class Context {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 187b08b..dd951b4 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -75,6 +75,7 @@
field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
field @Deprecated public static final String BROADCAST_NETWORK_PRIVILEGED = "android.permission.BROADCAST_NETWORK_PRIVILEGED";
field public static final String BYPASS_ROLE_QUALIFICATION = "android.permission.BYPASS_ROLE_QUALIFICATION";
+ field public static final String CALL_AUDIO_INTERCEPTION = "android.permission.CALL_AUDIO_INTERCEPTION";
field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";
@@ -965,6 +966,7 @@
}
public class DevicePolicyManager {
+ method public int checkProvisioningPreCondition(@NonNull String, @NonNull String);
method @Nullable public android.content.Intent createProvisioningIntentFromNfcIntent(@NonNull android.content.Intent);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
@@ -990,6 +992,7 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName);
method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setUserProvisioningState(int, @NonNull android.os.UserHandle);
field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE";
@@ -1003,6 +1006,21 @@
field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
field @Deprecated public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
field public static final String ACTION_UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER = "android.app.action.UPDATE_DEVICE_MANAGEMENT_ROLE_HOLDER";
+ field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
+ field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
+ field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
+ field public static final int CODE_HAS_DEVICE_OWNER = 1; // 0x1
+ field public static final int CODE_HAS_PAIRED = 8; // 0x8
+ field public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
+ field public static final int CODE_NONSYSTEM_USER_EXISTS = 5; // 0x5
+ field public static final int CODE_NOT_SYSTEM_USER = 7; // 0x7
+ field public static final int CODE_OK = 0; // 0x0
+ field public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; // 0xf
+ field public static final int CODE_SYSTEM_USER = 10; // 0xa
+ field public static final int CODE_UNKNOWN_ERROR = -1; // 0xffffffff
+ field public static final int CODE_USER_HAS_PROFILE_OWNER = 2; // 0x2
+ field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3
+ field public static final int CODE_USER_SETUP_COMPLETED = 4; // 0x4
field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI";
field @Deprecated public static final String EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL = "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL";
@@ -5402,17 +5420,21 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForCapturePresetChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener) throws java.lang.SecurityException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForStrategyChangedListener) throws java.lang.SecurityException;
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void cancelMuteAwaitConnection(@NonNull android.media.AudioDeviceAttributes) throws java.lang.IllegalStateException;
method public void clearAudioServerStateCallback();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean clearPreferredDevicesForCapturePreset(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
method @IntRange(from=0) public long getAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies();
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups();
+ method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioTrack getCallUplinkInjectionAudioTrack(@NonNull android.media.AudioFormat);
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public java.util.List<android.media.AudioDeviceAttributes> getDevicesForAttributes(@NonNull android.media.AudioAttributes);
method @IntRange(from=0) public long getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
+ method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getMutingExpectedDevice();
method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAttributes getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForCapturePreset(int);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAttributes> getPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
@@ -5420,7 +5442,10 @@
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
method public boolean isAudioServerRunning();
method public boolean isHdmiSystemAudioSupported();
+ method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void muteAwaitConnection(@NonNull int[], @NonNull android.media.AudioDeviceAttributes, long, @NonNull java.util.concurrent.TimeUnit) throws java.lang.IllegalStateException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void registerMuteAwaitConnectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForCapturePresetChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener);
@@ -5440,6 +5465,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterMuteAwaitConnectionCallback(@NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
method public void unregisterVolumeGroupCallback(@NonNull android.media.AudioManager.VolumeGroupCallback);
field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1
field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
@@ -5459,6 +5485,15 @@
method public void onAudioServerUp();
}
+ public abstract static class AudioManager.MuteAwaitConnectionCallback {
+ ctor public AudioManager.MuteAwaitConnectionCallback();
+ method public void onMutedUntilConnection(@NonNull android.media.AudioDeviceAttributes, @NonNull int[]);
+ method public void onUnmutedEvent(int, @NonNull android.media.AudioDeviceAttributes, @NonNull int[]);
+ field public static final int EVENT_CANCEL = 3; // 0x3
+ field public static final int EVENT_CONNECTION = 1; // 0x1
+ field public static final int EVENT_TIMEOUT = 2; // 0x2
+ }
+
@Deprecated public static interface AudioManager.OnPreferredDeviceForStrategyChangedListener {
method @Deprecated public void onPreferredDeviceForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @Nullable android.media.AudioDeviceAttributes);
}
@@ -7585,7 +7620,7 @@
method @NonNull public static android.media.tv.tuner.frontend.IsdbtFrontendSettings.IsdbtLayerSettings.Builder builder();
method public int getCodeRate();
method public int getModulation();
- method @IntRange(from=0, to=255) public int getNumberOfSegment();
+ method @IntRange(from=0, to=255) public int getNumberOfSegments();
method public int getTimeInterleaveMode();
}
@@ -7593,7 +7628,7 @@
method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.IsdbtLayerSettings build();
method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.IsdbtLayerSettings.Builder setCodeRate(int);
method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.IsdbtLayerSettings.Builder setModulation(int);
- method @IntRange(from=0, to=255) @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.IsdbtLayerSettings.Builder setNumberOfSegment(int);
+ method @IntRange(from=0, to=255) @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.IsdbtLayerSettings.Builder setNumberOfSegments(int);
method @NonNull public android.media.tv.tuner.frontend.IsdbtFrontendSettings.IsdbtLayerSettings.Builder setTimeInterleaveMode(int);
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index f20f963..aa791aa 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -437,7 +437,6 @@
package android.app.admin {
public class DevicePolicyManager {
- method public int checkProvisioningPreCondition(@Nullable String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void clearOrganizationId();
method @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord();
method @Nullable public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
@@ -465,22 +464,7 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public void setNextOperationSafety(int, int);
field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
field public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED";
- field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
- field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
- field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
- field public static final int CODE_HAS_DEVICE_OWNER = 1; // 0x1
- field public static final int CODE_HAS_PAIRED = 8; // 0x8
- field public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
- field public static final int CODE_NONSYSTEM_USER_EXISTS = 5; // 0x5
- field public static final int CODE_NOT_SYSTEM_USER = 7; // 0x7
- field @Deprecated public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; // 0xc
- field public static final int CODE_OK = 0; // 0x0
- field public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; // 0xf
field @Deprecated public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
- field public static final int CODE_SYSTEM_USER = 10; // 0xa
- field public static final int CODE_USER_HAS_PROFILE_OWNER = 2; // 0x2
- field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3
- field public static final int CODE_USER_SETUP_COMPLETED = 4; // 0x4
field public static final int OPERATION_CLEAR_APPLICATION_USER_DATA = 23; // 0x17
field public static final int OPERATION_CREATE_AND_MANAGE_USER = 5; // 0x5
field public static final int OPERATION_INSTALL_CA_CERT = 24; // 0x18
@@ -1445,6 +1429,8 @@
public class AudioManager {
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int abandonAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat);
+ method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioTrack getCallUplinkInjectionAudioTrack(@NonNull android.media.AudioFormat);
method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int);
method @IntRange(from=0) @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFadeOutDurationOnFocusLossMillis(@NonNull android.media.AudioAttributes);
method public static final int[] getPublicStreamTypes();
@@ -1453,8 +1439,10 @@
method @NonNull public java.util.Map<java.lang.Integer,java.lang.Boolean> getSurroundFormats();
method public boolean hasRegisteredDynamicPolicy();
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE}) public boolean isFullVolumeDevice();
+ method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int requestAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String, int, int);
method public void setRampingRingerEnabled(boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setTestDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, boolean);
}
public static final class AudioRecord.MetricsConstants {
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 48ceef0..2392c9a 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -626,6 +626,9 @@
* foreground ({@link ActivityManager.RunningAppProcessInfo#IMPORTANCE_FOREGROUND}
* and the {@link android.service.quicksettings.TileService} must be exported.
*
+ * Note: the system can choose to auto-deny a request if the user has denied that specific
+ * request (user, ComponentName) enough times before.
+ *
* @param tileServiceComponentName {@link ComponentName} of the
* {@link android.service.quicksettings.TileService} for the request.
* @param tileLabel label of the tile to show to the user.
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 61bf9b3..772492d 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1489,18 +1489,27 @@
mContext.getUserId());
if (fd != null) {
FileOutputStream fos = null;
- boolean ok = false;
+ final Bitmap tmp = BitmapFactory.decodeStream(resources.openRawResource(resid));
try {
- fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
- copyStreamToWallpaperFile(resources.openRawResource(resid), fos);
- // The 'close()' is the trigger for any server-side image manipulation,
- // so we must do that before waiting for completion.
- fos.close();
- completion.waitForCompletion();
+ // If the stream can't be decoded, treat it as an invalid input.
+ if (tmp != null) {
+ fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+ tmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
+ // The 'close()' is the trigger for any server-side image manipulation,
+ // so we must do that before waiting for completion.
+ fos.close();
+ completion.waitForCompletion();
+ } else {
+ throw new IllegalArgumentException(
+ "Resource 0x" + Integer.toHexString(resid) + " is invalid");
+ }
} finally {
// Might be redundant but completion shouldn't wait unless the write
// succeeded; this is a fallback if it threw past the close+wait.
IoUtils.closeQuietly(fos);
+ if (tmp != null) {
+ tmp.recycle();
+ }
}
}
} catch (RemoteException e) {
@@ -1742,13 +1751,22 @@
result, which, completion, mContext.getUserId());
if (fd != null) {
FileOutputStream fos = null;
+ final Bitmap tmp = BitmapFactory.decodeStream(bitmapData);
try {
- fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
- copyStreamToWallpaperFile(bitmapData, fos);
- fos.close();
- completion.waitForCompletion();
+ // If the stream can't be decoded, treat it as an invalid input.
+ if (tmp != null) {
+ fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+ tmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
+ fos.close();
+ completion.waitForCompletion();
+ } else {
+ throw new IllegalArgumentException("InputStream is invalid");
+ }
} finally {
IoUtils.closeQuietly(fos);
+ if (tmp != null) {
+ tmp.recycle();
+ }
}
}
} catch (RemoteException e) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ee74bb8..ba28283 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2158,13 +2158,24 @@
/**
* Result code for {@link #checkProvisioningPreCondition}.
*
+ * <p>Unknown error code returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
+ * {@link #ACTION_PROVISION_MANAGED_PROFILE} and {@link #ACTION_PROVISION_MANAGED_USER}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int CODE_UNKNOWN_ERROR = -1;
+
+ /**
+ * Result code for {@link #checkProvisioningPreCondition}.
+ *
* <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE},
* {@link #ACTION_PROVISION_MANAGED_PROFILE} and {@link #ACTION_PROVISION_MANAGED_USER}
* when provisioning is allowed.
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_OK = 0;
/**
@@ -2175,7 +2186,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_HAS_DEVICE_OWNER = 1;
/**
@@ -2186,7 +2197,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_USER_HAS_PROFILE_OWNER = 2;
/**
@@ -2196,7 +2207,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_USER_NOT_RUNNING = 3;
/**
@@ -2207,7 +2218,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_USER_SETUP_COMPLETED = 4;
/**
@@ -2215,7 +2226,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_NONSYSTEM_USER_EXISTS = 5;
/**
@@ -2223,7 +2234,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_ACCOUNTS_NOT_EMPTY = 6;
/**
@@ -2233,7 +2244,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_NOT_SYSTEM_USER = 7;
/**
@@ -2244,7 +2255,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_HAS_PAIRED = 8;
/**
@@ -2256,7 +2267,7 @@
* @see {@link PackageManager#FEATURE_MANAGED_USERS}
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9;
/**
@@ -2268,7 +2279,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_SYSTEM_USER = 10;
/**
@@ -2279,7 +2290,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11;
/**
@@ -2289,7 +2300,6 @@
* @deprecated not used anymore but can't be removed since it's a @TestApi.
**/
@Deprecated
- @TestApi
public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12;
/**
@@ -2301,7 +2311,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13;
/**
@@ -2323,7 +2333,7 @@
*
* @hide
*/
- @TestApi
+ @SystemApi
public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15;
/**
@@ -2334,8 +2344,8 @@
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "CODE_" }, value = {
- CODE_OK, CODE_HAS_DEVICE_OWNER, CODE_USER_HAS_PROFILE_OWNER, CODE_USER_NOT_RUNNING,
- CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED,
+ CODE_UNKNOWN_ERROR, CODE_OK, CODE_HAS_DEVICE_OWNER, CODE_USER_HAS_PROFILE_OWNER,
+ CODE_USER_NOT_RUNNING, CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED,
CODE_MANAGED_USERS_NOT_SUPPORTED, CODE_SYSTEM_USER, CODE_CANNOT_ADD_MANAGED_PROFILE,
CODE_NOT_SYSTEM_USER_SPLIT, CODE_DEVICE_ADMIN_NOT_SUPPORTED,
CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER,
@@ -11413,9 +11423,9 @@
* @return A {@link ProvisioningPreCondition} value indicating whether provisioning is allowed.
* @hide
*/
- @TestApi
+ @SystemApi
public @ProvisioningPreCondition int checkProvisioningPreCondition(
- @Nullable String action, @NonNull String packageName) {
+ @NonNull String action, @NonNull String packageName) {
try {
return mService.checkProvisioningPreCondition(action, packageName);
} catch (RemoteException re) {
@@ -12135,8 +12145,10 @@
*
* @param state to store
* @param userHandle for user
+ *
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
public void setUserProvisioningState(@UserProvisioningState int state, int userHandle) {
if (mService != null) {
try {
@@ -12148,6 +12160,34 @@
}
/**
+ * Set the {@link UserProvisioningState} for the supplied user. The supplied user has to be
+ * manged, otherwise it will throw an {@link IllegalStateException}.
+ *
+ * <p> For managed users/profiles/devices, only the following state changes are allowed:
+ * <ul>
+ * <li>{@link #STATE_USER_UNMANAGED} can change to any other state except itself
+ * <li>{@link #STATE_USER_SETUP_INCOMPLETE} and {@link #STATE_USER_SETUP_COMPLETE} can only
+ * change to {@link #STATE_USER_SETUP_FINALIZED}</li>
+ * <li>{@link #STATE_USER_PROFILE_COMPLETE} can only change to
+ * {@link #STATE_USER_PROFILE_FINALIZED}</li>
+ * <li>{@link #STATE_USER_SETUP_FINALIZED} can't be changed to any other state</li>
+ * <li>{@link #STATE_USER_PROFILE_FINALIZED} can only change to
+ * {@link #STATE_USER_UNMANAGED}</li>
+ * </ul>
+ * @param state to store
+ * @param userHandle for user
+ * @throws IllegalStateException if called with an invalid state change.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)
+ public void setUserProvisioningState(
+ @UserProvisioningState int state, @NonNull UserHandle userHandle) {
+ setUserProvisioningState(state, userHandle.getIdentifier());
+ }
+
+ /**
* Indicates the entity that controls the device. Two users are
* affiliated if the set of ids set by the device owner and the admin of the secondary user.
*
diff --git a/core/java/android/companion/OWNERS b/core/java/android/companion/OWNERS
index 54b35fc..004f66c 100644
--- a/core/java/android/companion/OWNERS
+++ b/core/java/android/companion/OWNERS
@@ -1,4 +1,5 @@
ewol@google.com
evanxinchen@google.com
guojing@google.com
-svetoslavganov@google.com
\ No newline at end of file
+svetoslavganov@google.com
+sergeynv@google.com
\ No newline at end of file
diff --git a/core/java/android/content/AttributionSource.aidl b/core/java/android/content/AttributionSource.aidl
index 10d5c27..7554cb2 100644
--- a/core/java/android/content/AttributionSource.aidl
+++ b/core/java/android/content/AttributionSource.aidl
@@ -16,4 +16,5 @@
package android.content;
+@JavaOnlyStableParcelable
parcelable AttributionSource;
diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java
index 1d4d30d..f335ae4 100644
--- a/core/java/android/content/BroadcastReceiver.java
+++ b/core/java/android/content/BroadcastReceiver.java
@@ -27,6 +27,7 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.Trace;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
@@ -98,6 +99,7 @@
boolean mAbortBroadcast;
@UnsupportedAppUsage
boolean mFinished;
+ String mReceiverClassName;
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -219,6 +221,12 @@
* next broadcast will proceed.
*/
public final void finish() {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "PendingResult#finish#ClassName:" + mReceiverClassName,
+ 1);
+ }
+
if (mType == TYPE_COMPONENT) {
final IActivityManager mgr = ActivityManager.getService();
if (QueuedWork.hasPendingWork()) {
@@ -383,6 +391,14 @@
public final PendingResult goAsync() {
PendingResult res = mPendingResult;
mPendingResult = null;
+
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+ res.mReceiverClassName = getClass().getName();
+ Trace.traceCounter(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "BroadcastReceiver#goAsync#ClassName:" + res.mReceiverClassName,
+ 1);
+ }
+
return res;
}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index adae599..184acb1 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -2684,7 +2684,8 @@
* @hide
* @see #unregisterContentObserver
*/
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @RequiresPermission(value = android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ conditional = true)
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public final void registerContentObserverAsUser(@NonNull Uri uri,
boolean notifyForDescendants,
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 58f20ef..45d6ddd 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1017,12 +1017,12 @@
* This change id restricts treatments that force a given min aspect ratio to activities
* whose orientation is fixed to portrait.
*
- * This treatment only takes effect if OVERRIDE_MIN_ASPECT_RATIO is also enabled.
+ * This treatment is enabled by default and only takes effect if OVERRIDE_MIN_ASPECT_RATIO is
+ * also enabled.
* @hide
*/
@ChangeId
@Overridable
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S_V2)
@TestApi
public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // buganizer id
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 1911670..3bf5f31 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3651,6 +3651,14 @@
public static final String FEATURE_MANAGED_PROFILES = "android.software.managed_users";
/**
+ * @hide
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device supports Nearby mainline feature.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_NEARBY = "android.software.nearby";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
* The device supports verified boot.
*/
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index a6f2e40..632d8e5 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1923,6 +1923,25 @@
}
}
}
+
+ @Override
+ public int hashCode() {
+ return getKey().hashCode();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (o == null || getClass() != o.getClass() || hashCode() != o.hashCode()) {
+ return false;
+ }
+
+ final Theme other = (Theme) o;
+ return getKey().equals(other.getKey());
+ }
}
static class ThemeKey implements Cloneable {
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index 5bc235f..6c07356 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -16,8 +16,9 @@
package android.content.res;
-import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.app.Application;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Color;
import android.graphics.Paint;
@@ -238,7 +239,10 @@
if (type == ids.boldId) {
- buffer.setSpan(new StyleSpan(Typeface.BOLD),
+ Application application = ActivityThread.currentApplication();
+ int fontWeightAdjustment =
+ application.getResources().getConfiguration().fontWeightAdjustment;
+ buffer.setSpan(new StyleSpan(Typeface.BOLD, fontWeightAdjustment),
style[i+1], style[i+2]+1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else if (type == ids.italicId) {
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index 1db948a..ccb7cf1 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -143,7 +143,6 @@
throw new AssertionError(); // Not possible, the native code won't return it.
}
mCloseGuard.open("close");
- recordNewWindow(Binder.getCallingPid(), mWindowPtr);
}
/**
@@ -191,7 +190,6 @@
mCloseGuard.close();
}
if (mWindowPtr != 0) {
- recordClosingOfWindow(mWindowPtr);
nativeDispose(mWindowPtr);
mWindowPtr = 0;
}
@@ -746,64 +744,6 @@
dispose();
}
- @UnsupportedAppUsage
- private static final LongSparseArray<Integer> sWindowToPidMap = new LongSparseArray<Integer>();
-
- private void recordNewWindow(int pid, long window) {
- synchronized (sWindowToPidMap) {
- sWindowToPidMap.put(window, pid);
- if (Log.isLoggable(STATS_TAG, Log.VERBOSE)) {
- Log.i(STATS_TAG, "Created a new Cursor. " + printStats());
- }
- }
- }
-
- private void recordClosingOfWindow(long window) {
- synchronized (sWindowToPidMap) {
- if (sWindowToPidMap.size() == 0) {
- // this means we are not in the ContentProvider.
- return;
- }
- sWindowToPidMap.delete(window);
- }
- }
-
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- private String printStats() {
- StringBuilder buff = new StringBuilder();
- int myPid = Process.myPid();
- int total = 0;
- SparseIntArray pidCounts = new SparseIntArray();
- synchronized (sWindowToPidMap) {
- int size = sWindowToPidMap.size();
- if (size == 0) {
- // this means we are not in the ContentProvider.
- return "";
- }
- for (int indx = 0; indx < size; indx++) {
- int pid = sWindowToPidMap.valueAt(indx);
- int value = pidCounts.get(pid);
- pidCounts.put(pid, ++value);
- }
- }
- int numPids = pidCounts.size();
- for (int i = 0; i < numPids;i++) {
- buff.append(" (# cursors opened by ");
- int pid = pidCounts.keyAt(i);
- if (pid == myPid) {
- buff.append("this proc=");
- } else {
- buff.append("pid ").append(pid).append('=');
- }
- int num = pidCounts.get(pid);
- buff.append(num).append(')');
- total += num;
- }
- // limit the returned string size to 1000
- String s = (buff.length() > 980) ? buff.substring(0, 980) : buff.toString();
- return "# Open Cursors=" + total + s;
- }
-
private static int getCursorWindowSize() {
if (sCursorWindowSize < 0) {
// The cursor window size. resource xml file specifies the value in kB.
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 7e61b48..328858b 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -26,14 +26,13 @@
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.os.Trace;
+import android.text.TextUtils;
import android.util.Log;
import android.util.LruCache;
import android.util.Pair;
import android.util.Printer;
-
import dalvik.system.BlockGuard;
import dalvik.system.CloseGuard;
-
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
@@ -177,7 +176,7 @@
mConfiguration = new SQLiteDatabaseConfiguration(configuration);
mConnectionId = connectionId;
mIsPrimaryConnection = primaryConnection;
- mIsReadOnlyConnection = (configuration.openFlags & SQLiteDatabase.OPEN_READONLY) != 0;
+ mIsReadOnlyConnection = mConfiguration.isReadOnlyDatabase();
mPreparedStatementCache = new PreparedStatementCache(
mConfiguration.maxSqlCacheSize);
mCloseGuard.open("close");
@@ -266,7 +265,8 @@
}
setPageSize();
setForeignKeyModeFromConfiguration();
- setWalModeFromConfiguration();
+ setJournalFromConfiguration();
+ setSyncModeFromConfiguration();
setJournalSizeLimit();
setAutoCheckpointInterval();
setLocaleFromConfiguration();
@@ -334,30 +334,19 @@
}
}
- private void setWalModeFromConfiguration() {
- if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) {
- final boolean walEnabled =
- (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
- // Use compatibility WAL unless an app explicitly set journal/synchronous mode
- // or DISABLE_COMPATIBILITY_WAL flag is set
- final boolean isCompatibilityWalEnabled =
- mConfiguration.isLegacyCompatibilityWalEnabled();
- if (walEnabled || isCompatibilityWalEnabled) {
- setJournalMode("WAL");
- if (mConfiguration.syncMode != null) {
- setSyncMode(mConfiguration.syncMode);
- } else if (isCompatibilityWalEnabled) {
- setSyncMode(SQLiteCompatibilityWalFlags.getWALSyncMode());
- } else {
- setSyncMode(SQLiteGlobal.getWALSyncMode());
- }
- maybeTruncateWalFile();
- } else {
- setJournalMode(mConfiguration.journalMode == null
- ? SQLiteGlobal.getDefaultJournalMode() : mConfiguration.journalMode);
- setSyncMode(mConfiguration.syncMode == null
- ? SQLiteGlobal.getDefaultSyncMode() : mConfiguration.syncMode);
- }
+ private void setJournalFromConfiguration() {
+ if (!mIsReadOnlyConnection) {
+ setJournalMode(mConfiguration.resolveJournalMode());
+ maybeTruncateWalFile();
+ } else {
+ // No need to truncate for read only databases.
+ mConfiguration.shouldTruncateWalFile = false;
+ }
+ }
+
+ private void setSyncModeFromConfiguration() {
+ if (!mIsReadOnlyConnection) {
+ setSyncMode(mConfiguration.resolveSyncMode());
}
}
@@ -366,6 +355,10 @@
* PRAGMA wal_checkpoint.
*/
private void maybeTruncateWalFile() {
+ if (!mConfiguration.shouldTruncateWalFile) {
+ return;
+ }
+
final long threshold = SQLiteGlobal.getWALTruncateSize();
if (DEBUG) {
Log.d(TAG, "Truncate threshold=" + threshold);
@@ -390,12 +383,17 @@
+ threshold + "; truncating");
try {
executeForString("PRAGMA wal_checkpoint(TRUNCATE)", null, null);
+ mConfiguration.shouldTruncateWalFile = false;
} catch (SQLiteException e) {
Log.w(TAG, "Failed to truncate the -wal file", e);
}
}
- private void setSyncMode(String newValue) {
+ private void setSyncMode(@SQLiteDatabase.SyncMode String newValue) {
+ if (TextUtils.isEmpty(newValue)) {
+ // No change to the sync mode is intended
+ return;
+ }
String value = executeForString("PRAGMA synchronous", null, null);
if (!canonicalizeSyncMode(value).equalsIgnoreCase(
canonicalizeSyncMode(newValue))) {
@@ -403,16 +401,21 @@
}
}
- private static String canonicalizeSyncMode(String value) {
+ private static @SQLiteDatabase.SyncMode String canonicalizeSyncMode(String value) {
switch (value) {
- case "0": return "OFF";
- case "1": return "NORMAL";
- case "2": return "FULL";
+ case "0": return SQLiteDatabase.SYNC_MODE_OFF;
+ case "1": return SQLiteDatabase.SYNC_MODE_NORMAL;
+ case "2": return SQLiteDatabase.SYNC_MODE_FULL;
+ case "3": return SQLiteDatabase.SYNC_MODE_EXTRA;
}
return value;
}
- private void setJournalMode(String newValue) {
+ private void setJournalMode(@SQLiteDatabase.JournalMode String newValue) {
+ if (TextUtils.isEmpty(newValue)) {
+ // No change to the journal mode is intended
+ return;
+ }
String value = executeForString("PRAGMA journal_mode", null, null);
if (!value.equalsIgnoreCase(newValue)) {
try {
@@ -565,9 +568,6 @@
// Remember what changed.
boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled
!= mConfiguration.foreignKeyConstraintsEnabled;
- boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
- & (SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING
- | SQLiteDatabase.ENABLE_LEGACY_COMPATIBILITY_WAL)) != 0;
boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
boolean customScalarFunctionsChanged = !configuration.customScalarFunctions
.equals(mConfiguration.customScalarFunctions);
@@ -586,9 +586,19 @@
if (foreignKeyModeChanged) {
setForeignKeyModeFromConfiguration();
}
- if (walModeChanged) {
- setWalModeFromConfiguration();
+
+ boolean journalModeChanged = !configuration.resolveJournalMode().equalsIgnoreCase(
+ mConfiguration.resolveJournalMode());
+ if (journalModeChanged) {
+ setJournalFromConfiguration();
}
+
+ boolean syncModeChanged =
+ !configuration.resolveSyncMode().equalsIgnoreCase(mConfiguration.resolveSyncMode());
+ if (syncModeChanged) {
+ setSyncModeFromConfiguration();
+ }
+
if (localeChanged) {
setLocaleFromConfiguration();
}
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index 852f8f2..d3ad6bb 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -106,7 +106,11 @@
@GuardedBy("mLock")
private IdleConnectionHandler mIdleConnectionHandler;
- private final AtomicLong mTotalExecutionTimeCounter = new AtomicLong(0);
+ // whole execution time for this connection in milliseconds.
+ private final AtomicLong mTotalStatementsTime = new AtomicLong(0);
+
+ // total statements executed by this connection
+ private final AtomicLong mTotalStatementsCount = new AtomicLong(0);
// Describes what should happen to an acquired connection when it is returned to the pool.
enum AcquiredConnectionStatus {
@@ -286,8 +290,11 @@
synchronized (mLock) {
throwIfClosedLocked();
- boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags)
- & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
+ boolean isWalCurrentMode = mConfiguration.resolveJournalMode().equalsIgnoreCase(
+ SQLiteDatabase.JOURNAL_MODE_WAL);
+ boolean isWalNewMode = configuration.resolveJournalMode().equalsIgnoreCase(
+ SQLiteDatabase.JOURNAL_MODE_WAL);
+ boolean walModeChanged = isWalCurrentMode ^ isWalNewMode;
if (walModeChanged) {
// WAL mode can only be changed if there are no acquired connections
// because we need to close all but the primary connection first.
@@ -536,7 +543,8 @@
}
void onStatementExecuted(long executionTimeMs) {
- mTotalExecutionTimeCounter.addAndGet(executionTimeMs);
+ mTotalStatementsTime.addAndGet(executionTimeMs);
+ mTotalStatementsCount.incrementAndGet();
}
// Can't throw.
@@ -1037,8 +1045,7 @@
}
private void setMaxConnectionPoolSizeLocked() {
- if (!mConfiguration.isInMemoryDb()
- && (mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) {
+ if (mConfiguration.resolveJournalMode().equalsIgnoreCase(SQLiteDatabase.JOURNAL_MODE_WAL)) {
mMaxConnectionPoolSize = SQLiteGlobal.getWALConnectionPoolSize();
} else {
// We don't actually need to always restrict the connection pool size to 1
@@ -1117,11 +1124,18 @@
printer.println("Connection pool for " + mConfiguration.path + ":");
printer.println(" Open: " + mIsOpen);
printer.println(" Max connections: " + mMaxConnectionPoolSize);
- printer.println(" Total execution time: " + mTotalExecutionTimeCounter);
+ printer.println(" Total execution time (ms): " + mTotalStatementsTime);
+ printer.println(" Total statements executed: " + mTotalStatementsCount);
+ if (mTotalStatementsCount.get() > 0) {
+ // Avoid division by 0 by filtering out logs where there are no statements executed.
+ printer.println(" Average time per statement (ms): "
+ + mTotalStatementsTime.get() / mTotalStatementsCount.get());
+ }
printer.println(" Configuration: openFlags=" + mConfiguration.openFlags
+ ", isLegacyCompatibilityWalEnabled=" + isCompatibilityWalEnabled
- + ", journalMode=" + TextUtils.emptyIfNull(mConfiguration.journalMode)
- + ", syncMode=" + TextUtils.emptyIfNull(mConfiguration.syncMode));
+ + ", journalMode=" + TextUtils.emptyIfNull(mConfiguration.resolveJournalMode())
+ + ", syncMode=" + TextUtils.emptyIfNull(mConfiguration.resolveSyncMode()));
+ printer.println(" IsReadOnlyDatabase=" + mConfiguration.isReadOnlyDatabase());
if (isCompatibilityWalEnabled) {
printer.println(" Compatibility WAL enabled: wal_syncmode="
@@ -1182,6 +1196,14 @@
}
}
+ public long getTotalStatementsTime() {
+ return mTotalStatementsTime.get();
+ }
+
+ public long getTotalStatementsCount() {
+ return mTotalStatementsCount.get();
+ }
+
@Override
public String toString() {
return "SQLiteConnectionPool: " + mConfiguration.path;
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 9684ece..0d0615a 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -20,6 +20,8 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
@@ -42,11 +44,8 @@
import android.util.Log;
import android.util.Pair;
import android.util.Printer;
-
import com.android.internal.util.Preconditions;
-
import dalvik.system.CloseGuard;
-
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
@@ -290,15 +289,182 @@
*/
public static final int MAX_SQL_CACHE_SIZE = 100;
- private SQLiteDatabase(final String path, final int openFlags,
- CursorFactory cursorFactory, DatabaseErrorHandler errorHandler,
+ /**
+ * @hide
+ */
+ @StringDef(prefix = {"JOURNAL_MODE_"},
+ value =
+ {
+ JOURNAL_MODE_WAL,
+ JOURNAL_MODE_PERSIST,
+ JOURNAL_MODE_TRUNCATE,
+ JOURNAL_MODE_MEMORY,
+ JOURNAL_MODE_DELETE,
+ JOURNAL_MODE_OFF,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface JournalMode {}
+
+ /**
+ * The {@code WAL} journaling mode uses a write-ahead log instead of a rollback journal to
+ * implement transactions. The WAL journaling mode is persistent; after being set it stays
+ * in effect across multiple database connections and after closing and reopening the database.
+ *
+ * Performance Considerations:
+ * This mode is recommended when the goal is to improve write performance or parallel read/write
+ * performance. However, it is important to note that WAL introduces checkpoints which commit
+ * all transactions that have not been synced to the database thus to maximize read performance
+ * and lower checkpointing cost a small journal size is recommended. However, other modes such
+ * as {@code DELETE} will not perform checkpoints, so it is a trade off that needs to be
+ * considered as part of the decision of which journal mode to use.
+ *
+ * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
+ * details.</p>
+ */
+ public static final String JOURNAL_MODE_WAL = "WAL";
+
+ /**
+ * The {@code PERSIST} journaling mode prevents the rollback journal from being deleted at the
+ * end of each transaction. Instead, the header of the journal is overwritten with zeros.
+ * This will prevent other database connections from rolling the journal back.
+ *
+ * This mode is useful as an optimization on platforms where deleting or truncating a file is
+ * much more expensive than overwriting the first block of a file with zeros.
+ *
+ * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
+ * details.</p>
+ */
+ public static final String JOURNAL_MODE_PERSIST = "PERSIST";
+
+ /**
+ * The {@code TRUNCATE} journaling mode commits transactions by truncating the rollback journal
+ * to zero-length instead of deleting it. On many systems, truncating a file is much faster than
+ * deleting the file since the containing directory does not need to be changed.
+ *
+ * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
+ * details.</p>
+ */
+ public static final String JOURNAL_MODE_TRUNCATE = "TRUNCATE";
+
+ /**
+ * The {@code MEMORY} journaling mode stores the rollback journal in volatile RAM.
+ * This saves disk I/O but at the expense of database safety and integrity. If the application
+ * using SQLite crashes in the middle of a transaction when the MEMORY journaling mode is set,
+ * then the database file will very likely go corrupt.
+ *
+ * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
+ * details.</p>
+ */
+ public static final String JOURNAL_MODE_MEMORY = "MEMORY";
+
+ /**
+ * The {@code DELETE} journaling mode is the normal behavior. In the DELETE mode, the rollback
+ * journal is deleted at the conclusion of each transaction.
+ *
+ * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
+ * details.</p>
+ */
+ public static final String JOURNAL_MODE_DELETE = "DELETE";
+
+ /**
+ * The {@code OFF} journaling mode disables the rollback journal completely. No rollback journal
+ * is ever created and hence there is never a rollback journal to delete. The OFF journaling
+ * mode disables the atomic commit and rollback capabilities of SQLite. The ROLLBACK command
+ * behaves in an undefined way thus applications must avoid using the ROLLBACK command.
+ * If the application crashes in the middle of a transaction, then the database file will very
+ * likely go corrupt.
+ *
+ * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_journal_mode>here</a> for more
+ * details.</p>
+ */
+ public static final String JOURNAL_MODE_OFF = "OFF";
+
+ /**
+ * @hide
+ */
+ @StringDef(prefix = {"SYNC_MODE_"},
+ value =
+ {
+ SYNC_MODE_EXTRA,
+ SYNC_MODE_FULL,
+ SYNC_MODE_NORMAL,
+ SYNC_MODE_OFF,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SyncMode {}
+
+ /**
+ * The {@code EXTRA} sync mode is like {@code FULL} sync mode with the addition that the
+ * directory containing a rollback journal is synced after that journal is unlinked to commit a
+ * transaction in {@code DELETE} journal mode.
+ *
+ * {@code EXTRA} provides additional durability if the commit is followed closely by a
+ * power loss.
+ *
+ * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_synchronous>here</a> for more
+ * details.</p>
+ */
+ @SuppressLint("IntentName") public static final String SYNC_MODE_EXTRA = "EXTRA";
+
+ /**
+ * In {@code FULL} sync mode the SQLite database engine will use the xSync method of the VFS
+ * to ensure that all content is safely written to the disk surface prior to continuing.
+ * This ensures that an operating system crash or power failure will not corrupt the database.
+ * {@code FULL} is very safe, but it is also slower.
+ *
+ * {@code FULL} is the most commonly used synchronous setting when not in WAL mode.
+ *
+ * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_synchronous>here</a> for more
+ * details.</p>
+ */
+ public static final String SYNC_MODE_FULL = "FULL";
+
+ /**
+ * The {@code NORMAL} sync mode, the SQLite database engine will still sync at the most critical
+ * moments, but less often than in {@code FULL} mode. There is a very small chance that a
+ * power failure at the wrong time could corrupt the database in {@code DELETE} journal mode on
+ * an older filesystem.
+ *
+ * {@code WAL} journal mode is safe from corruption with {@code NORMAL} sync mode, and probably
+ * {@code DELETE} sync mode is safe too on modern filesystems. WAL mode is always consistent
+ * with {@code NORMAL} sync mode, but WAL mode does lose durability. A transaction committed in
+ * WAL mode with {@code NORMAL} might roll back following a power loss or system crash.
+ * Transactions are durable across application crashes regardless of the synchronous setting
+ * or journal mode.
+ *
+ * The {@code NORMAL} sync mode is a good choice for most applications running in WAL mode.
+ *
+ * <p>Caveat: Even though this sync mode is safe Be careful when using {@code NORMAL} sync mode
+ * when dealing with data dependencies between multiple databases, unless those databases use
+ * the same durability or are somehow synced, there could be corruption.</p>
+ *
+ * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_synchronous>here</a> for more
+ * details.</p>
+ */
+ public static final String SYNC_MODE_NORMAL = "NORMAL";
+
+ /**
+ * In {@code OFF} sync mode SQLite continues without syncing as soon as it has handed data off
+ * to the operating system. If the application running SQLite crashes, the data will be safe,
+ * but the database might become corrupted if the operating system crashes or the computer loses
+ * power before that data has been written to the disk surface. On the other hand, commits can
+ * be orders of magnitude faster with synchronous {@code OFF}.
+ *
+ * <p> See <a href=https://www.sqlite.org/pragma.html#pragma_synchronous>here</a> for more
+ * details.</p>
+ */
+ public static final String SYNC_MODE_OFF = "OFF";
+
+ private SQLiteDatabase(@Nullable final String path, @Nullable final int openFlags,
+ @Nullable CursorFactory cursorFactory, @Nullable DatabaseErrorHandler errorHandler,
int lookasideSlotSize, int lookasideSlotCount, long idleConnectionTimeoutMs,
- String journalMode, String syncMode) {
+ @Nullable String journalMode, @Nullable String syncMode) {
mCursorFactory = cursorFactory;
mErrorHandler = errorHandler != null ? errorHandler : new DefaultDatabaseErrorHandler();
mConfigurationLocked = new SQLiteDatabaseConfiguration(path, openFlags);
mConfigurationLocked.lookasideSlotSize = lookasideSlotSize;
mConfigurationLocked.lookasideSlotCount = lookasideSlotCount;
+
// Disable lookaside allocator on low-RAM devices
if (ActivityManager.isLowRamDeviceStatic()) {
mConfigurationLocked.lookasideSlotCount = 0;
@@ -316,11 +482,11 @@
}
}
mConfigurationLocked.idleConnectionTimeoutMs = effectiveTimeoutMs;
- mConfigurationLocked.journalMode = journalMode;
- mConfigurationLocked.syncMode = syncMode;
if (SQLiteCompatibilityWalFlags.isLegacyCompatibilityWalEnabled()) {
mConfigurationLocked.openFlags |= ENABLE_LEGACY_COMPATIBILITY_WAL;
}
+ mConfigurationLocked.journalMode = journalMode;
+ mConfigurationLocked.syncMode = syncMode;
}
@Override
@@ -2191,7 +2357,8 @@
synchronized (mLock) {
throwIfNotOpenLocked();
- if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0) {
+ if (mConfigurationLocked.resolveJournalMode().equalsIgnoreCase(
+ SQLiteDatabase.JOURNAL_MODE_WAL)) {
return true;
}
@@ -2241,11 +2408,9 @@
throwIfNotOpenLocked();
final int oldFlags = mConfigurationLocked.openFlags;
- final boolean walEnabled = (oldFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0;
- final boolean compatibilityWalEnabled =
- (oldFlags & ENABLE_LEGACY_COMPATIBILITY_WAL) != 0;
// WAL was never enabled for this database, so there's nothing left to do.
- if (!walEnabled && !compatibilityWalEnabled) {
+ if (!mConfigurationLocked.resolveJournalMode().equalsIgnoreCase(
+ SQLiteDatabase.JOURNAL_MODE_WAL)) {
return;
}
@@ -2275,7 +2440,8 @@
synchronized (mLock) {
throwIfNotOpenLocked();
- return (mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0;
+ return mConfigurationLocked.resolveJournalMode().equalsIgnoreCase(
+ SQLiteDatabase.JOURNAL_MODE_WAL);
}
}
@@ -2309,6 +2475,20 @@
return databases;
}
+ private static ArrayList<SQLiteConnectionPool> getActiveDatabasePools() {
+ ArrayList<SQLiteConnectionPool> connectionPools = new ArrayList<SQLiteConnectionPool>();
+ synchronized (sActiveDatabases) {
+ for (SQLiteDatabase db : sActiveDatabases.keySet()) {
+ synchronized (db.mLock) {
+ if (db.mConnectionPoolLocked != null) {
+ connectionPools.add(db.mConnectionPoolLocked);
+ }
+ }
+ }
+ }
+ return connectionPools;
+ }
+
/**
* Dump detailed information about all open databases in the current process.
* Used by bug report.
@@ -2317,8 +2497,45 @@
// Use this ArraySet to collect file paths.
final ArraySet<String> directories = new ArraySet<>();
- for (SQLiteDatabase db : getActiveDatabases()) {
- db.dump(printer, verbose, isSystem, directories);
+ // Accounting across all databases
+ long totalStatementsTimeInMs = 0;
+ long totalStatementsCount = 0;
+
+ ArrayList<SQLiteConnectionPool> activeConnectionPools = getActiveDatabasePools();
+
+ activeConnectionPools.sort(
+ (a, b) -> Long.compare(b.getTotalStatementsCount(), a.getTotalStatementsCount()));
+ for (SQLiteConnectionPool dbPool : activeConnectionPools) {
+ dbPool.dump(printer, verbose, directories);
+ totalStatementsTimeInMs += dbPool.getTotalStatementsTime();
+ totalStatementsCount += dbPool.getTotalStatementsCount();
+ }
+
+ if (totalStatementsCount > 0) {
+ // Only print when there is information available
+
+ // Sorted statements per database
+ printer.println("Statements Executed per Database");
+ for (SQLiteConnectionPool dbPool : activeConnectionPools) {
+ printer.println(
+ " " + dbPool.getPath() + " : " + dbPool.getTotalStatementsCount());
+ }
+ printer.println("");
+ printer.println(
+ "Total Statements Executed for all Active Databases: " + totalStatementsCount);
+
+ // Sorted execution time per database
+ activeConnectionPools.sort(
+ (a, b) -> Long.compare(b.getTotalStatementsTime(), a.getTotalStatementsTime()));
+ printer.println("");
+ printer.println("");
+ printer.println("Statement Time per Database (ms)");
+ for (SQLiteConnectionPool dbPool : activeConnectionPools) {
+ printer.println(
+ " " + dbPool.getPath() + " : " + dbPool.getTotalStatementsTime());
+ }
+ printer.println("Total Statements Time for all Active Databases (ms): "
+ + totalStatementsTimeInMs);
}
// Dump DB files in the directories.
@@ -2331,15 +2548,6 @@
}
}
- private void dump(Printer printer, boolean verbose, boolean isSystem, ArraySet directories) {
- synchronized (mLock) {
- if (mConnectionPoolLocked != null) {
- printer.println("");
- mConnectionPoolLocked.dump(printer, verbose, directories);
- }
- }
- }
-
private static void dumpDatabaseDirectory(Printer pw, File dir, boolean isSystem) {
pw.println("");
pw.println("Database files in " + dir.getAbsolutePath() + ":");
@@ -2598,9 +2806,7 @@
/**
* Returns <a href="https://sqlite.org/pragma.html#pragma_journal_mode">journal mode</a>.
- * This journal mode will only be used if {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING}
- * flag is not set, otherwise a platform will use "WAL" journal mode.
- * @see Builder#setJournalMode(String)
+ * set via {@link Builder#setJournalMode(String)}.
*/
@Nullable
public String getJournalMode() {
@@ -2799,25 +3005,28 @@
return this;
}
-
/**
* Sets <a href="https://sqlite.org/pragma.html#pragma_journal_mode">journal mode</a>
- * to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} flag is not set.
+ * to use.
+ *
+ * <p>Note: If journal mode is not set, the platform will use a manufactured-specified
+ * default which can vary across devices.
*/
@NonNull
- public Builder setJournalMode(@NonNull String journalMode) {
+ public Builder setJournalMode(@JournalMode @NonNull String journalMode) {
Objects.requireNonNull(journalMode);
mJournalMode = journalMode;
return this;
}
- /**w
+ /**
* Sets <a href="https://sqlite.org/pragma.html#pragma_synchronous">synchronous mode</a>
- * .
- * @return
+ *
+ * <p>Note: If sync mode is not set, the platform will use a manufactured-specified
+ * default which can vary across devices.
*/
@NonNull
- public Builder setSynchronousMode(@NonNull String syncMode) {
+ public Builder setSynchronousMode(@SyncMode @NonNull String syncMode) {
Objects.requireNonNull(syncMode);
mSyncMode = syncMode;
return this;
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
index 21c21c9..8305843 100644
--- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -17,9 +17,9 @@
package android.database.sqlite;
import android.compat.annotation.UnsupportedAppUsage;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Pair;
-
import java.util.ArrayList;
import java.util.Locale;
import java.util.function.BinaryOperator;
@@ -132,14 +132,16 @@
* Journal mode to use when {@link SQLiteDatabase#ENABLE_WRITE_AHEAD_LOGGING} is not set.
* <p>Default is returned by {@link SQLiteGlobal#getDefaultJournalMode()}
*/
- public String journalMode;
+ public @SQLiteDatabase.JournalMode String journalMode;
/**
* Synchronous mode to use.
* <p>Default is returned by {@link SQLiteGlobal#getDefaultSyncMode()}
* or {@link SQLiteGlobal#getWALSyncMode()} depending on journal mode
*/
- public String syncMode;
+ public @SQLiteDatabase.SyncMode String syncMode;
+
+ public boolean shouldTruncateWalFile;
/**
* Creates a database configuration with the required parameters for opening a
@@ -217,6 +219,10 @@
return path.equalsIgnoreCase(MEMORY_DB_PATH);
}
+ public boolean isReadOnlyDatabase() {
+ return (openFlags & SQLiteDatabase.OPEN_READONLY) != 0;
+ }
+
boolean isLegacyCompatibilityWalEnabled() {
return journalMode == null && syncMode == null
&& (openFlags & SQLiteDatabase.ENABLE_LEGACY_COMPATIBILITY_WAL) != 0;
@@ -232,4 +238,81 @@
boolean isLookasideConfigSet() {
return lookasideSlotCount >= 0 && lookasideSlotSize >= 0;
}
+
+ /**
+ * Resolves the journal mode that should be used when opening a connection to the database.
+ *
+ * Note: assumes openFlags have already been set.
+ *
+ * @return Resolved journal mode that should be used for this database connection or an empty
+ * string if no journal mode should be set.
+ */
+ public @SQLiteDatabase.JournalMode String resolveJournalMode() {
+ if (isReadOnlyDatabase()) {
+ // No need to specify a journal mode when only reading.
+ return "";
+ }
+
+ if (isInMemoryDb()) {
+ if (journalMode != null
+ && journalMode.equalsIgnoreCase(SQLiteDatabase.JOURNAL_MODE_OFF)) {
+ return SQLiteDatabase.JOURNAL_MODE_OFF;
+ }
+ return SQLiteDatabase.JOURNAL_MODE_MEMORY;
+ }
+
+ shouldTruncateWalFile = false;
+
+ if (isWalEnabledInternal()) {
+ shouldTruncateWalFile = true;
+ return SQLiteDatabase.JOURNAL_MODE_WAL;
+ } else {
+ // WAL is not explicitly set so use requested journal mode or platform default
+ return this.journalMode != null ? this.journalMode
+ : SQLiteGlobal.getDefaultJournalMode();
+ }
+ }
+
+ /**
+ * Resolves the sync mode that should be used when opening a connection to the database.
+ *
+ * Note: assumes openFlags have already been set.
+ * @return Resolved journal mode that should be used for this database connection or null
+ * if no journal mode should be set.
+ */
+ public @SQLiteDatabase.SyncMode String resolveSyncMode() {
+ if (isReadOnlyDatabase()) {
+ // No sync mode will be used since database will be only used for reading.
+ return "";
+ }
+
+ if (isInMemoryDb()) {
+ // No sync mode will be used since database will be in volatile memory
+ return "";
+ }
+
+ if (!TextUtils.isEmpty(syncMode)) {
+ return syncMode;
+ }
+
+ if (isWalEnabledInternal()) {
+ if (isLegacyCompatibilityWalEnabled()) {
+ return SQLiteCompatibilityWalFlags.getWALSyncMode();
+ } else {
+ return SQLiteGlobal.getDefaultSyncMode();
+ }
+ } else {
+ return SQLiteGlobal.getDefaultSyncMode();
+ }
+ }
+
+ private boolean isWalEnabledInternal() {
+ final boolean walEnabled = (openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0;
+ // Use compatibility WAL unless an app explicitly set journal/synchronous mode
+ // or DISABLE_COMPATIBILITY_WAL flag is set
+ final boolean isCompatibilityWalEnabled = isLegacyCompatibilityWalEnabled();
+ return walEnabled || isCompatibilityWalEnabled
+ || (journalMode != null
+ && journalMode.equalsIgnoreCase(SQLiteDatabase.JOURNAL_MODE_WAL));
+ }
}
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index 1afa0f8..b84a8d2 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -70,7 +70,8 @@
/**
* True to enable database performance testing instrumentation.
*/
- public static final boolean DEBUG_LOG_SLOW_QUERIES = Build.IS_DEBUGGABLE;
+ public static final boolean DEBUG_LOG_SLOW_QUERIES =
+ Log.isLoggable("SQLiteSlowQueries", Log.VERBOSE);
private static final String SLOW_QUERY_THRESHOLD_PROP = "db.log.slow_query_threshold";
diff --git a/core/java/android/database/sqlite/SQLiteGlobal.java b/core/java/android/database/sqlite/SQLiteGlobal.java
index 5e2875d..28964fd 100644
--- a/core/java/android/database/sqlite/SQLiteGlobal.java
+++ b/core/java/android/database/sqlite/SQLiteGlobal.java
@@ -84,7 +84,7 @@
/**
* Gets the default journal mode when WAL is not in use.
*/
- public static String getDefaultJournalMode() {
+ public static @SQLiteDatabase.JournalMode String getDefaultJournalMode() {
return SystemProperties.get("debug.sqlite.journalmode",
Resources.getSystem().getString(
com.android.internal.R.string.db_default_journal_mode));
@@ -102,7 +102,7 @@
/**
* Gets the default database synchronization mode when WAL is not in use.
*/
- public static String getDefaultSyncMode() {
+ public static @SQLiteDatabase.SyncMode String getDefaultSyncMode() {
// Use the FULL synchronous mode for system processes by default.
String defaultMode = sDefaultSyncMode;
if (defaultMode != null) {
@@ -116,7 +116,7 @@
/**
* Gets the database synchronization mode when in WAL mode.
*/
- public static String getWALSyncMode() {
+ public static @SQLiteDatabase.SyncMode String getWALSyncMode() {
// Use the FULL synchronous mode for system processes by default.
String defaultMode = sDefaultSyncMode;
if (defaultMode != null) {
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 43ef33e..28046c5 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -151,6 +151,12 @@
int BIOMETRIC_ERROR_RE_ENROLL = 16;
/**
+ * The privacy setting has been enabled and will block use of the sensor.
+ * @hide
+ */
+ int BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED = 18;
+
+ /**
* This constant is only used by SystemUI. It notifies SystemUI that authentication was paused
* because the authentication attempt was unsuccessful.
* @hide
diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index fe43c83..fd46f24 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -69,7 +69,7 @@
BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL,
BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
BIOMETRIC_ERROR_RE_ENROLL,
- FACE_ERROR_UNKNOWN
+ FACE_ERROR_UNKNOWN,
})
@Retention(RetentionPolicy.SOURCE)
@interface FaceError {}
diff --git a/core/java/android/net/OWNERS b/core/java/android/net/OWNERS
index f55bcd3..b989488 100644
--- a/core/java/android/net/OWNERS
+++ b/core/java/android/net/OWNERS
@@ -2,5 +2,6 @@
include platform/frameworks/base:/services/core/java/com/android/server/net/OWNERS
+per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
per-file SSL*,Uri*,Url* = prb@google.com,oth@google.com,narayan@google.com,ngeoffray@google.com
per-file SntpClient* = file:/services/core/java/com/android/server/timedetector/OWNERS
diff --git a/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java
index 6b33e4f..50a6bfc 100644
--- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java
+++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java
@@ -29,6 +29,7 @@
import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.vcn.util.PersistableBundleUtils;
import java.util.ArrayList;
@@ -200,6 +201,15 @@
&& mRequireOpportunistic == rhs.mRequireOpportunistic;
}
+ /** @hide */
+ @Override
+ void dumpTransportSpecificFields(IndentingPrintWriter pw) {
+ pw.println("mAllowedNetworkPlmnIds: " + mAllowedNetworkPlmnIds.toString());
+ pw.println("mAllowedSpecificCarrierIds: " + mAllowedSpecificCarrierIds.toString());
+ pw.println("mAllowRoaming: " + mAllowRoaming);
+ pw.println("mRequireOpportunistic: " + mRequireOpportunistic);
+ }
+
/** This class is used to incrementally build WifiNetworkPriority objects. */
public static class Builder extends VcnUnderlyingNetworkPriority.Builder<Builder> {
@NonNull private final Set<String> mAllowedNetworkPlmnIds = new ArraySet<>();
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index de4ada2..31e38c0 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -160,7 +160,9 @@
TimeUnit.MINUTES.toMillis(15)
};
- private static final LinkedHashSet<VcnUnderlyingNetworkPriority>
+ /** @hide */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static final LinkedHashSet<VcnUnderlyingNetworkPriority>
DEFAULT_UNDERLYING_NETWORK_PRIORITIES = new LinkedHashSet<>();
static {
diff --git a/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java
index 82f6ae7..551f757 100644
--- a/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java
+++ b/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java
@@ -21,8 +21,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.PersistableBundle;
+import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
@@ -37,10 +39,17 @@
/** @hide */
protected static final int NETWORK_PRIORITY_TYPE_CELL = 2;
- /** Denotes that network quality needs to be OK */
- public static final int NETWORK_QUALITY_OK = 10000;
/** Denotes that any network quality is acceptable */
- public static final int NETWORK_QUALITY_ANY = Integer.MAX_VALUE;
+ public static final int NETWORK_QUALITY_ANY = 0;
+ /** Denotes that network quality needs to be OK */
+ public static final int NETWORK_QUALITY_OK = 100000;
+
+ private static final SparseArray<String> NETWORK_QUALITY_TO_STRING_MAP = new SparseArray<>();
+
+ static {
+ NETWORK_QUALITY_TO_STRING_MAP.put(NETWORK_QUALITY_ANY, "NETWORK_QUALITY_ANY");
+ NETWORK_QUALITY_TO_STRING_MAP.put(NETWORK_QUALITY_OK, "NETWORK_QUALITY_OK");
+ }
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -125,6 +134,28 @@
&& mAllowMetered == rhs.mAllowMetered;
}
+ /** @hide */
+ abstract void dumpTransportSpecificFields(IndentingPrintWriter pw);
+
+ /**
+ * Dumps the state of this record for logging and debugging purposes.
+ *
+ * @hide
+ */
+ public void dump(IndentingPrintWriter pw) {
+ pw.println(this.getClass().getSimpleName() + ":");
+ pw.increaseIndent();
+
+ pw.println(
+ "mNetworkQuality: "
+ + NETWORK_QUALITY_TO_STRING_MAP.get(
+ mNetworkQuality, "Invalid value " + mNetworkQuality));
+ pw.println("mAllowMetered: " + mAllowMetered);
+ dumpTransportSpecificFields(pw);
+
+ pw.decreaseIndent();
+ }
+
/** Retrieve the required network quality. */
@NetworkQuality
public int getNetworkQuality() {
diff --git a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
index fc7e7e2..2ba9169 100644
--- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
+++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java
@@ -22,6 +22,7 @@
import android.os.PersistableBundle;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
import java.util.Objects;
@@ -81,6 +82,12 @@
return mSsid == rhs.mSsid;
}
+ /** @hide */
+ @Override
+ void dumpTransportSpecificFields(IndentingPrintWriter pw) {
+ pw.println("mSsid: " + mSsid);
+ }
+
/** Retrieve the required SSID, or {@code null} if there is no requirement on SSID. */
@Nullable
public String getSsid() {
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java
index 74b814e..c8b4226e 100644
--- a/core/java/android/os/AppZygote.java
+++ b/core/java/android/os/AppZygote.java
@@ -45,6 +45,8 @@
// Last UID/GID of the range the AppZygote can setuid()/setgid() to
private final int mZygoteUidGidMax;
+ private final int mZygoteRuntimeFlags;
+
private final Object mLock = new Object();
/**
@@ -56,11 +58,13 @@
private final ApplicationInfo mAppInfo;
- public AppZygote(ApplicationInfo appInfo, int zygoteUid, int uidGidMin, int uidGidMax) {
+ public AppZygote(ApplicationInfo appInfo, int zygoteUid, int uidGidMin, int uidGidMax,
+ int runtimeFlags) {
mAppInfo = appInfo;
mZygoteUid = zygoteUid;
mZygoteUidGidMin = uidGidMin;
mZygoteUidGidMax = uidGidMax;
+ mZygoteRuntimeFlags = runtimeFlags;
}
/**
@@ -110,7 +114,7 @@
mZygoteUid,
mZygoteUid,
null, // gids
- 0, // runtimeFlags
+ mZygoteRuntimeFlags, // runtimeFlags
"app_zygote", // seInfo
abi, // abi
abi, // acceptedAbiList
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 53484d2..584f3c4 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -634,7 +634,7 @@
*/
public static int mapToInternalProcessState(int procState) {
if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
- return ActivityManager.PROCESS_STATE_NONEXISTENT;
+ return Uid.PROCESS_STATE_NONEXISTENT;
} else if (procState == ActivityManager.PROCESS_STATE_TOP) {
return Uid.PROCESS_STATE_TOP;
} else if (ActivityManager.isForegroundService(procState)) {
@@ -911,6 +911,11 @@
* Total number of process states we track.
*/
public static final int NUM_PROCESS_STATE = 7;
+ /**
+ * State of the UID when it has no running processes. It is intentionally out of
+ * bounds 0..NUM_PROCESS_STATE.
+ */
+ public static final int PROCESS_STATE_NONEXISTENT = NUM_PROCESS_STATE;
// Used in dump
static final String[] PROCESS_STATE_NAMES = {
@@ -930,16 +935,6 @@
"C" // CACHED
};
- /**
- * When the process exits one of these states, we need to make sure cpu time in this state
- * is not attributed to any non-critical process states.
- */
- public static final int[] CRITICAL_PROC_STATES = {
- Uid.PROCESS_STATE_TOP,
- Uid.PROCESS_STATE_FOREGROUND_SERVICE,
- Uid.PROCESS_STATE_FOREGROUND
- };
-
public abstract long getProcessStateTime(int state, long elapsedRealtimeUs, int which);
public abstract Timer getProcessStateTimer(int state);
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index b3416e9..8292f26 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -67,6 +67,9 @@
private static final String SYSTEM_DRIVER_NAME = "system";
private static final String SYSTEM_DRIVER_VERSION_NAME = "";
private static final long SYSTEM_DRIVER_VERSION_CODE = 0;
+ private static final String ANGLE_DRIVER_NAME = "angle";
+ private static final String ANGLE_DRIVER_VERSION_NAME = "";
+ private static final long ANGLE_DRIVER_VERSION_CODE = 0;
// System properties related to updatable graphics drivers.
private static final String PROPERTY_GFX_DRIVER_PRODUCTION = "ro.gfx.driver.0";
@@ -134,14 +137,24 @@
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupAngle");
- setupAngle(context, coreSettings, pm, packageName);
+ boolean useAngle = false;
+ if (setupAngle(context, coreSettings, pm, packageName)) {
+ if (shouldUseAngle(context, coreSettings, packageName)) {
+ useAngle = true;
+ setGpuStats(ANGLE_DRIVER_NAME, ANGLE_DRIVER_VERSION_NAME, ANGLE_DRIVER_VERSION_CODE,
+ 0, packageName, getVulkanVersion(pm));
+ }
+ }
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "chooseDriver");
if (!chooseDriver(context, coreSettings, pm, packageName, appInfoWithMetaData)) {
- setGpuStats(SYSTEM_DRIVER_NAME, SYSTEM_DRIVER_VERSION_NAME, SYSTEM_DRIVER_VERSION_CODE,
- SystemProperties.getLong(PROPERTY_GFX_DRIVER_BUILD_TIME, 0), packageName,
- getVulkanVersion(pm));
+ if (!useAngle) {
+ setGpuStats(SYSTEM_DRIVER_NAME, SYSTEM_DRIVER_VERSION_NAME,
+ SYSTEM_DRIVER_VERSION_CODE,
+ SystemProperties.getLong(PROPERTY_GFX_DRIVER_BUILD_TIME, 0),
+ packageName, getVulkanVersion(pm));
+ }
}
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index bc6dbd8..29d4d78 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -327,6 +327,43 @@
"no_sharing_admin_configured_wifi";
/**
+ * Specifies if a user is disallowed from using Wi-Fi Direct.
+ *
+ * <p>This restriction can only be set by a device owner,
+ * a profile owner of an organization-owned managed profile on the parent profile.
+ * When it is set by any of these owners, it prevents all users from using
+ * Wi-Fi Direct.
+ *
+ * <p>The default value is <code>false</code>.
+ *
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_WIFI_DIRECT = "no_wifi_direct";
+
+ /**
+ * Specifies if a user is disallowed from adding a new Wi-Fi configuration.
+ *
+ * <p>This restriction can only be set by a device owner,
+ * a profile owner of an organization-owned managed profile on the parent profile.
+ * When it is set by any of these owners, it prevents all users from adding
+ * a new Wi-Fi configuration. This does not limit the owner and carrier's ability
+ * to add a new configuration.
+ *
+ * <p>The default value is <code>false</code>.
+ *
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_ADD_WIFI_CONFIG = "no_add_wifi_config";
+
+ /**
* Specifies if a user is disallowed from changing the device
* language. The default value is <code>false</code>.
*
@@ -1500,6 +1537,8 @@
DISALLOW_CHANGE_WIFI_STATE,
DISALLOW_WIFI_TETHERING,
DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
+ DISALLOW_WIFI_DIRECT,
+ DISALLOW_ADD_WIFI_CONFIG,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserRestrictionKey {}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 627e09e..adf7955c 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -274,6 +274,15 @@
public static final int FLAG_STORAGE_EXTERNAL = IInstalld.FLAG_STORAGE_EXTERNAL;
/** {@hide} */
+ @IntDef(prefix = "FLAG_STORAGE_", value = {
+ FLAG_STORAGE_DE,
+ FLAG_STORAGE_CE,
+ FLAG_STORAGE_EXTERNAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StorageFlags {}
+
+ /** {@hide} */
public static final int FLAG_FOR_WRITE = 1 << 8;
/** {@hide} */
public static final int FLAG_REAL_STATE = 1 << 9;
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 5036abc..eef1ff7 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -2956,7 +2956,10 @@
*/
@Nullable
public static String getLocalAccountName(@NonNull Context context) {
- return null;
+ // config_rawContactsLocalAccountName is defined in
+ // platform/frameworks/base/core/res/res/values/config.xml
+ return TextUtils.nullIfEmpty(context.getString(
+ com.android.internal.R.string.config_rawContactsLocalAccountName));
}
/**
@@ -2972,7 +2975,10 @@
*/
@Nullable
public static String getLocalAccountType(@NonNull Context context) {
- return null;
+ // config_rawContactsLocalAccountType is defined in
+ // platform/frameworks/base/core/res/res/values/config.xml
+ return TextUtils.nullIfEmpty(context.getString(
+ com.android.internal.R.string.config_rawContactsLocalAccountType));
}
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 030f171..49a211f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -965,6 +965,22 @@
public static final String ACTION_LOCKSCREEN_SETTINGS = "android.settings.LOCK_SCREEN_SETTINGS";
/**
+ * Activity Action: Show settings to allow pairing bluetooth devices.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_BLUETOOTH_PAIRING_SETTINGS =
+ "android.settings.BLUETOOTH_PAIRING_SETTINGS";
+
+ /**
* Activity Action: Show settings to configure input methods, in particular
* allowing the user to enable input methods.
* <p>
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index b80b01f..ae12132 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -858,9 +858,17 @@
} else if (tag.equalsIgnoreCase("span")) {
endCssStyle(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("strong")) {
- end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
+ Application application = ActivityThread.currentApplication();
+ int fontWeightAdjustment =
+ application.getResources().getConfiguration().fontWeightAdjustment;
+ end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD,
+ fontWeightAdjustment));
} else if (tag.equalsIgnoreCase("b")) {
- end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
+ Application application = ActivityThread.currentApplication();
+ int fontWeightAdjustment =
+ application.getResources().getConfiguration().fontWeightAdjustment;
+ end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD,
+ fontWeightAdjustment));
} else if (tag.equalsIgnoreCase("em")) {
end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));
} else if (tag.equalsIgnoreCase("cite")) {
@@ -1028,8 +1036,11 @@
// Their ranges should not include the newlines at the end
Heading h = getLast(text, Heading.class);
if (h != null) {
+ Application application = ActivityThread.currentApplication();
+ int fontWeightAdjustment =
+ application.getResources().getConfiguration().fontWeightAdjustment;
setSpanFromMark(text, h, new RelativeSizeSpan(HEADING_SIZES[h.mLevel]),
- new StyleSpan(Typeface.BOLD));
+ new StyleSpan(Typeface.BOLD, fontWeightAdjustment));
}
endBlockElement(text);
diff --git a/core/java/android/text/InputType.java b/core/java/android/text/InputType.java
index 7f903b6..dfe4119 100644
--- a/core/java/android/text/InputType.java
+++ b/core/java/android/text/InputType.java
@@ -16,6 +16,12 @@
package android.text;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.TextAttribute;
+import android.view.inputmethod.TextAttribute.TextAttributeBuilder;
+
+import java.util.List;
+
/**
* Bit definitions for an integer defining the basic content type of text
* held in an {@link Editable} object. Supported classes may be combined
@@ -188,6 +194,21 @@
*/
public static final int TYPE_TEXT_FLAG_NO_SUGGESTIONS = 0x00080000;
+ /**
+ * Flag for {@link #TYPE_CLASS_TEXT}: Let the IME know the text conversion suggestions are
+ * required by the application. Text conversion suggestion is for the transliteration languages
+ * which has pronunciation characters and target characters. When the user is typing the
+ * pronunciation charactes, the IME could provide the possible target characters to the user.
+ * When this flag is set, the IME should insert the text conversion suggestions through
+ * {@link TextAttributeBuilder#setTextConversionSuggestions(List)} and
+ * the {@link TextAttribute} with initialized with the text conversion suggestions is provided
+ * by the IME to the application. To receive the additional information, the application needs
+ * to implement {@link InputConnection#setComposingText(CharSequence, int, TextAttribute)},
+ * {@link InputConnection#setComposingRegion(int, int, TextAttribute)}, and
+ * {@link InputConnection#commitText(CharSequence, int, TextAttribute)}.
+ */
+ public static final int TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS = 0x00100000;
+
// ----------------------------------------------------------------------
/**
diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java
index bdfa700..176a068 100644
--- a/core/java/android/text/style/StyleSpan.java
+++ b/core/java/android/text/style/StyleSpan.java
@@ -17,8 +17,10 @@
package android.text.style;
import android.annotation.NonNull;
+import android.content.res.Configuration;
import android.graphics.Paint;
import android.graphics.Typeface;
+import android.graphics.fonts.FontStyle;
import android.os.Parcel;
import android.text.ParcelableSpan;
import android.text.TextPaint;
@@ -45,6 +47,7 @@
public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan {
private final int mStyle;
+ private final int mFontWeightAdjustment;
/**
* Creates a {@link StyleSpan} from a style.
@@ -54,7 +57,24 @@
* in {@link Typeface}.
*/
public StyleSpan(int style) {
+ this(style, Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED);
+ }
+
+ /**
+ * Creates a {@link StyleSpan} from a style and font weight adjustment.
+ *
+ * @param style An integer constant describing the style for this span. Examples
+ * include bold, italic, and normal. Values are constants defined
+ * in {@link Typeface}.
+ * @param fontWeightAdjustment An integer describing the adjustment to be made to the font
+ * weight. This is added to the value of the current weight returned by
+ * {@link Typeface#getWeight()}.
+ * @see Configuration#fontWeightAdjustment This is the adjustment in text font weight
+ * that is used to reflect the current user's preference for increasing font weight.
+ */
+ public StyleSpan(@Typeface.Style int style, int fontWeightAdjustment) {
mStyle = style;
+ mFontWeightAdjustment = fontWeightAdjustment;
}
/**
@@ -64,6 +84,7 @@
*/
public StyleSpan(@NonNull Parcel src) {
mStyle = src.readInt();
+ mFontWeightAdjustment = src.readInt();
}
@Override
@@ -91,6 +112,7 @@
@Override
public void writeToParcelInternal(@NonNull Parcel dest, int flags) {
dest.writeInt(mStyle);
+ dest.writeInt(mFontWeightAdjustment);
}
/**
@@ -100,17 +122,27 @@
return mStyle;
}
+ /**
+ * Returns the font weight adjustment specified by this span.
+ * <p>
+ * This can be {@link Configuration#FONT_WEIGHT_ADJUSTMENT_UNDEFINED}. This is added to the
+ * value of the current weight returned by {@link Typeface#getWeight()}.
+ */
+ public int getFontWeightAdjustment() {
+ return mFontWeightAdjustment;
+ }
+
@Override
public void updateDrawState(TextPaint ds) {
- apply(ds, mStyle);
+ apply(ds, mStyle, mFontWeightAdjustment);
}
@Override
public void updateMeasureState(TextPaint paint) {
- apply(paint, mStyle);
+ apply(paint, mStyle, mFontWeightAdjustment);
}
- private static void apply(Paint paint, int style) {
+ private static void apply(Paint paint, int style, int fontWeightAdjustment) {
int oldStyle;
Typeface old = paint.getTypeface();
@@ -129,6 +161,18 @@
tf = Typeface.create(old, want);
}
+ // Base typeface may already be bolded by auto bold. Bold further.
+ if ((style & Typeface.BOLD) != 0) {
+ if (fontWeightAdjustment != 0
+ && fontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
+ int newWeight = Math.min(
+ Math.max(tf.getWeight() + fontWeightAdjustment, FontStyle.FONT_WEIGHT_MIN),
+ FontStyle.FONT_WEIGHT_MAX);
+ boolean italic = (want & Typeface.ITALIC) != 0;
+ tf = Typeface.create(tf, newWeight, italic);
+ }
+ }
+
int fake = want & ~tf.getStyle();
if ((fake & Typeface.BOLD) != 0) {
diff --git a/core/java/android/util/LocalLog.java b/core/java/android/util/LocalLog.java
index 8c4dcb3..cd077e1 100644
--- a/core/java/android/util/LocalLog.java
+++ b/core/java/android/util/LocalLog.java
@@ -22,6 +22,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.ArrayDeque;
@@ -63,7 +64,8 @@
if (mUseLocalTimestamps) {
logLine = LocalDateTime.now() + " - " + msg;
} else {
- logLine = SystemClock.elapsedRealtime() + " / " + Instant.now() + " - " + msg;
+ logLine = Duration.ofMillis(SystemClock.elapsedRealtime())
+ + " / " + Instant.now() + " - " + msg;
}
append(logLine);
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index ba436e1..3c0597c 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -948,9 +948,10 @@
// When the listener is updated, we will get at least a single position update call so we can
// guarantee any changes we post will be applied.
private void replacePositionUpdateListener(int surfaceWidth, int surfaceHeight,
- @Nullable Transaction geometryTransaction) {
+ Transaction geometryTransaction) {
if (mPositionListener != null) {
mRenderNode.removePositionUpdateListener(mPositionListener);
+ geometryTransaction = mPositionListener.getTransaction().merge(geometryTransaction);
}
mPositionListener = new SurfaceViewPositionUpdateListener(surfaceWidth, surfaceHeight,
geometryTransaction);
@@ -958,7 +959,8 @@
}
private boolean performSurfaceTransaction(ViewRootImpl viewRoot, Translator translator,
- boolean creating, boolean sizeChanged, boolean hintChanged) {
+ boolean creating, boolean sizeChanged, boolean hintChanged,
+ Transaction geometryTransaction) {
boolean realSizeChanged = false;
mSurfaceLock.lock();
@@ -996,10 +998,6 @@
mSurfaceAlpha = alpha;
}
- // While creating the surface, we will set it's initial
- // geometry. Outside of that though, we should generally
- // leave it to the RenderThread.
- Transaction geometryTransaction = new Transaction();
geometryTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
if ((sizeChanged || hintChanged) && !creating) {
setBufferSize(geometryTransaction);
@@ -1022,20 +1020,18 @@
mSurfaceHeight);
}
- boolean applyChangesOnRenderThread =
- sizeChanged && !creating && isHardwareAccelerated();
if (isHardwareAccelerated()) {
// This will consume the passed in transaction and the transaction will be
// applied on a render worker thread.
replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight,
- applyChangesOnRenderThread ? geometryTransaction : null);
+ geometryTransaction);
}
if (DEBUG_POSITION) {
Log.d(TAG, String.format(
- "%d updateSurfacePosition %s"
+ "%d performSurfaceTransaction %s "
+ "position = [%d, %d, %d, %d] surfaceSize = %dx%d",
System.identityHashCode(this),
- applyChangesOnRenderThread ? "RenderWorker" : "UiThread",
+ isHardwareAccelerated() ? "RenderWorker" : "UI Thread",
mScreenRect.left, mScreenRect.top, mScreenRect.right,
mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight));
}
@@ -1147,12 +1143,14 @@
final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets;
mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
-
+ // Collect all geometry changes and apply these changes on the RenderThread worker
+ // via the RenderNode.PositionUpdateListener.
+ final Transaction geometryTransaction = new Transaction();
if (creating) {
updateOpaqueFlag();
final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
if (mUseBlastAdapter) {
- createBlastSurfaceControls(viewRoot, name);
+ createBlastSurfaceControls(viewRoot, name, geometryTransaction);
} else {
mDeferredDestroySurfaceControl = createSurfaceControls(viewRoot, name);
}
@@ -1161,7 +1159,7 @@
}
final boolean realSizeChanged = performSurfaceTransaction(viewRoot,
- translator, creating, sizeChanged, hintChanged);
+ translator, creating, sizeChanged, hintChanged, geometryTransaction);
final boolean redrawNeeded = sizeChanged || creating || hintChanged
|| (mVisible && !mDrawFinished);
@@ -1335,7 +1333,8 @@
// is still alive, the old buffers will continue to be presented until replaced by buffers from
// the new adapter. This means we do not need to track the old surface control and destroy it
// after the client has drawn to avoid any flickers.
- private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name) {
+ private void createBlastSurfaceControls(ViewRootImpl viewRoot, String name,
+ Transaction geometryTransaction) {
if (mSurfaceControl == null) {
mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
.setName(name)
@@ -1376,8 +1375,9 @@
}
mTransformHint = viewRoot.getBufferTransformHint();
mBlastSurfaceControl.setTransformHint(mTransformHint);
- mBlastBufferQueue = new BLASTBufferQueue(name, mBlastSurfaceControl, mSurfaceWidth,
- mSurfaceHeight, mFormat);
+ mBlastBufferQueue = new BLASTBufferQueue(name);
+ mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat,
+ geometryTransaction);
}
private void onDrawFinished(Transaction t) {
@@ -1558,6 +1558,10 @@
applyOrMergeTransaction(mRtTransaction, frameNumber);
}
}
+
+ public Transaction getTransaction() {
+ return mPositionChangedTransaction;
+ }
}
private SurfaceViewPositionUpdateListener mPositionListener = null;
@@ -1651,6 +1655,11 @@
@Override
public void setFixedSize(int width, int height) {
if (mRequestedWidth != width || mRequestedHeight != height) {
+ if (DEBUG_POSITION) {
+ Log.d(TAG, String.format("%d setFixedSize %dx%d -> %dx%d",
+ System.identityHashCode(this), mRequestedWidth, mRequestedHeight, width,
+ height));
+ }
mRequestedWidth = width;
mRequestedHeight = height;
requestLayout();
@@ -1660,6 +1669,10 @@
@Override
public void setSizeFromLayout() {
if (mRequestedWidth != -1 || mRequestedHeight != -1) {
+ if (DEBUG_POSITION) {
+ Log.d(TAG, String.format("%d setSizeFromLayout was %dx%d",
+ System.identityHashCode(this), mRequestedWidth, mRequestedHeight));
+ }
mRequestedWidth = mRequestedHeight = -1;
requestLayout();
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index dc61727..7a33507 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -273,6 +273,9 @@
private final ArrayMap<AccessibilityServicesStateChangeListener, Executor>
mServicesStateChangeListeners = new ArrayMap<>();
+ private final ArrayMap<AudioDescriptionRequestedChangeListener, Executor>
+ mAudioDescriptionRequestedChangeListeners = new ArrayMap<>();
+
/**
* Map from a view's accessibility id to the list of request preparers set for that view
*/
@@ -353,6 +356,21 @@
}
/**
+ * Listener for the audio description by default state. To listen for
+ * changes to the audio description by default state on the device,
+ * implement this interface and register it with the system by calling
+ * {@link #addAudioDescriptionRequestedChangeListener}.
+ */
+ public interface AudioDescriptionRequestedChangeListener {
+ /**
+ * Called when the audio description enabled state changes.
+ *
+ * @param enabled Whether audio description by default is enabled.
+ */
+ void onAudioDescriptionRequestedChanged(boolean enabled);
+ }
+
+ /**
* Policy to inject behavior into the accessibility manager.
*
* @hide
@@ -1159,6 +1177,35 @@
}
/**
+ * Registers a {@link AudioDescriptionRequestedChangeListener}
+ * for changes in the audio description by default state of the system.
+ * The value could be read via {@link #isAudioDescriptionRequested}.
+ *
+ * @param executor The executor on which the listener should be called back.
+ * @param listener The listener.
+ */
+ public void addAudioDescriptionRequestedChangeListener(
+ @NonNull Executor executor,
+ @NonNull AudioDescriptionRequestedChangeListener listener) {
+ synchronized (mLock) {
+ mAudioDescriptionRequestedChangeListeners.put(listener, executor);
+ }
+ }
+
+ /**
+ * Unregisters a {@link AudioDescriptionRequestedChangeListener}.
+ *
+ * @param listener The listener.
+ * @return True if listener was previously registered.
+ */
+ public boolean removeAudioDescriptionRequestedChangeListener(
+ @NonNull AudioDescriptionRequestedChangeListener listener) {
+ synchronized (mLock) {
+ return (mAudioDescriptionRequestedChangeListeners.remove(listener) != null);
+ }
+ }
+
+ /**
* Sets the {@link AccessibilityPolicy} controlling this manager.
*
* @param policy The policy.
@@ -1303,7 +1350,7 @@
final boolean wasEnabled = isEnabled();
final boolean wasTouchExplorationEnabled = mIsTouchExplorationEnabled;
final boolean wasHighTextContrastEnabled = mIsHighTextContrastEnabled;
-
+ final boolean wasAudioDescriptionByDefaultRequested = mIsAudioDescriptionByDefaultRequested;
// Ensure listeners get current state from isZzzEnabled() calls.
mIsEnabled = enabled;
@@ -1323,6 +1370,11 @@
notifyHighTextContrastStateChanged();
}
+ if (wasAudioDescriptionByDefaultRequested
+ != audioDescriptionEnabled) {
+ notifyAudioDescriptionbyDefaultStateChanged();
+ }
+
updateAccessibilityTracingState(stateFlags);
}
@@ -1688,15 +1740,20 @@
/**
* Determines if users want to select sound track with audio description by default.
- *
+ * <p>
* Audio description, also referred to as a video description, described video, or
* more precisely called a visual description, is a form of narration used to provide
* information surrounding key visual elements in a media work for the benefit of
* blind and visually impaired consumers.
- *
+ * </p>
+ * <p>
* The method provides the preference value to content provider apps to select the
* default sound track during playing a video or movie.
- *
+ * </p>
+ * <p>
+ * Add listener to detect the state change via
+ * {@link #addAudioDescriptionRequestedChangeListener}
+ * </p>
* @return {@code true} if the audio description is enabled, {@code false} otherwise.
*/
public boolean isAudioDescriptionRequested() {
@@ -1804,6 +1861,29 @@
}
/**
+ * Notifies the registered {@link AudioDescriptionStateChangeListener}s.
+ */
+ private void notifyAudioDescriptionbyDefaultStateChanged() {
+ final boolean isAudioDescriptionByDefaultRequested;
+ final ArrayMap<AudioDescriptionRequestedChangeListener, Executor> listeners;
+ synchronized (mLock) {
+ if (mAudioDescriptionRequestedChangeListeners.isEmpty()) {
+ return;
+ }
+ isAudioDescriptionByDefaultRequested = mIsAudioDescriptionByDefaultRequested;
+ listeners = new ArrayMap<>(mAudioDescriptionRequestedChangeListeners);
+ }
+
+ final int numListeners = listeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ final AudioDescriptionRequestedChangeListener listener = listeners.keyAt(i);
+ listeners.valueAt(i).execute(() ->
+ listener.onAudioDescriptionRequestedChanged(
+ isAudioDescriptionByDefaultRequested));
+ }
+ }
+
+ /**
* Update mAccessibilityTracingState.
*/
private void updateAccessibilityTracingState(int stateFlag) {
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 74ca913..4cbd477 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -92,6 +92,7 @@
* 1 TYPE_TEXT_FLAG_MULTI_LINE
* 1 TYPE_TEXT_FLAG_IME_MULTI_LINE
* 1 TYPE_TEXT_FLAG_NO_SUGGESTIONS
+ * 1 TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS
* |-------|-------|-------|-------|
* 1 TYPE_CLASS_NUMBER
* 1 TYPE_NUMBER_VARIATION_PASSWORD
@@ -1090,4 +1091,4 @@
public int describeContents() {
return 0;
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 1ad0452..4399207 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -44,4 +44,10 @@
* Unregisters remote animations per transition type for the organizer.
*/
void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer);
+
+ /**
+ * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
+ * only occupies a portion of Task bounds.
+ */
+ boolean isActivityEmbedded(in IBinder activityToken);
}
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 7e7d370..9c2fde0 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -216,4 +216,17 @@
return null;
}
}
+
+ /**
+ * Checks if an activity organized by a {@link android.window.TaskFragmentOrganizer} and
+ * only occupies a portion of Task bounds.
+ * @hide
+ */
+ public boolean isActivityEmbedded(@NonNull IBinder activityToken) {
+ try {
+ return getController().isActivityEmbedded(activityToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 359c382..52122ee 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2736,7 +2736,7 @@
}
@Override
- public void onListRebuilt(ResolverListAdapter listAdapter) {
+ public void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildComplete) {
setupScrollListener();
maybeSetupGlobalLayoutListener();
@@ -2756,15 +2756,20 @@
chooserListAdapter.updateAlphabeticalList();
}
+ if (rebuildComplete) {
+ getChooserActivityLogger().logSharesheetAppLoadComplete();
+ maybeQueryAdditionalPostProcessingTargets(chooserListAdapter);
+ }
+ }
+
+ private void maybeQueryAdditionalPostProcessingTargets(ChooserListAdapter chooserListAdapter) {
// don't support direct share on low ram devices
if (ActivityManager.isLowRamDeviceStatic()) {
- getChooserActivityLogger().logSharesheetAppLoadComplete();
return;
}
// no need to query direct share for work profile when its locked or disabled
if (!shouldQueryShortcutManager(chooserListAdapter.getUserHandle())) {
- getChooserActivityLogger().logSharesheetAppLoadComplete();
return;
}
@@ -2775,8 +2780,6 @@
queryDirectShareTargets(chooserListAdapter, false);
}
-
- getChooserActivityLogger().logSharesheetAppLoadComplete();
}
@VisibleForTesting
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index fd8637a..b273f6d 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1159,11 +1159,11 @@
if (doPostProcessing) {
maybeCreateHeader(listAdapter);
resetButtonBar();
- onListRebuilt(listAdapter);
+ onListRebuilt(listAdapter, rebuildCompleted);
}
}
- protected void onListRebuilt(ResolverListAdapter listAdapter) {
+ protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildCompleted) {
final ItemClickListener listener = new ItemClickListener();
setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener);
if (shouldShowTabs() && isIntentPicker()) {
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index aba43d8..c2224b4 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -18,6 +18,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.os.BatteryStats.Uid.NUM_PROCESS_STATE;
import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
@@ -241,32 +242,16 @@
MeasuredEnergyStats.POWER_BUCKET_CPU,
};
+ // TimeInState counters need NUM_PROCESS_STATE states in order to accommodate
+ // Uid.PROCESS_STATE_NONEXISTENT, which is outside the range of legitimate proc states.
+ private static final int PROC_STATE_TIME_COUNTER_STATE_COUNT = NUM_PROCESS_STATE + 1;
+
@GuardedBy("this")
public boolean mPerProcStateCpuTimesAvailable = true;
- /**
- * When per process state cpu times tracking is off, cpu times in KernelSingleUidTimeReader are
- * not updated. So, when the setting is turned on later, we would end up with huge cpu time
- * deltas. This flag tracks the case where tracking is turned on from off so that we won't
- * end up attributing the huge deltas to wrong buckets.
- */
- @GuardedBy("this")
- private boolean mIsPerProcessStateCpuDataStale;
-
- /**
- * Uids for which per-procstate cpu times need to be updated.
- *
- * Contains uid -> procState mappings.
- */
- @GuardedBy("this")
- @VisibleForTesting
- protected final SparseIntArray mPendingUids = new SparseIntArray();
-
@GuardedBy("this")
private long mNumSingleUidCpuTimeReads;
@GuardedBy("this")
- private long mNumBatchedSingleUidCpuTimeReads;
- @GuardedBy("this")
private long mCpuTimeReadsTrackingStartTimeMs = SystemClock.uptimeMillis();
@GuardedBy("this")
private int mNumUidsRemoved;
@@ -443,88 +428,42 @@
}
/**
- * Update per-freq cpu times for all the uids in {@link #mPendingUids}.
+ * Update per-freq cpu times for the supplied UID.
*/
- public void updateProcStateCpuTimes(boolean onBattery, boolean onBatteryScreenOff) {
- final SparseIntArray uidStates;
- synchronized (BatteryStatsImpl.this) {
- if (!mConstants.TRACK_CPU_TIMES_BY_PROC_STATE) {
- return;
- }
- if(!initKernelSingleUidTimeReaderLocked()) {
- return;
- }
- // If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to
- // compute deltas since it might result in mis-attributing cpu times to wrong states.
- if (mIsPerProcessStateCpuDataStale) {
- mPendingUids.clear();
- return;
- }
-
- if (mPendingUids.size() == 0) {
- return;
- }
- uidStates = mPendingUids.clone();
- mPendingUids.clear();
+ @GuardedBy("this")
+ @SuppressWarnings("GuardedBy") // errorprone false positive on getProcStateTimeCounter
+ @VisibleForTesting
+ public void updateProcStateCpuTimesLocked(int uid, long timestampMs) {
+ if (!initKernelSingleUidTimeReaderLocked()) {
+ return;
}
- final long timestampMs = mClock.elapsedRealtime();
- LongArrayMultiStateCounter.LongArrayContainer deltaContainer = null;
- for (int i = uidStates.size() - 1; i >= 0; --i) {
- final int uid = uidStates.keyAt(i);
- final int procState = uidStates.valueAt(i);
- final int[] isolatedUids;
- final LongArrayMultiStateCounter[] isolatedUidTimeInFreqCounters;
- final Uid u;
- synchronized (BatteryStatsImpl.this) {
- // It's possible that uid no longer exists and any internal references have
- // already been deleted, so using {@link #getAvailableUidStatsLocked} to avoid
- // creating an UidStats object if it doesn't already exist.
- u = getAvailableUidStatsLocked(uid);
- if (u == null) {
- continue;
- }
- if (u.mChildUids == null) {
- isolatedUids = null;
- isolatedUidTimeInFreqCounters = null;
- } else {
- int childUidCount = u.mChildUids.size();
- isolatedUids = new int[childUidCount];
- isolatedUidTimeInFreqCounters = new LongArrayMultiStateCounter[childUidCount];
- for (int j = childUidCount - 1; j >= 0; --j) {
- isolatedUids[j] = u.mChildUids.keyAt(j);
- isolatedUidTimeInFreqCounters[j] =
- u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
- if (deltaContainer == null && isolatedUidTimeInFreqCounters[j] != null) {
- deltaContainer = getCpuTimeInFreqContainer();
- }
- }
+
+ final Uid u = getUidStatsLocked(uid);
+
+ mNumSingleUidCpuTimeReads++;
+
+ LongArrayMultiStateCounter onBatteryCounter =
+ u.getProcStateTimeCounter().getCounter();
+ LongArrayMultiStateCounter onBatteryScreenOffCounter =
+ u.getProcStateScreenOffTimeCounter().getCounter();
+
+ mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, timestampMs);
+ mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, timestampMs);
+
+ if (u.mChildUids != null) {
+ LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
+ getCpuTimeInFreqContainer();
+ int childUidCount = u.mChildUids.size();
+ for (int j = childUidCount - 1; j >= 0; --j) {
+ LongArrayMultiStateCounter cpuTimeInFreqCounter =
+ u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
+ if (cpuTimeInFreqCounter != null) {
+ mKernelSingleUidTimeReader.addDelta(u.mChildUids.keyAt(j),
+ cpuTimeInFreqCounter, timestampMs, deltaContainer);
+ onBatteryCounter.addCounts(deltaContainer);
+ onBatteryScreenOffCounter.addCounts(deltaContainer);
}
}
-
- LongArrayMultiStateCounter onBatteryCounter =
- u.getProcStateTimeCounter().getCounter();
- LongArrayMultiStateCounter onBatteryScreenOffCounter =
- u.getProcStateScreenOffTimeCounter().getCounter();
-
- onBatteryCounter.setState(procState, timestampMs);
- mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, timestampMs);
-
- onBatteryScreenOffCounter.setState(procState, timestampMs);
- mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, timestampMs);
-
- if (isolatedUids != null) {
- for (int j = isolatedUids.length - 1; j >= 0; --j) {
- if (isolatedUidTimeInFreqCounters[j] != null) {
- mKernelSingleUidTimeReader.addDelta(isolatedUids[j],
- isolatedUidTimeInFreqCounters[j], timestampMs, deltaContainer);
- onBatteryCounter.addCounts(deltaContainer);
- onBatteryScreenOffCounter.addCounts(deltaContainer);
- }
- }
- }
-
- onBatteryCounter.setState(u.mProcessState, timestampMs);
- onBatteryScreenOffCounter.setState(u.mProcessState, timestampMs);
}
}
@@ -542,24 +481,17 @@
}
}
- public void copyFromAllUidsCpuTimes() {
- synchronized (BatteryStatsImpl.this) {
- copyFromAllUidsCpuTimes(
- mOnBatteryTimeBase.isRunning(), mOnBatteryScreenOffTimeBase.isRunning());
- }
- }
-
/**
* When the battery/screen state changes, we don't attribute the cpu times to any process
- * but we still need to snapshots of all uids to get correct deltas later on. Since we
- * already read this data for updating per-freq cpu times, we can use the same data for
- * per-procstate cpu times.
+ * but we still need to take snapshots of all uids to get correct deltas later on.
*/
- public void copyFromAllUidsCpuTimes(boolean onBattery, boolean onBatteryScreenOff) {
+ @SuppressWarnings("GuardedBy") // errorprone false positive on getProcStateTimeCounter
+ public void updateCpuTimesForAllUids() {
synchronized (BatteryStatsImpl.this) {
- if (!mConstants.TRACK_CPU_TIMES_BY_PROC_STATE) {
+ if (!trackPerProcStateCpuTimes()) {
return;
}
+
if(!initKernelSingleUidTimeReaderLocked()) {
return;
}
@@ -567,14 +499,6 @@
// TODO(b/197162116): just get a list of UIDs
final SparseArray<long[]> allUidCpuFreqTimesMs =
mCpuUidFreqTimeReader.getAllUidCpuFreqTimeMs();
- // If the KernelSingleUidTimeReader has stale cpu times, then we shouldn't try to
- // compute deltas since it might result in mis-attributing cpu times to wrong states.
- if (mIsPerProcessStateCpuDataStale) {
- mKernelSingleUidTimeReader.setAllUidsCpuTimesMs(allUidCpuFreqTimesMs);
- mIsPerProcessStateCpuDataStale = false;
- mPendingUids.clear();
- return;
- }
for (int i = allUidCpuFreqTimesMs.size() - 1; i >= 0; --i) {
final int uid = allUidCpuFreqTimesMs.keyAt(i);
final int parentUid = mapUid(uid);
@@ -583,16 +507,8 @@
continue;
}
- final int procState;
- final int idx = mPendingUids.indexOfKey(uid);
- if (idx >= 0) {
- procState = mPendingUids.valueAt(idx);
- mPendingUids.removeAt(idx);
- } else {
- procState = u.mProcessState;
- }
-
- if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ final int procState = u.mProcessState;
+ if (procState == Uid.PROCESS_STATE_NONEXISTENT) {
continue;
}
@@ -602,27 +518,19 @@
final LongArrayMultiStateCounter onBatteryScreenOffCounter =
u.getProcStateScreenOffTimeCounter().getCounter();
- onBatteryCounter.setState(procState, timestampMs);
if (uid == parentUid) {
mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, timestampMs);
- }
-
- onBatteryScreenOffCounter.setState(procState, timestampMs);
- if (uid == parentUid) {
mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter,
timestampMs);
- }
-
- if (u.mChildUids != null) {
- for (int j = u.mChildUids.size() - 1; j >= 0; --j) {
- final LongArrayMultiStateCounter counter =
- u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
+ } else {
+ Uid.ChildUid childUid = u.getChildUid(uid);
+ if (childUid != null) {
+ final LongArrayMultiStateCounter counter = childUid.cpuTimeInFreqCounter;
if (counter != null) {
- final int isolatedUid = u.mChildUids.keyAt(j);
final LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
getCpuTimeInFreqContainer();
- mKernelSingleUidTimeReader.addDelta(isolatedUid,
- counter, timestampMs, deltaContainer);
+ mKernelSingleUidTimeReader.addDelta(uid, counter, timestampMs,
+ deltaContainer);
onBatteryCounter.addCounts(deltaContainer);
onBatteryScreenOffCounter.addCounts(deltaContainer);
}
@@ -689,9 +597,6 @@
Future<?> scheduleSync(String reason, int flags);
Future<?> scheduleCpuSyncDueToRemovedUid(int uid);
- Future<?> scheduleReadProcStateCpuTimes(boolean onBattery, boolean onBatteryScreenOff,
- long delayMillis);
- Future<?> scheduleCopyFromAllUidsCpuTimes(boolean onBattery, boolean onBatteryScreenOff);
Future<?> scheduleCpuSyncDueToSettingChange();
/**
* Schedule a sync because of a screen state change.
@@ -1839,10 +1744,6 @@
return mCounter.getStateCount();
}
- public void setTrackingEnabled(boolean enabled, long timestampMs) {
- mCounter.setEnabled(enabled && mTimeBase.isRunning(), timestampMs);
- }
-
private void setState(@BatteryConsumer.ProcessState int processState,
long elapsedRealtimeMs) {
mCounter.setState(processState, elapsedRealtimeMs);
@@ -8073,7 +7974,7 @@
Counter mBluetoothScanResultCounter;
Counter mBluetoothScanResultBgCounter;
- int mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+ int mProcessState = Uid.PROCESS_STATE_NONEXISTENT;
StopwatchTimer[] mProcessStateTimer;
boolean mInForegroundService = false;
@@ -8415,6 +8316,11 @@
mChildUids.remove(idx);
}
+ @GuardedBy("mBsi")
+ ChildUid getChildUid(int childUid) {
+ return mChildUids == null ? null : mChildUids.get(childUid);
+ }
+
private long[] nullIfAllZeros(LongSamplingCounterArray cpuTimesMs, int which) {
if (cpuTimesMs == null) {
return null;
@@ -8432,6 +8338,7 @@
return null;
}
+ @GuardedBy("mBsi")
private void ensureMultiStateCounters() {
if (mProcStateTimeMs != null) {
return;
@@ -8440,31 +8347,26 @@
final long timestampMs = mBsi.mClock.elapsedRealtime();
mProcStateTimeMs =
new TimeInFreqMultiStateCounter(mBsi.mOnBatteryTimeBase,
- NUM_PROCESS_STATE, mBsi.getCpuFreqCount(), timestampMs);
+ PROC_STATE_TIME_COUNTER_STATE_COUNT, mBsi.getCpuFreqCount(),
+ timestampMs);
mProcStateScreenOffTimeMs =
new TimeInFreqMultiStateCounter(mBsi.mOnBatteryScreenOffTimeBase,
- NUM_PROCESS_STATE, mBsi.getCpuFreqCount(), timestampMs);
+ PROC_STATE_TIME_COUNTER_STATE_COUNT, mBsi.getCpuFreqCount(),
+ timestampMs);
}
+ @GuardedBy("mBsi")
private TimeInFreqMultiStateCounter getProcStateTimeCounter() {
ensureMultiStateCounters();
return mProcStateTimeMs;
}
+ @GuardedBy("mBsi")
private TimeInFreqMultiStateCounter getProcStateScreenOffTimeCounter() {
ensureMultiStateCounters();
return mProcStateScreenOffTimeMs;
}
- private void setProcStateTimesTrackingEnabled(boolean enabled, long timestampMs) {
- if (mProcStateTimeMs != null) {
- mProcStateTimeMs.setTrackingEnabled(enabled, timestampMs);
- }
- if (mProcStateScreenOffTimeMs != null) {
- mProcStateScreenOffTimeMs.setTrackingEnabled(enabled, timestampMs);
- }
- }
-
@Override
public Timer getAggregatedPartialWakelockTimer() {
return mAggregatedPartialWakelockTimer;
@@ -8774,6 +8676,7 @@
processState);
}
+ @GuardedBy("mBsi")
@Override
public long getGnssMeasuredBatteryConsumptionUC() {
return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_GNSS);
@@ -9553,7 +9456,7 @@
for (int i = 0; i < NUM_PROCESS_STATE; i++) {
active |= !resetIfNotNull(mProcessStateTimer[i], false, realtimeUs);
}
- active |= (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT);
+ active |= (mProcessState != Uid.PROCESS_STATE_NONEXISTENT);
}
if (mVibratorOnTimer != null) {
if (mVibratorOnTimer.reset(false, realtimeUs)) {
@@ -10270,7 +10173,7 @@
} else {
mBluetoothScanResultBgCounter = null;
}
- mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
+ mProcessState = Uid.PROCESS_STATE_NONEXISTENT;
for (int i = 0; i < NUM_PROCESS_STATE; i++) {
if (in.readInt() != 0) {
makeProcessState(i, in);
@@ -10360,7 +10263,7 @@
// Read the object from the Parcel, whether it's usable or not
TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter(
mBsi.mOnBatteryTimeBase, in, timestampMs);
- if (stateCount == NUM_PROCESS_STATE) {
+ if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
mProcStateTimeMs = counter;
}
} else {
@@ -10373,7 +10276,7 @@
TimeInFreqMultiStateCounter counter =
new TimeInFreqMultiStateCounter(
mBsi.mOnBatteryScreenOffTimeBase, in, timestampMs);
- if (stateCount == NUM_PROCESS_STATE) {
+ if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
mProcStateScreenOffTimeMs = counter;
}
} else {
@@ -11244,12 +11147,6 @@
}
@GuardedBy("mBsi")
- public void updateUidProcessStateLocked(int procState) {
- updateUidProcessStateLocked(procState,
- mBsi.mClock.elapsedRealtime(), mBsi.mClock.uptimeMillis());
- }
-
- @GuardedBy("mBsi")
public void updateUidProcessStateLocked(int procState,
long elapsedRealtimeMs, long uptimeMs) {
int uidRunningState;
@@ -11263,40 +11160,35 @@
}
if (mProcessState != uidRunningState) {
- if (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ if (mProcessState != Uid.PROCESS_STATE_NONEXISTENT) {
mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtimeMs);
-
- if (mBsi.trackPerProcStateCpuTimes()) {
- if (mBsi.mPendingUids.size() == 0) {
- mBsi.mExternalSync.scheduleReadProcStateCpuTimes(
- mBsi.mOnBatteryTimeBase.isRunning(),
- mBsi.mOnBatteryScreenOffTimeBase.isRunning(),
- mBsi.mConstants.PROC_STATE_CPU_TIMES_READ_DELAY_MS);
- mBsi.mNumSingleUidCpuTimeReads++;
- } else {
- mBsi.mNumBatchedSingleUidCpuTimeReads++;
- }
- if (mBsi.mPendingUids.indexOfKey(mUid) < 0
- || ArrayUtils.contains(CRITICAL_PROC_STATES, mProcessState)) {
- mBsi.mPendingUids.put(mUid, mProcessState);
- }
- } else {
- mBsi.mPendingUids.clear();
- }
}
- mProcessState = uidRunningState;
- if (uidRunningState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ if (uidRunningState != Uid.PROCESS_STATE_NONEXISTENT) {
if (mProcessStateTimer[uidRunningState] == null) {
makeProcessState(uidRunningState, null);
}
mProcessStateTimer[uidRunningState].startRunningLocked(elapsedRealtimeMs);
}
+ if (mBsi.trackPerProcStateCpuTimes()) {
+ mBsi.updateProcStateCpuTimesLocked(mUid, elapsedRealtimeMs);
+
+ LongArrayMultiStateCounter onBatteryCounter =
+ getProcStateTimeCounter().getCounter();
+ LongArrayMultiStateCounter onBatteryScreenOffCounter =
+ getProcStateScreenOffTimeCounter().getCounter();
+
+ onBatteryCounter.setState(uidRunningState, elapsedRealtimeMs);
+ onBatteryScreenOffCounter.setState(uidRunningState, elapsedRealtimeMs);
+ }
+
+ mProcessState = uidRunningState;
+
updateOnBatteryBgTimeBase(uptimeMs * 1000, elapsedRealtimeMs * 1000);
updateOnBatteryScreenOffBgTimeBase(uptimeMs * 1000, elapsedRealtimeMs * 1000);
final int batteryConsumerProcessState =
- mapUidProcessStateToBatteryConsumerProcessState(mProcessState);
+ mapUidProcessStateToBatteryConsumerProcessState(uidRunningState);
getCpuActiveTimeCounter().setState(batteryConsumerProcessState, elapsedRealtimeMs);
final MeasuredEnergyStats energyStats =
@@ -11318,7 +11210,7 @@
/** Whether to consider Uid to be in the background for background timebase purposes. */
public boolean isInBackground() {
- // Note that PROCESS_STATE_CACHED and ActivityManager.PROCESS_STATE_NONEXISTENT is
+ // Note that PROCESS_STATE_CACHED and Uid.PROCESS_STATE_NONEXISTENT is
// also considered to be 'background' for our purposes, because it's not foreground.
return mProcessState >= PROCESS_STATE_BACKGROUND;
}
@@ -15660,7 +15552,7 @@
@GuardedBy("this")
public boolean trackPerProcStateCpuTimes() {
- return mConstants.TRACK_CPU_TIMES_BY_PROC_STATE && mPerProcStateCpuTimesAvailable;
+ return mCpuUidFreqTimeReader.isFastCpuTimesReader();
}
@GuardedBy("this")
@@ -15747,8 +15639,6 @@
@VisibleForTesting
public final class Constants extends ContentObserver {
- public static final String KEY_TRACK_CPU_TIMES_BY_PROC_STATE
- = "track_cpu_times_by_proc_state";
public static final String KEY_TRACK_CPU_ACTIVE_CLUSTER_TIME
= "track_cpu_active_cluster_time";
public static final String KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS
@@ -15766,9 +15656,7 @@
public static final String KEY_BATTERY_CHARGED_DELAY_MS =
"battery_charged_delay_ms";
- private static final boolean DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE = false;
private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true;
- private static final long DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS = 5_000;
private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 1_000;
private static final long DEFAULT_UID_REMOVE_DELAY_MS = 5L * 60L * 1000L;
private static final long DEFAULT_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS = 600_000;
@@ -15779,9 +15667,7 @@
private static final int DEFAULT_MAX_HISTORY_BUFFER_LOW_RAM_DEVICE_KB = 64; /*Kilo Bytes*/
private static final int DEFAULT_BATTERY_CHARGED_DELAY_MS = 900000; /* 15 min */
- public boolean TRACK_CPU_TIMES_BY_PROC_STATE = DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE;
public boolean TRACK_CPU_ACTIVE_CLUSTER_TIME = DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME;
- public long PROC_STATE_CPU_TIMES_READ_DELAY_MS = DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS;
/* Do not set default value for KERNEL_UID_READERS_THROTTLE_TIME. Need to trigger an
* update when startObserving. */
public long KERNEL_UID_READERS_THROTTLE_TIME;
@@ -15843,14 +15729,8 @@
Slog.e(TAG, "Bad batterystats settings", e);
}
- updateTrackCpuTimesByProcStateLocked(TRACK_CPU_TIMES_BY_PROC_STATE,
- mParser.getBoolean(KEY_TRACK_CPU_TIMES_BY_PROC_STATE,
- DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE));
TRACK_CPU_ACTIVE_CLUSTER_TIME = mParser.getBoolean(
KEY_TRACK_CPU_ACTIVE_CLUSTER_TIME, DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME);
- updateProcStateCpuTimesReadDelayMs(PROC_STATE_CPU_TIMES_READ_DELAY_MS,
- mParser.getLong(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS,
- DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS));
updateKernelUidReadersThrottleTime(KERNEL_UID_READERS_THROTTLE_TIME,
mParser.getLong(KEY_KERNEL_UID_READERS_THROTTLE_TIME,
DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME));
@@ -15887,33 +15767,6 @@
DEFAULT_BATTERY_CHARGED_DELAY_MS);
}
- @GuardedBy("BatteryStatsImpl.this")
- private void updateTrackCpuTimesByProcStateLocked(boolean wasEnabled, boolean isEnabled) {
- TRACK_CPU_TIMES_BY_PROC_STATE = isEnabled;
- if (isEnabled && !wasEnabled) {
- mIsPerProcessStateCpuDataStale = true;
- mExternalSync.scheduleCpuSyncDueToSettingChange();
-
- mNumSingleUidCpuTimeReads = 0;
- mNumBatchedSingleUidCpuTimeReads = 0;
- mCpuTimeReadsTrackingStartTimeMs = mClock.uptimeMillis();
- }
- final long timestampMs = mClock.elapsedRealtime();
- for (int i = mUidStats.size() - 1; i >= 0; i--) {
- mUidStats.valueAt(i).setProcStateTimesTrackingEnabled(isEnabled, timestampMs);
- }
- }
-
- @GuardedBy("BatteryStatsImpl.this")
- private void updateProcStateCpuTimesReadDelayMs(long oldDelayMillis, long newDelayMillis) {
- PROC_STATE_CPU_TIMES_READ_DELAY_MS = newDelayMillis;
- if (oldDelayMillis != newDelayMillis) {
- mNumSingleUidCpuTimeReads = 0;
- mNumBatchedSingleUidCpuTimeReads = 0;
- mCpuTimeReadsTrackingStartTimeMs = mClock.uptimeMillis();
- }
- }
-
private void updateKernelUidReadersThrottleTime(long oldTimeMs, long newTimeMs) {
KERNEL_UID_READERS_THROTTLE_TIME = newTimeMs;
if (oldTimeMs != newTimeMs) {
@@ -15932,12 +15785,8 @@
}
public void dumpLocked(PrintWriter pw) {
- pw.print(KEY_TRACK_CPU_TIMES_BY_PROC_STATE); pw.print("=");
- pw.println(TRACK_CPU_TIMES_BY_PROC_STATE);
pw.print(KEY_TRACK_CPU_ACTIVE_CLUSTER_TIME); pw.print("=");
pw.println(TRACK_CPU_ACTIVE_CLUSTER_TIME);
- pw.print(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS); pw.print("=");
- pw.println(PROC_STATE_CPU_TIMES_READ_DELAY_MS);
pw.print(KEY_KERNEL_UID_READERS_THROTTLE_TIME); pw.print("=");
pw.println(KERNEL_UID_READERS_THROTTLE_TIME);
pw.print(KEY_EXTERNAL_STATS_COLLECTION_RATE_LIMIT_MS); pw.print("=");
@@ -16606,8 +16455,8 @@
if (in.readInt() != 0) {
u.createBluetoothScanResultBgCounterLocked().readSummaryFromParcelLocked(in);
}
- u.mProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
- for (int i = 0; i < Uid.NUM_PROCESS_STATE; i++) {
+ u.mProcessState = Uid.PROCESS_STATE_NONEXISTENT;
+ for (int i = 0; i < NUM_PROCESS_STATE; i++) {
if (in.readInt() != 0) {
u.makeProcessState(i, null);
u.mProcessStateTimer[i].readSummaryFromParcelLocked(in);
@@ -16699,7 +16548,7 @@
// Read the object from the Parcel, whether it's usable or not
TimeInFreqMultiStateCounter counter = new TimeInFreqMultiStateCounter(
mOnBatteryTimeBase, in, mClock.elapsedRealtime());
- if (stateCount == Uid.NUM_PROCESS_STATE) {
+ if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
u.mProcStateTimeMs = counter;
}
}
@@ -16714,7 +16563,7 @@
TimeInFreqMultiStateCounter counter =
new TimeInFreqMultiStateCounter(
mOnBatteryScreenOffTimeBase, in, mClock.elapsedRealtime());
- if (stateCount == Uid.NUM_PROCESS_STATE) {
+ if (stateCount == PROC_STATE_TIME_COUNTER_STATE_COUNT) {
u.mProcStateScreenOffTimeMs = counter;
}
}
@@ -17152,7 +17001,7 @@
} else {
out.writeInt(0);
}
- for (int i = 0; i < Uid.NUM_PROCESS_STATE; i++) {
+ for (int i = 0; i < NUM_PROCESS_STATE; i++) {
if (u.mProcessStateTimer[i] != null) {
out.writeInt(1);
u.mProcessStateTimer[i].writeSummaryFromParcelLocked(out, nowRealtime);
@@ -17982,10 +17831,10 @@
}
super.dumpLocked(context, pw, flags, reqUid, histStart);
+ pw.print("Per process state tracking available: ");
+ pw.println(trackPerProcStateCpuTimes());
pw.print("Total cpu time reads: ");
pw.println(mNumSingleUidCpuTimeReads);
- pw.print("Batched cpu time reads: ");
- pw.println(mNumBatchedSingleUidCpuTimeReads);
pw.print("Batching Duration (min): ");
pw.println((mClock.uptimeMillis() - mCpuTimeReadsTrackingStartTimeMs) / (60 * 1000));
pw.print("All UID cpu time reads since the later of device start or stats reset: ");
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index faeb8fc..c801be0 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -620,6 +620,10 @@
}
return numClusterFreqs;
}
+
+ public boolean isFastCpuTimesReader() {
+ return mBpfTimesAvailable;
+ }
}
/**
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 424632fe..6541b14 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -270,8 +270,6 @@
private Drawable mCaptionBackgroundDrawable;
private Drawable mUserCaptionBackgroundDrawable;
- private float mAvailableWidth;
-
String mLogTag = TAG;
private final Rect mFloatingInsets = new Rect();
private boolean mApplyFloatingVerticalInsets = false;
@@ -315,8 +313,6 @@
mSemiTransparentBarColor = context.getResources().getColor(
R.color.system_bar_background_semi_transparent, null /* theme */);
- updateAvailableWidth();
-
setWindow(window);
updateLogTag(params);
@@ -697,7 +693,8 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+ final Resources res = getContext().getResources();
+ final DisplayMetrics metrics = res.getDisplayMetrics();
final boolean isPortrait =
getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
@@ -767,17 +764,19 @@
if (!fixedWidth && widthMode == AT_MOST) {
final TypedValue tv = isPortrait ? mWindow.mMinWidthMinor : mWindow.mMinWidthMajor;
+ final float availableWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ res.getConfiguration().screenWidthDp, metrics);
if (tv.type != TypedValue.TYPE_NULL) {
final int min;
if (tv.type == TypedValue.TYPE_DIMENSION) {
- min = (int)tv.getDimension(metrics);
+ min = (int) tv.getDimension(metrics);
} else if (tv.type == TypedValue.TYPE_FRACTION) {
- min = (int)tv.getFraction(mAvailableWidth, mAvailableWidth);
+ min = (int) tv.getFraction(availableWidth, availableWidth);
} else {
min = 0;
}
if (DEBUG_MEASURE) Log.d(mLogTag, "Adjust for min width: " + min + ", value::"
- + tv.coerceToString() + ", mAvailableWidth=" + mAvailableWidth);
+ + tv.coerceToString() + ", mAvailableWidth=" + availableWidth);
if (width < min) {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
@@ -2144,7 +2143,6 @@
updateDecorCaptionStatus(newConfig);
- updateAvailableWidth();
initializeElevation();
}
@@ -2616,12 +2614,6 @@
mLogTag = TAG + "[" + getTitleSuffix(params) + "]";
}
- private void updateAvailableWidth() {
- Resources res = getResources();
- mAvailableWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- res.getConfiguration().screenWidthDp, res.getDisplayMetrics());
- }
-
/**
* @hide
*/
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index be5dc00..d66f461 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -870,7 +870,8 @@
boolean allowedMinSdk = minDeviceSdk <= Build.VERSION.SDK_INT;
boolean allowedMaxSdk =
maxDeviceSdk == 0 || maxDeviceSdk >= Build.VERSION.SDK_INT;
- if (allowedMinSdk && allowedMaxSdk) {
+ final boolean exists = new File(lfile).exists();
+ if (allowedMinSdk && allowedMaxSdk && exists) {
int bcpSince = XmlUtils.readIntAttribute(parser,
"on-bootclasspath-since", 0);
int bcpBefore = XmlUtils.readIntAttribute(parser,
@@ -880,6 +881,19 @@
? new String[0] : ldependency.split(":"),
bcpSince, bcpBefore);
mSharedLibraries.put(lname, entry);
+ } else {
+ final StringBuilder msg = new StringBuilder(
+ "Ignore shared library ").append(lname).append(":");
+ if (!allowedMinSdk) {
+ msg.append(" min-device-sdk=").append(minDeviceSdk);
+ }
+ if (!allowedMaxSdk) {
+ msg.append(" max-device-sdk=").append(maxDeviceSdk);
+ }
+ if (!exists) {
+ msg.append(" ").append(lfile).append(" does not exist");
+ }
+ Slog.i(TAG, msg.toString());
}
}
} else {
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index a059dd6..78e5adc 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -30,21 +30,19 @@
namespace android {
-static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl,
- jlong width, jlong height, jint format) {
- String8 str8;
- if (jName) {
- const jchar* str16 = env->GetStringCritical(jName, nullptr);
- if (str16) {
- str8 = String8(reinterpret_cast<const char16_t*>(str16), env->GetStringLength(jName));
- env->ReleaseStringCritical(jName, str16);
- str16 = nullptr;
- }
- }
- std::string name = str8.string();
+static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName) {
+ ScopedUtfChars name(env, jName);
+ sp<BLASTBufferQueue> queue = new BLASTBufferQueue(name.c_str());
+ queue->incStrong((void*)nativeCreate);
+ return reinterpret_cast<jlong>(queue.get());
+}
+
+static jlong nativeCreateAndUpdate(JNIEnv* env, jclass clazz, jstring jName, jlong surfaceControl,
+ jlong width, jlong height, jint format) {
+ ScopedUtfChars name(env, jName);
sp<BLASTBufferQueue> queue =
- new BLASTBufferQueue(name, reinterpret_cast<SurfaceControl*>(surfaceControl), width,
- height, format);
+ new BLASTBufferQueue(name.c_str(), reinterpret_cast<SurfaceControl*>(surfaceControl),
+ width, height, format);
queue->incStrong((void*)nativeCreate);
return reinterpret_cast<jlong>(queue.get());
}
@@ -96,7 +94,8 @@
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
// clang-format off
- {"nativeCreate", "(Ljava/lang/String;JJJI)J", (void*)nativeCreate},
+ {"nativeCreate", "(Ljava/lang/String;)J", (void*)nativeCreate},
+ {"nativeCreateAndUpdate", "(Ljava/lang/String;JJJI)J", (void*)nativeCreateAndUpdate},
{"nativeGetSurface", "(JZ)Landroid/view/Surface;", (void*)nativeGetSurface},
{"nativeDestroy", "(J)V", (void*)nativeDestroy},
{"nativeSetSyncTransaction", "(JJZ)V", (void*)nativeSetSyncTransaction},
diff --git a/core/jni/android_media_AudioAttributes.cpp b/core/jni/android_media_AudioAttributes.cpp
index f1ae268..423ef7c 100644
--- a/core/jni/android_media_AudioAttributes.cpp
+++ b/core/jni/android_media_AudioAttributes.cpp
@@ -58,7 +58,7 @@
jmethodID setSystemUsage;
jmethodID setInternalCapturePreset;
jmethodID setContentType;
- jmethodID setFlags;
+ jmethodID replaceFlags;
jmethodID addTag;
} gAudioAttributesBuilderMethods;
@@ -130,7 +130,7 @@
gAudioAttributesBuilderMethods.setContentType,
attributes.content_type);
env->CallObjectMethod(jAttributeBuilder.get(),
- gAudioAttributesBuilderMethods.setFlags,
+ gAudioAttributesBuilderMethods.replaceFlags,
attributes.flags);
env->CallObjectMethod(jAttributeBuilder.get(),
gAudioAttributesBuilderMethods.addTag,
@@ -205,8 +205,8 @@
gAudioAttributesBuilderMethods.setContentType = GetMethodIDOrDie(
env, audioAttributesBuilderClass, "setContentType",
"(I)Landroid/media/AudioAttributes$Builder;");
- gAudioAttributesBuilderMethods.setFlags = GetMethodIDOrDie(
- env, audioAttributesBuilderClass, "setFlags",
+ gAudioAttributesBuilderMethods.replaceFlags = GetMethodIDOrDie(
+ env, audioAttributesBuilderClass, "replaceFlags",
"(I)Landroid/media/AudioAttributes$Builder;");
gAudioAttributesBuilderMethods.addTag = GetMethodIDOrDie(
env, audioAttributesBuilderClass, "addTag",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4d2d200..6e2c807 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4695,6 +4695,13 @@
<permission android:name="android.permission.MODIFY_AUDIO_ROUTING"
android:protectionLevel="signature|privileged|role" />
+ <!-- @SystemApi Allows an application to access the uplink and downlink audio of an ongoing
+ call.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.CALL_AUDIO_INTERCEPTION"
+ android:protectionLevel="signature|privileged" />
+
<!-- @TestApi Allows an application to query audio related state.
@hide -->
<permission android:name="android.permission.QUERY_AUDIO_STATE"
@@ -5863,6 +5870,10 @@
<!-- Allows input events to be monitored. Very dangerous! @hide -->
<permission android:name="android.permission.MONITOR_INPUT"
android:protectionLevel="signature|recents" />
+ <!-- Allows the use of FLAG_SLIPPERY, which permits touch events to slip from the current
+ window to the window where the touch currently is on top of. @hide -->
+ <permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES"
+ android:protectionLevel="signature|recents" />
<!-- Allows the caller to change the associations between input devices and displays.
Very dangerous! @hide -->
<permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 697ec20..6076645 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1329,6 +1329,11 @@
dictionary-based word suggestions. Corresponds to
{@link android.text.InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS}. -->
<flag name="textNoSuggestions" value="0x00080001" />
+ <!-- Can be combined with <var>text</var> and its variations to
+ indicate that if there is extra information, the IME should provide
+ {@link android.view.inputmethod.TextAttribute}. Corresponds to
+ {@link android.text.InputType#TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS}. -->
+ <flag name="textEnableTextConversionSuggestions" value="0x00100001" />
<!-- Text that will be used as a URI. Corresponds to
{@link android.text.InputType#TYPE_CLASS_TEXT} |
{@link android.text.InputType#TYPE_TEXT_VARIATION_URI}. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 650bd3d..769e667 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1762,6 +1762,8 @@
<string name="face_setup_notification_title">Set up Face Unlock</string>
<!-- Contents of a notification that directs the user to set up face unlock by enrolling their face. [CHAR LIMIT=NONE] -->
<string name="face_setup_notification_content">Unlock your phone by looking at it</string>
+ <!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] -->
+ <string name="face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string>
<!-- Title of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->
<string name="fingerprint_setup_notification_title">Set up more ways to unlock</string>
<!-- Contents of a notification that directs the user to enroll a fingerprint. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ccb487e..134235d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2587,6 +2587,7 @@
<java-symbol type="string" name="face_recalibrate_notification_name" />
<java-symbol type="string" name="face_recalibrate_notification_title" />
<java-symbol type="string" name="face_recalibrate_notification_content" />
+ <java-symbol type="string" name="face_sensor_privacy_enabled" />
<java-symbol type="string" name="face_error_unable_to_process" />
<java-symbol type="string" name="face_error_hw_not_available" />
<java-symbol type="string" name="face_error_no_space" />
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java
index 6471492..c3d707c 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java
@@ -45,7 +45,6 @@
assertEquals("INVALID CODEC", leAudioCodecConfig.getCodecName());
}
- assertEquals(1, leAudioCodecConfig.getMaxCodecType());
assertEquals(codecType, leAudioCodecConfig.getCodecType());
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
index 8ec33bf..c1a45c4 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
@@ -37,7 +37,6 @@
import android.app.ActivityManager;
import android.os.BatteryStats;
import android.util.SparseArray;
-import android.util.SparseIntArray;
import android.view.Display;
import androidx.test.filters.LargeTest;
@@ -53,6 +52,7 @@
@LargeTest
@RunWith(AndroidJUnit4.class)
+@SuppressWarnings("GuardedBy")
public class BatteryStatsImplTest {
private static final long[] CPU_FREQS = {1, 2, 3, 4, 5};
private static final int NUM_CPU_FREQS = CPU_FREQS.length;
@@ -62,29 +62,26 @@
@Mock
private KernelSingleUidTimeReader mKernelSingleUidTimeReader;
+ private final MockClock mMockClock = new MockClock();
private MockBatteryStatsImpl mBatteryStatsImpl;
- private MockClock mMockClock = new MockClock();
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ when(mKernelUidCpuFreqTimeReader.isFastCpuTimesReader()).thenReturn(true);
when(mKernelUidCpuFreqTimeReader.readFreqs(any())).thenReturn(CPU_FREQS);
when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock)
.setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader)
- .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader)
- .setTrackingCpuByProcStateEnabled(true);
+ .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader);
}
@Test
public void testUpdateProcStateCpuTimes() {
mBatteryStatsImpl.setOnBatteryInternal(true);
- synchronized (mBatteryStatsImpl) {
- mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
- }
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
final int[] testUids = {10032, 10048, 10145, 10139};
final int[] activityManagerProcStates = {
@@ -99,15 +96,24 @@
PROCESS_STATE_TOP,
PROCESS_STATE_CACHED
};
- addPendingUids(testUids, testProcStates);
// Initialize time-in-freq counters
mMockClock.realtime = 1000;
for (int i = 0; i < testUids.length; ++i) {
- mBatteryStatsImpl.noteUidProcessStateLocked(testUids[i], activityManagerProcStates[i]);
mockKernelSingleUidTimeReader(testUids[i], new long[5]);
+ mBatteryStatsImpl.noteUidProcessStateLocked(testUids[i], activityManagerProcStates[i]);
}
- mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
+
+ final long[] timeInFreqs = new long[NUM_CPU_FREQS];
+
+ // Verify there are no cpu times initially.
+ for (int i = 0; i < testUids.length; ++i) {
+ final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUids[i]);
+ for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+ assertFalse(u.getCpuFreqTimes(timeInFreqs, procState));
+ assertFalse(u.getScreenOffCpuFreqTimes(timeInFreqs, procState));
+ }
+ }
// Obtain initial CPU time-in-freq counts
final long[][] cpuTimes = {
@@ -117,24 +123,14 @@
{4859048, 348903, 4578967, 5973894, 298549}
};
- final long[] timeInFreqs = new long[NUM_CPU_FREQS];
+ mMockClock.realtime += 1000;
for (int i = 0; i < testUids.length; ++i) {
mockKernelSingleUidTimeReader(testUids[i], cpuTimes[i]);
-
- // Verify there are no cpu times initially.
- final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUids[i]);
- for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
- assertFalse(u.getCpuFreqTimes(timeInFreqs, procState));
- assertFalse(u.getScreenOffCpuFreqTimes(timeInFreqs, procState));
- }
+ mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+ mMockClock.realtime);
}
- addPendingUids(testUids, testProcStates);
- mMockClock.realtime += 1000;
- mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
-
- verifyNoPendingUids();
for (int i = 0; i < testUids.length; ++i) {
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
@@ -155,19 +151,19 @@
{945894, 9089432, 19478, 3834, 7845},
{843895, 43948, 949582, 99, 384}
};
+
+ mMockClock.realtime += 1000;
+
for (int i = 0; i < testUids.length; ++i) {
long[] newCpuTimes = new long[cpuTimes[i].length];
for (int j = 0; j < cpuTimes[i].length; j++) {
newCpuTimes[j] = cpuTimes[i][j] + delta1[i][j];
}
mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
+ mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+ mMockClock.realtime);
}
- addPendingUids(testUids, testProcStates);
- mMockClock.realtime += 1000;
- mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
-
- verifyNoPendingUids();
for (int i = 0; i < testUids.length; ++i) {
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
@@ -186,10 +182,8 @@
}
// Validate the on-battery-screen-off counter
- synchronized (mBatteryStatsImpl) {
- mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0,
- mMockClock.realtime * 1000);
- }
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0,
+ mMockClock.realtime * 1000);
final long[][] delta2 = {
{95932, 2943, 49834, 89034, 139},
@@ -197,19 +191,19 @@
{678, 7498, 9843, 889, 4894},
{488, 998, 8498, 394, 574}
};
+
+ mMockClock.realtime += 1000;
+
for (int i = 0; i < testUids.length; ++i) {
long[] newCpuTimes = new long[cpuTimes[i].length];
for (int j = 0; j < cpuTimes[i].length; j++) {
newCpuTimes[j] = cpuTimes[i][j] + delta1[i][j] + delta2[i][j];
}
mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
+ mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+ mMockClock.realtime);
}
- addPendingUids(testUids, testProcStates);
- mMockClock.realtime += 1000;
- mBatteryStatsImpl.updateProcStateCpuTimes(true, true);
-
- verifyNoPendingUids();
for (int i = 0; i < testUids.length; ++i) {
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
@@ -239,24 +233,25 @@
{3049509483598l, 4597834, 377654, 94589035, 7854},
{9493, 784, 99895, 8974893, 9879843}
};
- for (int i = 0; i < testUids.length; ++i) {
- long[] newCpuTimes = new long[cpuTimes[i].length];
- for (int j = 0; j < cpuTimes[i].length; j++) {
- newCpuTimes[j] = cpuTimes[i][j] + delta1[i][j] + delta2[i][j] + delta3[i][j];
- }
- mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
- }
- addPendingUids(testUids, testProcStates);
+
+ mMockClock.realtime += 1000;
+
final int parentUid = testUids[1];
final int childUid = 99099;
addIsolatedUid(parentUid, childUid);
final long[] isolatedUidCpuTimes = {495784, 398473, 4895, 4905, 30984093};
mockKernelSingleUidTimeReader(childUid, isolatedUidCpuTimes, isolatedUidCpuTimes);
- mMockClock.realtime += 1000;
- mBatteryStatsImpl.updateProcStateCpuTimes(true, true);
+ for (int i = 0; i < testUids.length; ++i) {
+ long[] newCpuTimes = new long[cpuTimes[i].length];
+ for (int j = 0; j < cpuTimes[i].length; j++) {
+ newCpuTimes[j] = cpuTimes[i][j] + delta1[i][j] + delta2[i][j] + delta3[i][j];
+ }
+ mockKernelSingleUidTimeReader(testUids[i], newCpuTimes);
+ mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+ mMockClock.realtime);
+ }
- verifyNoPendingUids();
for (int i = 0; i < testUids.length; ++i) {
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
@@ -284,11 +279,9 @@
}
@Test
- public void testCopyFromAllUidsCpuTimes() {
+ public void testUpdateCpuTimesForAllUids() {
mBatteryStatsImpl.setOnBatteryInternal(false);
- synchronized (mBatteryStatsImpl) {
- mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
- }
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
mMockClock.realtime = 1000;
@@ -299,14 +292,14 @@
PROCESS_STATE_TOP,
PROCESS_STATE_CACHED
};
- addPendingUids(testUids, testProcStates);
for (int i = 0; i < testUids.length; ++i) {
BatteryStatsImpl.Uid uid = mBatteryStatsImpl.getUidStatsLocked(testUids[i]);
uid.setProcessStateForTest(testProcStates[i], mMockClock.elapsedRealtime());
mockKernelSingleUidTimeReader(testUids[i], new long[NUM_CPU_FREQS]);
+ mBatteryStatsImpl.updateProcStateCpuTimesLocked(testUids[i],
+ mMockClock.elapsedRealtime());
}
- mBatteryStatsImpl.updateProcStateCpuTimes(true, false);
final SparseArray<long[]> allUidCpuTimes = new SparseArray<>();
long[][] allCpuTimes = {
@@ -330,9 +323,8 @@
}
mMockClock.realtime += 1000;
- mBatteryStatsImpl.copyFromAllUidsCpuTimes(true, false);
- verifyNoPendingUids();
+ mBatteryStatsImpl.updateCpuTimesForAllUids();
final long[] timeInFreqs = new long[NUM_CPU_FREQS];
@@ -411,9 +403,7 @@
final int releaseTimeMs = 1005;
final int currentTimeMs = 1011;
- synchronized (mBatteryStatsImpl) {
- mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
- }
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
// Create a Uid Object
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid);
@@ -436,9 +426,7 @@
final int acquireTimeMs = 1000;
final int currentTimeMs = 1011;
- synchronized (mBatteryStatsImpl) {
- mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
- }
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
// Create a Uid Object
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid);
@@ -464,9 +452,7 @@
final int releaseTimeMs_2 = 1009;
final int currentTimeMs = 1011;
- synchronized (mBatteryStatsImpl) {
- mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
- }
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
// Create a Uid Object
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid);
@@ -496,9 +482,7 @@
final int releaseTimeMs_2 = 1009;
final int currentTimeMs = 1011;
- synchronized (mBatteryStatsImpl) {
- mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
- }
+ mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
// Create a Uid Object
final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUid);
@@ -523,17 +507,4 @@
final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(parentUid);
u.addIsolatedUid(childUid);
}
-
- private void addPendingUids(int[] uids, int[] procStates) {
- final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
- for (int i = 0; i < uids.length; ++i) {
- pendingUids.put(uids[i], procStates[i]);
- }
- }
-
- private void verifyNoPendingUids() {
- final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
- assertEquals("There shouldn't be any pending uids left: " + pendingUids,
- 0, pendingUids.size());
- }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
index 441e85d..9172d34 100644
--- a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
@@ -26,9 +26,6 @@
import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING;
import static android.os.BatteryStats.Uid.UID_PROCESS_TYPES;
-import static com.android.internal.os.BatteryStatsImpl.Constants.KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS;
-import static com.android.internal.os.BatteryStatsImpl.Constants.KEY_TRACK_CPU_TIMES_BY_PROC_STATE;
-
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
@@ -51,7 +48,6 @@
import android.os.SystemClock;
import android.provider.Settings;
import android.support.test.uiautomator.UiDevice;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.DebugUtils;
import android.util.KeyValueListParser;
@@ -125,11 +121,6 @@
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_PKG, 0);
executeCmd("cmd deviceidle whitelist +" + TEST_PKG);
-
- final ArrayMap<String, String> desiredConstants = new ArrayMap<>();
- desiredConstants.put(KEY_TRACK_CPU_TIMES_BY_PROC_STATE, Boolean.toString(true));
- desiredConstants.put(KEY_PROC_STATE_CPU_TIMES_READ_DELAY_MS, Integer.toString(0));
- updateBatteryStatsConstants(desiredConstants);
checkCpuTimesAvailability();
}
@@ -517,125 +508,6 @@
batteryOffScreenOn();
}
- @Test
- public void testCpuFreqTimes_trackingDisabled() throws Exception {
- if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
- Log.w(TAG, "Skipping " + testName.getMethodName()
- + "; freqTimesAvailable=" + sCpuFreqTimesAvailable
- + ", procStateTimesAvailable=" + sPerProcStateTimesAvailable);
- return;
- }
-
- final String bstatsConstants = Settings.Global.getString(sContext.getContentResolver(),
- Settings.Global.BATTERY_STATS_CONSTANTS);
- try {
- batteryOnScreenOn();
- forceStop();
- resetBatteryStats();
- final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
- assertNull("Initial snapshot should be null, initial="
- + Arrays.toString(initialSnapshot), initialSnapshot);
- assertNull("Initial top state snapshot should be null",
- getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP));
-
- doSomeWork(PROCESS_STATE_TOP);
- forceStop();
-
- final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
- final String msgCpuTimes = getAllCpuTimesMsg();
- assertCpuTimesValid(cpuTimesMs);
- long actualCpuTimeMs = 0;
- for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
- actualCpuTimeMs += cpuTimesMs[i];
- }
- assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
- WORK_DURATION_MS, actualCpuTimeMs);
-
- updateTrackPerProcStateCpuTimesSetting(bstatsConstants, false);
-
- doSomeWork(PROCESS_STATE_TOP);
- forceStop();
-
- final long[] cpuTimesMs2 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
- assertCpuTimesValid(cpuTimesMs2);
- assertCpuTimesEqual(cpuTimesMs2, cpuTimesMs, 20,
- "Unexpected cpu times with tracking off");
-
- updateTrackPerProcStateCpuTimesSetting(bstatsConstants, true);
-
- final long[] cpuTimesMs3 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
- assertCpuTimesValid(cpuTimesMs3);
- assertCpuTimesEqual(cpuTimesMs3, cpuTimesMs, 500,
- "Unexpected cpu times after turning on tracking");
-
- doSomeWork(PROCESS_STATE_TOP);
- forceStop();
-
- final long[] cpuTimesMs4 = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
- assertCpuTimesValid(cpuTimesMs4);
- actualCpuTimeMs = 0;
- for (int i = 0; i < cpuTimesMs4.length / 2; ++i) {
- actualCpuTimeMs += cpuTimesMs4[i];
- }
- assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
- 2 * WORK_DURATION_MS, actualCpuTimeMs);
-
- batteryOffScreenOn();
- } finally {
- Settings.Global.putString(sContext.getContentResolver(),
- Settings.Global.BATTERY_STATS_CONSTANTS, bstatsConstants);
- }
- }
-
- private void assertCpuTimesEqual(long[] actual, long[] expected, long delta, String errMsg) {
- for (int i = actual.length - 1; i >= 0; --i) {
- if (actual[i] > expected[i] + delta || actual[i] < expected[i]) {
- fail(errMsg + ", actual=" + Arrays.toString(actual)
- + ", expected=" + Arrays.toString(expected) + ", delta=" + delta);
- }
- }
- }
-
- private void updateTrackPerProcStateCpuTimesSetting(String originalConstants, boolean enabled)
- throws Exception {
- final String newConstants;
- final String setting = KEY_TRACK_CPU_TIMES_BY_PROC_STATE + "=" + enabled;
- if (originalConstants == null || "null".equals(originalConstants)) {
- newConstants = setting;
- } else if (originalConstants.contains(KEY_TRACK_CPU_TIMES_BY_PROC_STATE)) {
- newConstants = originalConstants.replaceAll(
- KEY_TRACK_CPU_TIMES_BY_PROC_STATE + "=(true|false)", setting);
- } else {
- newConstants = originalConstants + "," + setting;
- }
- Settings.Global.putString(sContext.getContentResolver(),
- Settings.Global.BATTERY_STATS_CONSTANTS, newConstants);
- assertTrackPerProcStateCpuTimesSetting(enabled);
- }
-
- private void assertTrackPerProcStateCpuTimesSetting(boolean enabled) throws Exception {
- final String expectedValue = Boolean.toString(enabled);
- assertDelayedCondition("Unexpected value for " + KEY_TRACK_CPU_TIMES_BY_PROC_STATE, () -> {
- final String actualValue = getSettingValueFromDump(KEY_TRACK_CPU_TIMES_BY_PROC_STATE);
- return expectedValue.equals(actualValue)
- ? null : "expected=" + expectedValue + ", actual=" + actualValue;
- }, SETTING_UPDATE_TIMEOUT_MS, SETTING_UPDATE_CHECK_INTERVAL_MS);
- }
-
- private String getSettingValueFromDump(String key) throws Exception {
- final String settingsDump = executeCmdSilent("dumpsys batterystats --settings");
- final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n');
- splitter.setString(settingsDump);
- String next;
- while (splitter.hasNext()) {
- next = splitter.next().trim();
- if (next.startsWith(key)) {
- return next.split("=")[1];
- }
- }
- return null;
- }
-
private void assertCpuTimesValid(long[] cpuTimes) {
assertNotNull(cpuTimes);
for (int i = 0; i < cpuTimes.length; ++i) {
diff --git a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
index 1ae30db..d5b0f0a 100644
--- a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
@@ -98,6 +98,8 @@
new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
supportedPowerBuckets[MeasuredEnergyStats.POWER_BUCKET_CPU] = true;
+ when(mMockCpuUidFreqTimeReader.isFastCpuTimesReader()).thenReturn(true);
+
mStatsRule.getBatteryStats()
.setUserInfoProvider(mMockUserInfoProvider)
.setKernelCpuSpeedReaders(mMockKernelCpuSpeedReaders)
@@ -277,8 +279,6 @@
@Test
public void testTimerBasedModel_byProcessState() {
- mStatsRule.getBatteryStats().setTrackingCpuByProcStateEnabled(true);
-
when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true);
when(mMockCpuUidFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
@@ -311,7 +311,7 @@
}).when(mMockKerneCpuUidActiveTimeReader).readAbsolute(any());
mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, null);
- mStatsRule.getBatteryStats().copyFromAllUidsCpuTimes(true, true);
+ mStatsRule.getBatteryStats().updateCpuTimesForAllUids();
mockSingleUidTimeReader(APP_UID1, new long[]{1000, 2000, 3000, 4000});
mockSingleUidTimeReader(APP_UID2, new long[]{1111, 2222, 3333, 4444});
@@ -326,7 +326,7 @@
}).when(mMockKerneCpuUidActiveTimeReader).readAbsolute(any());
mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, null);
- mStatsRule.getBatteryStats().copyFromAllUidsCpuTimes(true, true);
+ mStatsRule.getBatteryStats().updateCpuTimesForAllUids();
mockSingleUidTimeReader(APP_UID1, new long[] {5000, 6000, 7000, 8000});
mockSingleUidTimeReader(APP_UID2, new long[]{5555, 6666, 7777, 8888});
@@ -346,7 +346,7 @@
}).when(mMockKerneCpuUidActiveTimeReader).readAbsolute(any());
mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, null);
- mStatsRule.getBatteryStats().copyFromAllUidsCpuTimes(true, true);
+ mStatsRule.getBatteryStats().updateCpuTimesForAllUids();
CpuPowerCalculator calculator =
new CpuPowerCalculator(mStatsRule.getPowerProfile());
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index d16689c..e4c83f1 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -16,10 +16,13 @@
package com.android.internal.os;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
import android.net.NetworkStats;
import android.os.Handler;
import android.os.Looper;
-import android.util.SparseIntArray;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
@@ -59,6 +62,9 @@
// A no-op handler.
mHandler = new Handler(Looper.getMainLooper()) {
};
+
+ mCpuUidFreqTimeReader = mock(KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader.class);
+ when(mCpuUidFreqTimeReader.readFreqs(any())).thenReturn(new long[]{100, 200});
}
public void initMeasuredEnergyStats(String[] customBucketNames) {
@@ -178,15 +184,6 @@
return this;
}
- public MockBatteryStatsImpl setTrackingCpuByProcStateEnabled(boolean enabled) {
- mConstants.TRACK_CPU_TIMES_BY_PROC_STATE = enabled;
- return this;
- }
-
- public SparseIntArray getPendingUids() {
- return mPendingUids;
- }
-
public int getAndClearExternalStatsSyncFlags() {
final int flags = mExternalStatsSync.flags;
mExternalStatsSync.flags = 0;
@@ -217,18 +214,6 @@
}
@Override
- public Future<?> scheduleReadProcStateCpuTimes(boolean onBattery,
- boolean onBatteryScreenOff, long delayMillis) {
- return null;
- }
-
- @Override
- public Future<?> scheduleCopyFromAllUidsCpuTimes(
- boolean onBattery, boolean onBatteryScreenOff) {
- return null;
- }
-
- @Override
public Future<?> scheduleSyncDueToScreenStateChange(int flag, boolean onBattery,
boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
flags |= flag;
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 088d5b3..60cb9d3 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -495,6 +495,8 @@
<!-- Permission required for CTS test - SystemMediaRouter2Test -->
<permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
<permission name="android.permission.MODIFY_AUDIO_ROUTING"/>
+ <!-- Permission required for CTS test - CallAudioInterceptionTest -->
+ <permission name="android.permission.CALL_AUDIO_INTERCEPTION"/>
<!-- Permission required for CTS test - CtsPermission5TestCases -->
<permission name="android.permission.RENOUNCE_PERMISSIONS" />
<permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
@@ -515,6 +517,8 @@
<permission name="android.permission.MANAGE_VOICE_KEYPHRASES" />
<!-- Permission required for ATS test - CarDevicePolicyManagerTest -->
<permission name="android.permission.LOCK_DEVICE" />
+ <!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
+ <permission name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
index 8844370..8d3eadb 100644
--- a/graphics/java/android/graphics/BLASTBufferQueue.java
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -27,8 +27,9 @@
// Note: This field is accessed by native code.
public long mNativeObject; // BLASTBufferQueue*
- private static native long nativeCreate(String name, long surfaceControl, long width,
+ private static native long nativeCreateAndUpdate(String name, long surfaceControl, long width,
long height, int format);
+ private static native long nativeCreate(String name);
private static native void nativeDestroy(long ptr);
private static native Surface nativeGetSurface(long ptr, boolean includeSurfaceControlHandle);
private static native void nativeSetSyncTransaction(long ptr, long transactionPtr,
@@ -43,7 +44,11 @@
/** Create a new connection with the surface flinger. */
public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height,
@PixelFormat.Format int format) {
- mNativeObject = nativeCreate(name, sc.mNativeObject, width, height, format);
+ mNativeObject = nativeCreateAndUpdate(name, sc.mNativeObject, width, height, format);
+ }
+
+ public BLASTBufferQueue(String name) {
+ mNativeObject = nativeCreate(name);
}
public void destroy() {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index fe6c7ba..af19bd0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -91,11 +91,13 @@
*/
public void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent,
@Nullable Bundle options, @NonNull SplitRule sideRule,
- @NonNull Consumer<Exception> failureCallback) {
+ @Nullable Consumer<Exception> failureCallback) {
try {
mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule);
} catch (Exception e) {
- failureCallback.accept(e);
+ if (failureCallback != null) {
+ failureCallback.accept(e);
+ }
}
}
@@ -858,4 +860,12 @@
launchingContainer.getTaskFragmentToken());
}
}
+
+ /**
+ * Checks if an activity is embedded and its presentation is customized by a
+ * {@link android.window.TaskFragmentOrganizer} to only occupy a portion of Task bounds.
+ */
+ public boolean isActivityEmbedded(@NonNull Activity activity) {
+ return mPresenter.isActivityEmbedded(activity.getActivityToken());
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index 8c8ef92..46bdf6d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -16,6 +16,7 @@
package androidx.window.extensions.embedding;
+import static android.os.Process.THREAD_PRIORITY_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
@@ -29,7 +30,7 @@
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.os.Handler;
-import android.os.Looper;
+import android.os.HandlerThread;
import android.os.RemoteException;
import android.util.Log;
import android.view.IRemoteAnimationFinishedCallback;
@@ -50,10 +51,14 @@
class TaskFragmentAnimationRunner extends IRemoteAnimationRunner.Stub {
private static final String TAG = "TaskFragAnimationRunner";
- private final Handler mHandler = new Handler(Looper.myLooper());
+ private final Handler mHandler;
private final TaskFragmentAnimationSpec mAnimationSpec;
TaskFragmentAnimationRunner() {
+ HandlerThread animationThread = new HandlerThread(
+ "androidx.window.extensions.embedding", THREAD_PRIORITY_DISPLAY);
+ animationThread.start();
+ mHandler = animationThread.getThreadHandler();
mAnimationSpec = new TaskFragmentAnimationSpec(mHandler);
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index a1a53bc..4d2d055 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -103,7 +103,7 @@
ActivityThread activityThread = ActivityThread.currentActivityThread();
for (IBinder token : mInfo.getActivities()) {
Activity activity = activityThread.getActivity(token);
- if (activity != null && !allActivities.contains(activity)) {
+ if (activity != null && !activity.isFinishing() && !allActivities.contains(activity)) {
allActivities.add(activity);
}
}
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index d6678bf..f54ab08 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index 0fbdf90..8467cc5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -120,6 +120,11 @@
mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
+ if (mTargetViewContainer != null) {
+ // init can be called multiple times, remove the old one from view hierarchy first.
+ mWindowManager.removeViewImmediate(mTargetViewContainer);
+ }
+
mTargetView = new DismissCircleView(mContext);
mTargetViewContainer = new FrameLayout(mContext);
mTargetViewContainer.setBackgroundDrawable(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 82e8273..da4bbe8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -189,7 +189,7 @@
mEnterSplitButton = findViewById(R.id.enter_split);
mEnterSplitButton.setAlpha(0);
mEnterSplitButton.setOnClickListener(v -> {
- if (mMenuContainer.getAlpha() != 0) {
+ if (mEnterSplitButton.getAlpha() != 0) {
enterSplit();
}
});
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e30e6c5..d1feee4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -965,11 +965,17 @@
private void updateUnfoldBounds() {
if (mMainUnfoldController != null && mSideUnfoldController != null) {
- mMainUnfoldController.onLayoutChanged(getMainStageBounds());
- mSideUnfoldController.onLayoutChanged(getSideStageBounds());
+ mMainUnfoldController.onLayoutChanged(getMainStageBounds(), getMainStagePosition(),
+ isLandscape());
+ mSideUnfoldController.onLayoutChanged(getSideStageBounds(), getSideStagePosition(),
+ isLandscape());
}
}
+ private boolean isLandscape() {
+ return mSplitLayout.isLandscape();
+ }
+
/**
* Populates `wct` with operations that match the split windows to the current layout.
* To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java
index e904f6a..0683a25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java
@@ -18,11 +18,15 @@
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+
import android.animation.RectEvaluator;
import android.animation.TypeEvaluator;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.Context;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.util.SparseArray;
import android.view.InsetsSource;
@@ -33,6 +37,7 @@
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
@@ -161,12 +166,13 @@
* Called when split screen stage bounds changed
* @param bounds new bounds for this stage
*/
- public void onLayoutChanged(Rect bounds) {
+ public void onLayoutChanged(Rect bounds, @SplitPosition int splitPosition,
+ boolean isLandscape) {
mStageBounds.set(bounds);
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- context.update();
+ context.update(splitPosition, isLandscape);
}
}
@@ -195,20 +201,27 @@
final Rect mEndCropRect = new Rect();
final Rect mCurrentCropRect = new Rect();
+ private @SplitPosition int mSplitPosition = SPLIT_POSITION_UNDEFINED;
+ private boolean mIsLandscape = false;
+
private AnimationContext(SurfaceControl leash) {
this.mLeash = leash;
update();
}
+ private void update(@SplitPosition int splitPosition, boolean isLandscape) {
+ this.mSplitPosition = splitPosition;
+ this.mIsLandscape = isLandscape;
+ update();
+ }
+
private void update() {
mStartCropRect.set(mStageBounds);
- if (mTaskbarInsetsSource != null) {
+ boolean taskbarExpanded = isTaskbarExpanded();
+ if (taskbarExpanded) {
// Only insets the cropping window with taskbar when taskbar is expanded
- if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
- mStartCropRect.inset(mTaskbarInsetsSource
- .calculateVisibleInsets(mStartCropRect));
- }
+ mStartCropRect.inset(mTaskbarInsetsSource.calculateVisibleInsets(mStartCropRect));
}
// Offset to surface coordinates as layout bounds are in screen coordinates
@@ -218,7 +231,46 @@
int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height());
int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION);
- mStartCropRect.inset(margin, margin, margin, margin);
+
+ // Sides adjacent to split bar or task bar are not be animated.
+ Insets margins;
+ if (mIsLandscape) { // Left and right splits.
+ margins = getLandscapeMargins(margin, taskbarExpanded);
+ } else { // Top and bottom splits.
+ margins = getPortraitMargins(margin, taskbarExpanded);
+ }
+ mStartCropRect.inset(margins);
+ }
+
+ private Insets getLandscapeMargins(int margin, boolean taskbarExpanded) {
+ int left = margin;
+ int right = margin;
+ int bottom = taskbarExpanded ? 0 : margin; // Taskbar margin.
+ if (mSplitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
+ right = 0; // Divider margin.
+ } else {
+ left = 0; // Divider margin.
+ }
+ return Insets.of(left, /* top= */ margin, right, bottom);
+ }
+
+ private Insets getPortraitMargins(int margin, boolean taskbarExpanded) {
+ int bottom = margin;
+ int top = margin;
+ if (mSplitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
+ bottom = 0; // Divider margin.
+ } else { // Bottom split.
+ top = 0; // Divider margin.
+ if (taskbarExpanded) {
+ bottom = 0; // Taskbar margin.
+ }
+ }
+ return Insets.of(/* left= */ margin, top, /* right= */ margin, bottom);
+ }
+
+ private boolean isTaskbarExpanded() {
+ return mTaskbarInsetsSource != null
+ && mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight;
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
index 1a3e42c..20a9475 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.apppairs
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
@@ -68,7 +67,7 @@
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
- @Postsubmit
+ @Presubmit
@Test
override fun statusBarLayerRotatesScales() {
// This test doesn't work in shell transitions because of b/206753786
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 3210976..2a53bef 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.view.Surface
import androidx.test.filters.FlakyTest
@@ -96,7 +95,7 @@
super.statusBarLayerRotatesScales()
}
- @Postsubmit
+ @Presubmit
@Test
fun pipWindowInsideDisplay() {
testSpec.assertWmStart {
@@ -112,7 +111,7 @@
}
}
- @Postsubmit
+ @Presubmit
@Test
fun pipLayerInsideDisplay() {
testSpec.assertLayersStart {
@@ -126,7 +125,7 @@
this.isAppWindowVisible(pipApp.component)
}
- @Postsubmit
+ @Presubmit
@Test
fun pipAppLayerCoversFullScreen() {
testSpec.assertLayersEnd {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 85f6789..fb6300c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -114,6 +114,7 @@
when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
+ when(mSplitLayout.isLandscape()).thenReturn(false);
}
@Test
@@ -168,8 +169,9 @@
mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
- verify(mMainUnfoldController).onLayoutChanged(mBounds2);
- verify(mSideUnfoldController).onLayoutChanged(mBounds1);
+ verify(mMainUnfoldController).onLayoutChanged(mBounds2, SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ false);
+ verify(mSideUnfoldController).onLayoutChanged(mBounds1, SPLIT_POSITION_TOP_OR_LEFT, false);
}
@Test
@@ -180,8 +182,10 @@
mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
- verify(mMainUnfoldController).onLayoutChanged(mBounds1);
- verify(mSideUnfoldController).onLayoutChanged(mBounds2);
+ verify(mMainUnfoldController).onLayoutChanged(mBounds1, SPLIT_POSITION_TOP_OR_LEFT,
+ false);
+ verify(mSideUnfoldController).onLayoutChanged(mBounds2, SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ false);
}
@Test
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 54252b5..9993ce9 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -481,13 +481,20 @@
*/
public static final int FLAG_NEVER_SPATIALIZE = 0x1 << 15;
+ /**
+ * @hide
+ * Flag indicating the audio is part of a call redirection.
+ * Valid for playback and capture.
+ */
+ public static final int FLAG_CALL_REDIRECTION = 0x1 << 16;
+
// Note that even though FLAG_MUTE_HAPTIC is stored as a flag bit, it is not here since
// it is known as a boolean value outside of AudioAttributes.
private static final int FLAG_ALL = FLAG_AUDIBILITY_ENFORCED | FLAG_SECURE | FLAG_SCO
| FLAG_BEACON | FLAG_HW_AV_SYNC | FLAG_HW_HOTWORD | FLAG_BYPASS_INTERRUPTION_POLICY
| FLAG_BYPASS_MUTE | FLAG_LOW_LATENCY | FLAG_DEEP_BUFFER | FLAG_NO_MEDIA_PROJECTION
| FLAG_NO_SYSTEM_CAPTURE | FLAG_CAPTURE_PRIVATE | FLAG_CONTENT_SPATIALIZED
- | FLAG_NEVER_SPATIALIZE;
+ | FLAG_NEVER_SPATIALIZE | FLAG_CALL_REDIRECTION;
private final static int FLAG_ALL_PUBLIC = FLAG_AUDIBILITY_ENFORCED |
FLAG_HW_AV_SYNC | FLAG_LOW_LATENCY;
/* mask of flags that can be set by SDK and System APIs through the Builder */
@@ -707,6 +714,14 @@
return ALLOW_CAPTURE_BY_ALL;
}
+ /**
+ * @hide
+ * Indicates if the audio is used for call redirection
+ * @return true if used for call redirection, false otherwise.
+ */
+ public boolean isForCallRedirection() {
+ return (mFlags & FLAG_CALL_REDIRECTION) == FLAG_CALL_REDIRECTION;
+ }
/**
* Builder class for {@link AudioAttributes} objects.
@@ -763,11 +778,15 @@
public Builder(AudioAttributes aa) {
mUsage = aa.mUsage;
mContentType = aa.mContentType;
+ mSource = aa.mSource;
mFlags = aa.getAllFlags();
mTags = (HashSet<String>) aa.mTags.clone();
mMuteHapticChannels = aa.areHapticChannelsMuted();
mIsContentSpatialized = aa.isContentSpatialized();
mSpatializationBehavior = aa.getSpatializationBehavior();
+ if ((mFlags & FLAG_CAPTURE_PRIVATE) != 0) {
+ mPrivacySensitive = PRIVACY_SENSITIVE_ENABLED;
+ }
}
/**
@@ -1071,6 +1090,17 @@
}
/**
+ * @hide
+ * Replace all custom tags
+ * @param tags
+ * @return the same Builder instance.
+ */
+ public Builder replaceTags(HashSet<String> tags) {
+ mTags = (HashSet<String>) tags.clone();
+ return this;
+ }
+
+ /**
* Sets attributes as inferred from the legacy stream types.
* Warning: do not use this method in combination with setting any other attributes such as
* usage, content type, flags or haptic control, as this method will overwrite (the more
@@ -1245,6 +1275,16 @@
privacySensitive ? PRIVACY_SENSITIVE_ENABLED : PRIVACY_SENSITIVE_DISABLED;
return this;
}
+
+ /**
+ * @hide
+ * Designates the audio to be used for call redirection
+ * @return the same Builder instance.
+ */
+ public Builder setForCallRedirection() {
+ mFlags |= FLAG_CALL_REDIRECTION;
+ return this;
+ }
};
@Override
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index faae9a5..d721291 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -88,7 +88,8 @@
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
-
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
/**
* AudioManager provides access to volume and ringer mode control.
@@ -5775,6 +5776,23 @@
}
/**
+ * Indicate wired accessory connection state change.
+ * @param device {@link AudioDeviceAttributes} of the device to "fake-connect"
+ * @param connected true for connected, false for disconnected
+ * {@hide}
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
+ boolean connected) {
+ try {
+ getService().setTestDeviceConnectionState(device, connected);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Indicate Bluetooth profile connection state change.
* Configuration changes for A2DP are indicated by having the same <code>newDevice</code> and
* <code>previousDevice</code>
@@ -7570,7 +7588,7 @@
return getDeviceInfoFromTypeAndAddress(deviceType, null);
}
- /**
+ /**
* @hide
* Returns an {@link AudioDeviceInfo} corresponding to a connected device of the type and
* address provided.
@@ -7692,6 +7710,506 @@
}
}
+
+ /**
+ * @hide
+ * Indicates if the platform allows accessing the uplink and downlink audio of an ongoing
+ * PSTN call.
+ * When true, {@link getCallUplinkInjectionAudioTrack(AudioFormat)} can be used to obtain
+ * an AudioTrack for call uplink audio injection and
+ * {@link getCallDownlinkExtractionAudioRecord(AudioFormat)} can be used to obtain
+ * an AudioRecord for call downlink audio extraction.
+ * @return true if PSTN call audio is accessible, false otherwise.
+ */
+ @TestApi
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION)
+ public boolean isPstnCallAudioInterceptable() {
+ final IAudioService service = getService();
+ try {
+ return service.isPstnCallAudioInterceptable();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ @IntDef(flag = false, prefix = "CALL_REDIRECT_", value = {
+ CALL_REDIRECT_NONE,
+ CALL_REDIRECT_PSTN,
+ CALL_REDIRECT_VOIP }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CallRedirectionMode {}
+
+ /**
+ * Not used for call redirection
+ * @hide
+ */
+ public static final int CALL_REDIRECT_NONE = 0;
+ /**
+ * Used to redirect PSTN call
+ * @hide
+ */
+ public static final int CALL_REDIRECT_PSTN = 1;
+ /**
+ * Used to redirect VoIP call
+ * @hide
+ */
+ public static final int CALL_REDIRECT_VOIP = 2;
+
+
+ private @CallRedirectionMode int getCallRedirectMode() {
+ int mode = getMode();
+ if (mode == MODE_IN_CALL || mode == MODE_CALL_SCREENING
+ || mode == MODE_CALL_REDIRECT) {
+ return CALL_REDIRECT_PSTN;
+ } else if (mode == MODE_IN_COMMUNICATION || mode == MODE_COMMUNICATION_REDIRECT) {
+ return CALL_REDIRECT_VOIP;
+ }
+ return CALL_REDIRECT_NONE;
+ }
+
+ private void checkCallRedirectionFormat(AudioFormat format, boolean isOutput) {
+ if (format.getEncoding() != AudioFormat.ENCODING_PCM_16BIT
+ && format.getEncoding() != AudioFormat.ENCODING_PCM_FLOAT) {
+ throw new UnsupportedOperationException(" Unsupported encoding ");
+ }
+ if (format.getSampleRate() < 8000
+ || format.getSampleRate() > 48000) {
+ throw new UnsupportedOperationException(" Unsupported sample rate ");
+ }
+ if (isOutput && format.getChannelMask() != AudioFormat.CHANNEL_OUT_MONO
+ && format.getChannelMask() != AudioFormat.CHANNEL_OUT_STEREO) {
+ throw new UnsupportedOperationException(" Unsupported output channel mask ");
+ }
+ if (!isOutput && format.getChannelMask() != AudioFormat.CHANNEL_IN_MONO
+ && format.getChannelMask() != AudioFormat.CHANNEL_IN_STEREO) {
+ throw new UnsupportedOperationException(" Unsupported input channel mask ");
+ }
+ }
+
+ class CallIRedirectionClientInfo {
+ public WeakReference trackOrRecord;
+ public int redirectMode;
+ }
+
+ private Object mCallRedirectionLock = new Object();
+ @GuardedBy("mCallRedirectionLock")
+ private CallInjectionModeChangedListener mCallRedirectionModeListener;
+ @GuardedBy("mCallRedirectionLock")
+ private ArrayList<CallIRedirectionClientInfo> mCallIRedirectionClients;
+
+ /**
+ * @hide
+ * Returns an AudioTrack that can be used to inject audio to an active call uplink.
+ * This can be used for functions like call screening or call audio redirection and is reserved
+ * to system apps with privileged permission.
+ * @param format the desired audio format for audio playback.
+ * p>Formats accepted are:
+ * <ul>
+ * <li><em>Sampling rate</em> - 8kHz to 48kHz. </li>
+ * <li><em>Channel mask</em> - Mono or Stereo </li>
+ * <li><em>Sample format</em> - PCM 16 bit or FLOAT 32 bit </li>
+ * </ul>
+ *
+ * @return The AudioTrack used for audio injection
+ * @throws NullPointerException if AudioFormat argument is null.
+ * @throws UnsupportedOperationException if on unsupported AudioFormat is specified.
+ * @throws IllegalArgumentException if an invalid AudioFormat is specified.
+ * @throws SecurityException if permission CALL_AUDIO_INTERCEPTION is missing .
+ * @throws IllegalStateException if current audio mode is not MODE_IN_CALL,
+ * MODE_IN_COMMUNICATION, MODE_CALL_SCREENING, MODE_CALL_REDIRECT
+ * or MODE_COMMUNICATION_REDIRECT.
+ */
+ @TestApi
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION)
+ public @NonNull AudioTrack getCallUplinkInjectionAudioTrack(@NonNull AudioFormat format) {
+ Objects.requireNonNull(format);
+ checkCallRedirectionFormat(format, true /* isOutput */);
+
+ AudioTrack track = null;
+ int redirectMode = getCallRedirectMode();
+ if (redirectMode == CALL_REDIRECT_NONE) {
+ throw new IllegalStateException(
+ " not available in mode " + AudioSystem.modeToString(getMode()));
+ } else if (redirectMode == CALL_REDIRECT_PSTN && !isPstnCallAudioInterceptable()) {
+ throw new UnsupportedOperationException(" PSTN Call audio not accessible ");
+ }
+
+ track = new AudioTrack.Builder()
+ .setAudioAttributes(new AudioAttributes.Builder()
+ .setSystemUsage(AudioAttributes.USAGE_CALL_ASSISTANT)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+ .build())
+ .setAudioFormat(format)
+ .setCallRedirectionMode(redirectMode)
+ .build();
+
+ if (track != null && track.getState() != AudioTrack.STATE_UNINITIALIZED) {
+ synchronized (mCallRedirectionLock) {
+ if (mCallRedirectionModeListener == null) {
+ mCallRedirectionModeListener = new CallInjectionModeChangedListener();
+ try {
+ addOnModeChangedListener(
+ Executors.newSingleThreadExecutor(), mCallRedirectionModeListener);
+ } catch (Exception e) {
+ Log.e(TAG, "addOnModeChangedListener failed with exception: " + e);
+ mCallRedirectionModeListener = null;
+ throw new UnsupportedOperationException(" Cannot register mode listener ");
+ }
+ mCallIRedirectionClients = new ArrayList<CallIRedirectionClientInfo>();
+ }
+ CallIRedirectionClientInfo info = new CallIRedirectionClientInfo();
+ info.redirectMode = redirectMode;
+ info.trackOrRecord = new WeakReference<AudioTrack>(track);
+ mCallIRedirectionClients.add(info);
+ }
+ } else {
+ throw new UnsupportedOperationException(" Cannot create the AudioTrack");
+ }
+ return track;
+ }
+
+ /**
+ * @hide
+ * Returns an AudioRecord that can be used to extract audio from an active call downlink.
+ * This can be used for functions like call screening or call audio redirection and is reserved
+ * to system apps with privileged permission.
+ * @param format the desired audio format for audio capture.
+ *<p>Formats accepted are:
+ * <ul>
+ * <li><em>Sampling rate</em> - 8kHz to 48kHz. </li>
+ * <li><em>Channel mask</em> - Mono or Stereo </li>
+ * <li><em>Sample format</em> - PCM 16 bit or FLOAT 32 bit </li>
+ * </ul>
+ *
+ * @return The AudioRecord used for audio extraction
+ * @throws UnsupportedOperationException if on unsupported AudioFormat is specified.
+ * @throws IllegalArgumentException if an invalid AudioFormat is specified.
+ * @throws NullPointerException if AudioFormat argument is null.
+ * @throws SecurityException if permission CALL_AUDIO_INTERCEPTION is missing .
+ * @throws IllegalStateException if current audio mode is not MODE_IN_CALL,
+ * MODE_IN_COMMUNICATION, MODE_CALL_SCREENING, MODE_CALL_REDIRECT
+ * or MODE_COMMUNICATION_REDIRECT.
+ */
+ @TestApi
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION)
+ public @NonNull AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull AudioFormat format) {
+ Objects.requireNonNull(format);
+ checkCallRedirectionFormat(format, false /* isOutput */);
+
+ AudioRecord record = null;
+ int redirectMode = getCallRedirectMode();
+ if (redirectMode == CALL_REDIRECT_NONE) {
+ throw new IllegalStateException(
+ " not available in mode " + AudioSystem.modeToString(getMode()));
+ } else if (redirectMode == CALL_REDIRECT_PSTN && !isPstnCallAudioInterceptable()) {
+ throw new UnsupportedOperationException(" PSTN Call audio not accessible ");
+ }
+
+ record = new AudioRecord.Builder()
+ .setAudioAttributes(new AudioAttributes.Builder()
+ .setInternalCapturePreset(MediaRecorder.AudioSource.VOICE_DOWNLINK)
+ .build())
+ .setAudioFormat(format)
+ .setCallRedirectionMode(redirectMode)
+ .build();
+
+ if (record != null && record.getState() != AudioRecord.STATE_UNINITIALIZED) {
+ synchronized (mCallRedirectionLock) {
+ if (mCallRedirectionModeListener == null) {
+ mCallRedirectionModeListener = new CallInjectionModeChangedListener();
+ try {
+ addOnModeChangedListener(
+ Executors.newSingleThreadExecutor(), mCallRedirectionModeListener);
+ } catch (Exception e) {
+ Log.e(TAG, "addOnModeChangedListener failed with exception: " + e);
+ mCallRedirectionModeListener = null;
+ throw new UnsupportedOperationException(" Cannot register mode listener ");
+ }
+ mCallIRedirectionClients = new ArrayList<CallIRedirectionClientInfo>();
+ }
+ CallIRedirectionClientInfo info = new CallIRedirectionClientInfo();
+ info.redirectMode = redirectMode;
+ info.trackOrRecord = new WeakReference<AudioRecord>(record);
+ mCallIRedirectionClients.add(info);
+ }
+ } else {
+ throw new UnsupportedOperationException(" Cannot create the AudioRecord");
+ }
+ return record;
+ }
+
+ class CallInjectionModeChangedListener implements OnModeChangedListener {
+ @Override
+ public void onModeChanged(@AudioMode int mode) {
+ synchronized (mCallRedirectionLock) {
+ final ArrayList<CallIRedirectionClientInfo> clientInfos =
+ (ArrayList<CallIRedirectionClientInfo>) mCallIRedirectionClients.clone();
+ for (CallIRedirectionClientInfo info : clientInfos) {
+ Object trackOrRecord = info.trackOrRecord.get();
+ if (trackOrRecord != null) {
+ if ((info.redirectMode == CALL_REDIRECT_PSTN
+ && mode != MODE_IN_CALL && mode != MODE_CALL_SCREENING
+ && mode != MODE_CALL_REDIRECT)
+ || (info.redirectMode == CALL_REDIRECT_VOIP
+ && mode != MODE_IN_COMMUNICATION
+ && mode != MODE_COMMUNICATION_REDIRECT)) {
+ if (trackOrRecord instanceof AudioTrack) {
+ AudioTrack track = (AudioTrack) trackOrRecord;
+ track.release();
+ } else {
+ AudioRecord record = (AudioRecord) trackOrRecord;
+ record.release();
+ }
+ mCallIRedirectionClients.remove(info);
+ }
+ }
+ }
+ if (mCallIRedirectionClients.isEmpty()) {
+ try {
+ if (mCallRedirectionModeListener != null) {
+ removeOnModeChangedListener(mCallRedirectionModeListener);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "removeOnModeChangedListener failed with exception: " + e);
+ } finally {
+ mCallRedirectionModeListener = null;
+ mCallIRedirectionClients = null;
+ }
+ }
+ }
+ }
+ }
+
+ //---------------------------------------------------------
+ // audio device connection-dependent muting
+ /**
+ * @hide
+ * Mute a set of playback use cases until a given audio device is connected.
+ * Automatically unmute upon connection of the device, or after the given timeout, whichever
+ * happens first.
+ * @param usagesToMute non-empty array of {@link AudioAttributes} usages (for example
+ * {@link AudioAttributes#USAGE_MEDIA}) to mute until the
+ * device connects
+ * @param device the audio device expected to connect within the timeout duration
+ * @param timeout the maximum amount of time to wait for the device connection
+ * @param timeUnit the unit for the timeout
+ * @throws IllegalStateException when trying to issue the command while another is already in
+ * progress and hasn't been cancelled by
+ * {@link #cancelMuteAwaitConnection(AudioDeviceAttributes)}. See
+ * {@link #getMutingExpectedDevice()} to check if a muting command is active.
+ * @see #registerMuteAwaitConnectionCallback(Executor, AudioManager.MuteAwaitConnectionCallback)
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void muteAwaitConnection(@NonNull int[] usagesToMute,
+ @NonNull AudioDeviceAttributes device,
+ long timeout, @NonNull TimeUnit timeUnit) throws IllegalStateException {
+ if (timeout <= 0) {
+ throw new IllegalArgumentException("Timeout must be greater than 0");
+ }
+ Objects.requireNonNull(usagesToMute);
+ if (usagesToMute.length == 0) {
+ throw new IllegalArgumentException("Array of usages to mute cannot be empty");
+ }
+ Objects.requireNonNull(device);
+ Objects.requireNonNull(timeUnit);
+ try {
+ getService().muteAwaitConnection(usagesToMute, device, timeUnit.toMillis(timeout));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Query which audio device, if any, is causing some playback use cases to be muted until it
+ * connects.
+ * @return the audio device used in
+ * {@link #muteAwaitConnection(int[], AudioDeviceAttributes, long, TimeUnit)}, or null
+ * if there is no active muting command (either because the muting command was not issued
+ * or because it timed out)
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public @Nullable AudioDeviceAttributes getMutingExpectedDevice() {
+ try {
+ return getService().getMutingExpectedDevice();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * Cancel a {@link #muteAwaitConnection(int[], AudioDeviceAttributes, long, TimeUnit)}
+ * command.
+ * @param device the device whose connection was expected when the {@code muteAwaitConnection}
+ * command was issued.
+ * @throws IllegalStateException when trying to issue the command for a device whose connection
+ * is not anticipated by a previous call to
+ * {@link #muteAwaitConnection(int[], AudioDeviceAttributes, long, TimeUnit)}
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void cancelMuteAwaitConnection(@NonNull AudioDeviceAttributes device)
+ throws IllegalStateException {
+ Objects.requireNonNull(device);
+ try {
+ getService().cancelMuteAwaitConnection(device);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ * A callback class to receive events about the muting and unmuting of playback use cases
+ * conditional on the upcoming connection of an audio device.
+ * @see #registerMuteAwaitConnectionCallback(Executor, AudioManager.MuteAwaitConnectionCallback)
+ */
+ @SystemApi
+ public abstract static class MuteAwaitConnectionCallback {
+
+ /**
+ * An event where the expected audio device connected
+ * @see MuteAwaitConnectionCallback#onUnmutedEvent(int, AudioDeviceAttributes, int[])
+ */
+ public static final int EVENT_CONNECTION = 1;
+ /**
+ * An event where the expected audio device failed connect before the timeout happened
+ * @see MuteAwaitConnectionCallback#onUnmutedEvent(int, AudioDeviceAttributes, int[])
+ */
+ public static final int EVENT_TIMEOUT = 2;
+ /**
+ * An event where the {@code muteAwaitConnection()} command
+ * was cancelled with {@link #cancelMuteAwaitConnection(AudioDeviceAttributes)}
+ * @see MuteAwaitConnectionCallback#onUnmutedEvent(int, AudioDeviceAttributes, int[])
+ */
+ public static final int EVENT_CANCEL = 3;
+
+ /** @hide */
+ @IntDef(flag = false, prefix = "EVENT_", value = {
+ EVENT_CONNECTION,
+ EVENT_TIMEOUT,
+ EVENT_CANCEL }
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UnmuteEvent {}
+
+ /**
+ * Called when a number of playback use cases are muted in response to a call to
+ * {@link #muteAwaitConnection(int[], AudioDeviceAttributes, long, TimeUnit)}.
+ * @param device the audio device whose connection is expected. Playback use cases are
+ * unmuted when that device connects
+ * @param mutedUsages an array of {@link AudioAttributes} usages that describe the affected
+ * playback use cases.
+ */
+ public void onMutedUntilConnection(
+ @NonNull AudioDeviceAttributes device,
+ @NonNull int[] mutedUsages) {}
+
+ /**
+ * Called when an event occurred that caused playback uses cases to be unmuted
+ * @param unmuteEvent the nature of the event
+ * @param device the device that was expected to connect
+ * @param mutedUsages the array of {@link AudioAttributes} usages that were muted until
+ * the event occurred
+ */
+ public void onUnmutedEvent(
+ @UnmuteEvent int unmuteEvent,
+ @NonNull AudioDeviceAttributes device, @NonNull int[] mutedUsages) {}
+ }
+
+
+ /**
+ * @hide
+ * Register a callback to receive updates on the playback muting conditional on a specific
+ * audio device connection.
+ * @param executor the {@link Executor} handling the callback
+ * @param callback the callback to register
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void registerMuteAwaitConnectionCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull MuteAwaitConnectionCallback callback) {
+ synchronized (mMuteAwaitConnectionListenerLock) {
+ final Pair<ArrayList<ListenerInfo<MuteAwaitConnectionCallback>>,
+ MuteAwaitConnectionDispatcherStub> res =
+ CallbackUtil.addListener("registerMuteAwaitConnectionCallback",
+ executor, callback, mMuteAwaitConnectionListeners,
+ mMuteAwaitConnDispatcherStub,
+ () -> new MuteAwaitConnectionDispatcherStub(),
+ stub -> stub.register(true));
+ mMuteAwaitConnectionListeners = res.first;
+ mMuteAwaitConnDispatcherStub = res.second;
+ }
+ }
+
+ /**
+ * @hide
+ * Unregister a previously registered callback for playback muting conditional on device
+ * connection.
+ * @param callback the callback to unregister
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public void unregisterMuteAwaitConnectionCallback(
+ @NonNull MuteAwaitConnectionCallback callback) {
+ synchronized (mMuteAwaitConnectionListenerLock) {
+ final Pair<ArrayList<ListenerInfo<MuteAwaitConnectionCallback>>,
+ MuteAwaitConnectionDispatcherStub> res =
+ CallbackUtil.removeListener("unregisterMuteAwaitConnectionCallback",
+ callback, mMuteAwaitConnectionListeners, mMuteAwaitConnDispatcherStub,
+ stub -> stub.register(false));
+ mMuteAwaitConnectionListeners = res.first;
+ mMuteAwaitConnDispatcherStub = res.second;
+ }
+ }
+
+ private final Object mMuteAwaitConnectionListenerLock = new Object();
+
+ @GuardedBy("mMuteAwaitConnectionListenerLock")
+ private @Nullable ArrayList<ListenerInfo<MuteAwaitConnectionCallback>>
+ mMuteAwaitConnectionListeners;
+
+ @GuardedBy("mMuteAwaitConnectionListenerLock")
+ private MuteAwaitConnectionDispatcherStub mMuteAwaitConnDispatcherStub;
+
+ private final class MuteAwaitConnectionDispatcherStub
+ extends IMuteAwaitConnectionCallback.Stub {
+ public void register(boolean register) {
+ try {
+ getService().registerMuteAwaitConnectionDispatcher(this, register);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ @SuppressLint("GuardedBy") // lock applied inside callListeners method
+ public void dispatchOnMutedUntilConnection(AudioDeviceAttributes device,
+ int[] mutedUsages) {
+ CallbackUtil.callListeners(mMuteAwaitConnectionListeners,
+ mMuteAwaitConnectionListenerLock,
+ (listener) -> listener.onMutedUntilConnection(device, mutedUsages));
+ }
+
+ @Override
+ @SuppressLint("GuardedBy") // lock applied inside callListeners method
+ public void dispatchOnUnmutedEvent(int event, AudioDeviceAttributes device,
+ int[] mutedUsages) {
+ CallbackUtil.callListeners(mMuteAwaitConnectionListeners,
+ mMuteAwaitConnectionListenerLock,
+ (listener) -> listener.onUnmutedEvent(event, device, mutedUsages));
+ }
+ }
+
//---------------------------------------------------------
// Inner classes
//--------------------
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 7c6ae28..e76bb42 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -32,6 +32,7 @@
import android.content.Context;
import android.media.MediaRecorder.Source;
import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
import android.media.audiopolicy.AudioPolicy;
import android.media.metrics.LogSessionId;
import android.media.projection.MediaProjection;
@@ -58,6 +59,7 @@
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
@@ -404,7 +406,9 @@
// is this AudioRecord using REMOTE_SUBMIX at full volume?
if (attributes.getCapturePreset() == MediaRecorder.AudioSource.REMOTE_SUBMIX) {
- final AudioAttributes.Builder filteredAttr = new AudioAttributes.Builder();
+ final AudioAttributes.Builder ab =
+ new AudioAttributes.Builder(attributes);
+ HashSet<String> filteredTags = new HashSet<String>();
final Iterator<String> tagsIter = attributes.getTags().iterator();
while (tagsIter.hasNext()) {
final String tag = tagsIter.next();
@@ -412,15 +416,15 @@
mIsSubmixFullVolume = true;
Log.v(TAG, "Will record from REMOTE_SUBMIX at full fixed volume");
} else { // SUBMIX_FIXED_VOLUME: is not to be propagated to the native layers
- filteredAttr.addTag(tag);
+ filteredTags.add(tag);
}
}
- filteredAttr.setInternalCapturePreset(attributes.getCapturePreset());
- mAudioAttributes = filteredAttr.build();
- } else {
- mAudioAttributes = attributes;
+ ab.replaceTags(filteredTags);
+ attributes = ab.build();
}
+ mAudioAttributes = attributes;
+
int rate = format.getSampleRate();
if (rate == AudioFormat.SAMPLE_RATE_UNSPECIFIED) {
rate = 0;
@@ -432,7 +436,7 @@
encoding = format.getEncoding();
}
- audioParamCheck(attributes.getCapturePreset(), rate, encoding);
+ audioParamCheck(mAudioAttributes.getCapturePreset(), rate, encoding);
if ((format.getPropertySetMask()
& AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK) != 0) {
@@ -595,6 +599,8 @@
private int mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT;
private int mMaxSharedAudioHistoryMs = 0;
+ private int mCallRedirectionMode = AudioManager.CALL_REDIRECT_NONE;
+
private static final int PRIVACY_SENSITIVE_DEFAULT = -1;
private static final int PRIVACY_SENSITIVE_DISABLED = 0;
private static final int PRIVACY_SENSITIVE_ENABLED = 1;
@@ -791,6 +797,65 @@
/**
* @hide
+ * Sets the {@link AudioRecord} call redirection mode.
+ * Used when creating an AudioRecord to extract audio from call downlink path. The mode
+ * indicates if the call is a PSTN call or a VoIP call in which case a dynamic audio
+ * policy is created to forward all playback with voice communication usage this record.
+ *
+ * @param callRedirectionMode one of
+ * {@link AudioManager#CALL_REDIRECT_NONE},
+ * {@link AudioManager#CALL_REDIRECT_PSTN},
+ * or {@link AAudioManager#CALL_REDIRECT_VOIP}.
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException if {@code callRedirectionMode} is not valid.
+ */
+ public @NonNull Builder setCallRedirectionMode(
+ @AudioManager.CallRedirectionMode int callRedirectionMode) {
+ switch (callRedirectionMode) {
+ case AudioManager.CALL_REDIRECT_NONE:
+ case AudioManager.CALL_REDIRECT_PSTN:
+ case AudioManager.CALL_REDIRECT_VOIP:
+ mCallRedirectionMode = callRedirectionMode;
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid call redirection mode " + callRedirectionMode);
+ }
+ return this;
+ }
+
+ private @NonNull AudioRecord buildCallExtractionRecord() {
+ AudioMixingRule audioMixingRule = new AudioMixingRule.Builder()
+ .addMixRule(AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE,
+ new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+ .setForCallRedirection()
+ .build())
+ .addMixRule(AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE,
+ new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
+ .setForCallRedirection()
+ .build())
+ .setTargetMixRole(AudioMixingRule.MIX_ROLE_PLAYERS)
+ .build();
+ AudioMix audioMix = new AudioMix.Builder(audioMixingRule)
+ .setFormat(mFormat)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK)
+ .build();
+ AudioPolicy audioPolicy = new AudioPolicy.Builder(null).addMix(audioMix).build();
+ if (AudioManager.registerAudioPolicyStatic(audioPolicy) != 0) {
+ throw new UnsupportedOperationException("Error: could not register audio policy");
+ }
+ AudioRecord record = audioPolicy.createAudioRecordSink(audioMix);
+ if (record == null) {
+ throw new UnsupportedOperationException("Cannot create extraction AudioRecord");
+ }
+ record.unregisterAudioPolicyOnRelease(audioPolicy);
+ return record;
+ }
+
+ /**
+ * @hide
* Specifies the maximum duration in the past of the this AudioRecord's capture buffer
* that can be shared with another app by calling
* {@link AudioRecord#shareAudioHistory(String, long)}.
@@ -897,6 +962,14 @@
.build();
}
+ if (mCallRedirectionMode == AudioManager.CALL_REDIRECT_VOIP) {
+ return buildCallExtractionRecord();
+ } else if (mCallRedirectionMode == AudioManager.CALL_REDIRECT_PSTN) {
+ mAttributes = new AudioAttributes.Builder(mAttributes)
+ .setForCallRedirection()
+ .build();
+ }
+
try {
// If the buffer size is not specified,
// use a single frame for the buffer size and let the
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 0450a80..ea692b9 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -26,6 +26,9 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
+import android.media.audiopolicy.AudioPolicy;
import android.media.metrics.LogSessionId;
import android.os.Binder;
import android.os.Build;
@@ -574,6 +577,8 @@
*/
@NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
+ private AudioPolicy mAudioPolicy;
+
//--------------------------------
// Used exclusively by native code
//--------------------
@@ -1032,6 +1037,7 @@
private int mPerformanceMode = PERFORMANCE_MODE_NONE;
private boolean mOffload = false;
private TunerConfiguration mTunerConfiguration;
+ private int mCallRedirectionMode = AudioManager.CALL_REDIRECT_NONE;
/**
* Constructs a new Builder with the default values as described above.
@@ -1227,6 +1233,74 @@
}
/**
+ * Sets the tuner configuration for the {@code AudioTrack}.
+ *
+ * The {@link AudioTrack.TunerConfiguration} consists of parameters obtained from
+ * the Android TV tuner API which indicate the audio content stream id and the
+ * synchronization id for the {@code AudioTrack}.
+ *
+ * @param tunerConfiguration obtained by {@link AudioTrack.TunerConfiguration.Builder}.
+ * @return the same Builder instance.
+ * @hide
+ */
+
+ /**
+ * @hide
+ * Sets the {@link AudioTrack} call redirection mode.
+ * Used when creating an AudioTrack to inject audio to call uplink path. The mode
+ * indicates if the call is a PSTN call or a VoIP call in which case a dynamic audio
+ * policy is created to use this track as the source for all capture with voice
+ * communication preset.
+ *
+ * @param callRedirectionMode one of
+ * {@link AudioManager#CALL_REDIRECT_NONE},
+ * {@link AudioManager#CALL_REDIRECT_PSTN},
+ * or {@link AAudioManager#CALL_REDIRECT_VOIP}.
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException if {@code callRedirectionMode} is not valid.
+ */
+ public @NonNull Builder setCallRedirectionMode(
+ @AudioManager.CallRedirectionMode int callRedirectionMode) {
+ switch (callRedirectionMode) {
+ case AudioManager.CALL_REDIRECT_NONE:
+ case AudioManager.CALL_REDIRECT_PSTN:
+ case AudioManager.CALL_REDIRECT_VOIP:
+ mCallRedirectionMode = callRedirectionMode;
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Invalid call redirection mode " + callRedirectionMode);
+ }
+ return this;
+ }
+
+ private @NonNull AudioTrack buildCallInjectionTrack() {
+ AudioMixingRule audioMixingRule = new AudioMixingRule.Builder()
+ .addMixRule(AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET,
+ new AudioAttributes.Builder()
+ .setCapturePreset(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
+ .setForCallRedirection()
+ .build())
+ .setTargetMixRole(AudioMixingRule.MIX_ROLE_INJECTOR)
+ .build();
+ AudioMix audioMix = new AudioMix.Builder(audioMixingRule)
+ .setFormat(mFormat)
+ .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK)
+ .build();
+ AudioPolicy audioPolicy =
+ new AudioPolicy.Builder(/*context=*/ null).addMix(audioMix).build();
+ if (AudioManager.registerAudioPolicyStatic(audioPolicy) != 0) {
+ throw new UnsupportedOperationException("Error: could not register audio policy");
+ }
+ AudioTrack track = audioPolicy.createAudioTrackSource(audioMix);
+ if (track == null) {
+ throw new UnsupportedOperationException("Cannot create injection AudioTrack");
+ }
+ track.unregisterAudioPolicyOnRelease(audioPolicy);
+ return track;
+ }
+
+ /**
* Builds an {@link AudioTrack} instance initialized with all the parameters set
* on this <code>Builder</code>.
* @return a new successfully initialized {@link AudioTrack} instance.
@@ -1270,6 +1344,14 @@
.build();
}
+ if (mCallRedirectionMode == AudioManager.CALL_REDIRECT_VOIP) {
+ return buildCallInjectionTrack();
+ } else if (mCallRedirectionMode == AudioManager.CALL_REDIRECT_PSTN) {
+ mAttributes = new AudioAttributes.Builder(mAttributes)
+ .setForCallRedirection()
+ .build();
+ }
+
if (mOffload) {
if (mPerformanceMode == PERFORMANCE_MODE_LOW_LATENCY) {
throw new UnsupportedOperationException(
@@ -1315,6 +1397,16 @@
}
/**
+ * Sets an {@link AudioPolicy} to automatically unregister when the track is released.
+ *
+ * <p>This is to prevent users of the call audio injection API from having to manually
+ * unregister the policy that was used to create the track.
+ */
+ private void unregisterAudioPolicyOnRelease(AudioPolicy audioPolicy) {
+ mAudioPolicy = audioPolicy;
+ }
+
+ /**
* Configures the delay and padding values for the current compressed stream playing
* in offload mode.
* This can only be used on a track successfully initialized with
@@ -1879,6 +1971,11 @@
} catch(IllegalStateException ise) {
// don't raise an exception, we're releasing the resources.
}
+ if (mAudioPolicy != null) {
+ AudioManager.unregisterAudioPolicyAsyncStatic(mAudioPolicy);
+ mAudioPolicy = null;
+ }
+
baseRelease();
native_release();
synchronized (mPlayStateLock) {
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 35191bd..afcbc57 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -32,6 +32,7 @@
import android.media.IAudioServerStateDispatcher;
import android.media.ICapturePresetDevicesRoleDispatcher;
import android.media.ICommunicationDeviceDispatcher;
+import android.media.IMuteAwaitConnectionCallback;
import android.media.IPlaybackConfigDispatcher;
import android.media.IRecordingConfigDispatcher;
import android.media.IRingtonePlayer;
@@ -445,4 +446,18 @@
void unregisterSpatializerOutputCallback(in ISpatializerOutputCallback cb);
boolean isVolumeFixed();
+
+ boolean isPstnCallAudioInterceptable();
+
+ oneway void muteAwaitConnection(in int[] usagesToMute, in AudioDeviceAttributes dev,
+ long timeOutMs);
+
+ oneway void cancelMuteAwaitConnection(in AudioDeviceAttributes dev);
+
+ AudioDeviceAttributes getMutingExpectedDevice();
+
+ void registerMuteAwaitConnectionDispatcher(in IMuteAwaitConnectionCallback cb,
+ boolean register);
+
+ void setTestDeviceConnectionState(in AudioDeviceAttributes device, boolean connected);
}
diff --git a/media/java/android/media/IMuteAwaitConnectionCallback.aidl b/media/java/android/media/IMuteAwaitConnectionCallback.aidl
new file mode 100644
index 0000000..77fc029
--- /dev/null
+++ b/media/java/android/media/IMuteAwaitConnectionCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2021 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 android.media;
+
+import android.media.AudioDeviceAttributes;
+
+/**
+ * AIDL for the AudioService to signal mute events tied to audio device connections.
+ *
+ * {@hide}
+ */
+oneway interface IMuteAwaitConnectionCallback {
+
+ void dispatchOnMutedUntilConnection(in AudioDeviceAttributes device, in int[] mutedUsages);
+
+ void dispatchOnUnmutedEvent(int event, in AudioDeviceAttributes device, in int[] mutedUsages);
+}
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index 8080aad..f85bdee 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -241,6 +241,11 @@
}
/** @hide */
+ public boolean isForCallRedirection() {
+ return mRule.isForCallRedirection();
+ }
+
+ /** @hide */
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index 6112290..c912759 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -23,6 +23,7 @@
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.media.AudioAttributes;
+import android.media.MediaRecorder;
import android.os.Build;
import android.os.Parcel;
import android.util.Log;
@@ -262,6 +263,22 @@
}
/** @hide */
+ public boolean isForCallRedirection() {
+ for (AudioMixMatchCriterion criterion : mCriteria) {
+ if (criterion.mAttr != null
+ && (criterion.mRule == RULE_MATCH_ATTRIBUTE_USAGE
+ && criterion.mAttr.getUsage() == AudioAttributes.USAGE_VOICE_COMMUNICATION)
+ || (criterion.mRule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET
+ && criterion.mAttr.getCapturePreset()
+ == MediaRecorder.AudioSource.VOICE_COMMUNICATION)
+ && criterion.mAttr.isForCallRedirection()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** @hide */
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 3e8d76a..0f08d79 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -588,6 +588,10 @@
boolean canModifyAudioRouting = PackageManager.PERMISSION_GRANTED
== checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING);
+ boolean canInterceptCallAudio = PackageManager.PERMISSION_GRANTED
+ == checkCallingOrSelfPermission(
+ android.Manifest.permission.CALL_AUDIO_INTERCEPTION);
+
boolean canProjectAudio;
try {
canProjectAudio = mProjection != null && mProjection.getProjection().canProjectAudio();
@@ -596,7 +600,9 @@
throw e.rethrowFromSystemServer();
}
- if (!((isLoopbackRenderPolicy() && canProjectAudio) || canModifyAudioRouting)) {
+ if (!((isLoopbackRenderPolicy() && canProjectAudio)
+ || (isCallRedirectionPolicy() && canInterceptCallAudio)
+ || canModifyAudioRouting)) {
Slog.w(TAG, "Cannot use AudioPolicy for pid " + Binder.getCallingPid() + " / uid "
+ Binder.getCallingUid() + ", needs MODIFY_AUDIO_ROUTING or "
+ "MediaProjection that can project audio.");
@@ -612,6 +618,17 @@
}
}
+ private boolean isCallRedirectionPolicy() {
+ synchronized (mLock) {
+ for (AudioMix mix : mConfig.mMixes) {
+ if (mix.isForCallRedirection()) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
/**
* Returns {@link PackageManager#PERMISSION_GRANTED} if the caller has the given permission.
*/
@@ -728,13 +745,16 @@
.setChannelMask(AudioFormat.inChannelMaskFromOutChannelMask(
mix.getFormat().getChannelMask()))
.build();
+
+ AudioAttributes.Builder ab = new AudioAttributes.Builder()
+ .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX)
+ .addTag(addressForTag(mix))
+ .addTag(AudioRecord.SUBMIX_FIXED_VOLUME);
+ if (mix.isForCallRedirection()) {
+ ab.setForCallRedirection();
+ }
// create the AudioRecord, configured for loop back, using the same format as the mix
- AudioRecord ar = new AudioRecord(
- new AudioAttributes.Builder()
- .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX)
- .addTag(addressForTag(mix))
- .addTag(AudioRecord.SUBMIX_FIXED_VOLUME)
- .build(),
+ AudioRecord ar = new AudioRecord(ab.build(),
mixFormat,
AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(),
// using stereo for buffer size to avoid the current poor support for masks
@@ -768,11 +788,13 @@
}
checkMixReadyToUse(mix, true/*for an AudioTrack*/);
// create the AudioTrack, configured for loop back, using the same format as the mix
- AudioTrack at = new AudioTrack(
- new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
- .addTag(addressForTag(mix))
- .build(),
+ AudioAttributes.Builder ab = new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
+ .addTag(addressForTag(mix));
+ if (mix.isForCallRedirection()) {
+ ab.setForCallRedirection();
+ }
+ AudioTrack at = new AudioTrack(ab.build(),
mix.getFormat(),
AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(),
mix.getFormat().getChannelMask(), mix.getFormat().getEncoding()),
diff --git a/media/java/android/media/tv/BroadcastInfoRequest.java b/media/java/android/media/tv/BroadcastInfoRequest.java
index 4d8eda1..c439356 100644
--- a/media/java/android/media/tv/BroadcastInfoRequest.java
+++ b/media/java/android/media/tv/BroadcastInfoRequest.java
@@ -23,19 +23,35 @@
/** @hide */
public abstract class BroadcastInfoRequest implements Parcelable {
- protected static final int PARCEL_TOKEN_TS_REQUEST = 1;
+
+ // todo: change const declaration to intdef
+ public static final int REQUEST_OPTION_REPEAT = 11;
+ public static final int REQUEST_OPTION_AUTO_UPDATE = 12;
public static final @NonNull Parcelable.Creator<BroadcastInfoRequest> CREATOR =
new Parcelable.Creator<BroadcastInfoRequest>() {
@Override
public BroadcastInfoRequest createFromParcel(Parcel source) {
- int token = source.readInt();
- switch (token) {
- case PARCEL_TOKEN_TS_REQUEST:
+ int type = source.readInt();
+ switch (type) {
+ case BroadcastInfoType.TS:
return TsRequest.createFromParcelBody(source);
+ case BroadcastInfoType.TABLE:
+ return TableRequest.createFromParcelBody(source);
+ case BroadcastInfoType.SECTION:
+ return SectionRequest.createFromParcelBody(source);
+ case BroadcastInfoType.PES:
+ return PesRequest.createFromParcelBody(source);
+ case BroadcastInfoType.STREAM_EVENT:
+ return StreamEventRequest.createFromParcelBody(source);
+ case BroadcastInfoType.DSMCC:
+ return DsmccRequest.createFromParcelBody(source);
+ case BroadcastInfoType.TV_PROPRIETARY_FUNCTION:
+ return TvProprietaryFunctionRequest.createFromParcelBody(source);
default:
throw new IllegalStateException(
- "Unexpected broadcast info request type token in parcel.");
+ "Unexpected broadcast info request type (value "
+ + type + ") in parcel.");
}
}
@@ -45,18 +61,32 @@
}
};
- int requestId;
+ protected final int mType;
+ protected final int mRequestId;
+ protected final int mOption;
- public BroadcastInfoRequest(int requestId) {
- this.requestId = requestId;
+ protected BroadcastInfoRequest(int type, int requestId, int option) {
+ mType = type;
+ mRequestId = requestId;
+ mOption = option;
}
- protected BroadcastInfoRequest(Parcel source) {
- requestId = source.readInt();
+ protected BroadcastInfoRequest(int type, Parcel source) {
+ mType = type;
+ mRequestId = source.readInt();
+ mOption = source.readInt();
+ }
+
+ public int getType() {
+ return mType;
}
public int getRequestId() {
- return requestId;
+ return mRequestId;
+ }
+
+ public int getOption() {
+ return mOption;
}
@Override
@@ -66,6 +96,8 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(requestId);
+ dest.writeInt(mType);
+ dest.writeInt(mRequestId);
+ dest.writeInt(mOption);
}
}
diff --git a/media/java/android/media/tv/BroadcastInfoResponse.java b/media/java/android/media/tv/BroadcastInfoResponse.java
index fe4e8b7..288f2f9 100644
--- a/media/java/android/media/tv/BroadcastInfoResponse.java
+++ b/media/java/android/media/tv/BroadcastInfoResponse.java
@@ -22,12 +22,37 @@
import android.annotation.NonNull;
/** @hide */
-public final class BroadcastInfoResponse implements Parcelable {
+public abstract class BroadcastInfoResponse implements Parcelable {
+ // todo: change const declaration to intdef
+ public static final int ERROR = 1;
+ public static final int OK = 2;
+ public static final int CANCEL = 3;
+
public static final @NonNull Parcelable.Creator<BroadcastInfoResponse> CREATOR =
new Parcelable.Creator<BroadcastInfoResponse>() {
@Override
public BroadcastInfoResponse createFromParcel(Parcel source) {
- return new BroadcastInfoResponse(source);
+ int type = source.readInt();
+ switch (type) {
+ case BroadcastInfoType.TS:
+ return TsResponse.createFromParcelBody(source);
+ case BroadcastInfoType.TABLE:
+ return TableResponse.createFromParcelBody(source);
+ case BroadcastInfoType.SECTION:
+ return SectionResponse.createFromParcelBody(source);
+ case BroadcastInfoType.PES:
+ return PesResponse.createFromParcelBody(source);
+ case BroadcastInfoType.STREAM_EVENT:
+ return StreamEventResponse.createFromParcelBody(source);
+ case BroadcastInfoType.DSMCC:
+ return DsmccResponse.createFromParcelBody(source);
+ case BroadcastInfoType.TV_PROPRIETARY_FUNCTION:
+ return TvProprietaryFunctionResponse.createFromParcelBody(source);
+ default:
+ throw new IllegalStateException(
+ "Unexpected broadcast info response type (value "
+ + type + ") in parcel.");
+ }
}
@Override
@@ -36,18 +61,39 @@
}
};
- int requestId;
+ protected final int mType;
+ protected final int mRequestId;
+ protected final int mSequence;
+ protected final int mResponseResult;
- public BroadcastInfoResponse(int requestId) {
- this.requestId = requestId;
+ protected BroadcastInfoResponse(int type, int requestId, int sequence, int responseResult) {
+ mType = type;
+ mRequestId = requestId;
+ mSequence = sequence;
+ mResponseResult = responseResult;
}
- private BroadcastInfoResponse(Parcel source) {
- requestId = source.readInt();
+ protected BroadcastInfoResponse(int type, Parcel source) {
+ mType = type;
+ mRequestId = source.readInt();
+ mSequence = source.readInt();
+ mResponseResult = source.readInt();
+ }
+
+ public int getType() {
+ return mType;
}
public int getRequestId() {
- return requestId;
+ return mRequestId;
+ }
+
+ public int getSequence() {
+ return mSequence;
+ }
+
+ public int getResponseResult() {
+ return mResponseResult;
}
@Override
@@ -57,6 +103,9 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(requestId);
+ dest.writeInt(mType);
+ dest.writeInt(mRequestId);
+ dest.writeInt(mSequence);
+ dest.writeInt(mResponseResult);
}
}
diff --git a/media/java/android/media/tv/BroadcastInfoType.java b/media/java/android/media/tv/BroadcastInfoType.java
new file mode 100644
index 0000000..e7a0595
--- /dev/null
+++ b/media/java/android/media/tv/BroadcastInfoType.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 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 android.media.tv;
+
+/** @hide */
+public final class BroadcastInfoType {
+ // todo: change const declaration to intdef in TvInputManager
+ public static final int TS = 1;
+ public static final int TABLE = 2;
+ public static final int SECTION = 3;
+ public static final int PES = 4;
+ public static final int STREAM_EVENT = 5;
+ public static final int DSMCC = 6;
+ public static final int TV_PROPRIETARY_FUNCTION = 7;
+
+ private BroadcastInfoType() {
+ }
+}
diff --git a/media/java/android/media/tv/DsmccRequest.java b/media/java/android/media/tv/DsmccRequest.java
new file mode 100644
index 0000000..f2e4750
--- /dev/null
+++ b/media/java/android/media/tv/DsmccRequest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 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 android.media.tv;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class DsmccRequest extends BroadcastInfoRequest implements Parcelable {
+ public static final int requestType = BroadcastInfoType.DSMCC;
+
+ public static final @NonNull Parcelable.Creator<DsmccRequest> CREATOR =
+ new Parcelable.Creator<DsmccRequest>() {
+ @Override
+ public DsmccRequest createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public DsmccRequest[] newArray(int size) {
+ return new DsmccRequest[size];
+ }
+ };
+
+ private final Uri mUri;
+
+ public static DsmccRequest createFromParcelBody(Parcel in) {
+ return new DsmccRequest(in);
+ }
+
+ public DsmccRequest(int requestId, int option, Uri uri) {
+ super(requestType, requestId, option);
+ mUri = uri;
+ }
+
+ protected DsmccRequest(Parcel source) {
+ super(requestType, source);
+ String uriString = source.readString();
+ mUri = uriString == null ? null : Uri.parse(uriString);
+ }
+
+ public Uri getUri() {
+ return mUri;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ String uriString = mUri == null ? null : mUri.toString();
+ dest.writeString(uriString);
+ }
+}
diff --git a/media/java/android/media/tv/DsmccResponse.java b/media/java/android/media/tv/DsmccResponse.java
new file mode 100644
index 0000000..3bdfb95
--- /dev/null
+++ b/media/java/android/media/tv/DsmccResponse.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 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 android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+
+/** @hide */
+public class DsmccResponse extends BroadcastInfoResponse implements Parcelable {
+ public static final int responseType = BroadcastInfoType.DSMCC;
+
+ public static final @NonNull Parcelable.Creator<DsmccResponse> CREATOR =
+ new Parcelable.Creator<DsmccResponse>() {
+ @Override
+ public DsmccResponse createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public DsmccResponse[] newArray(int size) {
+ return new DsmccResponse[size];
+ }
+ };
+
+ private final ParcelFileDescriptor mFile;
+
+ public static DsmccResponse createFromParcelBody(Parcel in) {
+ return new DsmccResponse(in);
+ }
+
+ public DsmccResponse(int requestId, int sequence, int responseResult,
+ ParcelFileDescriptor file) {
+ super(responseType, requestId, sequence, responseResult);
+ mFile = file;
+ }
+
+ protected DsmccResponse(Parcel source) {
+ super(responseType, source);
+ mFile = source.readFileDescriptor();
+ }
+
+ public ParcelFileDescriptor getFile() {
+ return mFile;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ mFile.writeToParcel(dest, flags);
+ }
+}
diff --git a/media/java/android/media/tv/PesRequest.java b/media/java/android/media/tv/PesRequest.java
new file mode 100644
index 0000000..0e444b8
--- /dev/null
+++ b/media/java/android/media/tv/PesRequest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 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 android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class PesRequest extends BroadcastInfoRequest implements Parcelable {
+ public static final int requestType = BroadcastInfoType.PES;
+
+ public static final @NonNull Parcelable.Creator<PesRequest> CREATOR =
+ new Parcelable.Creator<PesRequest>() {
+ @Override
+ public PesRequest createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public PesRequest[] newArray(int size) {
+ return new PesRequest[size];
+ }
+ };
+
+ private final int mTsPid;
+ private final int mStreamId;
+
+ public static PesRequest createFromParcelBody(Parcel in) {
+ return new PesRequest(in);
+ }
+
+ public PesRequest(int requestId, int option, int tsPid, int streamId) {
+ super(requestType, requestId, option);
+ mTsPid = tsPid;
+ mStreamId = streamId;
+ }
+
+ protected PesRequest(Parcel source) {
+ super(requestType, source);
+ mTsPid = source.readInt();
+ mStreamId = source.readInt();
+ }
+
+ public int getTsPid() {
+ return mTsPid;
+ }
+
+ public int getStreamId() {
+ return mStreamId;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mTsPid);
+ dest.writeInt(mStreamId);
+ }
+}
diff --git a/media/java/android/media/tv/PesResponse.java b/media/java/android/media/tv/PesResponse.java
new file mode 100644
index 0000000..d46e6fc
--- /dev/null
+++ b/media/java/android/media/tv/PesResponse.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 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 android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class PesResponse extends BroadcastInfoResponse implements Parcelable {
+ public static final int responseType = BroadcastInfoType.PES;
+
+ public static final @NonNull Parcelable.Creator<PesResponse> CREATOR =
+ new Parcelable.Creator<PesResponse>() {
+ @Override
+ public PesResponse createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public PesResponse[] newArray(int size) {
+ return new PesResponse[size];
+ }
+ };
+
+ private final String mSharedFilterToken;
+
+ public static PesResponse createFromParcelBody(Parcel in) {
+ return new PesResponse(in);
+ }
+
+ public PesResponse(int requestId, int sequence, int responseResult, String sharedFilterToken) {
+ super(responseType, requestId, sequence, responseResult);
+ mSharedFilterToken = sharedFilterToken;
+ }
+
+ protected PesResponse(Parcel source) {
+ super(responseType, source);
+ mSharedFilterToken = source.readString();
+ }
+
+ public String getSharedFilterToken() {
+ return mSharedFilterToken;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mSharedFilterToken);
+ }
+}
diff --git a/media/java/android/media/tv/SectionRequest.java b/media/java/android/media/tv/SectionRequest.java
new file mode 100644
index 0000000..3e8e909
--- /dev/null
+++ b/media/java/android/media/tv/SectionRequest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2021 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 android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class SectionRequest extends BroadcastInfoRequest implements Parcelable {
+ public static final int requestType = BroadcastInfoType.SECTION;
+
+ public static final @NonNull Parcelable.Creator<SectionRequest> CREATOR =
+ new Parcelable.Creator<SectionRequest>() {
+ @Override
+ public SectionRequest createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public SectionRequest[] newArray(int size) {
+ return new SectionRequest[size];
+ }
+ };
+
+ private final int mTsPid;
+ private final int mTableId;
+ private final int mVersion;
+
+ public static SectionRequest createFromParcelBody(Parcel in) {
+ return new SectionRequest(in);
+ }
+
+ public SectionRequest(int requestId, int option, int tsPid, int tableId, int version) {
+ super(requestType, requestId, option);
+ mTsPid = tsPid;
+ mTableId = tableId;
+ mVersion = version;
+ }
+
+ protected SectionRequest(Parcel source) {
+ super(requestType, source);
+ mTsPid = source.readInt();
+ mTableId = source.readInt();
+ mVersion = source.readInt();
+ }
+
+ public int getTsPid() {
+ return mTsPid;
+ }
+
+ public int getTableId() {
+ return mTableId;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mTsPid);
+ dest.writeInt(mTableId);
+ dest.writeInt(mVersion);
+ }
+}
diff --git a/media/java/android/media/tv/SectionResponse.java b/media/java/android/media/tv/SectionResponse.java
new file mode 100644
index 0000000..1c8f965
--- /dev/null
+++ b/media/java/android/media/tv/SectionResponse.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 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 android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class SectionResponse extends BroadcastInfoResponse implements Parcelable {
+ public static final int responseType = BroadcastInfoType.SECTION;
+
+ public static final @NonNull Parcelable.Creator<SectionResponse> CREATOR =
+ new Parcelable.Creator<SectionResponse>() {
+ @Override
+ public SectionResponse createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public SectionResponse[] newArray(int size) {
+ return new SectionResponse[size];
+ }
+ };
+
+ private final int mSessionId;
+ private final int mVersion;
+ private final Bundle mSessionData;
+
+ public static SectionResponse createFromParcelBody(Parcel in) {
+ return new SectionResponse(in);
+ }
+
+ public SectionResponse(int requestId, int sequence, int responseResult, int sessionId,
+ int version, Bundle sessionData) {
+ super(responseType, requestId, sequence, responseResult);
+ mSessionId = sessionId;
+ mVersion = version;
+ mSessionData = sessionData;
+ }
+
+ protected SectionResponse(Parcel source) {
+ super(responseType, source);
+ mSessionId = source.readInt();
+ mVersion = source.readInt();
+ mSessionData = source.readBundle();
+ }
+
+ public int getSessionId() {
+ return mSessionId;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ public Bundle getSessionData() {
+ return mSessionData;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mSessionId);
+ dest.writeInt(mVersion);
+ dest.writeBundle(mSessionData);
+ }
+}
diff --git a/media/java/android/media/tv/StreamEventRequest.java b/media/java/android/media/tv/StreamEventRequest.java
new file mode 100644
index 0000000..09399c2
--- /dev/null
+++ b/media/java/android/media/tv/StreamEventRequest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 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 android.media.tv;
+
+import android.annotation.NonNull;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class StreamEventRequest extends BroadcastInfoRequest implements Parcelable {
+ public static final int requestType = BroadcastInfoType.STREAM_EVENT;
+
+ public static final @NonNull Parcelable.Creator<StreamEventRequest> CREATOR =
+ new Parcelable.Creator<StreamEventRequest>() {
+ @Override
+ public StreamEventRequest createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public StreamEventRequest[] newArray(int size) {
+ return new StreamEventRequest[size];
+ }
+ };
+
+ private final Uri mTargetUri;
+ private final String mEventName;
+
+ public static StreamEventRequest createFromParcelBody(Parcel in) {
+ return new StreamEventRequest(in);
+ }
+
+ public StreamEventRequest(int requestId, int option, Uri targetUri, String eventName) {
+ super(requestType, requestId, option);
+ this.mTargetUri = targetUri;
+ this.mEventName = eventName;
+ }
+
+ protected StreamEventRequest(Parcel source) {
+ super(requestType, source);
+ String uriString = source.readString();
+ mTargetUri = uriString == null ? null : Uri.parse(uriString);
+ mEventName = source.readString();
+ }
+
+ public Uri getTargetUri() {
+ return mTargetUri;
+ }
+
+ public String getEventName() {
+ return mEventName;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ String uriString = mTargetUri == null ? null : mTargetUri.toString();
+ dest.writeString(uriString);
+ dest.writeString(mEventName);
+ }
+}
diff --git a/media/java/android/media/tv/StreamEventResponse.java b/media/java/android/media/tv/StreamEventResponse.java
new file mode 100644
index 0000000..027b735
--- /dev/null
+++ b/media/java/android/media/tv/StreamEventResponse.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 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 android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class StreamEventResponse extends BroadcastInfoResponse implements Parcelable {
+ public static final int responseType = BroadcastInfoType.STREAM_EVENT;
+
+ public static final @NonNull Parcelable.Creator<StreamEventResponse> CREATOR =
+ new Parcelable.Creator<StreamEventResponse>() {
+ @Override
+ public StreamEventResponse createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public StreamEventResponse[] newArray(int size) {
+ return new StreamEventResponse[size];
+ }
+ };
+
+ private final String mName;
+ private final String mText;
+ private final String mData;
+ private final String mStatus;
+
+ public static StreamEventResponse createFromParcelBody(Parcel in) {
+ return new StreamEventResponse(in);
+ }
+
+ public StreamEventResponse(int requestId, int sequence, int responseResult, String name,
+ String text, String data, String status) {
+ super(responseType, requestId, sequence, responseResult);
+ mName = name;
+ mText = text;
+ mData = data;
+ mStatus = status;
+ }
+
+ protected StreamEventResponse(Parcel source) {
+ super(responseType, source);
+ mName = source.readString();
+ mText = source.readString();
+ mData = source.readString();
+ mStatus = source.readString();
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getText() {
+ return mText;
+ }
+
+ public String getData() {
+ return mData;
+ }
+
+ public String getStatus() {
+ return mStatus;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mName);
+ dest.writeString(mText);
+ dest.writeString(mData);
+ dest.writeString(mStatus);
+ }
+}
diff --git a/media/java/android/media/tv/TableRequest.java b/media/java/android/media/tv/TableRequest.java
new file mode 100644
index 0000000..5432215
--- /dev/null
+++ b/media/java/android/media/tv/TableRequest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 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 android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TableRequest extends BroadcastInfoRequest implements Parcelable {
+ public static final int requestType = BroadcastInfoType.TABLE;
+
+ // todo: change const declaration to intdef
+ public static final int PAT = 1;
+ public static final int PMT = 2;
+
+ public static final @NonNull Parcelable.Creator<TableRequest> CREATOR =
+ new Parcelable.Creator<TableRequest>() {
+ @Override
+ public TableRequest createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public TableRequest[] newArray(int size) {
+ return new TableRequest[size];
+ }
+ };
+
+ private final int mTableId;
+ private final int mTableName;
+ private final int mVersion;
+
+ public static TableRequest createFromParcelBody(Parcel in) {
+ return new TableRequest(in);
+ }
+
+ public TableRequest(int requestId, int option, int tableId, int tableName, int version) {
+ super(requestType, requestId, option);
+ mTableId = tableId;
+ mTableName = tableName;
+ mVersion = version;
+ }
+
+ protected TableRequest(Parcel source) {
+ super(requestType, source);
+ mTableId = source.readInt();
+ mTableName = source.readInt();
+ mVersion = source.readInt();
+ }
+
+ public int getTableId() {
+ return mTableId;
+ }
+
+ public int getTableName() {
+ return mTableName;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mTableId);
+ dest.writeInt(mTableName);
+ dest.writeInt(mVersion);
+ }
+}
diff --git a/media/java/android/media/tv/TableResponse.java b/media/java/android/media/tv/TableResponse.java
new file mode 100644
index 0000000..a6d3e39
--- /dev/null
+++ b/media/java/android/media/tv/TableResponse.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 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 android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.net.Uri;
+
+/** @hide */
+public class TableResponse extends BroadcastInfoResponse implements Parcelable {
+ public static final int responseType = BroadcastInfoType.TABLE;
+
+ public static final @NonNull Parcelable.Creator<TableResponse> CREATOR =
+ new Parcelable.Creator<TableResponse>() {
+ @Override
+ public TableResponse createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public TableResponse[] newArray(int size) {
+ return new TableResponse[size];
+ }
+ };
+
+ private final Uri mTableUri;
+ private final int mVersion;
+ private final int mSize;
+
+ public static TableResponse createFromParcelBody(Parcel in) {
+ return new TableResponse(in);
+ }
+
+ public TableResponse(int requestId, int sequence, int responseResult, Uri tableUri,
+ int version, int size) {
+ super(responseType, requestId, sequence, responseResult);
+ mTableUri = tableUri;
+ mVersion = version;
+ mSize = size;
+ }
+
+ protected TableResponse(Parcel source) {
+ super(responseType, source);
+ String uriString = source.readString();
+ mTableUri = uriString == null ? null : Uri.parse(uriString);
+ mVersion = source.readInt();
+ mSize = source.readInt();
+ }
+
+ public Uri getTableUri() {
+ return mTableUri;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ public int getSize() {
+ return mSize;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ String uriString = mTableUri == null ? null : mTableUri.toString();
+ dest.writeString(uriString);
+ dest.writeInt(mVersion);
+ dest.writeInt(mSize);
+ }
+}
diff --git a/media/java/android/media/tv/TsRequest.aidl b/media/java/android/media/tv/TsRequest.aidl
deleted file mode 100644
index 0abc7ec..0000000
--- a/media/java/android/media/tv/TsRequest.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2021 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 android.media.tv;
-
-parcelable TsRequest;
diff --git a/media/java/android/media/tv/TsRequest.java b/media/java/android/media/tv/TsRequest.java
index 3690d05..141f3ac 100644
--- a/media/java/android/media/tv/TsRequest.java
+++ b/media/java/android/media/tv/TsRequest.java
@@ -22,6 +22,8 @@
/** @hide */
public class TsRequest extends BroadcastInfoRequest implements Parcelable {
+ public static final int requestType = BroadcastInfoType.TS;
+
public static final @NonNull Parcelable.Creator<TsRequest> CREATOR =
new Parcelable.Creator<TsRequest>() {
@Override
@@ -36,30 +38,29 @@
}
};
- int tsPid;
+ private final int mTsPid;
public static TsRequest createFromParcelBody(Parcel in) {
return new TsRequest(in);
}
- public TsRequest(int requestId, int tsPid) {
- super(requestId);
- this.tsPid = tsPid;
+ public TsRequest(int requestId, int option, int tsPid) {
+ super(requestType, requestId, option);
+ mTsPid = tsPid;
}
protected TsRequest(Parcel source) {
- super(source);
- tsPid = source.readInt();
+ super(requestType, source);
+ mTsPid = source.readInt();
}
public int getTsPid() {
- return tsPid;
+ return mTsPid;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(PARCEL_TOKEN_TS_REQUEST);
super.writeToParcel(dest, flags);
- dest.writeInt(tsPid);
+ dest.writeInt(mTsPid);
}
}
diff --git a/media/java/android/media/tv/TsResponse.java b/media/java/android/media/tv/TsResponse.java
new file mode 100644
index 0000000..e30ff54
--- /dev/null
+++ b/media/java/android/media/tv/TsResponse.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 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 android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TsResponse extends BroadcastInfoResponse implements Parcelable {
+ public static final int responseType = BroadcastInfoType.TS;
+
+ public static final @NonNull Parcelable.Creator<TsResponse> CREATOR =
+ new Parcelable.Creator<TsResponse>() {
+ @Override
+ public TsResponse createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public TsResponse[] newArray(int size) {
+ return new TsResponse[size];
+ }
+ };
+
+ private final String mSharedFilterToken;
+
+ public static TsResponse createFromParcelBody(Parcel in) {
+ return new TsResponse(in);
+ }
+
+ public TsResponse(int requestId, int sequence, int responseResult, String sharedFilterToken) {
+ super(responseType, requestId, sequence, responseResult);
+ this.mSharedFilterToken = sharedFilterToken;
+ }
+
+ protected TsResponse(Parcel source) {
+ super(responseType, source);
+ mSharedFilterToken = source.readString();
+ }
+
+ public String getSharedFilterToken() {
+ return mSharedFilterToken;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mSharedFilterToken);
+ }
+}
diff --git a/media/java/android/media/tv/TvProprietaryFunctionRequest.java b/media/java/android/media/tv/TvProprietaryFunctionRequest.java
new file mode 100644
index 0000000..845641d
--- /dev/null
+++ b/media/java/android/media/tv/TvProprietaryFunctionRequest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 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 android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TvProprietaryFunctionRequest extends BroadcastInfoRequest implements Parcelable {
+ public static final int requestType = BroadcastInfoType.TV_PROPRIETARY_FUNCTION;
+
+ public static final @NonNull Parcelable.Creator<TvProprietaryFunctionRequest> CREATOR =
+ new Parcelable.Creator<TvProprietaryFunctionRequest>() {
+ @Override
+ public TvProprietaryFunctionRequest createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public TvProprietaryFunctionRequest[] newArray(int size) {
+ return new TvProprietaryFunctionRequest[size];
+ }
+ };
+
+ private final String mNameSpace;
+ private final String mName;
+ private final String mArguments;
+
+ public static TvProprietaryFunctionRequest createFromParcelBody(Parcel in) {
+ return new TvProprietaryFunctionRequest(in);
+ }
+
+ public TvProprietaryFunctionRequest(int requestId, int option, String nameSpace,
+ String name, String arguments) {
+ super(requestType, requestId, option);
+ mNameSpace = nameSpace;
+ mName = name;
+ mArguments = arguments;
+ }
+
+ protected TvProprietaryFunctionRequest(Parcel source) {
+ super(requestType, source);
+ mNameSpace = source.readString();
+ mName = source.readString();
+ mArguments = source.readString();
+ }
+
+ public String getNameSpace() {
+ return mNameSpace;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public String getArguments() {
+ return mArguments;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mNameSpace);
+ dest.writeString(mName);
+ dest.writeString(mArguments);
+ }
+}
diff --git a/media/java/android/media/tv/TvProprietaryFunctionResponse.java b/media/java/android/media/tv/TvProprietaryFunctionResponse.java
new file mode 100644
index 0000000..3181b08
--- /dev/null
+++ b/media/java/android/media/tv/TvProprietaryFunctionResponse.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 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 android.media.tv;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** @hide */
+public class TvProprietaryFunctionResponse extends BroadcastInfoResponse implements Parcelable {
+ public static final int responseType = BroadcastInfoType.TV_PROPRIETARY_FUNCTION;
+
+ public static final @NonNull Parcelable.Creator<TvProprietaryFunctionResponse> CREATOR =
+ new Parcelable.Creator<TvProprietaryFunctionResponse>() {
+ @Override
+ public TvProprietaryFunctionResponse createFromParcel(Parcel source) {
+ source.readInt();
+ return createFromParcelBody(source);
+ }
+
+ @Override
+ public TvProprietaryFunctionResponse[] newArray(int size) {
+ return new TvProprietaryFunctionResponse[size];
+ }
+ };
+
+ private final String mResponse;
+
+ public static TvProprietaryFunctionResponse createFromParcelBody(Parcel in) {
+ return new TvProprietaryFunctionResponse(in);
+ }
+
+ public TvProprietaryFunctionResponse(int requestId, int sequence, int responseResult,
+ String response) {
+ super(responseType, requestId, sequence, responseResult);
+ mResponse = response;
+ }
+
+ protected TvProprietaryFunctionResponse(Parcel source) {
+ super(responseType, source);
+ mResponse = source.readString();
+ }
+
+ public String getResponse() {
+ return mResponse;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mResponse);
+ }
+}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 73a821e..94de7fa 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -74,6 +74,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.concurrent.locks.ReentrantLock;
/**
* This class is used to interact with hardware tuners devices.
@@ -248,6 +249,7 @@
private static final int FILTER_CLEANUP_THRESHOLD = 256;
+
/** @hide */
@IntDef(prefix = "DVR_TYPE_", value = {DVR_TYPE_RECORD, DVR_TYPE_PLAYBACK})
@Retention(RetentionPolicy.SOURCE)
@@ -304,6 +306,11 @@
private final Object mOnTuneEventLock = new Object();
private final Object mScanCallbackLock = new Object();
private final Object mOnResourceLostListenerLock = new Object();
+ private final ReentrantLock mFrontendLock = new ReentrantLock();
+ private final ReentrantLock mLnbLock = new ReentrantLock();
+ private final ReentrantLock mFrontendCiCamLock = new ReentrantLock();
+ private final ReentrantLock mDemuxLock = new ReentrantLock();
+ private int mRequestedCiCamId;
private Integer mDemuxHandle;
private Integer mFrontendCiCamHandle;
@@ -391,7 +398,12 @@
/** @hide */
public List<Integer> getFrontendIds() {
- return nativeGetFrontendIds();
+ mFrontendLock.lock();
+ try {
+ return nativeGetFrontendIds();
+ } finally {
+ mFrontendLock.unlock();
+ }
}
/**
@@ -426,13 +438,20 @@
* @param tuner the Tuner instance to share frontend resource with.
*/
public void shareFrontendFromTuner(@NonNull Tuner tuner) {
- mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId);
- synchronized (mIsSharedFrontend) {
- mFrontendHandle = tuner.mFrontendHandle;
- mFrontend = tuner.mFrontend;
- mIsSharedFrontend = true;
+ acquireTRMSLock("shareFrontendFromTuner()");
+ mFrontendLock.lock();
+ try {
+ mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId);
+ synchronized (mIsSharedFrontend) {
+ mFrontendHandle = tuner.mFrontendHandle;
+ mFrontend = tuner.mFrontend;
+ mIsSharedFrontend = true;
+ }
+ nativeShareFrontend(mFrontend.mId);
+ } finally {
+ releaseTRMSLock();
+ mFrontendLock.unlock();
}
- nativeShareFrontend(mFrontend.mId);
}
/**
@@ -498,39 +517,62 @@
*/
@Override
public void close() {
- releaseAll();
- TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
+ acquireTRMSLock("close()");
+ try {
+ releaseAll();
+ TunerUtils.throwExceptionForResult(nativeClose(), "failed to close tuner");
+ } finally {
+ releaseTRMSLock();
+ }
}
private void releaseAll() {
- if (mFrontendHandle != null) {
- synchronized (mIsSharedFrontend) {
- if (!mIsSharedFrontend) {
- int res = nativeCloseFrontend(mFrontendHandle);
- if (res != Tuner.RESULT_SUCCESS) {
- TunerUtils.throwExceptionForResult(res, "failed to close frontend");
+ mFrontendLock.lock();
+ try {
+ if (mFrontendHandle != null) {
+ synchronized (mIsSharedFrontend) {
+ if (!mIsSharedFrontend) {
+ int res = nativeCloseFrontend(mFrontendHandle);
+ if (res != Tuner.RESULT_SUCCESS) {
+ TunerUtils.throwExceptionForResult(res, "failed to close frontend");
+ }
+ mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
}
- mTunerResourceManager.releaseFrontend(mFrontendHandle, mClientId);
+ mIsSharedFrontend = false;
}
- mIsSharedFrontend = false;
+ FrameworkStatsLog
+ .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+ FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
+ mFrontendHandle = null;
+ mFrontend = null;
}
- FrameworkStatsLog
- .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
- FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__UNKNOWN);
- mFrontendHandle = null;
- mFrontend = null;
+ } finally {
+ mFrontendLock.unlock();
}
- if (mLnb != null) {
- mLnb.close();
- }
- if (mFrontendCiCamHandle != null) {
- int result = nativeUnlinkCiCam(mFrontendCiCamId);
- if (result == RESULT_SUCCESS) {
- mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
- mFrontendCiCamId = null;
- mFrontendCiCamHandle = null;
+
+ mLnbLock.lock();
+ try {
+ if (mLnb != null) {
+ mLnb.close();
}
+ } finally {
+ mLnbLock.unlock();
}
+
+ mFrontendCiCamLock.lock();
+ try {
+ if (mFrontendCiCamHandle != null) {
+ int result = nativeUnlinkCiCam(mFrontendCiCamId);
+ if (result == RESULT_SUCCESS) {
+ mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
+ mFrontendCiCamId = null;
+ mFrontendCiCamHandle = null;
+ }
+ }
+ } finally {
+ mFrontendCiCamLock.unlock();
+ }
+
synchronized (mDescramblers) {
if (!mDescramblers.isEmpty()) {
for (Map.Entry<Integer, WeakReference<Descrambler>> d : mDescramblers.entrySet()) {
@@ -543,6 +585,7 @@
mDescramblers.clear();
}
}
+
synchronized (mFilters) {
if (!mFilters.isEmpty()) {
for (WeakReference<Filter> weakFilter : mFilters) {
@@ -554,13 +597,19 @@
mFilters.clear();
}
}
- if (mDemuxHandle != null) {
- int res = nativeCloseDemux(mDemuxHandle);
- if (res != Tuner.RESULT_SUCCESS) {
- TunerUtils.throwExceptionForResult(res, "failed to close demux");
+
+ mDemuxLock.lock();
+ try {
+ if (mDemuxHandle != null) {
+ int res = nativeCloseDemux(mDemuxHandle);
+ if (res != Tuner.RESULT_SUCCESS) {
+ TunerUtils.throwExceptionForResult(res, "failed to close demux");
+ }
+ mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId);
+ mDemuxHandle = null;
}
- mTunerResourceManager.releaseDemux(mDemuxHandle, mClientId);
- mDemuxHandle = null;
+ } finally {
+ mDemuxLock.unlock();
}
mTunerResourceManager.unregisterClientProfile(mClientId);
@@ -763,28 +812,37 @@
*/
@Result
public int tune(@NonNull FrontendSettings settings) {
- final int type = settings.getType();
- if (mFrontendHandle != null && type != mFrontendType) {
- Log.e(TAG, "Frontend was opened with type " + mFrontendType + ", new type is " + type);
- return RESULT_INVALID_STATE;
- }
- Log.d(TAG, "Tune to " + settings.getFrequencyLong());
- mFrontendType = type;
- if (mFrontendType == FrontendSettings.TYPE_DTMB) {
- if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
- TunerVersionChecker.TUNER_VERSION_1_1, "Tuner with DTMB Frontend")) {
+ mFrontendLock.lock();
+ try {
+ final int type = settings.getType();
+ if (mFrontendHandle != null && type != mFrontendType) {
+ Log.e(TAG, "Frontend was opened with type " + mFrontendType
+ + ", new type is " + type);
+ return RESULT_INVALID_STATE;
+ }
+ Log.d(TAG, "Tune to " + settings.getFrequencyLong());
+ mFrontendType = type;
+ if (mFrontendType == FrontendSettings.TYPE_DTMB) {
+ if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_1_1, "Tuner with DTMB Frontend")) {
+ return RESULT_UNAVAILABLE;
+ }
+ }
+
+ if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
+ mFrontendInfo = null;
+ Log.d(TAG, "Write Stats Log for tuning.");
+ FrameworkStatsLog
+ .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+ FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__TUNING);
+ int res = nativeTune(settings.getType(), settings);
+ return res;
+ } else {
return RESULT_UNAVAILABLE;
}
+ } finally {
+ mFrontendLock.unlock();
}
- if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
- mFrontendInfo = null;
- Log.d(TAG, "Write Stats Log for tuning.");
- FrameworkStatsLog
- .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
- FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__TUNING);
- return nativeTune(settings.getType(), settings);
- }
- return RESULT_UNAVAILABLE;
}
/**
@@ -797,7 +855,12 @@
*/
@Result
public int cancelTuning() {
- return nativeStopTune();
+ mFrontendLock.lock();
+ try {
+ return nativeStopTune();
+ } finally {
+ mFrontendLock.unlock();
+ }
}
/**
@@ -824,33 +887,41 @@
@Result
public int scan(@NonNull FrontendSettings settings, @ScanType int scanType,
@NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) {
- synchronized (mScanCallbackLock) {
- // Scan can be called again for blink scan if scanCallback and executor are same as
- //before.
- if (((mScanCallback != null) && (mScanCallback != scanCallback))
- || ((mScanCallbackExecutor != null) && (mScanCallbackExecutor != executor))) {
- throw new IllegalStateException(
- "Different Scan session already in progress. stopScan must be called "
- + "before a new scan session can be " + "started.");
- }
- mFrontendType = settings.getType();
- if (mFrontendType == FrontendSettings.TYPE_DTMB) {
- if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
- TunerVersionChecker.TUNER_VERSION_1_1,
- "Scan with DTMB Frontend")) {
- return RESULT_UNAVAILABLE;
+
+ mFrontendLock.lock();
+ try {
+ synchronized (mScanCallbackLock) {
+ // Scan can be called again for blink scan if scanCallback and executor are same as
+ //before.
+ if (((mScanCallback != null) && (mScanCallback != scanCallback))
+ || ((mScanCallbackExecutor != null)
+ && (mScanCallbackExecutor != executor))) {
+ throw new IllegalStateException(
+ "Different Scan session already in progress. stopScan must be called "
+ + "before a new scan session can be " + "started.");
}
+ mFrontendType = settings.getType();
+ if (mFrontendType == FrontendSettings.TYPE_DTMB) {
+ if (!TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_1_1,
+ "Scan with DTMB Frontend")) {
+ return RESULT_UNAVAILABLE;
+ }
+ }
+ if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND,
+ mFrontendLock)) {
+ mScanCallback = scanCallback;
+ mScanCallbackExecutor = executor;
+ mFrontendInfo = null;
+ FrameworkStatsLog
+ .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+ FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCANNING);
+ return nativeScan(settings.getType(), settings, scanType);
+ }
+ return RESULT_UNAVAILABLE;
}
- if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
- mScanCallback = scanCallback;
- mScanCallbackExecutor = executor;
- mFrontendInfo = null;
- FrameworkStatsLog
- .write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
- FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCANNING);
- return nativeScan(settings.getType(), settings, scanType);
- }
- return RESULT_UNAVAILABLE;
+ } finally {
+ mFrontendLock.unlock();
}
}
@@ -867,14 +938,19 @@
*/
@Result
public int cancelScanning() {
- synchronized (mScanCallbackLock) {
- FrameworkStatsLog.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
- FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCAN_STOPPED);
+ mFrontendLock.lock();
+ try {
+ synchronized (mScanCallbackLock) {
+ FrameworkStatsLog.write(FrameworkStatsLog.TV_TUNER_STATE_CHANGED, mUserId,
+ FrameworkStatsLog.TV_TUNER_STATE_CHANGED__STATE__SCAN_STOPPED);
- int retVal = nativeStopScan();
- mScanCallback = null;
- mScanCallbackExecutor = null;
- return retVal;
+ int retVal = nativeStopScan();
+ mScanCallback = null;
+ mScanCallbackExecutor = null;
+ return retVal;
+ }
+ } finally {
+ mFrontendLock.unlock();
}
}
@@ -903,7 +979,12 @@
*/
@Result
private int setLnb(@NonNull Lnb lnb) {
- return nativeSetLnb(lnb);
+ mLnbLock.lock();
+ try {
+ return nativeSetLnb(lnb);
+ } finally {
+ mLnbLock.unlock();
+ }
}
/**
@@ -929,10 +1010,15 @@
*/
@Nullable
public FrontendStatus getFrontendStatus(@NonNull @FrontendStatusType int[] statusTypes) {
- if (mFrontend == null) {
- throw new IllegalStateException("frontend is not initialized");
+ mFrontendLock.lock();
+ try {
+ if (mFrontend == null) {
+ throw new IllegalStateException("frontend is not initialized");
+ }
+ return nativeGetFrontendStatus(statusTypes);
+ } finally {
+ mFrontendLock.unlock();
}
- return nativeGetFrontendStatus(statusTypes);
}
/**
@@ -942,11 +1028,16 @@
* @return the id of hardware A/V sync.
*/
public int getAvSyncHwId(@NonNull Filter filter) {
- if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
- return INVALID_AV_SYNC_ID;
+ mDemuxLock.lock();
+ try {
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+ return INVALID_AV_SYNC_ID;
+ }
+ Integer id = nativeGetAvSyncHwId(filter);
+ return id == null ? INVALID_AV_SYNC_ID : id;
+ } finally {
+ mDemuxLock.unlock();
}
- Integer id = nativeGetAvSyncHwId(filter);
- return id == null ? INVALID_AV_SYNC_ID : id;
}
/**
@@ -959,11 +1050,16 @@
* @return the current timestamp of hardware A/V sync.
*/
public long getAvSyncTime(int avSyncHwId) {
- if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
- return INVALID_TIMESTAMP;
+ mDemuxLock.lock();
+ try {
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+ return INVALID_TIMESTAMP;
+ }
+ Long time = nativeGetAvSyncTime(avSyncHwId);
+ return time == null ? INVALID_TIMESTAMP : time;
+ } finally {
+ mDemuxLock.unlock();
}
- Long time = nativeGetAvSyncTime(avSyncHwId);
- return time == null ? INVALID_TIMESTAMP : time;
}
/**
@@ -980,10 +1076,15 @@
*/
@Result
public int connectCiCam(int ciCamId) {
- if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
- return nativeConnectCiCam(ciCamId);
+ mDemuxLock.lock();
+ try {
+ if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+ return nativeConnectCiCam(ciCamId);
+ }
+ return RESULT_UNAVAILABLE;
+ } finally {
+ mDemuxLock.unlock();
}
- return RESULT_UNAVAILABLE;
}
/**
@@ -1011,14 +1112,30 @@
* {@link TunerVersionChecker#getTunerVersion()}.
*/
public int connectFrontendToCiCam(int ciCamId) {
- if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
- "linkFrontendToCiCam")) {
- if (checkCiCamResource(ciCamId)
- && checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
- return nativeLinkCiCam(ciCamId);
+ // TODO: change this so TRMS lock is held only when the resource handles for
+ // CiCam/Frontend is null. Current implementation can only handle one local lock for that.
+ acquireTRMSLock("connectFrontendToCiCam()");
+ mFrontendCiCamLock.lock();
+ mFrontendLock.lock();
+ try {
+ if (TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_1_1,
+ "linkFrontendToCiCam")) {
+ mRequestedCiCamId = ciCamId;
+ // No need to unlock mFrontendCiCamLock and mFrontendLock below becauase
+ // TRMS lock is already acquired. Pass null to disable lock related operations
+ if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, null)
+ && checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, null)
+ ) {
+ return nativeLinkCiCam(ciCamId);
+ }
}
+ return INVALID_LTS_ID;
+ } finally {
+ releaseTRMSLock();
+ mFrontendCiCamLock.unlock();
+ mFrontendLock.unlock();
}
- return INVALID_LTS_ID;
}
/**
@@ -1033,10 +1150,15 @@
*/
@Result
public int disconnectCiCam() {
- if (mDemuxHandle != null) {
- return nativeDisconnectCiCam();
+ mDemuxLock.lock();
+ try {
+ if (mDemuxHandle != null) {
+ return nativeDisconnectCiCam();
+ }
+ return RESULT_UNAVAILABLE;
+ } finally {
+ mDemuxLock.unlock();
}
- return RESULT_UNAVAILABLE;
}
/**
@@ -1057,20 +1179,30 @@
*/
@Result
public int disconnectFrontendToCiCam(int ciCamId) {
- if (TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
- "unlinkFrontendToCiCam")) {
- if (mFrontendCiCamHandle != null && mFrontendCiCamId != null
- && mFrontendCiCamId == ciCamId) {
- int result = nativeUnlinkCiCam(ciCamId);
- if (result == RESULT_SUCCESS) {
- mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
- mFrontendCiCamId = null;
- mFrontendCiCamHandle = null;
+ acquireTRMSLock("disconnectFrontendToCiCam()");
+ try {
+ if (TunerVersionChecker.checkHigherOrEqualVersionTo(
+ TunerVersionChecker.TUNER_VERSION_1_1,
+ "unlinkFrontendToCiCam")) {
+ mFrontendCiCamLock.lock();
+ if (mFrontendCiCamHandle != null && mFrontendCiCamId != null
+ && mFrontendCiCamId == ciCamId) {
+ int result = nativeUnlinkCiCam(ciCamId);
+ if (result == RESULT_SUCCESS) {
+ mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId);
+ mFrontendCiCamId = null;
+ mFrontendCiCamHandle = null;
+ }
+ return result;
}
- return result;
}
+ return RESULT_UNAVAILABLE;
+ } finally {
+ if (mFrontendCiCamLock.isLocked()) {
+ mFrontendCiCamLock.unlock();
+ }
+ releaseTRMSLock();
}
- return RESULT_UNAVAILABLE;
}
/**
@@ -1082,16 +1214,21 @@
*/
@Nullable
public FrontendInfo getFrontendInfo() {
- if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) {
- return null;
+ mFrontendLock.lock();
+ try {
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, mFrontendLock)) {
+ return null;
+ }
+ if (mFrontend == null) {
+ throw new IllegalStateException("frontend is not initialized");
+ }
+ if (mFrontendInfo == null) {
+ mFrontendInfo = getFrontendInfoById(mFrontend.mId);
+ }
+ return mFrontendInfo;
+ } finally {
+ mFrontendLock.unlock();
}
- if (mFrontend == null) {
- throw new IllegalStateException("frontend is not initialized");
- }
- if (mFrontendInfo == null) {
- mFrontendInfo = getFrontendInfoById(mFrontend.mId);
- }
- return mFrontendInfo;
}
/**
@@ -1114,7 +1251,12 @@
/** @hide */
public FrontendInfo getFrontendInfoById(int id) {
- return nativeGetFrontendInfo(id);
+ mFrontendLock.lock();
+ try {
+ return nativeGetFrontendInfo(id);
+ } finally {
+ mFrontendLock.unlock();
+ }
}
/**
@@ -1125,7 +1267,12 @@
*/
@Nullable
public DemuxCapabilities getDemuxCapabilities() {
- return nativeGetDemuxCapabilities();
+ mDemuxLock.lock();
+ try {
+ return nativeGetDemuxCapabilities();
+ } finally {
+ mDemuxLock.unlock();
+ }
}
private void onFrontendEvent(int eventType) {
@@ -1417,32 +1564,37 @@
public Filter openFilter(@Type int mainType, @Subtype int subType,
@BytesLong long bufferSize, @CallbackExecutor @Nullable Executor executor,
@Nullable FilterCallback cb) {
- if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
- return null;
- }
- Filter filter = nativeOpenFilter(
- mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize);
- if (filter != null) {
- filter.setType(mainType, subType);
- filter.setCallback(cb, executor);
- if (mHandler == null) {
- mHandler = createEventHandler();
+ mDemuxLock.lock();
+ try {
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+ return null;
}
- synchronized (mFilters) {
- WeakReference<Filter> weakFilter = new WeakReference<Filter>(filter);
- mFilters.add(weakFilter);
- if (mFilters.size() > FILTER_CLEANUP_THRESHOLD) {
- Iterator<WeakReference<Filter>> iterator = mFilters.iterator();
- while (iterator.hasNext()) {
- WeakReference<Filter> wFilter = iterator.next();
- if (wFilter.get() == null) {
- iterator.remove();
+ Filter filter = nativeOpenFilter(
+ mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize);
+ if (filter != null) {
+ filter.setType(mainType, subType);
+ filter.setCallback(cb, executor);
+ if (mHandler == null) {
+ mHandler = createEventHandler();
+ }
+ synchronized (mFilters) {
+ WeakReference<Filter> weakFilter = new WeakReference<Filter>(filter);
+ mFilters.add(weakFilter);
+ if (mFilters.size() > FILTER_CLEANUP_THRESHOLD) {
+ Iterator<WeakReference<Filter>> iterator = mFilters.iterator();
+ while (iterator.hasNext()) {
+ WeakReference<Filter> wFilter = iterator.next();
+ if (wFilter.get() == null) {
+ iterator.remove();
+ }
}
}
}
}
+ return filter;
+ } finally {
+ mDemuxLock.unlock();
}
- return filter;
}
/**
@@ -1457,18 +1609,24 @@
*/
@Nullable
public Lnb openLnb(@CallbackExecutor @NonNull Executor executor, @NonNull LnbCallback cb) {
- Objects.requireNonNull(executor, "executor must not be null");
- Objects.requireNonNull(cb, "LnbCallback must not be null");
- if (mLnb != null) {
- mLnb.setCallback(executor, cb, this);
- return mLnb;
+ mLnbLock.lock();
+ try {
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(cb, "LnbCallback must not be null");
+ if (mLnb != null) {
+ mLnb.setCallback(executor, cb, this);
+ return mLnb;
+ }
+ if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, mLnbLock)
+ && mLnb != null) {
+ mLnb.setCallback(executor, cb, this);
+ setLnb(mLnb);
+ return mLnb;
+ }
+ return null;
+ } finally {
+ mLnbLock.unlock();
}
- if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB) && mLnb != null) {
- mLnb.setCallback(executor, cb, this);
- setLnb(mLnb);
- return mLnb;
- }
- return null;
}
/**
@@ -1483,20 +1641,25 @@
@Nullable
public Lnb openLnbByName(@NonNull String name, @CallbackExecutor @NonNull Executor executor,
@NonNull LnbCallback cb) {
- Objects.requireNonNull(name, "LNB name must not be null");
- Objects.requireNonNull(executor, "executor must not be null");
- Objects.requireNonNull(cb, "LnbCallback must not be null");
- Lnb newLnb = nativeOpenLnbByName(name);
- if (newLnb != null) {
- if (mLnb != null) {
- mLnb.close();
- mLnbHandle = null;
+ mLnbLock.lock();
+ try {
+ Objects.requireNonNull(name, "LNB name must not be null");
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(cb, "LnbCallback must not be null");
+ Lnb newLnb = nativeOpenLnbByName(name);
+ if (newLnb != null) {
+ if (mLnb != null) {
+ mLnb.close();
+ mLnbHandle = null;
+ }
+ mLnb = newLnb;
+ mLnb.setCallback(executor, cb, this);
+ setLnb(mLnb);
}
- mLnb = newLnb;
- mLnb.setCallback(executor, cb, this);
- setLnb(mLnb);
+ return mLnb;
+ } finally {
+ mLnbLock.unlock();
}
- return mLnb;
}
private boolean requestLnb() {
@@ -1518,10 +1681,15 @@
*/
@Nullable
public TimeFilter openTimeFilter() {
- if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
- return null;
+ mDemuxLock.lock();
+ try {
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+ return null;
+ }
+ return nativeOpenTimeFilter();
+ } finally {
+ mDemuxLock.unlock();
}
- return nativeOpenTimeFilter();
}
/**
@@ -1532,10 +1700,15 @@
@RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER)
@Nullable
public Descrambler openDescrambler() {
- if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
- return null;
+ mDemuxLock.lock();
+ try {
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+ return null;
+ }
+ return requestDescrambler();
+ } finally {
+ mDemuxLock.unlock();
}
- return requestDescrambler();
}
/**
@@ -1553,14 +1726,19 @@
@BytesLong long bufferSize,
@CallbackExecutor @NonNull Executor executor,
@NonNull OnRecordStatusChangedListener l) {
- Objects.requireNonNull(executor, "executor must not be null");
- Objects.requireNonNull(l, "OnRecordStatusChangedListener must not be null");
- if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
- return null;
+ mDemuxLock.lock();
+ try {
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(l, "OnRecordStatusChangedListener must not be null");
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+ return null;
+ }
+ DvrRecorder dvr = nativeOpenDvrRecorder(bufferSize);
+ dvr.setListener(executor, l);
+ return dvr;
+ } finally {
+ mDemuxLock.unlock();
}
- DvrRecorder dvr = nativeOpenDvrRecorder(bufferSize);
- dvr.setListener(executor, l);
- return dvr;
}
/**
@@ -1578,14 +1756,19 @@
@BytesLong long bufferSize,
@CallbackExecutor @NonNull Executor executor,
@NonNull OnPlaybackStatusChangedListener l) {
- Objects.requireNonNull(executor, "executor must not be null");
- Objects.requireNonNull(l, "OnPlaybackStatusChangedListener must not be null");
- if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) {
- return null;
+ mDemuxLock.lock();
+ try {
+ Objects.requireNonNull(executor, "executor must not be null");
+ Objects.requireNonNull(l, "OnPlaybackStatusChangedListener must not be null");
+ if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, mDemuxLock)) {
+ return null;
+ }
+ DvrPlayback dvr = nativeOpenDvrPlayback(bufferSize);
+ dvr.setListener(executor, l);
+ return dvr;
+ } finally {
+ mDemuxLock.unlock();
}
- DvrPlayback dvr = nativeOpenDvrPlayback(bufferSize);
- dvr.setListener(executor, l);
- return dvr;
}
/**
@@ -1602,6 +1785,8 @@
static public SharedFilter openSharedFilter(@NonNull Context context,
@NonNull String sharedFilterToken, @CallbackExecutor @NonNull Executor executor,
@NonNull SharedFilterCallback cb) {
+ // TODO: check what happenes when onReclaimResources() is called and see if
+ // this needs to be protected with TRMS lock
Objects.requireNonNull(sharedFilterToken, "sharedFilterToken must not be null");
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(cb, "SharedFilterCallback must not be null");
@@ -1665,22 +1850,28 @@
return granted;
}
- private boolean checkResource(int resourceType) {
+ private boolean checkResource(int resourceType, ReentrantLock localLock) {
switch (resourceType) {
case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: {
- if (mFrontendHandle == null && !requestFrontend()) {
+ if (mFrontendHandle == null && !requestResource(resourceType, localLock)) {
return false;
}
break;
}
case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB: {
- if (mLnb == null && !requestLnb()) {
+ if (mLnb == null && !requestResource(resourceType, localLock)) {
return false;
}
break;
}
case TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX: {
- if (mDemuxHandle == null && !requestDemux()) {
+ if (mDemuxHandle == null && !requestResource(resourceType, localLock)) {
+ return false;
+ }
+ break;
+ }
+ case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM: {
+ if (mFrontendCiCamHandle == null && !requestResource(resourceType, localLock)) {
return false;
}
break;
@@ -1691,24 +1882,91 @@
return true;
}
- private boolean checkCiCamResource(int ciCamId) {
- if (mFrontendCiCamHandle == null && !requestFrontendCiCam(ciCamId)) {
- return false;
+ // Expected flow of how to use this function is:
+ // 1) lock the localLock and check if the resource is already held
+ // 2) if yes, no need to call this function and continue with the handle with the lock held
+ // 3) if no, then first release the held lock and grab the TRMS lock to avoid deadlock
+ // 4) grab the local lock again and release the TRMS lock
+ // If localLock is null, we'll assume the caller does not want the lock related operations
+ private boolean requestResource(int resourceType, ReentrantLock localLock) {
+ boolean enableLockOperations = localLock != null;
+
+ // release the local lock first to avoid deadlock
+ if (enableLockOperations) {
+ if (localLock.isLocked()) {
+ localLock.unlock();
+ } else {
+ throw new IllegalStateException("local lock must be locked beforehand");
+ }
}
- return true;
+
+ // now safe to grab TRMS lock
+ if (enableLockOperations) {
+ acquireTRMSLock("requestResource:" + resourceType);
+ }
+
+ try {
+ // lock the local lock
+ if (enableLockOperations) {
+ localLock.lock();
+ }
+ switch (resourceType) {
+ case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND: {
+ return requestFrontend();
+ }
+ case TunerResourceManager.TUNER_RESOURCE_TYPE_LNB: {
+ return requestLnb();
+ }
+ case TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX: {
+ return requestDemux();
+ }
+ case TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM: {
+ return requestFrontendCiCam(mRequestedCiCamId);
+ }
+ default:
+ return false;
+ }
+ } finally {
+ if (enableLockOperations) {
+ releaseTRMSLock();
+ }
+ }
}
/* package */ void releaseLnb() {
- if (mLnbHandle != null) {
- // LNB handle can be null if it's opened by name.
- mTunerResourceManager.releaseLnb(mLnbHandle, mClientId);
- mLnbHandle = null;
+ acquireTRMSLock("releaseLnb()");
+ mLnbLock.lock();
+ try {
+ if (mLnbHandle != null) {
+ // LNB handle can be null if it's opened by name.
+ mTunerResourceManager.releaseLnb(mLnbHandle, mClientId);
+ mLnbHandle = null;
+ }
+ mLnb = null;
+ } finally {
+ releaseTRMSLock();
+ mLnbLock.unlock();
}
- mLnb = null;
}
/** @hide */
public int getClientId() {
return mClientId;
}
+
+ private void acquireTRMSLock(String functionNameForLog) {
+ if (DEBUG) {
+ Log.d(TAG, "ATTEMPT:acquireLock() in " + functionNameForLog
+ + "for clientId:" + mClientId);
+ }
+ if (!mTunerResourceManager.acquireLock(mClientId)) {
+ Log.e(TAG, "FAILED:acquireLock() in " + functionNameForLog
+ + " for clientId:" + mClientId + " - this can cause deadlock between"
+ + " Tuner API calls and onReclaimResources()");
+ }
+ }
+
+ private void releaseTRMSLock() {
+ mTunerResourceManager.releaseLock(mClientId);
+ }
}
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
index 403bfa7..89512a0 100644
--- a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
@@ -546,14 +546,14 @@
private final int mModulation;
private final int mTimeInterleaveMode;
private final int mCodeRate;
- private final int mNumOfSegment;
+ private final int mNumOfSegments;
private IsdbtLayerSettings(
- int modulation, int timeInterleaveMode, int codeRate, int numOfSegment) {
+ int modulation, int timeInterleaveMode, int codeRate, int numOfSegments) {
mModulation = modulation;
mTimeInterleaveMode = timeInterleaveMode;
mCodeRate = codeRate;
- mNumOfSegment = numOfSegment;
+ mNumOfSegments = numOfSegments;
}
/**
@@ -578,11 +578,11 @@
return mCodeRate;
}
/**
- * Gets Number of Segment.
+ * Gets Number of Segments.
*/
@IntRange(from = 0, to = 0xff)
- public int getNumberOfSegment() {
- return mNumOfSegment;
+ public int getNumberOfSegments() {
+ return mNumOfSegments;
}
/**
@@ -600,7 +600,7 @@
private int mModulation = MODULATION_UNDEFINED;
private int mTimeInterleaveMode = TIME_INTERLEAVE_MODE_UNDEFINED;
private int mCodeRate = DvbtFrontendSettings.CODERATE_UNDEFINED;
- private int mNumOfSegment = 0;
+ private int mNumOfSegments = 0;
private Builder() {}
@@ -633,14 +633,14 @@
return this;
}
/**
- * Sets number of segment.
+ * Sets number of segments.
*
* <p>Default value is 0.
*/
@NonNull
@IntRange(from = 0, to = 0xff)
- public Builder setNumberOfSegment(int numOfSegment) {
- mNumOfSegment = numOfSegment;
+ public Builder setNumberOfSegments(int numOfSegments) {
+ mNumOfSegments = numOfSegments;
return this;
}
@@ -650,7 +650,7 @@
@NonNull
public IsdbtLayerSettings build() {
return new IsdbtLayerSettings(
- mModulation, mTimeInterleaveMode, mCodeRate, mNumOfSegment);
+ mModulation, mTimeInterleaveMode, mCodeRate, mNumOfSegments);
}
}
}
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index 244fd0e..fe611c7 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -320,6 +320,48 @@
}
/**
+ * Grants the lock to the caller for public {@link Tuner} APIs
+ *
+ * <p>{@link Tuner} functions that call both [@link TunerResourceManager} APIs and
+ * grabs lock that are also used in {@link IResourcesReclaimListener#onReclaimResources()}
+ * must call this API before acquiring lock used in onReclaimResources().
+ *
+ * <p>This API will block until it releases the lock or fails
+ *
+ * @param clientId The ID of the caller.
+ *
+ * @return true if the lock is granted. If false is returned, calling this API again is not
+ * guaranteed to work and may be unrecoverrable. (This should not happen.)
+ */
+ public boolean acquireLock(int clientId) {
+ try {
+ return mService.acquireLock(clientId, Thread.currentThread().getId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Releases the lock to the caller for public {@link Tuner} APIs
+ *
+ * <p>This API must be called in pair with {@link #acquireLock(int, int)}
+ *
+ * <p>This API will block until it releases the lock or fails
+ *
+ * @param clientId The ID of the caller.
+ *
+ * @return true if the lock is granted. If false is returned, calling this API again is not
+ * guaranteed to work and may be unrecoverrable. (This should not happen.)
+ */
+ public boolean releaseLock(int clientId) {
+ try {
+ return mService.releaseLock(clientId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Requests a frontend resource.
*
* <p>There are three possible scenarios:
diff --git a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index 7bc5058..5f35820 100644
--- a/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/aidl/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -412,4 +412,34 @@
* @param resourceType The resource type to restore the map for.
*/
void restoreResourceMap(in int resourceType);
+
+ /**
+ * Grants the lock to the caller for public {@link Tuner} APIs
+ *
+ * <p>{@link Tuner} functions that call both [@link TunerResourceManager} APIs and
+ * grabs lock that are also used in {@link IResourcesReclaimListener#onReclaimResources()}
+ * must call this API before acquiring lock used in onReclaimResources().
+ *
+ * <p>This API will block until it releases the lock or fails
+ *
+ * @param clientId The ID of the caller.
+ *
+ * @return true if the lock is granted. If false is returned, calling this API again is not
+ * guaranteed to work and may be unrecoverrable. (This should not happen.)
+ */
+ boolean acquireLock(in int clientId, in long clientThreadId);
+
+ /**
+ * Releases the lock to the caller for public {@link Tuner} APIs
+ *
+ * <p>This API must be called in pair with {@link #acquireLock(int, int)}
+ *
+ * <p>This API will block until it releases the lock or fails
+ *
+ * @param clientId The ID of the caller.
+ *
+ * @return true if the lock is granted. If false is returned, calling this API again is not
+ * guaranteed to work and may be unrecoverrable. (This should not happen.)
+ */
+ boolean releaseLock(in int clientId);
}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 4c1ed45..ded9652 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -2961,7 +2961,7 @@
frontendIsdbtSettings.layerSettings[i].coderate = static_cast<FrontendIsdbtCoderate>(
env->GetIntField(layer, env->GetFieldID(layerClazz, "mCodeRate", "I")));
frontendIsdbtSettings.layerSettings[i].numOfSegment =
- env->GetIntField(layer, env->GetFieldID(layerClazz, "mNumOfSegment", "I"));
+ env->GetIntField(layer, env->GetFieldID(layerClazz, "mNumOfSegments", "I"));
}
frontendSettings.set<FrontendSettings::Tag::isdbt>(frontendIsdbtSettings);
diff --git a/packages/EasterEgg/AndroidManifest.xml b/packages/EasterEgg/AndroidManifest.xml
index 9d3ce34..cc7bb4a 100644
--- a/packages/EasterEgg/AndroidManifest.xml
+++ b/packages/EasterEgg/AndroidManifest.xml
@@ -15,6 +15,8 @@
<!-- controls -->
<uses-permission android:name="android.permission.BIND_CONTROLS" />
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+
<application
android:icon="@drawable/icon"
android:label="@string/app_name">
diff --git a/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java b/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
index 02475dd..ec8d779 100644
--- a/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
+++ b/packages/Nsd/service/src/com/android/server/NativeDaemonConnector.java
@@ -20,16 +20,15 @@
import android.net.LocalSocketAddress;
import android.os.Build;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.util.LocalLog;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.power.ShutdownThread;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -83,13 +82,6 @@
NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl) {
- this(callbacks, socket, responseQueueSize, logTag, maxLogSize, wl,
- FgThread.get().getLooper());
- }
-
- NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
- int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl,
- Looper looper) {
mCallbacks = callbacks;
mSocket = socket;
mResponseQueue = new ResponseQueue(responseQueueSize);
@@ -97,10 +89,12 @@
if (mWakeLock != null) {
mWakeLock.setReferenceCounted(true);
}
- mLooper = looper;
mSequenceNumber = new AtomicInteger(0);
TAG = logTag != null ? logTag : "NativeDaemonConnector";
mLocalLog = new LocalLog(maxLogSize);
+ final HandlerThread thread = new HandlerThread(TAG);
+ thread.start();
+ mLooper = thread.getLooper();
}
/**
@@ -135,23 +129,15 @@
mCallbackHandler = new Handler(mLooper, this);
while (true) {
- if (isShuttingDown()) break;
try {
listenToSocket();
} catch (Exception e) {
loge("Error in NativeDaemonConnector: " + e);
- if (isShuttingDown()) break;
SystemClock.sleep(5000);
}
}
}
- private static boolean isShuttingDown() {
- String shutdownAct = SystemProperties.get(
- ShutdownThread.SHUTDOWN_ACTION_PROPERTY, "");
- return shutdownAct != null && shutdownAct.length() > 0;
- }
-
@Override
public boolean handleMessage(Message msg) {
final String event = (String) msg.obj;
diff --git a/packages/Nsd/service/src/com/android/server/NsdService.java b/packages/Nsd/service/src/com/android/server/NsdService.java
index 76ecea7..497107d 100644
--- a/packages/Nsd/service/src/com/android/server/NsdService.java
+++ b/packages/Nsd/service/src/com/android/server/NsdService.java
@@ -16,12 +16,9 @@
package com.android.server;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.database.ContentObserver;
-import android.net.Uri;
import android.net.nsd.INsdManager;
import android.net.nsd.INsdManagerCallback;
import android.net.nsd.INsdServiceConnector;
@@ -33,7 +30,6 @@
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.provider.Settings;
import android.util.Base64;
import android.util.Log;
import android.util.Pair;
@@ -66,7 +62,6 @@
private static final long CLEANUP_DELAY_MS = 10000;
private final Context mContext;
- private final NsdSettings mNsdSettings;
private final NsdStateMachine mNsdStateMachine;
private final DaemonConnection mDaemon;
private final NativeCallbackReceiver mDaemonCallback;
@@ -121,30 +116,14 @@
this.removeMessages(NsdManager.DAEMON_CLEANUP);
}
- /**
- * Observes the NSD on/off setting, and takes action when changed.
- */
- private void registerForNsdSetting() {
- final ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
- @Override
- public void onChange(boolean selfChange) {
- notifyEnabled(isNsdEnabled());
- }
- };
-
- final Uri uri = Settings.Global.getUriFor(Settings.Global.NSD_ON);
- mNsdSettings.registerContentObserver(uri, contentObserver);
- }
-
NsdStateMachine(String name, Handler handler) {
super(name, handler);
addState(mDefaultState);
addState(mDisabledState, mDefaultState);
addState(mEnabledState, mDefaultState);
- State initialState = isNsdEnabled() ? mEnabledState : mDisabledState;
+ State initialState = mEnabledState;
setInitialState(initialState);
setLogRecSize(25);
- registerForNsdSetting();
}
class DefaultState extends State {
@@ -580,11 +559,9 @@
}
@VisibleForTesting
- NsdService(Context ctx, NsdSettings settings, Handler handler,
- DaemonConnectionSupplier fn, long cleanupDelayMs) {
+ NsdService(Context ctx, Handler handler, DaemonConnectionSupplier fn, long cleanupDelayMs) {
mCleanupDelayMs = cleanupDelayMs;
mContext = ctx;
- mNsdSettings = settings;
mNsdStateMachine = new NsdStateMachine(TAG, handler);
mNsdStateMachine.start();
mDaemonCallback = new NativeCallbackReceiver();
@@ -592,12 +569,11 @@
}
public static NsdService create(Context context) throws InterruptedException {
- NsdSettings settings = NsdSettings.makeDefault(context);
HandlerThread thread = new HandlerThread(TAG);
thread.start();
Handler handler = new Handler(thread.getLooper());
- NsdService service = new NsdService(context, settings, handler,
- DaemonConnection::new, CLEANUP_DELAY_MS);
+ NsdService service =
+ new NsdService(context, handler, DaemonConnection::new, CLEANUP_DELAY_MS);
service.mDaemonCallback.awaitConnection();
return service;
}
@@ -669,10 +645,6 @@
}
}
- private void notifyEnabled(boolean isEnabled) {
- mNsdStateMachine.sendMessage(isEnabled ? NsdManager.ENABLE : NsdManager.DISABLE);
- }
-
private void sendNsdStateChangeBroadcast(boolean isEnabled) {
final Intent intent = new Intent(NsdManager.ACTION_NSD_STATE_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -681,14 +653,6 @@
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
- private boolean isNsdEnabled() {
- boolean ret = mNsdSettings.isEnabled();
- if (DBG) {
- Log.d(TAG, "Network service discovery is " + (ret ? "enabled" : "disabled"));
- }
- return ret;
- }
-
private int getUniqueId() {
if (++mUniqueId == INVALID_ID) return ++mUniqueId;
return mUniqueId;
@@ -1075,35 +1039,4 @@
}
}
}
-
- /**
- * Interface which encapsulates dependencies of NsdService that are hard to mock, hard to
- * override, or have side effects on global state in unit tests.
- */
- @VisibleForTesting
- public interface NsdSettings {
- boolean isEnabled();
- void putEnabledStatus(boolean isEnabled);
- void registerContentObserver(Uri uri, ContentObserver observer);
-
- static NsdSettings makeDefault(Context context) {
- final ContentResolver resolver = context.getContentResolver();
- return new NsdSettings() {
- @Override
- public boolean isEnabled() {
- return Settings.Global.getInt(resolver, Settings.Global.NSD_ON, 1) == 1;
- }
-
- @Override
- public void putEnabledStatus(boolean isEnabled) {
- Settings.Global.putInt(resolver, Settings.Global.NSD_ON, isEnabled ? 1 : 0);
- }
-
- @Override
- public void registerContentObserver(Uri uri, ContentObserver observer) {
- resolver.registerContentObserver(uri, false, observer);
- }
- };
- }
- }
}
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml
index 2272a37..2624a41 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml
@@ -21,10 +21,10 @@
<dimen name="settingslib_switchbar_margin">16dp</dimen>
<!-- Size of layout margin left -->
- <dimen name="settingslib_switchbar_padding_left">24dp</dimen>
+ <dimen name="settingslib_switchbar_padding_left">20dp</dimen>
<!-- Size of layout margin right -->
- <dimen name="settingslib_switchbar_padding_right">16dp</dimen>
+ <dimen name="settingslib_switchbar_padding_right">20dp</dimen>
<!-- Minimum width of switch -->
<dimen name="settingslib_min_switch_width">52dp</dimen>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
index 6362882..157a54e 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
@@ -24,7 +24,7 @@
<dimen name="settingslib_restricted_icon_margin_end">16dp</dimen>
<!-- Size of title margin -->
- <dimen name="settingslib_switch_title_margin">16dp</dimen>
+ <dimen name="settingslib_switch_title_margin">24dp</dimen>
<!-- SwitchBar sub settings margin start / end -->
<dimen name="settingslib_switchbar_subsettings_margin_start">72dp</dimen>
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index cb858c8..6d5615d 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -109,7 +109,7 @@
a.recycle();
}
- setBackground(true);
+ setBackground(mSwitch.isChecked());
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
index ff00fb3..0f34023 100644
--- a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
@@ -26,6 +26,7 @@
import android.system.StructUtsname;
import android.telephony.PhoneNumberUtils;
import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.BidiFormatter;
import android.text.TextDirectionHeuristics;
@@ -33,7 +34,9 @@
import android.text.format.DateFormat;
import android.util.Log;
+import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
+import androidx.core.os.BuildCompat;
import java.io.BufferedReader;
import java.io.FileReader;
@@ -179,10 +182,8 @@
SubscriptionInfo subscriptionInfo) {
String formattedNumber = null;
if (subscriptionInfo != null) {
- final TelephonyManager telephonyManager = context.getSystemService(
- TelephonyManager.class);
- final String rawNumber = telephonyManager.createForSubscriptionId(
- subscriptionInfo.getSubscriptionId()).getLine1Number();
+ final String rawNumber = getRawPhoneNumber(
+ context, subscriptionInfo.getSubscriptionId());
if (!TextUtils.isEmpty(rawNumber)) {
formattedNumber = PhoneNumberUtils.formatNumber(rawNumber);
}
@@ -194,12 +195,10 @@
List<SubscriptionInfo> subscriptionInfoList) {
StringBuilder sb = new StringBuilder();
if (subscriptionInfoList != null) {
- final TelephonyManager telephonyManager = context.getSystemService(
- TelephonyManager.class);
final int count = subscriptionInfoList.size();
for (SubscriptionInfo subscriptionInfo : subscriptionInfoList) {
- final String rawNumber = telephonyManager.createForSubscriptionId(
- subscriptionInfo.getSubscriptionId()).getLine1Number();
+ final String rawNumber = getRawPhoneNumber(
+ context, subscriptionInfo.getSubscriptionId());
if (!TextUtils.isEmpty(rawNumber)) {
sb.append(PhoneNumberUtils.formatNumber(rawNumber)).append("\n");
}
@@ -219,4 +218,21 @@
final String phoneNumber = getFormattedPhoneNumber(context, subscriptionInfo);
return BidiFormatter.getInstance().unicodeWrap(phoneNumber, TextDirectionHeuristics.LTR);
}
+
+ private static String getRawPhoneNumber(Context context, int subscriptionId) {
+ if (BuildCompat.isAtLeastT()) {
+ return getRawPhoneNumberFromT(context, subscriptionId);
+ } else {
+ final TelephonyManager telephonyManager = context.getSystemService(
+ TelephonyManager.class);
+ return telephonyManager.createForSubscriptionId(subscriptionId).getLine1Number();
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+ private static String getRawPhoneNumberFromT(Context context, int subscriptionId) {
+ final SubscriptionManager subscriptionManager = context.getSystemService(
+ SubscriptionManager.class);
+ return subscriptionManager.getPhoneNumber(subscriptionId);
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 389892e..8ac4e38 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -170,6 +170,12 @@
}
@VisibleForTesting
+ void registerIntentReceiver() {
+ mContext.registerReceiverAsUser(mBroadcastReceiver, mUserHandle, mAdapterIntentFilter,
+ null, mReceiverHandler);
+ }
+
+ @VisibleForTesting
void addProfileHandler(String action, Handler handler) {
mHandlerMap.put(action, handler);
mProfileIntentFilter.addAction(action);
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index 8b17be1..dee6894 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -85,6 +85,7 @@
@VisibleForTesting
protected Context mContext;
private final int mThemeResId;
+ private final boolean mCancelIsNeutral;
@VisibleForTesting
protected TextView mZenAlarmWarning;
@VisibleForTesting
@@ -101,8 +102,13 @@
}
public EnableZenModeDialog(Context context, int themeResId) {
+ this(context, themeResId, false /* cancelIsNeutral */);
+ }
+
+ public EnableZenModeDialog(Context context, int themeResId, boolean cancelIsNeutral) {
mContext = context;
mThemeResId = themeResId;
+ mCancelIsNeutral = cancelIsNeutral;
}
public AlertDialog createDialog() {
@@ -115,7 +121,6 @@
final AlertDialog.Builder builder = new AlertDialog.Builder(mContext, mThemeResId)
.setTitle(R.string.zen_mode_settings_turn_on_dialog_title)
- .setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.zen_mode_enable_dialog_turn_on,
new DialogInterface.OnClickListener() {
@Override
@@ -145,6 +150,12 @@
}
});
+ if (mCancelIsNeutral) {
+ builder.setNeutralButton(R.string.cancel, null);
+ } else {
+ builder.setNegativeButton(R.string.cancel, null);
+ }
+
View contentView = getContentView();
bindConditions(forever());
builder.setView(contentView);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index 4bff78f..bee466d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -109,7 +109,7 @@
/* handler= */ null, /* userHandle= */ null);
verify(mockContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class),
- eq(null), eq(null));
+ eq(null), eq(null), eq(Context.RECEIVER_EXPORTED));
}
@Test
@@ -120,7 +120,7 @@
/* handler= */ null, UserHandle.ALL);
verify(mockContext).registerReceiverAsUser(any(BroadcastReceiver.class), eq(UserHandle.ALL),
- any(IntentFilter.class), eq(null), eq(null));
+ any(IntentFilter.class), eq(null), eq(null), eq(Context.RECEIVER_EXPORTED));
}
/**
@@ -129,6 +129,7 @@
@Test
public void intentWithExtraState_audioStateChangedShouldDispatchToRegisterCallback() {
mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
mContext.sendBroadcast(mIntent);
@@ -142,6 +143,7 @@
@Test
public void intentWithExtraState_phoneStateChangedShouldDispatchToRegisterCallback() {
mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
mContext.sendBroadcast(mIntent);
@@ -167,6 +169,7 @@
@Test
public void dispatchAclConnectionStateChanged_aclDisconnected_shouldDispatchCallback() {
mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
@@ -179,6 +182,7 @@
@Test
public void dispatchAclConnectionStateChanged_aclConnected_shouldDispatchCallback() {
mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
@@ -192,6 +196,7 @@
public void dispatchAclConnectionStateChanged_aclDisconnected_shouldNotCallbackSubDevice() {
when(mCachedDeviceManager.isSubDevice(mBluetoothDevice)).thenReturn(true);
mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
@@ -205,6 +210,7 @@
public void dispatchAclConnectionStateChanged_aclConnected_shouldNotCallbackSubDevice() {
when(mCachedDeviceManager.isSubDevice(mBluetoothDevice)).thenReturn(true);
mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
@@ -218,6 +224,7 @@
public void dispatchAclConnectionStateChanged_findDeviceReturnNull_shouldNotDispatchCallback() {
when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(null);
mBluetoothEventManager.registerCallback(mBluetoothCallback);
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
@@ -354,6 +361,7 @@
@Test
public void showUnbondMessage_reasonAuthTimeout_showCorrectedErrorCode() {
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
@@ -369,6 +377,7 @@
@Test
public void showUnbondMessage_reasonRemoteDeviceDown_showCorrectedErrorCode() {
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
@@ -385,6 +394,7 @@
@Test
public void showUnbondMessage_reasonAuthRejected_showCorrectedErrorCode() {
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
@@ -400,6 +410,7 @@
@Test
public void showUnbondMessage_reasonAuthFailed_showCorrectedErrorCode() {
+ mBluetoothEventManager.registerIntentReceiver();
mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 892cd63..1e2d4e9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -25,6 +25,7 @@
import static android.provider.Settings.SET_ALL_RESULT_DISABLED;
import static android.provider.Settings.SET_ALL_RESULT_FAILURE;
import static android.provider.Settings.SET_ALL_RESULT_SUCCESS;
+import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER;
import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
@@ -3623,7 +3624,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 207;
+ private static final int SETTINGS_VERSION = 208;
private final int mUserId;
@@ -5472,6 +5473,30 @@
currentVersion = 207;
}
+ if (currentVersion == 207) {
+ // Version 207: Reset the
+ // Secure#ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT as enabled
+ // status for showing the tooltips.
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ final Setting accessibilityButtonMode = secureSettings.getSettingLocked(
+ Secure.ACCESSIBILITY_BUTTON_MODE);
+ if (!accessibilityButtonMode.isNull()
+ && accessibilityButtonMode.getValue().equals(
+ String.valueOf(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU))) {
+ if (isGestureNavigateEnabled()
+ && hasValueInA11yButtonTargets(secureSettings)) {
+ secureSettings.insertSettingLocked(
+ Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
+ /* enabled */ "1",
+ /* tag= */ null,
+ /* makeDefault= */ false,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ }
+
+ currentVersion = 208;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index c5c3dd7..262cf53 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -571,6 +571,9 @@
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
+ <!-- Permission required for CTS test - CallAudioInterceptionTest -->
+ <uses-permission android:name="android.permission.CALL_AUDIO_INTERCEPTION" />
+
<!-- Permission required for CTS test - CtsRotationResolverServiceDeviceTestCases -->
<uses-permission android:name="android.permission.MANAGE_ROTATION_RESOLVER" />
@@ -600,6 +603,9 @@
<!-- Permission required for ATS test - CarDevicePolicyManagerTest -->
<uses-permission android:name="android.permission.LOCK_DEVICE" />
+ <!-- Permission required for CTS test - CtsSafetyCenterTestCases -->
+ <uses-permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index bcf95d8..5c19b41 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -121,6 +121,7 @@
<uses-permission android:name="android.permission.SET_ORIENTATION" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.MONITOR_INPUT" />
+ <uses-permission android:name="android.permission.ALLOW_SLIPPERY_TOUCHES" />
<uses-permission android:name="android.permission.INPUT_CONSUMER" />
<!-- DreamManager -->
diff --git a/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_x.xml b/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_x.xml
deleted file mode 100644
index 620dd48..0000000
--- a/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_x.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2021 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0, 0 C 0.1217, 0.0462, 0.15, 0.4686, 0.1667, 0.66 C 0.1834, 0.8878, 0.1667, 1, 1, 1" />
\ No newline at end of file
diff --git a/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_y.xml b/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_y.xml
deleted file mode 100644
index a268abc..0000000
--- a/packages/SystemUI/animation/res/interpolator/launch_animation_interpolator_y.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2021 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.
--->
-<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
- android:pathData="M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1" />
\ No newline at end of file
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index fb80f1c..a0d335d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -21,6 +21,7 @@
import android.app.PendingIntent
import android.app.TaskInfo
import android.graphics.Matrix
+import android.graphics.Path
import android.graphics.Rect
import android.graphics.RectF
import android.os.Looper
@@ -34,6 +35,7 @@
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import android.view.animation.Interpolator
import android.view.animation.PathInterpolator
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
@@ -45,16 +47,46 @@
* A class that allows activities to be started in a seamless way from a view that is transforming
* nicely into the starting window.
*/
-class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) {
+class ActivityLaunchAnimator(
+ private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS)
+) {
companion object {
+ @JvmField
+ val TIMINGS = LaunchAnimator.Timings(
+ totalDuration = 500L,
+ contentBeforeFadeOutDelay = 0L,
+ contentBeforeFadeOutDuration = 150L,
+ contentAfterFadeInDelay = 150L,
+ contentAfterFadeInDuration = 183L
+ )
+
+ val INTERPOLATORS = LaunchAnimator.Interpolators(
+ positionInterpolator = Interpolators.EMPHASIZED,
+ positionXInterpolator = createPositionXInterpolator(),
+ contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN,
+ contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f)
+ )
+
+ /** Durations & interpolators for the navigation bar fading in & out. */
private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
- private const val ANIMATION_DELAY_NAV_FADE_IN =
- LaunchAnimator.ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN
+ private val ANIMATION_DELAY_NAV_FADE_IN =
+ TIMINGS.totalDuration - ANIMATION_DURATION_NAV_FADE_IN
+
+ private val NAV_FADE_IN_INTERPOLATOR = Interpolators.STANDARD_DECELERATE
+ private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
+
+ /** The time we wait before timing out the remote animation after starting the intent. */
private const val LAUNCH_TIMEOUT = 1000L
- private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f)
- private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
+ private fun createPositionXInterpolator(): Interpolator {
+ val path = Path().apply {
+ moveTo(0f, 0f)
+ cubicTo(0.1217f, 0.0462f, 0.15f, 0.4686f, 0.1667f, 0.66f)
+ cubicTo(0.1834f, 0.8878f, 0.1667f, 1f, 1f, 1f)
+ }
+ return PathInterpolator(path)
+ }
}
/**
@@ -107,8 +139,8 @@
val animationAdapter = if (!hideKeyguardWithAnimation) {
RemoteAnimationAdapter(
runner,
- LaunchAnimator.ANIMATION_DURATION,
- LaunchAnimator.ANIMATION_DURATION - 150 /* statusBarTransitionDelay */
+ TIMINGS.totalDuration,
+ TIMINGS.totalDuration - 150 /* statusBarTransitionDelay */
)
} else {
null
@@ -448,7 +480,7 @@
state: LaunchAnimator.State,
linearProgress: Float
) {
- val fadeInProgress = LaunchAnimator.getProgress(linearProgress,
+ val fadeInProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress,
ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_DURATION_NAV_FADE_OUT)
val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash)
@@ -463,7 +495,7 @@
.withWindowCrop(windowCrop)
.withVisibility(true)
} else {
- val fadeOutProgress = LaunchAnimator.getProgress(linearProgress, 0,
+ val fadeOutProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress, 0,
ANIMATION_DURATION_NAV_FADE_OUT)
params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress))
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index de82ebd..066e169 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -20,7 +20,6 @@
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.app.Dialog
-import android.content.Context
import android.graphics.Color
import android.graphics.Rect
import android.os.Looper
@@ -28,10 +27,11 @@
import android.util.Log
import android.util.MathUtils
import android.view.GhostView
+import android.view.SurfaceControl
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewTreeObserver.OnPreDrawListener
+import android.view.ViewRootImpl
import android.view.WindowManager
import android.widget.FrameLayout
import kotlin.math.roundToInt
@@ -42,12 +42,20 @@
* A class that allows dialogs to be started in a seamless way from a view that is transforming
* nicely into the starting dialog.
*/
-class DialogLaunchAnimator(
- private val context: Context,
- private val launchAnimator: LaunchAnimator,
- private val dreamManager: IDreamManager
+class DialogLaunchAnimator @JvmOverloads constructor(
+ private val dreamManager: IDreamManager,
+ private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
+ private var isForTesting: Boolean = false
) {
private companion object {
+ private val TIMINGS = ActivityLaunchAnimator.TIMINGS
+
+ // We use the same interpolator for X and Y axis to make sure the dialog does not move out
+ // of the screen bounds during the animation.
+ private val INTERPOLATORS = ActivityLaunchAnimator.INTERPOLATORS.copy(
+ positionXInterpolator = ActivityLaunchAnimator.INTERPOLATORS.positionInterpolator
+ )
+
private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.launch_animation_running
}
@@ -96,14 +104,14 @@
animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
val animatedDialog = AnimatedDialog(
- context,
launchAnimator,
dreamManager,
animateFrom,
onDialogDismissed = { openedDialogs.remove(it) },
dialog = dialog,
animateBackgroundBoundsChange,
- animatedParent
+ animatedParent,
+ isForTesting
)
openedDialogs.add(animatedDialog)
@@ -157,7 +165,6 @@
}
private class AnimatedDialog(
- private val context: Context,
private val launchAnimator: LaunchAnimator,
private val dreamManager: IDreamManager,
@@ -174,10 +181,16 @@
val dialog: Dialog,
/** Whether we should animate the dialog background when its bounds change. */
- private val animateBackgroundBoundsChange: Boolean,
+ animateBackgroundBoundsChange: Boolean,
/** Launch animation corresponding to the parent [AnimatedDialog]. */
- private val parentAnimatedDialog: AnimatedDialog? = null
+ private val parentAnimatedDialog: AnimatedDialog? = null,
+
+ /**
+ * Whether we are currently running in a test, in which case we need to disable
+ * synchronization.
+ */
+ private val isForTesting: Boolean
) {
/**
* The DecorView of this dialog window.
@@ -266,14 +279,14 @@
// and the view that we added so that we can dismiss the dialog when this view is
// clicked. This is necessary because DecorView overrides onTouchEvent and therefore we
// can't set the click listener directly on the (now fullscreen) DecorView.
- val fullscreenTransparentBackground = FrameLayout(context)
+ val fullscreenTransparentBackground = FrameLayout(dialog.context)
decorView.addView(
fullscreenTransparentBackground,
0 /* index */,
FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
)
- val dialogContentWithBackground = FrameLayout(context)
+ val dialogContentWithBackground = FrameLayout(dialog.context)
dialogContentWithBackground.background = decorView.background
// Make the window background transparent. Note that setting the window (or DecorView)
@@ -365,59 +378,77 @@
// Show the dialog.
dialog.show()
- // Add a temporary touch surface ghost as soon as the window is ready to draw. This
- // temporary ghost will be drawn together with the touch surface, but in the dialog
- // window. Once it is drawn, we will make the touch surface invisible, and then start the
- // animation. We do all this synchronization to avoid flicker that would occur if we made
- // the touch surface invisible too early (before its ghost is drawn), leading to one or more
- // frames with a hole instead of the touch surface (or its ghost).
- decorView.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
- override fun onPreDraw(): Boolean {
- decorView.viewTreeObserver.removeOnPreDrawListener(this)
- addTemporaryTouchSurfaceGhost()
- return true
- }
- })
- decorView.invalidate()
+ addTouchSurfaceGhost()
}
- private fun addTemporaryTouchSurfaceGhost() {
+ private fun addTouchSurfaceGhost() {
+ if (decorView.viewRootImpl == null) {
+ // Make sure that we have access to the dialog view root to synchronize the creation of
+ // the ghost.
+ decorView.post(::addTouchSurfaceGhost)
+ return
+ }
+
// Create a ghost of the touch surface (which will make the touch surface invisible) and add
- // it to the dialog. We will wait for this ghost to be drawn before starting the animation.
- val ghost = GhostView.addGhost(touchSurface, decorView)
-
- // The ghost of the touch surface was just created, so the touch surface was made invisible.
- // We make it visible again until the ghost is actually drawn.
- touchSurface.visibility = View.VISIBLE
-
- // Wait for the ghost to be drawn before continuing.
- ghost.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
- override fun onPreDraw(): Boolean {
- ghost.viewTreeObserver.removeOnPreDrawListener(this)
- onTouchSurfaceGhostDrawn()
- return true
- }
+ // it to the host dialog. We trigger a one off synchronization to make sure that this is
+ // done in sync between the two different windows.
+ synchronizeNextDraw(then = {
+ isTouchSurfaceGhostDrawn = true
+ maybeStartLaunchAnimation()
})
- ghost.invalidate()
+ GhostView.addGhost(touchSurface, decorView)
+
+ // The ghost of the touch surface was just created, so the touch surface is currently
+ // invisible. We need to make sure that it stays invisible as long as the dialog is shown or
+ // animating.
+ (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
}
- private fun onTouchSurfaceGhostDrawn() {
- // Make the touch surface invisible and make sure that it stays invisible as long as the
- // dialog is shown or animating.
- touchSurface.visibility = View.INVISIBLE
- (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
+ /**
+ * Synchronize the next draw of the touch surface and dialog view roots so that they are
+ * performed at the same time, in the same transaction. This is necessary to make sure that the
+ * ghost of the touch surface is drawn at the same time as the touch surface is made invisible
+ * (or inversely, removed from the UI when the touch surface is made visible).
+ */
+ private fun synchronizeNextDraw(then: () -> Unit) {
+ if (isForTesting || !touchSurface.isAttachedToWindow || touchSurface.viewRootImpl == null ||
+ !decorView.isAttachedToWindow || decorView.viewRootImpl == null) {
+ // No need to synchronize if either the touch surface or dialog view is not attached
+ // to a window.
+ then()
+ return
+ }
- // Add a pre draw listener to (maybe) start the animation once the touch surface is
- // actually invisible.
- touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
- override fun onPreDraw(): Boolean {
- touchSurface.viewTreeObserver.removeOnPreDrawListener(this)
- isTouchSurfaceGhostDrawn = true
- maybeStartLaunchAnimation()
- return true
+ // Consume the next frames of both view roots to make sure the ghost view is drawn at
+ // exactly the same time as when the touch surface is made invisible.
+ var remainingTransactions = 0
+ val mergedTransactions = SurfaceControl.Transaction()
+
+ fun onTransaction(transaction: SurfaceControl.Transaction?) {
+ remainingTransactions--
+ transaction?.let { mergedTransactions.merge(it) }
+
+ if (remainingTransactions == 0) {
+ mergedTransactions.apply()
+ then()
}
- })
- touchSurface.invalidate()
+ }
+
+ fun consumeNextDraw(viewRootImpl: ViewRootImpl) {
+ if (viewRootImpl.consumeNextDraw(::onTransaction)) {
+ remainingTransactions++
+
+ // Make sure we trigger a traversal.
+ viewRootImpl.view.invalidate()
+ }
+ }
+
+ consumeNextDraw(touchSurface.viewRootImpl)
+ consumeNextDraw(decorView.viewRootImpl)
+
+ if (remainingTransactions == 0) {
+ then()
+ }
}
private fun findFirstViewGroupWithBackground(view: View): ViewGroup? {
@@ -483,7 +514,7 @@
private fun onDialogDismissed() {
if (Looper.myLooper() != Looper.getMainLooper()) {
- context.mainExecutor.execute { onDialogDismissed() }
+ dialog.context.mainExecutor.execute { onDialogDismissed() }
return
}
@@ -556,25 +587,12 @@
.removeOnLayoutChangeListener(backgroundLayoutListener)
}
- // The animated ghost was just removed. We create a temporary ghost that will be
- // removed only once we draw the touch surface, to avoid flickering that would
- // happen when removing the ghost too early (before the touch surface is drawn).
- GhostView.addGhost(touchSurface, decorView)
-
- touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
- override fun onPreDraw(): Boolean {
- touchSurface.viewTreeObserver.removeOnPreDrawListener(this)
-
- // Now that the touch surface was drawn, we can remove the temporary ghost
- // and instantly dismiss the dialog.
- GhostView.removeGhost(touchSurface)
- onAnimationFinished(true /* instantDismiss */)
- onDialogDismissed(this@AnimatedDialog)
-
- return true
- }
+ // Make sure that the removal of the ghost and making the touch surface visible is
+ // done at the same time.
+ synchronizeNextDraw(then = {
+ onAnimationFinished(true /* instantDismiss */)
+ onDialogDismissed(this@AnimatedDialog)
})
- touchSurface.invalidate()
}
)
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
index 3bf6c5e..ebe96eb 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
@@ -27,25 +27,19 @@
import android.util.MathUtils
import android.view.View
import android.view.ViewGroup
-import android.view.animation.AnimationUtils
-import android.view.animation.PathInterpolator
+import android.view.animation.Interpolator
+import com.android.systemui.animation.Interpolators.LINEAR
import kotlin.math.roundToInt
private const val TAG = "LaunchAnimator"
/** A base class to animate a window launch (activity or dialog) from a view . */
-class LaunchAnimator @JvmOverloads constructor(
- context: Context,
- private val isForTesting: Boolean = false
+class LaunchAnimator(
+ private val timings: Timings,
+ private val interpolators: Interpolators
) {
companion object {
internal const val DEBUG = false
- const val ANIMATION_DURATION = 500L
- private const val ANIMATION_DURATION_FADE_OUT_CONTENT = 150L
- private const val ANIMATION_DURATION_FADE_IN_WINDOW = 183L
- private const val ANIMATION_DELAY_FADE_IN_WINDOW = ANIMATION_DURATION_FADE_OUT_CONTENT
-
- private val WINDOW_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0.6f, 1f)
private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
/**
@@ -53,23 +47,20 @@
* sub-animation starting [delay] ms after the launch animation and that lasts [duration].
*/
@JvmStatic
- fun getProgress(linearProgress: Float, delay: Long, duration: Long): Float {
+ fun getProgress(
+ timings: Timings,
+ linearProgress: Float,
+ delay: Long,
+ duration: Long
+ ): Float {
return MathUtils.constrain(
- (linearProgress * ANIMATION_DURATION - delay) / duration,
+ (linearProgress * timings.totalDuration - delay) / duration,
0.0f,
1.0f
)
}
}
- /** The interpolator used for the width, height, Y position and corner radius. */
- private val animationInterpolator = AnimationUtils.loadInterpolator(context,
- R.interpolator.launch_animation_interpolator_y)
-
- /** The interpolator used for the X position. */
- private val animationInterpolatorX = AnimationUtils.loadInterpolator(context,
- R.interpolator.launch_animation_interpolator_x)
-
private val launchContainerLocation = IntArray(2)
private val cornerRadii = FloatArray(8)
@@ -159,6 +150,45 @@
fun cancel()
}
+ /** The timings (durations and delays) used by this animator. */
+ class Timings(
+ /** The total duration of the animation. */
+ val totalDuration: Long,
+
+ /** The time to wait before fading out the expanding content. */
+ val contentBeforeFadeOutDelay: Long,
+
+ /** The duration of the expanding content fade out. */
+ val contentBeforeFadeOutDuration: Long,
+
+ /**
+ * The time to wait before fading in the expanded content (usually an activity or dialog
+ * window).
+ */
+ val contentAfterFadeInDelay: Long,
+
+ /** The duration of the expanded content fade in. */
+ val contentAfterFadeInDuration: Long
+ )
+
+ /** The interpolators used by this animator. */
+ data class Interpolators(
+ /** The interpolator used for the Y position, width, height and corner radius. */
+ val positionInterpolator: Interpolator,
+
+ /**
+ * The interpolator used for the X position. This can be different than
+ * [positionInterpolator] to create an arc-path during the animation.
+ */
+ val positionXInterpolator: Interpolator = positionInterpolator,
+
+ /** The interpolator used when fading out the expanding content. */
+ val contentBeforeFadeOutInterpolator: Interpolator,
+
+ /** The interpolator used when fading in the expanded content. */
+ val contentAfterFadeInInterpolator: Interpolator
+ )
+
/**
* Start a launch animation controlled by [controller] towards [endState]. An intermediary
* layer with [windowBackgroundColor] will fade in then fade out above the expanding view, and
@@ -221,8 +251,8 @@
// Update state.
val animator = ValueAnimator.ofFloat(0f, 1f)
- animator.duration = if (isForTesting) 0 else ANIMATION_DURATION
- animator.interpolator = Interpolators.LINEAR
+ animator.duration = timings.totalDuration
+ animator.interpolator = LINEAR
val launchContainerOverlay = launchContainer.overlay
var cancelled = false
@@ -260,8 +290,8 @@
// TODO(b/184121838): Use reverse interpolators to get the same path/arc as the non
// reversed animation.
val linearProgress = animation.animatedFraction
- val progress = animationInterpolator.getInterpolation(linearProgress)
- val xProgress = animationInterpolatorX.getInterpolation(linearProgress)
+ val progress = interpolators.positionInterpolator.getInterpolation(linearProgress)
+ val xProgress = interpolators.positionXInterpolator.getInterpolation(linearProgress)
val xCenter = MathUtils.lerp(startCenterX, endCenterX, xProgress)
val halfWidth = MathUtils.lerp(startWidth, endWidth, progress) / 2f
@@ -278,7 +308,12 @@
// The expanding view can/should be hidden once it is completely covered by the opening
// window.
- state.visible = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) < 1
+ state.visible = getProgress(
+ timings,
+ linearProgress,
+ timings.contentBeforeFadeOutDelay,
+ timings.contentBeforeFadeOutDuration
+ ) < 1
applyStateToWindowBackgroundLayer(
windowBackgroundLayer,
@@ -337,14 +372,25 @@
// We first fade in the background layer to hide the expanding view, then fade it out
// with SRC mode to draw a hole punch in the status bar and reveal the opening window.
- val fadeInProgress = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT)
+ val fadeInProgress = getProgress(
+ timings,
+ linearProgress,
+ timings.contentBeforeFadeOutDelay,
+ timings.contentBeforeFadeOutDuration
+ )
if (fadeInProgress < 1) {
- val alpha = Interpolators.LINEAR_OUT_SLOW_IN.getInterpolation(fadeInProgress)
+ val alpha =
+ interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress)
drawable.alpha = (alpha * 0xFF).roundToInt()
} else {
val fadeOutProgress = getProgress(
- linearProgress, ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW)
- val alpha = 1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(fadeOutProgress)
+ timings,
+ linearProgress,
+ timings.contentAfterFadeInDelay,
+ timings.contentAfterFadeInDuration
+ )
+ val alpha =
+ 1 - interpolators.contentAfterFadeInInterpolator.getInterpolation(fadeOutProgress)
drawable.alpha = (alpha * 0xFF).roundToInt()
if (drawHole) {
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml
index e9bd638..e80cfaf 100644
--- a/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-sw600dp/dimens.xml
@@ -25,6 +25,9 @@
<!-- Margin around the various security views -->
<dimen name="keyguard_security_view_top_margin">12dp</dimen>
+ <!-- Padding for the lock icon on the keyguard -->
+ <dimen name="lock_icon_padding">16dp</dimen>
+
<!-- Overload default clock widget parameters -->
<dimen name="widget_big_font_size">100dp</dimen>
<dimen name="widget_label_font_size">18sp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 4fc38a8..1601043 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -219,6 +219,9 @@
<!-- Face hint message when finger was not recognized. [CHAR LIMIT=20] -->
<string name="kg_face_not_recognized">Not recognized</string>
+ <!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=NONE] -->
+ <string name="kg_face_sensor_privacy_enabled">To use Face Unlock, turn on <b>Camera access</b> in Settings > Privacy</string>
+
<!-- Instructions telling the user remaining times when enter SIM PIN view. -->
<plurals name="kg_password_default_pin_message">
<item quantity="one">Enter SIM PIN. You have <xliff:g id="number">%d</xliff:g> remaining
diff --git a/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml b/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml
deleted file mode 100644
index 50267fd..0000000
--- a/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 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
- -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:shape="rectangle">
- <stroke
- android:color="?androidprv:attr/colorAccentPrimaryVariant"
- android:width="1dp"/>
- <corners android:radius="20dp"/>
- <padding
- android:left="8dp"
- android:right="8dp"
- android:top="4dp"
- android:bottom="4dp" />
- <solid android:color="@android:color/transparent" />
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml
deleted file mode 100644
index d9ae777..0000000
--- a/packages/SystemUI/res/drawable/media_output_dialog_button_background.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
- ~ Copyright (C) 2021 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.
- -->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetBottom="6dp"
- android:insetTop="6dp">
- <shape android:shape="rectangle">
- <stroke
- android:color="@color/media_dialog_outlined_button"
- android:width="1dp"/>
- <corners android:radius="20dp"/>
- <padding
- android:left="16dp"
- android:right="16dp"
- android:top="8dp"
- android:bottom="8dp"/>
- <solid android:color="@android:color/transparent"/>
- </shape>
-</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/media_output_dialog_solid_button_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_solid_button_background.xml
deleted file mode 100644
index d49454f..0000000
--- a/packages/SystemUI/res/drawable/media_output_dialog_solid_button_background.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
- ~ Copyright (C) 2021 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.
- -->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetBottom="6dp"
- android:insetTop="6dp">
- <shape android:shape="rectangle">
- <stroke
- android:color="@android:color/transparent"
- android:width="1dp"/>
- <corners android:radius="20dp"/>
- <padding
- android:left="@dimen/media_output_dialog_button_padding_horizontal"
- android:right="@dimen/media_output_dialog_button_padding_horizontal"
- android:top="@dimen/media_output_dialog_button_padding_vertical"
- android:bottom="@dimen/media_output_dialog_button_padding_vertical"/>
- <solid android:color="@color/media_dialog_solid_button_background"/>
- </shape>
-</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
index 665b456..a47299d 100644
--- a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
+++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml
@@ -29,7 +29,7 @@
<shape android:shape="rectangle">
<corners android:radius="?android:attr/buttonCornerRadius"/>
<solid android:color="@android:color/transparent"/>
- <stroke android:color="?androidprv:attr/colorAccentPrimary"
+ <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant"
android:width="1dp"
/>
<padding android:left="@dimen/dialog_button_horizontal_padding"
diff --git a/packages/SystemUI/res/drawable/screenrecord_button_background_outline.xml b/packages/SystemUI/res/drawable/screenrecord_button_background_outline.xml
deleted file mode 100644
index 59a31e8..0000000
--- a/packages/SystemUI/res/drawable/screenrecord_button_background_outline.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 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
- -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:shape="rectangle">
- <stroke
- android:color="?androidprv:attr/colorAccentPrimary"
- android:width="1dp"/>
- <corners android:radius="24dp"/>
- <padding
- android:left="16dp"
- android:right="16dp"
- android:top="8dp"
- android:bottom="8dp" />
- <solid android:color="@android:color/transparent" />
-</shape>
diff --git a/packages/SystemUI/res/layout/auth_credential_password_view.xml b/packages/SystemUI/res/layout/auth_credential_password_view.xml
index 1e0ce00..46cbc25 100644
--- a/packages/SystemUI/res/layout/auth_credential_password_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_password_view.xml
@@ -18,64 +18,71 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
- android:gravity="center_horizontal"
- android:elevation="@dimen/biometric_dialog_elevation">
+ android:elevation="@dimen/biometric_dialog_elevation"
+ android:orientation="vertical">
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
- <TextView
- android:id="@+id/title"
+ <RelativeLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Title"/>
+ android:layout_height="match_parent">
- <TextView
- android:id="@+id/subtitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Subtitle"/>
+ <LinearLayout
+ android:id="@+id/auth_credential_header"
+ style="@style/AuthCredentialHeaderStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true">
- <TextView
- android:id="@+id/description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Description"/>
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:contentDescription="@null" />
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.AuthCredential.Title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <ImeAwareEditText
- android:id="@+id/lockPassword"
- android:layout_width="208dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:minHeight="48dp"
- android:gravity="center"
- android:inputType="textPassword"
- android:maxLength="500"
- android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
- style="@style/TextAppearance.AuthCredential.PasswordEntry"/>
+ <TextView
+ android:id="@+id/subtitle"
+ style="@style/TextAppearance.AuthCredential.Subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <TextView
- android:id="@+id/error"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Error"/>
+ <TextView
+ android:id="@+id/description"
+ style="@style/TextAppearance.AuthCredential.Description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="5"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:layout_alignParentBottom="true">
+
+ <ImeAwareEditText
+ android:id="@+id/lockPassword"
+ style="@style/TextAppearance.AuthCredential.PasswordEntry"
+ android:layout_width="208dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:imeOptions="actionNext|flagNoFullscreen|flagForceAscii"
+ android:inputType="textPassword"
+ android:minHeight="48dp" />
+
+ <TextView
+ android:id="@+id/error"
+ style="@style/TextAppearance.AuthCredential.Error"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ </RelativeLayout>
</com.android.systemui.biometrics.AuthCredentialPasswordView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
index 4939ea2..470298e 100644
--- a/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
+++ b/packages/SystemUI/res/layout/auth_credential_pattern_view.xml
@@ -22,76 +22,81 @@
android:gravity="center_horizontal"
android:elevation="@dimen/biometric_dialog_elevation">
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
- <TextView
- android:id="@+id/title"
+ <RelativeLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Title"/>
+ android:layout_height="match_parent"
+ android:orientation="vertical">
- <TextView
- android:id="@+id/subtitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Subtitle"/>
+ <LinearLayout
+ android:id="@+id/auth_credential_header"
+ style="@style/AuthCredentialHeaderStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
- <TextView
- android:id="@+id/description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Description"/>
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:contentDescription="@null" />
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.AuthCredential.Title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:gravity="center"
- android:paddingLeft="0dp"
- android:paddingRight="0dp"
- android:paddingTop="0dp"
- android:paddingBottom="16dp"
- android:clipToPadding="false">
+ <TextView
+ android:id="@+id/subtitle"
+ style="@style/TextAppearance.AuthCredential.Subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="0dp"
- android:layout_weight="1"
- style="@style/LockPatternContainerStyle">
+ <TextView
+ android:id="@+id/description"
+ style="@style/TextAppearance.AuthCredential.Description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
- <com.android.internal.widget.LockPatternView
- android:id="@+id/lockPattern"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- style="@style/LockPatternStyleBiometricPrompt"/>
-
- </FrameLayout>
-
- <TextView
- android:id="@+id/error"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/TextAppearance.AuthCredential.Error"/>
+ android:layout_below="@id/auth_credential_header"
+ android:gravity="center"
+ android:orientation="vertical"
+ android:paddingBottom="16dp"
+ android:paddingTop="60dp">
- </LinearLayout>
+ <FrameLayout
+ style="@style/LockPatternContainerStyle"
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1">
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1"/>
+ <com.android.internal.widget.LockPatternView
+ android:id="@+id/lockPattern"
+ style="@style/LockPatternStyle"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center" />
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true">
+
+ <TextView
+ android:id="@+id/error"
+ style="@style/TextAppearance.AuthCredential.Error"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ </LinearLayout>
+
+ </RelativeLayout>
</com.android.systemui.biometrics.AuthCredentialPatternView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
index e90a644..75fa66b 100644
--- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -67,7 +67,6 @@
<ProgressBar
android:id="@+id/wifi_searching_progress"
- android:indeterminate="true"
android:layout_width="340dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@@ -381,54 +380,44 @@
android:id="@+id/button_layout"
android:orientation="horizontal"
android:layout_width="match_parent"
- android:layout_height="48dp"
- android:layout_marginStart="24dp"
- android:layout_marginEnd="24dp"
+ android:layout_height="wrap_content"
android:layout_marginTop="8dp"
- android:layout_marginBottom="34dp"
+ android:layout_marginStart="@dimen/dialog_side_padding"
+ android:layout_marginEnd="@dimen/dialog_side_padding"
+ android:layout_marginBottom="@dimen/dialog_bottom_padding"
android:clickable="false"
android:focusable="false">
<FrameLayout
android:id="@+id/apm_layout"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:layout_gravity="start|center_vertical"
android:orientation="vertical">
<Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:text="@string/turn_off_airplane_mode"
android:ellipsize="end"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
- android:layout_width="wrap_content"
- android:layout_height="36dp"
- android:layout_gravity="start|center_vertical"
- android:textAppearance="@style/TextAppearance.InternetDialog"
- android:textSize="14sp"
- android:background="@drawable/internet_dialog_footer_background"
+ style="@style/Widget.Dialog.Button.BorderButton"
android:clickable="false"/>
</FrameLayout>
<FrameLayout
android:id="@+id/done_layout"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="wrap_content"
android:layout_marginStart="16dp"
- android:clickable="true"
- android:focusable="true"
android:layout_gravity="end|center_vertical"
- android:orientation="vertical">
+ android:clickable="true"
+ android:focusable="true">
<Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:text="@string/inline_done_button"
- android:ellipsize="end"
- style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
- android:layout_width="67dp"
- android:layout_height="36dp"
- android:layout_gravity="end|center_vertical"
- android:textAppearance="@style/TextAppearance.InternetDialog"
- android:textSize="14sp"
- android:background="@drawable/internet_dialog_footer_background"
+ style="@style/Widget.Dialog.Button"
android:clickable="false"/>
</FrameLayout>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml
index 8437702..b7265b9 100644
--- a/packages/SystemUI/res/layout/media_output_dialog.xml
+++ b/packages/SystemUI/res/layout/media_output_dialog.xml
@@ -41,7 +41,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="12dp"
- android:background="@color/media_dialog_background"
android:orientation="vertical">
<ImageView
android:id="@+id/app_source_icon"
@@ -76,7 +75,6 @@
android:id="@+id/device_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@color/media_dialog_background"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
@@ -91,18 +89,16 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
- android:layout_marginStart="16dp"
- android:layout_marginBottom="24dp"
- android:layout_marginEnd="16dp"
- android:background="@color/media_dialog_background"
+ android:layout_marginStart="@dimen/dialog_side_padding"
+ android:layout_marginEnd="@dimen/dialog_side_padding"
+ android:layout_marginBottom="@dimen/dialog_bottom_padding"
android:orientation="horizontal">
<Button
android:id="@+id/stop"
- style="@style/MediaOutputRoundedOutlinedButton"
+ style="@style/Widget.Dialog.Button.BorderButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minWidth="0dp"
android:text="@string/keyboard_key_media_stop"
android:visibility="gone"/>
@@ -113,10 +109,9 @@
<Button
android:id="@+id/done"
- style="@style/MediaOutputRoundedButton"
+ style="@style/Widget.Dialog.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minWidth="0dp"
android:text="@string/inline_done_button"/>
</LinearLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
index e43a149..f57d65a 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -27,10 +27,10 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingStart="24dp"
- android:paddingEnd="24dp"
- android:paddingTop="26dp"
- android:paddingBottom="30dp"
+ android:paddingStart="@dimen/dialog_side_padding"
+ android:paddingEnd="@dimen/dialog_side_padding"
+ android:paddingTop="@dimen/dialog_top_padding"
+ android:paddingBottom="@dimen/dialog_bottom_padding"
android:orientation="vertical">
<!-- Header -->
@@ -143,10 +143,7 @@
android:layout_weight="0"
android:layout_gravity="start"
android:text="@string/cancel"
- android:textColor="?android:textColorPrimary"
- android:background="@drawable/screenrecord_button_background_outline"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="14sp"/>
+ style="@style/Widget.Dialog.Button.BorderButton" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
@@ -158,10 +155,7 @@
android:layout_weight="0"
android:layout_gravity="end"
android:text="@string/screenrecord_start"
- android:textColor="@android:color/system_neutral1_900"
- android:background="@drawable/screenrecord_button_background_solid"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="14sp"/>
+ style="@style/Widget.Dialog.Button" />
</LinearLayout>
</LinearLayout>
</ScrollView>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index c434285..9d4c2c3 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -67,10 +67,6 @@
<!-- media output dialog-->
<color name="media_dialog_background">@android:color/system_neutral1_900</color>
- <color name="media_dialog_solid_button_background">@android:color/system_accent1_100</color>
- <color name="media_dialog_solid_button_text">@android:color/system_accent2_800</color>
- <color name="media_dialog_outlined_button">@android:color/system_accent1_100</color>
- <color name="media_dialog_outlined_button_text">@android:color/system_neutral1_50</color>
<color name="media_dialog_active_item_main_content">@android:color/system_accent2_800</color>
<color name="media_dialog_inactive_item_main_content">@android:color/system_accent1_100</color>
<color name="media_dialog_item_status">@android:color/system_accent1_100</color>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 11865a5..8cad5b3 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -174,10 +174,6 @@
<!-- media output dialog-->
<color name="media_dialog_background" android:lstar="98">@android:color/system_neutral1_100</color>
- <color name="media_dialog_solid_button_background">@android:color/system_accent1_600</color>
- <color name="media_dialog_solid_button_text">@android:color/system_neutral1_50</color>
- <color name="media_dialog_outlined_button">@android:color/system_accent1_600</color>
- <color name="media_dialog_outlined_button_text">@android:color/system_neutral1_900</color>
<color name="media_dialog_active_item_main_content">@android:color/system_accent1_900</color>
<color name="media_dialog_inactive_item_main_content">@android:color/system_accent1_600</color>
<color name="media_dialog_item_status">@android:color/system_accent1_900</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c09658b..c7350a1 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1098,8 +1098,6 @@
<dimen name="media_output_dialog_header_icon_padding">16dp</dimen>
<dimen name="media_output_dialog_icon_corner_radius">16dp</dimen>
<dimen name="media_output_dialog_title_anim_y_delta">12.5dp</dimen>
- <dimen name="media_output_dialog_button_padding_horizontal">16dp</dimen>
- <dimen name="media_output_dialog_button_padding_vertical">8dp</dimen>
<!-- Distance that the full shade transition takes in order for qs to fully transition to the
shade -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9a85a70..99508a5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -634,7 +634,7 @@
<!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] -->
<string name="quick_settings_brightness_dialog_title">Brightness</string>
<!-- QuickSettings: Label for the toggle that controls whether display inversion is enabled. [CHAR LIMIT=NONE] -->
- <string name="quick_settings_inversion_label">Invert colors</string>
+ <string name="quick_settings_inversion_label">Color inversion</string>
<!-- QuickSettings: Label for the toggle that controls whether display color correction is enabled. [CHAR LIMIT=NONE] -->
<!-- QuickSettings: Control panel: Label for button that navigates to settings. [CHAR LIMIT=NONE] -->
<string name="quick_settings_more_settings">More settings</string>
@@ -2050,8 +2050,8 @@
<string name="magnification_mode_switch_click_label">Switch</string>
<!-- Accessibility floating menu strings -->
- <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user the accessibility gesture had been replaced by accessibility floating button. [CHAR LIMIT=100] -->
- <string name="accessibility_floating_button_migration_tooltip">Accessibility button replaced the accessibility gesture\n\n<annotation id="link">View settings</annotation></string>
+ <!-- Message for the accessibility floating button migration tooltip. It shows when the user use gestural navigation then upgrade their system. It will tell the user they could customize or replace the floating button in Settings. [CHAR LIMIT=100] -->
+ <string name="accessibility_floating_button_migration_tooltip">Tap to open accessibility features. Customize or replace this button in Settings.\n\n<annotation id="link">View settings</annotation></string>
<!-- Message for the accessibility floating button docking tooltip. It shows when the user first time drag the button. It will tell the user about docking behavior. [CHAR LIMIT=70] -->
<string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string>
<!-- Action in accessibility menu to move the accessibility floating button to the top left of the screen. [CHAR LIMIT=30] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 1a4f3e2..01f0c12 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -200,9 +200,9 @@
<style name="TextAppearance.DeviceManagementDialog.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"/>
- <style name="TextAppearance.AuthCredential">
+ <style name="TextAppearance.AuthCredential"
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:accessibilityLiveRegion">polite</item>
- <item name="android:gravity">center_horizontal</item>
<item name="android:textAlignment">gravity</item>
<item name="android:layout_gravity">top</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
@@ -210,44 +210,57 @@
<style name="TextAppearance.AuthCredential.Title">
<item name="android:fontFamily">google-sans</item>
- <item name="android:paddingTop">12dp</item>
- <item name="android:paddingHorizontal">24dp</item>
- <item name="android:textSize">24sp</item>
+ <item name="android:layout_marginTop">20dp</item>
+ <item name="android:textSize">36sp</item>
</style>
<style name="TextAppearance.AuthCredential.Subtitle">
<item name="android:fontFamily">google-sans</item>
- <item name="android:paddingTop">8dp</item>
- <item name="android:paddingHorizontal">24dp</item>
- <item name="android:textSize">16sp</item>
+ <item name="android:layout_marginTop">20dp</item>
+ <item name="android:textSize">18sp</item>
</style>
<style name="TextAppearance.AuthCredential.Description">
<item name="android:fontFamily">google-sans</item>
- <item name="android:paddingTop">8dp</item>
- <item name="android:paddingHorizontal">24dp</item>
- <item name="android:textSize">14sp</item>
+ <item name="android:layout_marginTop">20dp</item>
+ <item name="android:textSize">16sp</item>
</style>
<style name="TextAppearance.AuthCredential.Error">
<item name="android:paddingTop">6dp</item>
+ <item name="android:paddingBottom">18dp</item>
<item name="android:paddingHorizontal">24dp</item>
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/colorError</item>
+ <item name="android:gravity">center</item>
</style>
- <style name="TextAppearance.AuthCredential.PasswordEntry" parent="@android:style/TextAppearance.DeviceDefault">
+ <style name="TextAppearance.AuthCredential.PasswordEntry">
<item name="android:gravity">center</item>
<item name="android:singleLine">true</item>
<item name="android:textColor">?android:attr/colorForeground</item>
<item name="android:textSize">24sp</item>
</style>
+ <style name="AuthCredentialHeaderStyle">
+ <item name="android:paddingStart">48dp</item>
+ <item name="android:paddingEnd">24dp</item>
+ <item name="android:paddingTop">28dp</item>
+ <item name="android:paddingBottom">20dp</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:layout_gravity">top</item>
+ </style>
+
<style name="DeviceManagementDialogTitle">
<item name="android:gravity">center</item>
<item name="android:textAppearance">@style/TextAppearance.DeviceManagementDialog.Title</item>
</style>
+ <style name="AuthCredentialPasswordTheme" parent="@style/Theme.MaterialComponents.DayNight">
+ <item name="colorPrimary">?android:attr/colorPrimary</item>
+ <item name="colorPrimaryDark">?android:attr/colorPrimary</item>
+ </style>
+
<style name="TextAppearance.DeviceManagementDialog.Content" parent="@*android:style/TextAppearance.DeviceDefault.Subhead"/>
<style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI">
@@ -307,9 +320,8 @@
<item name="android:maxWidth">420dp</item>
<item name="android:minHeight">0dp</item>
<item name="android:minWidth">0dp</item>
- <item name="android:paddingBottom">0dp</item>
- <item name="android:paddingHorizontal">44dp</item>
- <item name="android:paddingTop">0dp</item>
+ <item name="android:paddingHorizontal">60dp</item>
+ <item name="android:paddingBottom">40dp</item>
</style>
<style name="LockPatternStyle">
@@ -454,20 +466,6 @@
<item name="android:colorBackground">@color/media_dialog_background</item>
</style>
- <style name="MediaOutputRoundedOutlinedButton" parent="@android:style/Widget.Material.Button">
- <item name="android:background">@drawable/media_output_dialog_button_background</item>
- <item name="android:textColor">@color/media_dialog_outlined_button_text</item>
- <item name="android:textSize">14sp</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
- </style>
-
- <style name="MediaOutputRoundedButton" parent="@android:style/Widget.Material.Button">
- <item name="android:background">@drawable/media_output_dialog_solid_button_background</item>
- <item name="android:textColor">@color/media_dialog_solid_button_text</item>
- <item name="android:textSize">14sp</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
- </style>
-
<style name="MediaOutputItemInactiveTitle">
<item name="android:textSize">16sp</item>
<item name="android:textColor">@color/media_dialog_inactive_item_main_content</item>
@@ -900,13 +898,18 @@
<item name="android:textAlignment">center</item>
</style>
- <style name="Widget.Dialog.Button" parent = "Theme.SystemUI.Dialog">
+
+ <style name="Widget" />
+ <style name="Widget.Dialog" />
+ <style name="Widget.Dialog.Button">
+ <item name="android:buttonCornerRadius">28dp</item>
<item name="android:background">@drawable/qs_dialog_btn_filled</item>
<item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
<item name="android:textSize">14sp</item>
<item name="android:lineHeight">20sp</item>
<item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
<item name="android:stateListAnimator">@null</item>
+ <item name="android:minWidth">0dp</item>
</style>
<style name="Widget.Dialog.Button.BorderButton">
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index e46b6f1..ea93a3b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -60,14 +60,12 @@
hingeAngleProvider,
screenStatusProvider,
deviceStateManager,
- mainExecutor
+ mainExecutor,
+ mainHandler
)
val unfoldTransitionProgressProvider = if (config.isHingeAngleEnabled) {
- PhysicsBasedUnfoldTransitionProgressProvider(
- mainHandler,
- foldStateProvider
- )
+ PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider)
} else {
FixedTimingTransitionProgressProvider(foldStateProvider)
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 90f5998..51eae57 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -15,7 +15,6 @@
*/
package com.android.systemui.unfold.progress
-import android.os.Handler
import android.util.Log
import android.util.MathUtils.saturate
import androidx.dynamicanimation.animation.DynamicAnimation
@@ -24,9 +23,10 @@
import androidx.dynamicanimation.animation.SpringForce
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.updates.FOLD_UPDATE_ABORTED
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
-import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE
import com.android.systemui.unfold.updates.FoldStateProvider
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
@@ -39,7 +39,6 @@
* - doesn't handle postures
*/
internal class PhysicsBasedUnfoldTransitionProgressProvider(
- private val handler: Handler,
private val foldStateProvider: FoldStateProvider
) :
UnfoldTransitionProgressProvider,
@@ -51,8 +50,6 @@
addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider)
}
- private val timeoutRunnable = TimeoutRunnable()
-
private var isTransitionRunning = false
private var isAnimatedCancelRunning = false
@@ -92,7 +89,7 @@
cancelTransition(endValue = 1f, animate = true)
}
}
- FOLD_UPDATE_FINISH_FULL_OPEN -> {
+ FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_ABORTED -> {
// Do not cancel if we haven't started the transition yet.
// This could happen when we fully unfolded the device before the screen
// became available. In this case we start and immediately cancel the animation
@@ -106,7 +103,11 @@
cancelTransition(endValue = 0f, animate = false)
}
FOLD_UPDATE_START_CLOSING -> {
- startTransition(startValue = 1f)
+ // The transition might be already running as the device might start closing several
+ // times before reaching an end state.
+ if (!isTransitionRunning) {
+ startTransition(startValue = 1f)
+ }
}
}
@@ -116,8 +117,6 @@
}
private fun cancelTransition(endValue: Float, animate: Boolean) {
- handler.removeCallbacks(timeoutRunnable)
-
if (isTransitionRunning && animate) {
isAnimatedCancelRunning = true
springAnimation.animateToFinalPosition(endValue)
@@ -175,8 +174,6 @@
}
springAnimation.start()
-
- handler.postDelayed(timeoutRunnable, TRANSITION_TIMEOUT_MILLIS)
}
override fun addCallback(listener: TransitionProgressListener) {
@@ -187,13 +184,6 @@
listeners.remove(listener)
}
- private inner class TimeoutRunnable : Runnable {
-
- override fun run() {
- cancelTransition(endValue = 1f, animate = true)
- }
- }
-
private object AnimationProgressProperty :
FloatPropertyCompat<PhysicsBasedUnfoldTransitionProgressProvider>("animation_progress") {
@@ -212,7 +202,6 @@
private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider"
private const val DEBUG = true
-private const val TRANSITION_TIMEOUT_MILLIS = 2000L
private const val SPRING_STIFFNESS = 200.0f
private const val MINIMAL_VISIBLE_CHANGE = 0.001f
private const val FINAL_HINGE_ANGLE_POSITION = 165f
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 35e2b30..6d9631c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -15,14 +15,19 @@
*/
package com.android.systemui.unfold.updates
+import android.annotation.FloatRange
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
+import android.os.Handler
+import android.util.Log
+import androidx.annotation.VisibleForTesting
import androidx.core.util.Consumer
-import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
+import com.android.systemui.unfold.updates.hinge.FULLY_CLOSED_DEGREES
import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import java.util.concurrent.Executor
class DeviceFoldStateProvider(
@@ -30,7 +35,8 @@
private val hingeAngleProvider: HingeAngleProvider,
private val screenStatusProvider: ScreenStatusProvider,
private val deviceStateManager: DeviceStateManager,
- private val mainExecutor: Executor
+ private val mainExecutor: Executor,
+ private val handler: Handler
) : FoldStateProvider {
private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf()
@@ -38,9 +44,13 @@
@FoldUpdate
private var lastFoldUpdate: Int? = null
+ @FloatRange(from = 0.0, to = 180.0)
+ private var lastHingeAngle: Float = 0f
+
private val hingeAngleListener = HingeAngleListener()
private val screenListener = ScreenStatusListener()
private val foldStateListener = FoldStateListener(context)
+ private val timeoutRunnable = TimeoutRunnable()
private var isFolded = false
private var isUnfoldHandled = true
@@ -72,47 +82,69 @@
override val isFullyOpened: Boolean
get() = !isFolded && lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN
+ private val isTransitionInProgess: Boolean
+ get() = lastFoldUpdate == FOLD_UPDATE_START_OPENING ||
+ lastFoldUpdate == FOLD_UPDATE_START_CLOSING
+
private fun onHingeAngle(angle: Float) {
- when (lastFoldUpdate) {
- FOLD_UPDATE_FINISH_FULL_OPEN -> {
- if (FULLY_OPEN_DEGREES - angle > START_CLOSING_THRESHOLD_DEGREES) {
- lastFoldUpdate = FOLD_UPDATE_START_CLOSING
- outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_CLOSING) }
- }
- }
- FOLD_UPDATE_START_OPENING -> {
- if (FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES) {
- lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN
- outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }
- }
- }
- FOLD_UPDATE_START_CLOSING -> {
- if (FULLY_OPEN_DEGREES - angle < START_CLOSING_THRESHOLD_DEGREES) {
- lastFoldUpdate = FOLD_UPDATE_FINISH_FULL_OPEN
- outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }
- }
+ if (DEBUG) { Log.d(TAG, "Hinge angle: $angle, lastHingeAngle: $lastHingeAngle") }
+
+ val isClosing = angle < lastHingeAngle
+ val isFullyOpened = FULLY_OPEN_DEGREES - angle < FULLY_OPEN_THRESHOLD_DEGREES
+ val closingEventDispatched = lastFoldUpdate == FOLD_UPDATE_START_CLOSING
+
+ if (isClosing && !closingEventDispatched && !isFullyOpened) {
+ notifyFoldUpdate(FOLD_UPDATE_START_CLOSING)
+ }
+
+ if (isTransitionInProgess) {
+ if (isFullyOpened) {
+ notifyFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN)
+ cancelTimeout()
+ } else {
+ // The timeout will trigger some constant time after the last angle update.
+ rescheduleAbortAnimationTimeout()
}
}
+ lastHingeAngle = angle
outputListeners.forEach { it.onHingeAngleUpdate(angle) }
}
private inner class FoldStateListener(context: Context) :
DeviceStateManager.FoldStateListener(context, { folded: Boolean ->
isFolded = folded
+ lastHingeAngle = FULLY_CLOSED_DEGREES
if (folded) {
- lastFoldUpdate = FOLD_UPDATE_FINISH_CLOSED
- outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) }
hingeAngleProvider.stop()
+ notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
+ cancelTimeout()
isUnfoldHandled = false
} else {
- lastFoldUpdate = FOLD_UPDATE_START_OPENING
- outputListeners.forEach { it.onFoldUpdate(FOLD_UPDATE_START_OPENING) }
+ notifyFoldUpdate(FOLD_UPDATE_START_OPENING)
+ rescheduleAbortAnimationTimeout()
hingeAngleProvider.start()
}
})
+ private fun notifyFoldUpdate(@FoldUpdate update: Int) {
+ if (DEBUG) { Log.d(TAG, stateToString(update)) }
+ outputListeners.forEach { it.onFoldUpdate(update) }
+ lastFoldUpdate = update
+ }
+
+ private fun rescheduleAbortAnimationTimeout() {
+ if (isTransitionInProgess) {
+ cancelTimeout()
+ }
+ handler.postDelayed(timeoutRunnable, ABORT_CLOSING_MILLIS)
+ }
+
+ private fun cancelTimeout() {
+ handler.removeCallbacks(timeoutRunnable)
+ }
+
private inner class ScreenStatusListener :
ScreenStatusProvider.ScreenListener {
@@ -136,7 +168,39 @@
onHingeAngle(angle)
}
}
+
+ private inner class TimeoutRunnable : Runnable {
+
+ override fun run() {
+ notifyFoldUpdate(FOLD_UPDATE_ABORTED)
+ }
+ }
}
-private const val START_CLOSING_THRESHOLD_DEGREES = 95f
-private const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
\ No newline at end of file
+private fun stateToString(@FoldUpdate update: Int): String {
+ return when (update) {
+ FOLD_UPDATE_START_OPENING -> "START_OPENING"
+ FOLD_UPDATE_HALF_OPEN -> "HALF_OPEN"
+ FOLD_UPDATE_START_CLOSING -> "START_CLOSING"
+ FOLD_UPDATE_ABORTED -> "ABORTED"
+ FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> "UNFOLDED_SCREEN_AVAILABLE"
+ FOLD_UPDATE_FINISH_HALF_OPEN -> "FINISH_HALF_OPEN"
+ FOLD_UPDATE_FINISH_FULL_OPEN -> "FINISH_FULL_OPEN"
+ FOLD_UPDATE_FINISH_CLOSED -> "FINISH_CLOSED"
+ else -> "UNKNOWN"
+ }
+}
+
+private const val TAG = "DeviceFoldProvider"
+private const val DEBUG = false
+
+/**
+ * Time after which [FOLD_UPDATE_ABORTED] is emitted following a [FOLD_UPDATE_START_CLOSING] or
+ * [FOLD_UPDATE_START_OPENING] event, if an end state is not reached.
+ */
+@VisibleForTesting
+const val ABORT_CLOSING_MILLIS = 1000L
+
+/** Threshold after which we consider the device fully unfolded. */
+@VisibleForTesting
+const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
index 643ece3..bffebcd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
@@ -39,6 +39,7 @@
FOLD_UPDATE_START_OPENING,
FOLD_UPDATE_HALF_OPEN,
FOLD_UPDATE_START_CLOSING,
+ FOLD_UPDATE_ABORTED,
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE,
FOLD_UPDATE_FINISH_HALF_OPEN,
FOLD_UPDATE_FINISH_FULL_OPEN,
@@ -51,7 +52,8 @@
const val FOLD_UPDATE_START_OPENING = 0
const val FOLD_UPDATE_HALF_OPEN = 1
const val FOLD_UPDATE_START_CLOSING = 2
-const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 3
-const val FOLD_UPDATE_FINISH_HALF_OPEN = 4
-const val FOLD_UPDATE_FINISH_FULL_OPEN = 5
-const val FOLD_UPDATE_FINISH_CLOSED = 6
+const val FOLD_UPDATE_ABORTED = 3
+const val FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE = 4
+const val FOLD_UPDATE_FINISH_HALF_OPEN = 5
+const val FOLD_UPDATE_FINISH_FULL_OPEN = 6
+const val FOLD_UPDATE_FINISH_CLOSED = 7
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index ba67716..237ca71 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -51,6 +51,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
+import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
@@ -335,6 +336,8 @@
private boolean mLockIconPressed;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private final Executor mBackgroundExecutor;
+ private SensorPrivacyManager mSensorPrivacyManager;
+ private int mFaceAuthUserId;
/**
* Short delay before restarting fingerprint authentication after a successful try. This should
@@ -1016,6 +1019,12 @@
// Error is always the end of authentication lifecycle
mFaceCancelSignal = null;
+ boolean cameraPrivacyEnabled = false;
+ if (mSensorPrivacyManager != null) {
+ cameraPrivacyEnabled = mSensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA,
+ mFaceAuthUserId);
+ }
if (msgId == FaceManager.FACE_ERROR_CANCELED
&& mFaceRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING) {
@@ -1025,7 +1034,9 @@
setFaceRunningState(BIOMETRIC_STATE_STOPPED);
}
- if (msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE
+ final boolean isHwUnavailable = msgId == FaceManager.FACE_ERROR_HW_UNAVAILABLE;
+
+ if (isHwUnavailable
|| msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS) {
if (mHardwareFaceUnavailableRetryCount < HAL_ERROR_RETRY_MAX) {
mHardwareFaceUnavailableRetryCount++;
@@ -1041,6 +1052,10 @@
requireStrongAuthIfAllLockedOut();
}
+ if (isHwUnavailable && cameraPrivacyEnabled) {
+ errString = mContext.getString(R.string.kg_face_sensor_privacy_enabled);
+ }
+
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
if (cb != null) {
@@ -1816,6 +1831,7 @@
mLockPatternUtils = lockPatternUtils;
mAuthController = authController;
dumpManager.registerDumpable(getClass().getName(), this);
+ mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
mHandler = new Handler(mainLooper) {
@Override
@@ -2517,6 +2533,7 @@
// This would need to be updated for multi-sensor devices
final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty()
&& mFaceSensorProperties.get(0).supportsFaceDetection;
+ mFaceAuthUserId = userId;
if (isEncryptedOrLockdown(userId) && supportsFaceDetection) {
mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 29e5574..6edf2a8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -31,6 +31,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.PointF;
+import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator.Modality;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager.Authenticators;
@@ -89,6 +90,7 @@
private static final String TAG = "AuthController";
private static final boolean DEBUG = true;
+ private static final int SENSOR_PRIVACY_DELAY = 500;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final CommandQueue mCommandQueue;
@@ -122,6 +124,7 @@
@Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
@NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
+ private SensorPrivacyManager mSensorPrivacyManager;
private class BiometricTaskStackListener extends TaskStackListener {
@Override
@@ -492,6 +495,7 @@
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
context.registerReceiver(mBroadcastReceiver, filter);
+ mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
}
private void updateFingerprintLocation() {
@@ -642,10 +646,16 @@
final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT)
|| (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
+ boolean isCameraPrivacyEnabled = false;
+ if (error == BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE
+ && mSensorPrivacyManager.isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA,
+ mCurrentDialogArgs.argi1 /* userId */)) {
+ isCameraPrivacyEnabled = true;
+ }
// TODO(b/141025588): Create separate methods for handling hard and soft errors.
final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED
- || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT);
-
+ || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT
+ || isCameraPrivacyEnabled);
if (mCurrentDialog != null) {
if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
if (DEBUG) Log.d(TAG, "onBiometricError, lockout");
@@ -655,12 +665,23 @@
? mContext.getString(R.string.biometric_not_recognized)
: getErrorString(modality, error, vendorCode);
if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage);
- mCurrentDialog.onAuthenticationFailed(modality, errorMessage);
+ // The camera privacy error can return before the prompt initializes its state,
+ // causing the prompt to appear to endlessly authenticate. Add a small delay
+ // to stop this.
+ if (isCameraPrivacyEnabled) {
+ mHandler.postDelayed(() -> {
+ mCurrentDialog.onAuthenticationFailed(modality,
+ mContext.getString(R.string.face_sensor_privacy_enabled));
+ }, SENSOR_PRIVACY_DELAY);
+ } else {
+ mCurrentDialog.onAuthenticationFailed(modality, errorMessage);
+ }
} else {
final String errorMessage = getErrorString(modality, error, vendorCode);
if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage);
mCurrentDialog.onError(modality, errorMessage);
}
+
} else {
Log.w(TAG, "onBiometricError callback but dialog is gone");
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index f74fbf4..7f5744c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -85,7 +85,9 @@
public MediaOutputBaseDialog(Context context, MediaOutputController mediaOutputController) {
super(context, R.style.Theme_SystemUI_Dialog_Media);
- mContext = context;
+
+ // Save the context that is wrapped with our theme.
+ mContext = getContext();
mMediaOutputController = mediaOutputController;
mLayoutManager = new LinearLayoutManager(mContext);
mListMaxHeight = context.getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 012eda7..56317c6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -16,6 +16,8 @@
package com.android.systemui.media.dialog;
+import static android.provider.Settings.ACTION_BLUETOOTH_PAIRING_SETTINGS;
+
import android.app.Notification;
import android.content.Context;
import android.content.Intent;
@@ -35,6 +37,7 @@
import android.media.session.PlaybackState;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -51,7 +54,6 @@
import com.android.settingslib.media.InfoMediaManager;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
-import com.android.settingslib.media.MediaOutputConstants;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.R;
import com.android.systemui.animation.DialogLaunchAnimator;
@@ -74,7 +76,8 @@
private static final String TAG = "MediaOutputController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
+ private static final String PAGE_CONNECTED_DEVICES_KEY =
+ "top_level_connected_devices";
private final String mPackageName;
private final Context mContext;
private final MediaSessionManager mMediaSessionManager;
@@ -485,14 +488,24 @@
mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations();
mCallback.dismissDialog();
- final ActivityStarter.OnDismissAction postKeyguardAction = () -> {
- mContext.sendBroadcast(new Intent()
- .setAction(MediaOutputConstants.ACTION_LAUNCH_BLUETOOTH_PAIRING)
- .setPackage(MediaOutputConstants.SETTINGS_PACKAGE_NAME));
- mShadeController.animateCollapsePanels();
- return true;
- };
- mActivityStarter.dismissKeyguardThenExecute(postKeyguardAction, null, true);
+ Intent launchIntent =
+ new Intent(ACTION_BLUETOOTH_PAIRING_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ final Intent deepLinkIntent =
+ new Intent(Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY);
+ if (deepLinkIntent.resolveActivity(mContext.getPackageManager()) != null) {
+ Log.d(TAG, "Device support split mode, launch page with deep link");
+ deepLinkIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ deepLinkIntent.putExtra(
+ Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI,
+ launchIntent.toUri(Intent.URI_INTENT_SCHEME));
+ deepLinkIntent.putExtra(
+ Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY,
+ PAGE_CONNECTED_DEVICES_KEY);
+ mActivityStarter.startActivity(deepLinkIntent, true);
+ return;
+ }
+ mActivityStarter.startActivity(launchIntent, true);
}
void launchMediaOutputGroupDialog(View mediaOutputDialog) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index eddc206..d470fa2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -219,9 +219,8 @@
}
private void addTile(final QSTile tile, boolean collapsedView) {
- final TileRecord r = new TileRecord();
- r.tile = tile;
- r.tileView = mHost.createTileView(getContext(), tile, collapsedView);
+ final TileRecord r =
+ new TileRecord(tile, mHost.createTileView(getContext(), tile, collapsedView));
mView.addTile(r);
mRecords.add(r);
mCachedSpecs = getTilesSpecs();
@@ -418,6 +417,11 @@
/** */
public static final class TileRecord extends QSPanel.Record {
+ public TileRecord(QSTile tile, com.android.systemui.plugins.qs.QSTileView tileView) {
+ this.tile = tile;
+ this.tileView = tileView;
+ }
+
public QSTile tile;
public com.android.systemui.plugins.qs.QSTileView tileView;
public boolean scanState;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 7ba9cc2..a2577d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -98,7 +98,7 @@
toggleDataSaver();
Prefs.putBoolean(mContext, Prefs.Key.QS_DATA_SAVER_DIALOG_SHOWN, true);
});
- dialog.setNegativeButton(com.android.internal.R.string.cancel, null);
+ dialog.setNeutralButton(com.android.internal.R.string.cancel, null);
dialog.setShowForAllUsers(true);
if (view != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 83506b2..bb27458 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -190,7 +190,6 @@
case Settings.Secure.ZEN_DURATION_PROMPT:
mUiHandler.post(() -> {
Dialog dialog = makeZenModeDialog();
- SystemUIDialog.registerDismissListener(dialog);
if (view != null) {
mDialogLaunchAnimator.showFromView(dialog, view, false);
} else {
@@ -211,10 +210,12 @@
}
private Dialog makeZenModeDialog() {
- AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog)
- .createDialog();
+ AlertDialog dialog = new EnableZenModeDialog(mContext, R.style.Theme_SystemUI_Dialog,
+ true /* cancelIsNeutral */).createDialog();
SystemUIDialog.applyFlags(dialog);
SystemUIDialog.setShowForAllUsers(dialog, true);
+ SystemUIDialog.registerDismissListener(dialog);
+ SystemUIDialog.setDialogSize(dialog);
return dialog;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
index 26c89ff..e3f085c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -78,7 +78,7 @@
private static final String TAG = "InternetDialog";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- static final long PROGRESS_DELAY_MS = 2000L;
+ static final long PROGRESS_DELAY_MS = 1500L;
private final Handler mHandler;
private final Executor mBackgroundExecutor;
@@ -137,6 +137,8 @@
protected WifiEntry mConnectedWifiEntry;
@VisibleForTesting
protected int mWifiEntriesCount;
+ @VisibleForTesting
+ protected boolean mHasMoreEntry;
// Wi-Fi scanning progress bar
protected boolean mIsProgressBarVisible;
@@ -157,7 +159,9 @@
if (DEBUG) {
Log.d(TAG, "Init InternetDialog");
}
- mContext = context;
+
+ // Save the context that is wrapped with our theme.
+ mContext = getContext();
mHandler = handler;
mBackgroundExecutor = executor;
mInternetDialogFactory = internetDialogFactory;
@@ -464,8 +468,7 @@
}
mWifiRecyclerView.setMinimumHeight(mWifiNetworkHeight * getWifiListMaxCount());
mWifiRecyclerView.setVisibility(View.VISIBLE);
- final boolean showSeeAll = mConnectedWifiEntry != null || mWifiEntriesCount > 0;
- mSeeAllLayout.setVisibility(showSeeAll ? View.VISIBLE : View.INVISIBLE);
+ mSeeAllLayout.setVisibility(mHasMoreEntry ? View.VISIBLE : View.INVISIBLE);
}
@VisibleForTesting
@@ -549,9 +552,13 @@
}
private void setProgressBarVisible(boolean visible) {
+ if (mIsProgressBarVisible == visible) {
+ return;
+ }
mIsProgressBarVisible = visible;
- mProgressBar.setVisibility(mIsProgressBarVisible ? View.VISIBLE : View.GONE);
- mDivider.setVisibility(mIsProgressBarVisible ? View.GONE : View.VISIBLE);
+ mProgressBar.setVisibility(visible ? View.VISIBLE : View.GONE);
+ mProgressBar.setIndeterminate(visible);
+ mDivider.setVisibility(visible ? View.GONE : View.VISIBLE);
mInternetDialogSubTitle.setText(getSubtitleText());
}
@@ -651,13 +658,14 @@
@Override
@WorkerThread
public void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
- @Nullable WifiEntry connectedEntry) {
+ @Nullable WifiEntry connectedEntry, boolean hasMoreEntry) {
// Should update the carrier network layout when it is connected under airplane mode ON.
boolean shouldUpdateCarrierNetwork = mMobileNetworkLayout.getVisibility() == View.VISIBLE
&& mInternetDialogController.isAirplaneModeEnabled();
mHandler.post(() -> {
mConnectedWifiEntry = connectedEntry;
mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size();
+ mHasMoreEntry = hasMoreEntry;
updateDialog(shouldUpdateCarrierNetwork /* shouldUpdateMobileNetwork */);
mAdapter.setWifiEntries(wifiEntries, mWifiEntriesCount);
mAdapter.notifyDataSetChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 6f63a08..1fee1b4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -348,6 +348,10 @@
return mContext.getText(SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS);
}
+ if (isCarrierNetworkActive()) {
+ return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE);
+ }
+
// Sub-Title:
// show non_carrier_network_unavailable
// - while Wi-Fi on + no Wi-Fi item
@@ -879,20 +883,25 @@
mConnectedEntry = null;
mWifiEntriesCount = 0;
if (mCallback != null) {
- mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */);
+ mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */,
+ false /* hasMoreEntry */);
}
return;
}
+ boolean hasMoreEntry = false;
int count = MAX_WIFI_ENTRY_COUNT;
if (mHasEthernet) {
count -= 1;
}
- if (hasActiveSubId()) {
+ if (hasActiveSubId() || isCarrierNetworkActive()) {
count -= 1;
}
- if (count > accessPoints.size()) {
- count = accessPoints.size();
+ final int wifiTotalCount = accessPoints.size();
+ if (count > wifiTotalCount) {
+ count = wifiTotalCount;
+ } else if (count < wifiTotalCount) {
+ hasMoreEntry = true;
}
WifiEntry connectedEntry = null;
@@ -909,7 +918,7 @@
mWifiEntriesCount = wifiEntries.size();
if (mCallback != null) {
- mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry);
+ mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry, hasMoreEntry);
}
}
@@ -1060,7 +1069,7 @@
void dismissDialog();
void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
- @Nullable WifiEntry connectedEntry);
+ @Nullable WifiEntry connectedEntry, boolean hasMoreEntry);
}
void makeOverlayToast(int stringId) {
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt
index c50365f..71c5fad 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt
@@ -15,7 +15,8 @@
class SensorUseDialog(
context: Context,
val sensor: Int,
- val clickListener: DialogInterface.OnClickListener
+ val clickListener: DialogInterface.OnClickListener,
+ val dismissListener: DialogInterface.OnDismissListener
) : SystemUIDialog(context) {
// TODO move to onCreate (b/200815309)
@@ -69,6 +70,8 @@
context.getString(com.android.internal.R.string
.cancel), clickListener)
+ setOnDismissListener(dismissListener)
+
setCancelable(false)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index b0071d9..dae375a 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -50,7 +50,7 @@
private val keyguardStateController: KeyguardStateController,
private val keyguardDismissUtil: KeyguardDismissUtil,
@Background private val bgHandler: Handler
-) : Activity(), DialogInterface.OnClickListener {
+) : Activity(), DialogInterface.OnClickListener, DialogInterface.OnDismissListener {
companion object {
private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName
@@ -120,7 +120,7 @@
}
}
- mDialog = SensorUseDialog(this, sensor, this)
+ mDialog = SensorUseDialog(this, sensor, this, this)
mDialog!!.show()
}
@@ -212,4 +212,8 @@
.suppressSensorPrivacyReminders(sensor, suppressed)
}
}
+
+ override fun onDismiss(dialog: DialogInterface?) {
+ finish()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 5437ce6..d6125ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -25,7 +25,6 @@
import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
-import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
@@ -35,9 +34,13 @@
import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.util.time.SystemClock;
import java.util.ArrayList;
+import java.util.Deque;
import java.util.List;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.Executor;
/**
* This class handles listening to notification updates and passing them along to
@@ -47,23 +50,31 @@
public class NotificationListener extends NotificationListenerWithPlugins {
private static final String TAG = "NotificationListener";
private static final boolean DEBUG = StatusBar.DEBUG;
+ private static final long MAX_RANKING_DELAY_MILLIS = 500L;
private final Context mContext;
private final NotificationManager mNotificationManager;
- private final Handler mMainHandler;
+ private final SystemClock mSystemClock;
+ private final Executor mMainExecutor;
private final List<NotificationHandler> mNotificationHandlers = new ArrayList<>();
private final ArrayList<NotificationSettingsListener> mSettingsListeners = new ArrayList<>();
+ private final Deque<RankingMap> mRankingMapQueue = new ConcurrentLinkedDeque<>();
+ private final Runnable mDispatchRankingUpdateRunnable = this::dispatchRankingUpdate;
+ private long mSkippingRankingUpdatesSince = -1;
+
/**
* Injected constructor. See {@link StatusBarModule}.
*/
public NotificationListener(
Context context,
NotificationManager notificationManager,
- @Main Handler mainHandler) {
+ SystemClock systemClock,
+ @Main Executor mainExecutor) {
mContext = context;
mNotificationManager = notificationManager;
- mMainHandler = mainHandler;
+ mSystemClock = systemClock;
+ mMainExecutor = mainExecutor;
}
/** Registers a listener that's notified when notifications are added/removed/etc. */
@@ -89,7 +100,7 @@
return;
}
final RankingMap currentRanking = getCurrentRanking();
- mMainHandler.post(() -> {
+ mMainExecutor.execute(() -> {
// There's currently a race condition between the calls to getActiveNotifications() and
// getCurrentRanking(). It's possible for the ranking that we store here to not contain
// entries for every notification in getActiveNotifications(). To prevent downstream
@@ -119,7 +130,7 @@
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
- mMainHandler.post(() -> {
+ mMainExecutor.execute(() -> {
processForRemoteInput(sbn.getNotification(), mContext);
for (NotificationHandler handler : mNotificationHandlers) {
@@ -134,7 +145,7 @@
int reason) {
if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn + " reason: " + reason);
if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
- mMainHandler.post(() -> {
+ mMainExecutor.execute(() -> {
for (NotificationHandler handler : mNotificationHandlers) {
handler.onNotificationRemoved(sbn, rankingMap, reason);
}
@@ -151,12 +162,49 @@
public void onNotificationRankingUpdate(final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onRankingUpdate");
if (rankingMap != null) {
+ // Add the ranking to the queue, then run dispatchRankingUpdate() on the main thread
RankingMap r = onPluginRankingUpdate(rankingMap);
- mMainHandler.post(() -> {
- for (NotificationHandler handler : mNotificationHandlers) {
- handler.onNotificationRankingUpdate(r);
+ mRankingMapQueue.addLast(r);
+ // Maintaining our own queue and always posting the runnable allows us to guarantee the
+ // relative ordering of all events which are dispatched, which is important so that the
+ // RankingMap always has exactly the same elements that are current, per add/remove
+ // events.
+ mMainExecutor.execute(mDispatchRankingUpdateRunnable);
+ }
+ }
+
+ /**
+ * This method is (and must be) the sole consumer of the RankingMap queue. After pulling an
+ * object off the queue, it checks if the queue is empty, and only dispatches the ranking update
+ * if the queue is still empty.
+ */
+ private void dispatchRankingUpdate() {
+ if (DEBUG) Log.d(TAG, "dispatchRankingUpdate");
+ RankingMap r = mRankingMapQueue.pollFirst();
+ if (r == null) {
+ Log.wtf(TAG, "mRankingMapQueue was empty!");
+ }
+ if (!mRankingMapQueue.isEmpty()) {
+ final long now = mSystemClock.elapsedRealtime();
+ if (mSkippingRankingUpdatesSince == -1) {
+ mSkippingRankingUpdatesSince = now;
+ }
+ final long timeSkippingRankingUpdates = now - mSkippingRankingUpdatesSince;
+ if (timeSkippingRankingUpdates < MAX_RANKING_DELAY_MILLIS) {
+ if (DEBUG) {
+ Log.d(TAG, "Skipping dispatch of onNotificationRankingUpdate() -- "
+ + mRankingMapQueue.size() + " more updates already in the queue.");
}
- });
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Proceeding with dispatch of onNotificationRankingUpdate() -- "
+ + mRankingMapQueue.size() + " more updates already in the queue.");
+ }
+ }
+ mSkippingRankingUpdatesSince = -1;
+ for (NotificationHandler handler : mNotificationHandlers) {
+ handler.onNotificationRankingUpdate(r);
}
}
@@ -165,7 +213,7 @@
String pkgName, UserHandle user, NotificationChannel channel, int modificationType) {
if (DEBUG) Log.d(TAG, "onNotificationChannelModified");
if (!onPluginNotificationChannelModified(pkgName, user, channel, modificationType)) {
- mMainHandler.post(() -> {
+ mMainExecutor.execute(() -> {
for (NotificationHandler handler : mNotificationHandlers) {
handler.onNotificationChannelModified(pkgName, user, channel, modificationType);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
index c4fadff..4551807 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/UserUtil.java
@@ -40,7 +40,7 @@
super(context);
setTitle(R.string.user_remove_user_title);
setMessage(context.getString(R.string.user_remove_user_message));
- setButton(DialogInterface.BUTTON_NEGATIVE,
+ setButton(DialogInterface.BUTTON_NEUTRAL,
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(R.string.user_remove_user_remove), this);
@@ -51,7 +51,7 @@
@Override
public void onClick(DialogInterface dialog, int which) {
- if (which == BUTTON_NEGATIVE) {
+ if (which == BUTTON_NEUTRAL) {
cancel();
} else {
dismiss();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index 4c7ee17..d574cda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -25,7 +25,6 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
-import com.android.systemui.animation.LaunchAnimator;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
@@ -169,9 +168,10 @@
static NotificationListener provideNotificationListener(
Context context,
NotificationManager notificationManager,
- @Main Handler mainHandler) {
+ SystemClock systemClock,
+ @Main Executor mainExecutor) {
return new NotificationListener(
- context, notificationManager, mainHandler);
+ context, notificationManager, systemClock, mainExecutor);
}
/** */
@@ -316,24 +316,15 @@
*/
@Provides
@SysUISingleton
- static LaunchAnimator provideLaunchAnimator(Context context) {
- return new LaunchAnimator(context);
+ static ActivityLaunchAnimator provideActivityLaunchAnimator() {
+ return new ActivityLaunchAnimator();
}
/**
*/
@Provides
@SysUISingleton
- static ActivityLaunchAnimator provideActivityLaunchAnimator(LaunchAnimator launchAnimator) {
- return new ActivityLaunchAnimator(launchAnimator);
- }
-
- /**
- */
- @Provides
- @SysUISingleton
- static DialogLaunchAnimator provideDialogLaunchAnimator(Context context,
- LaunchAnimator launchAnimator, IDreamManager dreamManager) {
- return new DialogLaunchAnimator(context, launchAnimator, dreamManager);
+ static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager) {
+ return new DialogLaunchAnimator(dreamManager);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt
index 64a7305..349b191 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt
@@ -2,6 +2,7 @@
import android.util.MathUtils
import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.LaunchAnimator
import kotlin.math.min
@@ -55,6 +56,7 @@
}
fun getProgress(delay: Long, duration: Long): Float {
- return LaunchAnimator.getProgress(linearProgress, delay, duration)
+ return LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS, linearProgress, delay,
+ duration)
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 8797335..ffe6e4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -683,6 +683,7 @@
boolean showFooterView = (showDismissView || mController.getVisibleNotificationCount() > 0)
&& mIsCurrentUserSetup // see: b/193149550
&& mStatusBarState != StatusBarState.KEYGUARD
+ && mQsExpansionFraction != 1
&& !mScreenOffAnimationController.shouldHideNotificationsFooter()
&& !mIsRemoteInputActive;
boolean showHistory = Settings.Secure.getIntForUser(mContext.getContentResolver(),
@@ -4769,6 +4770,8 @@
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void setQsExpansionFraction(float qsExpansionFraction) {
+ boolean footerAffected = mQsExpansionFraction != qsExpansionFraction
+ && (mQsExpansionFraction == 1 || qsExpansionFraction == 1);
mQsExpansionFraction = qsExpansionFraction;
updateUseRoundedRectClipping();
@@ -4777,6 +4780,9 @@
if (mOwnScrollY > 0) {
setOwnScrollY((int) MathUtils.lerp(mOwnScrollY, 0, mQsExpansionFraction));
}
+ if (footerAffected) {
+ updateFooter();
+ }
}
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
index 4651e8446..c68d39b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
@@ -66,11 +66,7 @@
@Override
public RankingMap getCurrentRanking() {
- RankingMap currentRanking = super.getCurrentRanking();
- for (NotificationListenerController plugin : mPlugins) {
- currentRanking = plugin.getCurrentRanking(currentRanking);
- }
- return currentRanking;
+ return onPluginRankingUpdate(super.getCurrentRanking());
}
public void onPluginConnected() {
@@ -120,8 +116,11 @@
return false;
}
- public RankingMap onPluginRankingUpdate(RankingMap rankingMap) {
- return getCurrentRanking();
+ protected RankingMap onPluginRankingUpdate(RankingMap rankingMap) {
+ for (NotificationListenerController plugin : mPlugins) {
+ rankingMap = plugin.getCurrentRanking(rankingMap);
+ }
+ return rankingMap;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 020c423..a64e579 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -109,6 +109,7 @@
import com.android.systemui.DejankUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.LaunchAnimator;
import com.android.systemui.biometrics.AuthController;
@@ -238,7 +239,8 @@
*/
private static final int FLING_HIDE = 2;
private static final long ANIMATION_DELAY_ICON_FADE_IN =
- LaunchAnimator.ANIMATION_DURATION - CollapsedStatusBarFragment.FADE_IN_DURATION
+ ActivityLaunchAnimator.TIMINGS.getTotalDuration()
+ - CollapsedStatusBarFragment.FADE_IN_DURATION
- CollapsedStatusBarFragment.FADE_IN_DELAY - 48;
private final DozeParameters mDozeParameters;
@@ -3786,8 +3788,8 @@
}
public void applyLaunchAnimationProgress(float linearProgress) {
- boolean hideIcons = LaunchAnimator.getProgress(linearProgress,
- ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
+ boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS,
+ linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
if (hideIcons != mHideIconsDuringLaunchAnimation) {
mHideIconsDuringLaunchAnimation = hideIcons;
if (!hideIcons) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
index 32aae6c..2ba37c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -23,7 +23,8 @@
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
statusBar.notificationPanelViewController.setIsLaunchAnimationRunning(true)
if (!isExpandingFullyAbove) {
- statusBar.collapsePanelWithDuration(LaunchAnimator.ANIMATION_DURATION.toInt())
+ statusBar.collapsePanelWithDuration(
+ ActivityLaunchAnimator.TIMINGS.totalDuration.toInt())
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 43264b6..8df7b45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -126,30 +126,7 @@
* the device configuration changes, and the result will be used to resize this dialog window.
*/
protected int getWidth() {
- boolean isOnTablet =
- mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600;
- if (!isOnTablet) {
- return ViewGroup.LayoutParams.MATCH_PARENT;
- }
-
- int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0);
- if (flagValue == -1) {
- // The width of bottom sheets (624dp).
- return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 624,
- mContext.getResources().getDisplayMetrics()));
- } else if (flagValue == -2) {
- // The suggested small width for all dialogs (348dp)
- return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 348,
- mContext.getResources().getDisplayMetrics()));
- } else if (flagValue > 0) {
- // Any given width.
- return Math.round(
- TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, flagValue,
- mContext.getResources().getDisplayMetrics()));
- } else {
- // By default we use the same width as the notification shade in portrait mode (504dp).
- return mContext.getResources().getDimensionPixelSize(R.dimen.large_dialog_width);
- }
+ return getDefaultDialogWidth(mContext);
}
/**
@@ -157,7 +134,7 @@
* the device configuration changes, and the result will be used to resize this dialog window.
*/
protected int getHeight() {
- return ViewGroup.LayoutParams.WRAP_CONTENT;
+ return getDefaultDialogHeight();
}
@Override
@@ -267,6 +244,45 @@
dismissReceiver.register();
}
+ /** Set an appropriate size to {@code dialog} depending on the current configuration. */
+ public static void setDialogSize(Dialog dialog) {
+ // We need to create the dialog first, otherwise the size will be overridden when it is
+ // created.
+ dialog.create();
+ dialog.getWindow().setLayout(getDefaultDialogWidth(dialog.getContext()),
+ getDefaultDialogHeight());
+ }
+
+ private static int getDefaultDialogWidth(Context context) {
+ boolean isOnTablet = context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
+ if (!isOnTablet) {
+ return ViewGroup.LayoutParams.MATCH_PARENT;
+ }
+
+ int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0);
+ if (flagValue == -1) {
+ // The width of bottom sheets (624dp).
+ return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 624,
+ context.getResources().getDisplayMetrics()));
+ } else if (flagValue == -2) {
+ // The suggested small width for all dialogs (348dp)
+ return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 348,
+ context.getResources().getDisplayMetrics()));
+ } else if (flagValue > 0) {
+ // Any given width.
+ return Math.round(
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, flagValue,
+ context.getResources().getDisplayMetrics()));
+ } else {
+ // By default we use the same width as the notification shade in portrait mode (504dp).
+ return context.getResources().getDimensionPixelSize(R.dimen.large_dialog_width);
+ }
+ }
+
+ private static int getDefaultDialogHeight() {
+ return ViewGroup.LayoutParams.WRAP_CONTENT;
+ }
+
private static class DismissReceiver extends BroadcastReceiver {
private static final IntentFilter INTENT_FILTER = new IntentFilter();
static {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 17dd26a..dc8dc99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -1161,7 +1161,7 @@
? com.android.settingslib.R.string.guest_reset_guest_dialog_title
: R.string.guest_exit_guest_dialog_title);
setMessage(context.getString(R.string.guest_exit_guest_dialog_message));
- setButton(DialogInterface.BUTTON_NEGATIVE,
+ setButton(DialogInterface.BUTTON_NEUTRAL,
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(mGuestUserAutoCreated
@@ -1180,7 +1180,7 @@
if (mFalsingManager.isFalseTap(penalty)) {
return;
}
- if (which == BUTTON_NEGATIVE) {
+ if (which == BUTTON_NEUTRAL) {
cancel();
} else {
mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
@@ -1198,7 +1198,7 @@
super(context);
setTitle(R.string.user_add_user_title);
setMessage(context.getString(R.string.user_add_user_message_short));
- setButton(DialogInterface.BUTTON_NEGATIVE,
+ setButton(DialogInterface.BUTTON_NEUTRAL,
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
context.getString(android.R.string.ok), this);
@@ -1212,7 +1212,7 @@
if (mFalsingManager.isFalseTap(penalty)) {
return;
}
- if (which == BUTTON_NEGATIVE) {
+ if (which == BUTTON_NEUTRAL) {
cancel();
} else {
mDialogLaunchAnimator.dismissStack(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index 8ea7d62..fb6861d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.tv;
import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -38,6 +39,10 @@
@SysUISingleton
public class TvStatusBar extends CoreStartable implements CommandQueue.Callbacks {
+ private static final String ACTION_SHOW_PIP_MENU =
+ "com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
+ private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
+
private final CommandQueue mCommandQueue;
private final Lazy<AssistManager> mAssistManagerLazy;
@@ -65,4 +70,9 @@
public void startAssist(Bundle args) {
mAssistManagerLazy.get().startAssist(args);
}
+
+ @Override
+ public void showPictureInPictureMenu() {
+ mContext.sendBroadcast(new Intent(ACTION_SHOW_PIP_MENU), SYSTEMUI_PERMISSION);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index d819fa2..1fe3d44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -46,7 +46,7 @@
@RunWithLooper
class ActivityLaunchAnimatorTest : SysuiTestCase() {
private val launchContainer = LinearLayout(mContext)
- private val launchAnimator = LaunchAnimator(mContext, isForTesting = true)
+ private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
@Mock lateinit var callback: ActivityLaunchAnimator.Callback
@Spy private val controller = TestLaunchAnimatorController(launchContainer)
@Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index f9ad740..b951345 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -33,7 +33,7 @@
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class DialogLaunchAnimatorTest : SysuiTestCase() {
- private val launchAnimator = LaunchAnimator(context, isForTesting = true)
+ private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
private val attachedViews = mutableSetOf<View>()
@@ -42,7 +42,8 @@
@Before
fun setUp() {
- dialogLaunchAnimator = DialogLaunchAnimator(context, launchAnimator, dreamManager)
+ dialogLaunchAnimator = DialogLaunchAnimator(
+ dreamManager, launchAnimator, isForTesting = true)
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TestValues.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TestValues.kt
new file mode 100644
index 0000000..dadf94e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TestValues.kt
@@ -0,0 +1,23 @@
+package com.android.systemui.animation
+
+/**
+ * A [LaunchAnimator.Timings] to be used in tests.
+ *
+ * Note that all timings except the total duration are non-zero to avoid divide-by-zero exceptions
+ * when computing the progress of a sub-animation (the contents fade in/out).
+ */
+val TEST_TIMINGS = LaunchAnimator.Timings(
+ totalDuration = 0L,
+ contentBeforeFadeOutDelay = 1L,
+ contentBeforeFadeOutDuration = 1L,
+ contentAfterFadeInDelay = 1L,
+ contentAfterFadeInDuration = 1L
+)
+
+/** A [LaunchAnimator.Interpolators] to be used in tests. */
+val TEST_INTERPOLATORS = LaunchAnimator.Interpolators(
+ positionInterpolator = Interpolators.STANDARD,
+ positionXInterpolator = Interpolators.STANDARD,
+ contentBeforeFadeOutInterpolator = Interpolators.STANDARD,
+ contentAfterFadeInInterpolator = Interpolators.STANDARD
+)
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
index 24e47c5..bd4bfff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java
@@ -58,10 +58,9 @@
}
private QSPanelControllerBase.TileRecord createTileRecord() {
- QSPanelControllerBase.TileRecord tileRecord = new QSPanelControllerBase.TileRecord();
- tileRecord.tile = mock(QSTile.class);
- tileRecord.tileView = spy(new QSTileViewImpl(mContext, new QSIconViewImpl(mContext)));
- return tileRecord;
+ return new QSPanelControllerBase.TileRecord(
+ mock(QSTile.class),
+ spy(new QSTileViewImpl(mContext, new QSIconViewImpl(mContext))));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
index 95e7a98c..6dca2a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -159,7 +159,6 @@
mAccessPoints.add(mWifiEntry1);
when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
- when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt()))
.thenReturn(mSystemUIToast);
when(mSystemUIToast.getView()).thenReturn(mToastView);
@@ -335,6 +334,17 @@
}
@Test
+ public void getSubtitleText_withCarrierNetworkActiveOnly_returnNoOtherAvailable() {
+ fakeAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+ when(mMergedCarrierEntry.isDefaultNetwork()).thenReturn(true);
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isEqualTo(getResourcesString("non_carrier_network_unavailable"));
+ }
+
+ @Test
public void getWifiDetailsSettingsIntent_withNoKey_returnNull() {
assertThat(mInternetDialogController.getWifiDetailsSettingsIntent(null)).isNull();
}
@@ -400,7 +410,7 @@
mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
- verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any());
+ verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any(), anyBoolean());
}
@Test
@@ -409,8 +419,8 @@
mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
- verify(mInternetDialogCallback)
- .onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */);
+ verify(mInternetDialogCallback).onAccessPointsChanged(null /* wifiEntries */,
+ null /* connectedEntry */, false /* hasMoreEntry */);
}
@Test
@@ -423,7 +433,8 @@
mInternetDialogController.onAccessPointsChanged(mAccessPoints);
mWifiEntries.clear();
- verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+ false /* hasMoreEntry */);
}
@Test
@@ -437,8 +448,8 @@
mWifiEntries.clear();
mWifiEntries.add(mWifiEntry1);
- verify(mInternetDialogCallback)
- .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries,
+ null /* connectedEntry */, false /* hasMoreEntry */);
}
@Test
@@ -453,7 +464,8 @@
mWifiEntries.clear();
mWifiEntries.add(mWifiEntry1);
- verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+ false /* hasMoreEntry */);
}
@Test
@@ -470,7 +482,8 @@
mWifiEntries.clear();
mWifiEntries.add(mWifiEntry1);
mWifiEntries.add(mWifiEntry2);
- verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+ false /* hasMoreEntry */);
}
@Test
@@ -489,7 +502,8 @@
mWifiEntries.add(mWifiEntry1);
mWifiEntries.add(mWifiEntry2);
mWifiEntries.add(mWifiEntry3);
- verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+ false /* hasMoreEntry */);
// Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
reset(mInternetDialogCallback);
@@ -498,7 +512,8 @@
mInternetDialogController.onAccessPointsChanged(mAccessPoints);
mWifiEntries.remove(mWifiEntry3);
- verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+ true /* hasMoreEntry */);
}
@Test
@@ -518,7 +533,8 @@
mWifiEntries.add(mWifiEntry1);
mWifiEntries.add(mWifiEntry2);
mWifiEntries.add(mWifiEntry3);
- verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+ true /* hasMoreEntry */);
// Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
reset(mInternetDialogCallback);
@@ -527,7 +543,38 @@
mInternetDialogController.onAccessPointsChanged(mAccessPoints);
mWifiEntries.remove(mWifiEntry3);
- verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry,
+ true /* hasMoreEntry */);
+ }
+
+ @Test
+ public void onAccessPointsChanged_oneCarrierWifiAndFourOthers_callbackCutMore() {
+ reset(mInternetDialogCallback);
+ fakeAirplaneModeEnabled(true);
+ when(mMergedCarrierEntry.isDefaultNetwork()).thenReturn(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mWifiEntry1);
+ mAccessPoints.add(mWifiEntry2);
+ mAccessPoints.add(mWifiEntry3);
+ mAccessPoints.add(mWifiEntry4);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ mWifiEntries.add(mWifiEntry2);
+ mWifiEntries.add(mWifiEntry3);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries,
+ null /* connectedEntry */, true /* hasMoreEntry */);
+
+ // Turn off airplane mode to has carrier WiFi, then Wi-Fi entries will keep the same.
+ reset(mInternetDialogCallback);
+ fakeAirplaneModeEnabled(false);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries,
+ null /* connectedEntry */, true /* hasMoreEntry */);
}
@Test
@@ -547,8 +594,8 @@
mWifiEntries.add(mWifiEntry2);
mWifiEntries.add(mWifiEntry3);
mWifiEntries.add(mWifiEntry4);
- verify(mInternetDialogCallback)
- .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries,
+ null /* connectedEntry */, false /* hasMoreEntry */);
// If the Ethernet exists, then Wi-Fi entries will cut last one.
reset(mInternetDialogCallback);
@@ -557,8 +604,8 @@
mInternetDialogController.onAccessPointsChanged(mAccessPoints);
mWifiEntries.remove(mWifiEntry4);
- verify(mInternetDialogCallback)
- .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries,
+ null /* connectedEntry */, true /* hasMoreEntry */);
// Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
reset(mInternetDialogCallback);
@@ -567,8 +614,8 @@
mInternetDialogController.onAccessPointsChanged(mAccessPoints);
mWifiEntries.remove(mWifiEntry3);
- verify(mInternetDialogCallback)
- .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries,
+ null /* connectedEntry */, true /* hasMoreEntry */);
}
@Test
@@ -584,8 +631,8 @@
mWifiEntries.clear();
mWifiEntries.add(mWifiEntry1);
- verify(mInternetDialogCallback)
- .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries,
+ null /* connectedEntry */, false /* hasMoreEntry */);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index 0cf063f..651bcde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -316,6 +316,20 @@
}
@Test
+ public void updateDialog_wifiOnAndOneWifiEntry_showWifiListAndSeeAllArea() {
+ // The precondition WiFi ON is already in setUp()
+ mInternetDialog.mConnectedWifiEntry = null;
+ mInternetDialog.mWifiEntriesCount = 1;
+
+ mInternetDialog.updateDialog(false);
+
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+ // Show a blank block to fix the dialog height even if there is no WiFi list
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
+ }
+
+ @Test
public void updateDialog_wifiOnAndHasConnectedWifi_showAllWifiAndSeeAllArea() {
// The preconditions WiFi ON and WiFi entries are already in setUp()
mInternetDialog.mWifiEntriesCount = 0;
@@ -325,13 +339,15 @@
assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
// Show a blank block to fix the dialog height even if there is no WiFi list
assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
}
@Test
- public void updateDialog_wifiOnAndHasWifiList_showWifiListAndSeeAll() {
+ public void updateDialog_wifiOnAndHasMaxWifiList_showWifiListAndSeeAll() {
// The preconditions WiFi ON and WiFi entries are already in setUp()
mInternetDialog.mConnectedWifiEntry = null;
+ mInternetDialog.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT;
+ mInternetDialog.mHasMoreEntry = true;
mInternetDialog.updateDialog(false);
@@ -343,6 +359,8 @@
@Test
public void updateDialog_wifiOnAndHasBothWifiEntry_showBothWifiEntryAndSeeAll() {
// The preconditions WiFi ON and WiFi entries are already in setUp()
+ mInternetDialog.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT - 1;
+ mInternetDialog.mHasMoreEntry = true;
mInternetDialog.updateDialog(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 7938511..89435ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -6,6 +6,7 @@
import android.testing.TestableLooper.RunWithLooper
import android.util.DisplayMetrics
import com.android.systemui.ExpandHelper
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.media.MediaHierarchyManager
@@ -81,6 +82,8 @@
mDependency,
TestableLooper.get(this))
row = helper.createRow()
+ context.getOrCreateTestableResources()
+ .addOverride(R.bool.config_use_split_notification_shade, false)
transitionController = LockscreenShadeTransitionController(
statusBarStateController = statusbarStateController,
lockscreenGestureLogger = lockscreenGestureLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
index afbe668..8c5f04f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java
@@ -16,27 +16,30 @@
package com.android.systemui.statusbar;
-import static org.mockito.ArgumentMatchers.any;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.NotificationManager;
-import android.os.Handler;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -46,7 +49,6 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
public class NotificationListenerTest extends SysuiTestCase {
private static final String TEST_PACKAGE_NAME = "test";
private static final int TEST_UID = 0;
@@ -54,6 +56,8 @@
@Mock private NotificationHandler mNotificationHandler;
@Mock private NotificationManager mNotificationManager;
+ private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock);
private NotificationListener mListener;
private StatusBarNotification mSbn;
private RankingMap mRanking = new RankingMap(new Ranking[0]);
@@ -65,7 +69,8 @@
mListener = new NotificationListener(
mContext,
mNotificationManager,
- new Handler(TestableLooper.get(this).getLooper()));
+ mFakeSystemClock,
+ mFakeExecutor);
mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
new Notification(), UserHandle.CURRENT, null, 0);
@@ -75,23 +80,67 @@
@Test
public void testNotificationAddCallsAddNotification() {
mListener.onNotificationPosted(mSbn, mRanking);
- TestableLooper.get(this).processAllMessages();
+ mFakeExecutor.runAllReady();
verify(mNotificationHandler).onNotificationPosted(mSbn, mRanking);
}
@Test
public void testNotificationRemovalCallsRemoveNotification() {
mListener.onNotificationRemoved(mSbn, mRanking);
- TestableLooper.get(this).processAllMessages();
+ mFakeExecutor.runAllReady();
verify(mNotificationHandler).onNotificationRemoved(eq(mSbn), eq(mRanking), anyInt());
}
@Test
public void testRankingUpdateCallsNotificationRankingUpdate() {
mListener.onNotificationRankingUpdate(mRanking);
- TestableLooper.get(this).processAllMessages();
- // RankingMap may be modified by plugins.
- verify(mNotificationHandler).onNotificationRankingUpdate(any());
+ assertThat(mFakeExecutor.runAllReady()).isEqualTo(1);
+ verify(mNotificationHandler).onNotificationRankingUpdate(eq(mRanking));
+ }
+
+ @Test
+ public void testRankingUpdateMultipleTimesCallsNotificationRankingUpdateOnce() {
+ // GIVEN multiple notification ranking updates
+ RankingMap ranking1 = mock(RankingMap.class);
+ RankingMap ranking2 = mock(RankingMap.class);
+ RankingMap ranking3 = mock(RankingMap.class);
+ mListener.onNotificationRankingUpdate(ranking1);
+ mListener.onNotificationRankingUpdate(ranking2);
+ mListener.onNotificationRankingUpdate(ranking3);
+
+ // WHEN executor runs with multiple updates in the queue
+ assertThat(mFakeExecutor.numPending()).isEqualTo(3);
+ assertThat(mFakeExecutor.runAllReady()).isEqualTo(3);
+
+ // VERIFY that only the last ranking actually gets handled
+ verify(mNotificationHandler, never()).onNotificationRankingUpdate(eq(ranking1));
+ verify(mNotificationHandler, never()).onNotificationRankingUpdate(eq(ranking2));
+ verify(mNotificationHandler).onNotificationRankingUpdate(eq(ranking3));
+ verifyNoMoreInteractions(mNotificationHandler);
+ }
+
+ @Test
+ public void testRankingUpdateWillCallAgainIfQueueIsSlow() {
+ // GIVEN multiple notification ranking updates
+ RankingMap ranking1 = mock(RankingMap.class);
+ RankingMap ranking2 = mock(RankingMap.class);
+ RankingMap ranking3 = mock(RankingMap.class);
+ mListener.onNotificationRankingUpdate(ranking1);
+ mListener.onNotificationRankingUpdate(ranking2);
+ mListener.onNotificationRankingUpdate(ranking3);
+
+ // WHEN executor runs with a 1-second gap between handling events 1 and 2
+ assertThat(mFakeExecutor.numPending()).isEqualTo(3);
+ assertThat(mFakeExecutor.runNextReady()).isTrue();
+ // delay a second, which empties the executor
+ mFakeSystemClock.advanceTime(1000);
+ assertThat(mFakeExecutor.numPending()).isEqualTo(0);
+
+ // VERIFY that both event 2 and event 3 are called
+ verify(mNotificationHandler, never()).onNotificationRankingUpdate(eq(ranking1));
+ verify(mNotificationHandler).onNotificationRankingUpdate(eq(ranking2));
+ verify(mNotificationHandler).onNotificationRankingUpdate(eq(ranking3));
+ verifyNoMoreInteractions(mNotificationHandler);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index a1d9a7b..be1720d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -18,7 +18,9 @@
import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.DeviceStateManager.FoldStateListener
+import android.os.Handler
import android.testing.AndroidTestingRunner
+import androidx.core.util.Consumer
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
@@ -31,9 +33,12 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import java.lang.Exception
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -48,16 +53,28 @@
@Mock
private lateinit var deviceStateManager: DeviceStateManager
- private lateinit var foldStateProvider: FoldStateProvider
+ @Mock
+ private lateinit var handler: Handler
+
+ @Captor
+ private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
+
+ @Captor
+ private lateinit var screenOnListenerCaptor: ArgumentCaptor<ScreenListener>
+
+ @Captor
+ private lateinit var hingeAngleCaptor: ArgumentCaptor<Consumer<Float>>
+
+ private lateinit var foldStateProvider: DeviceFoldStateProvider
private val foldUpdates: MutableList<Int> = arrayListOf()
private val hingeAngleUpdates: MutableList<Float> = arrayListOf()
- private val foldStateListenerCaptor = ArgumentCaptor.forClass(FoldStateListener::class.java)
private var foldedDeviceState: Int = 0
private var unfoldedDeviceState: Int = 0
- private val screenOnListenerCaptor = ArgumentCaptor.forClass(ScreenListener::class.java)
+ private var scheduledRunnable: Runnable? = null
+ private var scheduledRunnableDelay: Long? = null
@Before
fun setUp() {
@@ -75,7 +92,8 @@
hingeAngleProvider,
screenStatusProvider,
deviceStateManager,
- context.mainExecutor
+ context.mainExecutor,
+ handler
)
foldStateProvider.addCallback(object : FoldStateProvider.FoldUpdatesListener {
@@ -91,6 +109,22 @@
verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
verify(screenStatusProvider).addCallback(screenOnListenerCaptor.capture())
+ verify(hingeAngleProvider).addCallback(hingeAngleCaptor.capture())
+
+ whenever(handler.postDelayed(any<Runnable>(), any())).then { invocationOnMock ->
+ scheduledRunnable = invocationOnMock.getArgument<Runnable>(0)
+ scheduledRunnableDelay = invocationOnMock.getArgument<Long>(1)
+ null
+ }
+
+ whenever(handler.removeCallbacks(any<Runnable>())).then { invocationOnMock ->
+ val removedRunnable = invocationOnMock.getArgument<Runnable>(0)
+ if (removedRunnable == scheduledRunnable) {
+ scheduledRunnableDelay = null
+ scheduledRunnable = null
+ }
+ null
+ }
}
@Test
@@ -167,6 +201,86 @@
assertThat(foldUpdates).isEmpty()
}
+ @Test
+ fun startClosingEvent_afterTimeout_abortEmitted() {
+ sendHingeAngleEvent(90)
+ sendHingeAngleEvent(80)
+
+ simulateTimeout(ABORT_CLOSING_MILLIS)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING, FOLD_UPDATE_ABORTED)
+ }
+
+ @Test
+ fun startClosingEvent_beforeTimeout_abortNotEmitted() {
+ sendHingeAngleEvent(90)
+ sendHingeAngleEvent(80)
+
+ simulateTimeout(ABORT_CLOSING_MILLIS - 1)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+ }
+
+ @Test
+ fun startClosingEvent_eventBeforeTimeout_oneEventEmitted() {
+ sendHingeAngleEvent(180)
+ sendHingeAngleEvent(90)
+
+ simulateTimeout(ABORT_CLOSING_MILLIS - 1)
+ sendHingeAngleEvent(80)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+ }
+
+ @Test
+ fun startClosingEvent_timeoutAfterTimeoutRescheduled_abortEmitted() {
+ sendHingeAngleEvent(180)
+ sendHingeAngleEvent(90)
+
+ simulateTimeout(ABORT_CLOSING_MILLIS - 1) // The timeout should not trigger here.
+ sendHingeAngleEvent(80)
+ simulateTimeout(ABORT_CLOSING_MILLIS) // The timeout should trigger here.
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING, FOLD_UPDATE_ABORTED)
+ }
+
+ @Test
+ fun startClosingEvent_shortTimeBetween_emitsOnlyOneEvents() {
+ sendHingeAngleEvent(180)
+
+ sendHingeAngleEvent(90)
+ sendHingeAngleEvent(80)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+ }
+
+ @Test
+ fun startClosingEvent_whileClosing_emittedDespiteInitialAngle() {
+ val maxAngle = 180 - FULLY_OPEN_THRESHOLD_DEGREES.toInt()
+ for (i in 1..maxAngle) {
+ foldUpdates.clear()
+
+ simulateFolding(startAngle = i)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+ simulateTimeout() // Timeout to set the state to aborted.
+ }
+ }
+
+ private fun simulateTimeout(waitTime: Long = ABORT_CLOSING_MILLIS) {
+ val runnableDelay = scheduledRunnableDelay ?: throw Exception("No runnable scheduled.")
+ if (waitTime >= runnableDelay) {
+ scheduledRunnable?.run()
+ scheduledRunnable = null
+ scheduledRunnableDelay = null
+ }
+ }
+
+ private fun simulateFolding(startAngle: Int) {
+ sendHingeAngleEvent(startAngle)
+ sendHingeAngleEvent(startAngle - 1)
+ }
+
private fun setFoldState(folded: Boolean) {
val state = if (folded) foldedDeviceState else unfoldedDeviceState
foldStateListenerCaptor.value.onStateChanged(state)
@@ -175,4 +289,8 @@
private fun fireScreenOnEvent() {
screenOnListenerCaptor.value.onScreenTurnedOn()
}
+
+ private fun sendHingeAngleEvent(angle: Int) {
+ hingeAngleCaptor.value.accept(angle.toFloat())
+ }
}
diff --git a/services/backup/Android.bp b/services/backup/Android.bp
index 7b0d6c0..ead8aff 100644
--- a/services/backup/Android.bp
+++ b/services/backup/Android.bp
@@ -19,9 +19,5 @@
defaults: ["platform_service_defaults"],
srcs: [":services.backup-sources"],
libs: ["services.core"],
- static_libs: [
- "backuplib",
- "app-compat-annotations",
- "guava",
- ],
+ static_libs: ["backuplib", "app-compat-annotations"],
}
diff --git a/services/backup/backuplib/java/com/android/server/backup/TransportManager.java b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
index 594140e..21a22f4 100644
--- a/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
@@ -39,6 +39,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
import com.android.internal.util.Preconditions;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.OnTransportRegisteredListener;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.transport.TransportConnectionManager;
@@ -641,7 +642,7 @@
TransportConnection transportConnection =
mTransportConnectionManager.getTransportClient(
transportComponent, extras, callerLogString);
- final IBackupTransport transport;
+ final BackupTransportClient transport;
try {
transport = transportConnection.connectOrThrow(callerLogString);
} catch (TransportNotAvailableException e) {
@@ -653,10 +654,6 @@
int result;
try {
- // This is a temporary fix to allow blocking calls.
- // TODO: b/147702043. Redesign IBackupTransport so as to make the calls non-blocking.
- Binder.allowBlocking(transport.asBinder());
-
String transportName = transport.name();
String transportDirName = transport.transportDirName();
registerTransport(transportComponent, transport);
@@ -674,8 +671,8 @@
}
/** If {@link RemoteException} is thrown the transport is guaranteed to not be registered. */
- private void registerTransport(ComponentName transportComponent, IBackupTransport transport)
- throws RemoteException {
+ private void registerTransport(ComponentName transportComponent,
+ BackupTransportClient transport) throws RemoteException {
checkCanUseTransport();
TransportDescription description =
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
index a3f6eb6..85ab48c 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java
@@ -21,6 +21,7 @@
import android.app.backup.RestoreSet;
import android.content.Intent;
import android.content.pm.PackageInfo;
+import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -35,6 +36,10 @@
BackupTransportClient(IBackupTransport transportBinder) {
mTransportBinder = transportBinder;
+
+ // This is a temporary fix to allow blocking calls.
+ // TODO: b/147702043. Redesign IBackupTransport so as to make the calls non-blocking.
+ Binder.allowBlocking(mTransportBinder.asBinder());
}
/**
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java
index da77eba..f9a3c36 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnection.java
@@ -59,7 +59,7 @@
import java.util.concurrent.ExecutionException;
/**
- * A {@link TransportConnection} manages the connection to an {@link IBackupTransport} service,
+ * A {@link TransportConnection} manages the connection to a {@link BackupTransportClient},
* obtained via the {@param bindIntent} parameter provided in the constructor. A
* {@link TransportConnection} is responsible for only one connection to the transport service,
* not more.
@@ -67,9 +67,9 @@
* <p>After retrieved using {@link TransportManager#getTransportClient(String, String)}, you can
* call either {@link #connect(String)}, if you can block your thread, or {@link
* #connectAsync(TransportConnectionListener, String)}, otherwise, to obtain a {@link
- * IBackupTransport} instance. It's meant to be passed around as a token to a connected transport.
- * When the connection is not needed anymore you should call {@link #unbind(String)} or indirectly
- * via {@link TransportManager#disposeOfTransportClient(TransportConnection, String)}.
+ * BackupTransportClient} instance. It's meant to be passed around as a token to a connected
+ * transport. When the connection is not needed anymore you should call {@link #unbind(String)} or
+ * indirectly via {@link TransportManager#disposeOfTransportClient(TransportConnection, String)}.
*
* <p>DO NOT forget to unbind otherwise there will be dangling connections floating around.
*
@@ -106,7 +106,7 @@
private int mState = State.IDLE;
@GuardedBy("mStateLock")
- private volatile IBackupTransport mTransport;
+ private volatile BackupTransportClient mTransport;
TransportConnection(
@UserIdInt int userId,
@@ -174,10 +174,12 @@
* trigger another one, just piggyback on the original request.
*
* <p>It's guaranteed that you are going to get a call back to {@param listener} after this
- * call. However, the {@param IBackupTransport} parameter, the transport binder, is not
- * guaranteed to be non-null, or if it's non-null it's not guaranteed to be usable - i.e. it can
- * throw {@link DeadObjectException}s on method calls. You should check for both in your code.
- * The reasons for a null transport binder are:
+ * call. However, the {@link BackupTransportClient} parameter in
+ * {@link TransportConnectionListener#onTransportConnectionResult(BackupTransportClient,
+ * TransportConnection)}, the transport client, is not guaranteed to be non-null, or if it's
+ * non-null it's not guaranteed to be usable - i.e. it can throw {@link DeadObjectException}s
+ * on method calls. You should check for both in your code. The reasons for a null transport
+ * client are:
*
* <ul>
* <li>Some code called {@link #unbind(String)} before you got a callback.
@@ -193,7 +195,7 @@
* For unusable transport binders check {@link DeadObjectException}.
*
* @param listener The listener that will be called with the (possibly null or unusable) {@link
- * IBackupTransport} instance and this {@link TransportConnection} object.
+ * BackupTransportClient} instance and this {@link TransportConnection} object.
* @param caller A {@link String} identifying the caller for logging/debugging purposes. This
* should be a human-readable short string that is easily identifiable in the logs. Ideally
* TAG.methodName(), where TAG is the one used in logcat. In cases where this is is not very
@@ -293,8 +295,8 @@
*
* <p>Synchronous version of {@link #connectAsync(TransportConnectionListener, String)}. The
* same observations about state are valid here. Also, what was said about the {@link
- * IBackupTransport} parameter of {@link TransportConnectionListener} now apply to the return
- * value of this method.
+ * BackupTransportClient} parameter of {@link TransportConnectionListener} now apply to the
+ * return value of this method.
*
* <p>This is a potentially blocking operation, so be sure to call this carefully on the correct
* threads. You can't call this from the process main-thread (it throws an exception if you do
@@ -305,18 +307,18 @@
*
* @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
* {@link #connectAsync(TransportConnectionListener, String)} for more details.
- * @return A {@link IBackupTransport} transport binder instance or null. If it's non-null it can
- * still be unusable - throws {@link DeadObjectException} on method calls
+ * @return A {@link BackupTransportClient} transport client instance or null. If it's non-null
+ * it can still be unusable - throws {@link DeadObjectException} on method calls
*/
@WorkerThread
@Nullable
- public IBackupTransport connect(String caller) {
+ public BackupTransportClient connect(String caller) {
// If called on the main-thread this could deadlock waiting because calls to
// ServiceConnection are on the main-thread as well
Preconditions.checkState(
!Looper.getMainLooper().isCurrentThread(), "Can't call connect() on main thread");
- IBackupTransport transport = mTransport;
+ BackupTransportClient transport = mTransport;
if (transport != null) {
log(Priority.DEBUG, caller, "Sync connect: reusing transport");
return transport;
@@ -330,7 +332,7 @@
}
}
- CompletableFuture<IBackupTransport> transportFuture = new CompletableFuture<>();
+ CompletableFuture<BackupTransportClient> transportFuture = new CompletableFuture<>();
TransportConnectionListener requestListener =
(requestedTransport, transportClient) ->
transportFuture.complete(requestedTransport);
@@ -359,13 +361,14 @@
*
* @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
* {@link #connectAsync(TransportConnectionListener, String)} for more details.
- * @return A {@link IBackupTransport} transport binder instance.
+ * @return A {@link BackupTransportClient} transport binder instance.
* @see #connect(String)
* @throws TransportNotAvailableException if connection attempt fails.
*/
@WorkerThread
- public IBackupTransport connectOrThrow(String caller) throws TransportNotAvailableException {
- IBackupTransport transport = connect(caller);
+ public BackupTransportClient connectOrThrow(String caller)
+ throws TransportNotAvailableException {
+ BackupTransportClient transport = connect(caller);
if (transport == null) {
log(Priority.ERROR, caller, "Transport connection failed");
throw new TransportNotAvailableException();
@@ -379,12 +382,12 @@
*
* @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
* {@link #connectAsync(TransportConnectionListener, String)} for more details.
- * @return A {@link IBackupTransport} transport binder instance.
+ * @return A {@link BackupTransportClient} transport client instance.
* @throws TransportNotAvailableException if not connected.
*/
- public IBackupTransport getConnectedTransport(String caller)
+ public BackupTransportClient getConnectedTransport(String caller)
throws TransportNotAvailableException {
- IBackupTransport transport = mTransport;
+ BackupTransportClient transport = mTransport;
if (transport == null) {
log(Priority.ERROR, caller, "Transport not connected");
throw new TransportNotAvailableException();
@@ -425,7 +428,8 @@
}
private void onServiceConnected(IBinder binder) {
- IBackupTransport transport = IBackupTransport.Stub.asInterface(binder);
+ IBackupTransport transportBinder = IBackupTransport.Stub.asInterface(binder);
+ BackupTransportClient transport = new BackupTransportClient(transportBinder);
synchronized (mStateLock) {
checkStateIntegrityLocked();
@@ -492,15 +496,15 @@
private void notifyListener(
TransportConnectionListener listener,
- @Nullable IBackupTransport transport,
+ @Nullable BackupTransportClient transport,
String caller) {
- String transportString = (transport != null) ? "IBackupTransport" : "null";
+ String transportString = (transport != null) ? "BackupTransportClient" : "null";
log(Priority.INFO, "Notifying [" + caller + "] transport = " + transportString);
mListenerHandler.post(() -> listener.onTransportConnectionResult(transport, this));
}
@GuardedBy("mStateLock")
- private void notifyListenersAndClearLocked(@Nullable IBackupTransport transport) {
+ private void notifyListenersAndClearLocked(@Nullable BackupTransportClient transport) {
for (Map.Entry<TransportConnectionListener, String> entry : mListeners.entrySet()) {
TransportConnectionListener listener = entry.getKey();
String caller = entry.getValue();
@@ -510,7 +514,7 @@
}
@GuardedBy("mStateLock")
- private void setStateLocked(@State int state, @Nullable IBackupTransport transport) {
+ private void setStateLocked(@State int state, @Nullable BackupTransportClient transport) {
log(Priority.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state));
onStateTransition(mState, state);
mState = state;
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java
index 03d35e4..1776c41 100644
--- a/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java
+++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportConnectionListener.java
@@ -18,7 +18,7 @@
import android.annotation.Nullable;
-import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.transport.BackupTransportClient;
/**
* Listener to be called by {@link TransportConnection#connectAsync(TransportConnectionListener,
@@ -26,13 +26,14 @@
*/
public interface TransportConnectionListener {
/**
- * Called when {@link TransportConnection} has a transport binder available or that it decided
+ * Called when {@link TransportConnection} has a transport client available or that it decided
* it couldn't obtain one, in which case {@param transport} is null.
*
- * @param transport A {@link IBackupTransport} transport binder or null.
+ * @param transportClient A {@link BackupTransportClient} transport or null.
* @param transportConnection The {@link TransportConnection} used to retrieve this transport
- * binder.
+ * client.
*/
void onTransportConnectionResult(
- @Nullable IBackupTransport transport, TransportConnection transportConnection);
+ @Nullable BackupTransportClient transportClient,
+ TransportConnection transportConnection);
}
diff --git a/services/backup/java/com/android/server/backup/OperationStorage.java b/services/backup/java/com/android/server/backup/OperationStorage.java
index 155a4b57..466f647 100644
--- a/services/backup/java/com/android/server/backup/OperationStorage.java
+++ b/services/backup/java/com/android/server/backup/OperationStorage.java
@@ -110,6 +110,21 @@
void removeOperation(int token);
/**
+ * Obtain the number of currently registered operations.
+ *
+ * @return the number of currently registered operations.
+ */
+ int numOperations();
+
+ /**
+ * Determine if a backup operation is in progress or not.
+ *
+ * @return true if any operation is registered of type BACKUP and in
+ * state PENDING.
+ */
+ boolean isBackupOperationInProgress();
+
+ /**
* Obtain a set of operation tokens for all pending operations that were
* registered with an association to the specified package name.
*
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 2a6c90f..98ea03e 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -103,7 +103,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
import com.android.internal.util.Preconditions;
import com.android.server.AppWidgetBackupBridge;
import com.android.server.EventLogTags;
@@ -127,6 +126,7 @@
import com.android.server.backup.params.RestoreParams;
import com.android.server.backup.restore.ActiveRestoreSession;
import com.android.server.backup.restore.PerformUnifiedRestoreTask;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.transport.TransportNotAvailableException;
import com.android.server.backup.transport.TransportNotRegisteredException;
@@ -290,8 +290,8 @@
// Bookkeeping of in-flight operations. The operation token is the index of the entry in the
// pending operations list.
public static final int OP_PENDING = 0;
- public static final int OP_ACKNOWLEDGED = 1;
- public static final int OP_TIMEOUT = -1;
+ private static final int OP_ACKNOWLEDGED = 1;
+ private static final int OP_TIMEOUT = -1;
// Waiting for backup agent to respond during backup operation.
public static final int OP_TYPE_BACKUP_WAIT = 0;
@@ -3719,7 +3719,8 @@
mTransportManager.getTransportClient(newTransportName, callerLogString);
if (transportConnection != null) {
try {
- IBackupTransport transport = transportConnection.connectOrThrow(callerLogString);
+ BackupTransportClient transport = transportConnection.connectOrThrow(
+ callerLogString);
mCurrentToken = transport.getCurrentRestoreSet();
} catch (Exception e) {
// Oops. We can't know the current dataset token, so reset and figure it out
@@ -4371,7 +4372,7 @@
final long oldCallingId = Binder.clearCallingIdentity();
try {
- IBackupTransport transport = transportConnection.connectOrThrow(
+ BackupTransportClient transport = transportConnection.connectOrThrow(
/* caller */ "BMS.getOperationTypeFromTransport");
if ((transport.getTransportFlags() & BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER) != 0) {
return OperationType.MIGRATION;
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 1c86091..9ce4eab 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -41,7 +41,6 @@
import android.util.Log;
import android.util.Slog;
-import com.android.internal.backup.IBackupTransport;
import com.android.server.EventLogTags;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
@@ -51,6 +50,7 @@
import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.internal.Operation;
import com.android.server.backup.remote.RemoteCall;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.transport.TransportNotAvailableException;
import com.android.server.backup.utils.BackupEligibilityRules;
@@ -300,7 +300,7 @@
mUserBackupManagerService.handleCancel(mBackupRunnerOpToken, cancelAll);
try {
// If we're running a backup we should be connected to a transport
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.getConnectedTransport("PFTBT.handleCancel()");
transport.cancelFullBackup();
} catch (RemoteException | TransportNotAvailableException e) {
@@ -353,7 +353,7 @@
return;
}
- IBackupTransport transport = mTransportConnection.connect("PFTBT.run()");
+ BackupTransportClient transport = mTransportConnection.connect("PFTBT.run()");
if (transport == null) {
Slog.w(TAG, "Transport not present; full data backup not performed");
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
@@ -745,7 +745,7 @@
Slog.v(TAG, "Got preflight response; size=" + totalSize);
}
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow("PFTBT$SPBP.preflightFullBackup()");
result = transport.checkFullBackupSize(totalSize);
if (result == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 3b3bf8c6..5c24859 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -31,7 +31,6 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
import com.android.server.EventLogTags;
import com.android.server.backup.BackupAgentTimeoutParameters;
import com.android.server.backup.BackupRestoreTask;
@@ -51,6 +50,7 @@
import com.android.server.backup.params.RestoreParams;
import com.android.server.backup.restore.PerformAdbRestoreTask;
import com.android.server.backup.restore.PerformUnifiedRestoreTask;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import java.util.ArrayList;
@@ -149,7 +149,7 @@
String callerLogString = "BH/MSG_RUN_BACKUP";
TransportConnection transportConnection =
transportManager.getCurrentTransportClient(callerLogString);
- IBackupTransport transport =
+ BackupTransportClient transport =
transportConnection != null
? transportConnection.connect(callerLogString)
: null;
@@ -364,7 +364,7 @@
RestoreGetSetsParams params = (RestoreGetSetsParams) msg.obj;
String callerLogString = "BH/MSG_RUN_GET_RESTORE_SETS";
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
params.mTransportConnection.connectOrThrow(callerLogString);
sets = transport.getAvailableRestoreSets();
// cache the result in the active session
diff --git a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
index b58c973..6908c60 100644
--- a/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
+++ b/services/backup/java/com/android/server/backup/internal/LifecycleOperationStorage.java
@@ -27,7 +27,7 @@
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.OperationStorage;
-import com.google.common.collect.ImmutableSet;
+import com.google.android.collect.Sets;
import java.util.HashMap;
import java.util.HashSet;
@@ -36,8 +36,6 @@
import java.util.function.Consumer;
import java.util.function.IntConsumer;
-import javax.annotation.concurrent.ThreadSafe;
-
/**
* LifecycleOperationStorage is responsible for maintaining a set of currently
* active operations. Each operation has a type and state, and a callback that
@@ -67,7 +65,6 @@
* If type of operation is {@code OP_BACKUP}, it is a task running backups. It
* provides a handle to cancel backup tasks.
*/
-@ThreadSafe
public class LifecycleOperationStorage implements OperationStorage {
private static final String TAG = "LifecycleOperationStorage";
@@ -93,7 +90,7 @@
@Override
public void registerOperation(int token, @OpState int initialState,
BackupRestoreTask task, @OpType int type) {
- registerOperationForPackages(token, initialState, ImmutableSet.of(), task, type);
+ registerOperationForPackages(token, initialState, Sets.newHashSet(), task, type);
}
/** See {@link OperationStorage#registerOperationForPackages()} */
@@ -118,8 +115,7 @@
public void removeOperation(int token) {
synchronized (mOperationsLock) {
mOperations.remove(token);
- ImmutableSet<String> packagesWithTokens =
- ImmutableSet.copyOf(mOpTokensByPackage.keySet());
+ Set<String> packagesWithTokens = mOpTokensByPackage.keySet();
for (String packageName : packagesWithTokens) {
Set<Integer> tokens = mOpTokensByPackage.get(packageName);
if (tokens == null) {
@@ -131,22 +127,45 @@
}
}
+ /** See {@link OperationStorage#numOperations()}. */
+ @Override
+ public int numOperations() {
+ synchronized (mOperationsLock) {
+ return mOperations.size();
+ }
+ }
+
+ /** See {@link OperationStorage#isBackupOperationInProgress()}. */
+ @Override
+ public boolean isBackupOperationInProgress() {
+ synchronized (mOperationsLock) {
+ for (int i = 0; i < mOperations.size(); i++) {
+ Operation op = mOperations.valueAt(i);
+ if (op.type == OpType.BACKUP && op.state == OpState.PENDING) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
/** See {@link OperationStorage#operationTokensForPackage()} */
@Override
public Set<Integer> operationTokensForPackage(String packageName) {
synchronized (mOperationsLock) {
- Set<Integer> tokens = mOpTokensByPackage.get(packageName);
- if (tokens == null) {
- return ImmutableSet.of();
+ final Set<Integer> tokens = mOpTokensByPackage.get(packageName);
+ Set<Integer> result = Sets.newHashSet();
+ if (tokens != null) {
+ result.addAll(tokens);
}
- return ImmutableSet.copyOf(tokens);
+ return result;
}
}
/** See {@link OperationStorage#operationTokensForOpType()} */
@Override
public Set<Integer> operationTokensForOpType(@OpType int type) {
- ImmutableSet.Builder<Integer> tokens = ImmutableSet.builder();
+ Set<Integer> tokens = Sets.newHashSet();
synchronized (mOperationsLock) {
for (int i = 0; i < mOperations.size(); i++) {
final Operation op = mOperations.valueAt(i);
@@ -155,14 +174,14 @@
tokens.add(token);
}
}
- return tokens.build();
+ return tokens;
}
}
/** See {@link OperationStorage#operationTokensForOpState()} */
@Override
public Set<Integer> operationTokensForOpState(@OpState int state) {
- ImmutableSet.Builder<Integer> tokens = ImmutableSet.builder();
+ Set<Integer> tokens = Sets.newHashSet();
synchronized (mOperationsLock) {
for (int i = 0; i < mOperations.size(); i++) {
final Operation op = mOperations.valueAt(i);
@@ -171,7 +190,7 @@
tokens.add(token);
}
}
- return tokens.build();
+ return tokens;
}
}
diff --git a/services/backup/java/com/android/server/backup/internal/PerformClearTask.java b/services/backup/java/com/android/server/backup/internal/PerformClearTask.java
index 80bd604..de0177c 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformClearTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformClearTask.java
@@ -21,9 +21,9 @@
import android.content.pm.PackageInfo;
import android.util.Slog;
-import com.android.internal.backup.IBackupTransport;
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import java.io.File;
@@ -47,7 +47,7 @@
public void run() {
String callerLogString = "PerformClearTask.run()";
- IBackupTransport transport = null;
+ BackupTransportClient transport = null;
try {
// Clear the on-device backup state to ensure a full backup next time
String transportDirName =
diff --git a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
index 7636ef6..888f49d 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformInitializeTask.java
@@ -28,10 +28,10 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
import com.android.server.EventLogTags;
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import java.io.File;
@@ -128,7 +128,8 @@
EventLog.writeEvent(EventLogTags.BACKUP_START, transportDirName);
long startRealtime = SystemClock.elapsedRealtime();
- IBackupTransport transport = transportConnection.connectOrThrow(callerLogString);
+ BackupTransportClient transport = transportConnection.connectOrThrow(
+ callerLogString);
int status = transport.initializeDevice();
if (status != BackupTransport.TRANSPORT_OK) {
Slog.e(TAG, "Transport error in initializeDevice()");
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index bdb2e6f..30da8c1 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -51,7 +51,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
import com.android.internal.util.Preconditions;
import com.android.server.AppWidgetBackupBridge;
import com.android.server.backup.BackupAgentTimeoutParameters;
@@ -65,6 +64,7 @@
import com.android.server.backup.remote.RemoteCall;
import com.android.server.backup.remote.RemoteCallable;
import com.android.server.backup.remote.RemoteResult;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.transport.TransportNotAvailableException;
import com.android.server.backup.utils.BackupEligibilityRules;
@@ -111,7 +111,7 @@
* </ul>
*
* If there is no PackageManager (PM) pseudo-package state file in the state directory, the
- * specified transport will be initialized with {@link IBackupTransport#initializeDevice()}.
+ * specified transport will be initialized with {@link BackupTransportClient#initializeDevice()}.
*
* <p>The PM pseudo-package is the first package to be backed-up and sent to the transport in case
* of incremental choice. If non-incremental, PM will only be backed-up if specified in the queue,
@@ -141,8 +141,8 @@
* </ul>
* <li>Unbind the agent.
* <li>Assuming agent response, send the staged data that the agent wrote to disk to the transport
- * via {@link IBackupTransport#performBackup(PackageInfo, ParcelFileDescriptor, int)}.
- * <li>Call {@link IBackupTransport#finishBackup()} if previous call was successful.
+ * via {@link BackupTransportClient#performBackup(PackageInfo, ParcelFileDescriptor, int)}.
+ * <li>Call {@link BackupTransportClient#finishBackup()} if previous call was successful.
* <li>Save the new state in the state file. During the agent call it was being written to
* <state file>.new, here we rename it and replace the old one.
* <li>Delete the stage file.
@@ -155,7 +155,7 @@
* <li>Delete the {@link DataChangedJournal} provided. Note that this should not be the current
* journal.
* <li>Set {@link UserBackupManagerService} current token as {@link
- * IBackupTransport#getCurrentRestoreSet()}, if applicable.
+ * BackupTransportClient#getCurrentRestoreSet()}, if applicable.
* <li>Add the transport to the list of transports pending initialization ({@link
* UserBackupManagerService#getPendingInits()}) and kick-off initialization if the transport
* ever returned {@link BackupTransport#TRANSPORT_NOT_INITIALIZED}.
@@ -194,7 +194,7 @@
* @param backupManagerService The {@link UserBackupManagerService} instance.
* @param transportConnection The {@link TransportConnection} that contains the transport used
* for the operation.
- * @param transportDirName The value of {@link IBackupTransport#transportDirName()} for the
+ * @param transportDirName The value of {@link BackupTransportClient#transportDirName()} for the
* transport whose {@link TransportConnection} was provided above.
* @param queue The list of package names that will be backed-up.
* @param dataChangedJournal The old data-changed journal file that will be deleted when the
@@ -417,7 +417,7 @@
boolean noDataPackageEncountered = false;
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow("KVBT.informTransportOfEmptyBackups()");
for (String packageName : succeedingPackages) {
@@ -467,8 +467,8 @@
}
/** Send the "no data changed" message to a transport for a specific package */
- private void sendNoDataChangedTo(IBackupTransport transport, PackageInfo packageInfo, int flags)
- throws RemoteException {
+ private void sendNoDataChangedTo(BackupTransportClient transport, PackageInfo packageInfo,
+ int flags) throws RemoteException {
ParcelFileDescriptor pfd;
try {
pfd = ParcelFileDescriptor.open(mBlankStateFile, MODE_READ_ONLY | MODE_CREATE);
@@ -608,7 +608,8 @@
mReporter.onQueueReady(mQueue);
File pmState = new File(mStateDirectory, PM_PACKAGE);
try {
- IBackupTransport transport = mTransportConnection.connectOrThrow("KVBT.startTask()");
+ BackupTransportClient transport = mTransportConnection.connectOrThrow(
+ "KVBT.startTask()");
String transportName = transport.name();
if (transportName.contains("EncryptedLocalTransport")) {
// Temporary code for EiTF POC. Only supports non-incremental backups.
@@ -764,7 +765,8 @@
long currentToken = mBackupManagerService.getCurrentToken();
if (mHasDataToBackup && (status == BackupTransport.TRANSPORT_OK) && (currentToken == 0)) {
try {
- IBackupTransport transport = mTransportConnection.connectOrThrow(callerLogString);
+ BackupTransportClient transport = mTransportConnection.connectOrThrow(
+ callerLogString);
transportName = transport.name();
mBackupManagerService.setCurrentToken(transport.getCurrentRestoreSet());
mBackupManagerService.writeRestoreTokens();
@@ -835,7 +837,7 @@
@GuardedBy("mQueueLock")
private void triggerTransportInitializationLocked() throws Exception {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow("KVBT.triggerTransportInitializationLocked");
mBackupManagerService.getPendingInits().add(transport.name());
deletePmStateFile();
@@ -919,7 +921,7 @@
}
}
- IBackupTransport transport = mTransportConnection.connectOrThrow(
+ BackupTransportClient transport = mTransportConnection.connectOrThrow(
"KVBT.extractAgentData()");
long quota = transport.getBackupQuota(packageName, /* isFullBackup */ false);
int transportFlags = transport.getTransportFlags();
@@ -1078,7 +1080,7 @@
int status;
try (ParcelFileDescriptor backupData =
ParcelFileDescriptor.open(backupDataFile, MODE_READ_ONLY)) {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow("KVBT.transportPerformBackup()");
mReporter.onTransportPerformBackup(packageName);
int flags = getPerformBackupFlags(mUserInitiated, nonIncremental);
@@ -1131,7 +1133,7 @@
private void agentDoQuotaExceeded(@Nullable IBackupAgent agent, String packageName, long size) {
if (agent != null) {
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow("KVBT.agentDoQuotaExceeded()");
long quota = transport.getBackupQuota(packageName, false);
remoteCall(
@@ -1227,7 +1229,7 @@
mReporter.onRevertTask();
long delay;
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow("KVBT.revertTask()");
delay = transport.requestBackupTime();
} catch (Exception e) {
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 8c786d5..ac831af 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -54,7 +54,6 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
import com.android.server.AppWidgetBackupBridge;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
@@ -66,6 +65,7 @@
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.internal.OnTaskFinishedListener;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.utils.BackupEligibilityRules;
import com.android.server.backup.utils.BackupManagerMonitorUtils;
@@ -397,7 +397,7 @@
PackageInfo[] packages = mAcceptSet.toArray(new PackageInfo[0]);
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow("PerformUnifiedRestoreTask.startRestore()");
mStatus = transport.startRestore(mToken, packages);
@@ -495,7 +495,7 @@
private void dispatchNextRestore() {
UnifiedRestoreState nextState = UnifiedRestoreState.FINAL;
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow(
"PerformUnifiedRestoreTask.dispatchNextRestore()");
mRestoreDescription = transport.nextRestorePackage();
@@ -709,7 +709,7 @@
boolean startedAgentRestore = false;
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow(
"PerformUnifiedRestoreTask.initiateOneRestore()");
@@ -940,7 +940,8 @@
String callerLogString = "PerformUnifiedRestoreTask$StreamFeederThread.run()";
try {
- IBackupTransport transport = mTransportConnection.connectOrThrow(callerLogString);
+ BackupTransportClient transport = mTransportConnection.connectOrThrow(
+ callerLogString);
while (status == BackupTransport.TRANSPORT_OK) {
// have the transport write some of the restoring data to us
int result = transport.getNextFullRestoreDataChunk(tWriteEnd);
@@ -1032,7 +1033,7 @@
// Something went wrong somewhere. Whether it was at the transport
// level is immaterial; we need to tell the transport to bail
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow(callerLogString);
transport.abortFullRestore();
} catch (Exception e) {
@@ -1095,7 +1096,7 @@
String callerLogString = "PerformUnifiedRestoreTask.finalizeRestore()";
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
mTransportConnection.connectOrThrow(callerLogString);
transport.finishRestore();
} catch (Exception e) {
diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
index 652386f..bd1ac2dc 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
@@ -40,8 +40,8 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.backup.IBackupTransport;
import com.android.internal.util.ArrayUtils;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import com.google.android.collect.Sets;
@@ -237,7 +237,7 @@
}
if (transportConnection != null) {
try {
- IBackupTransport transport =
+ BackupTransportClient transport =
transportConnection.connectOrThrow(
"AppBackupUtils.appIsRunningAndEligibleForBackupWithTransport");
return transport.isAppEligibleForBackup(
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index a0a00f7..bcc345f 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -20,7 +20,7 @@
import static com.android.internal.util.FunctionalUtils.uncheckExceptions;
import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
-import static com.android.server.companion.PermissionsUtils.enforceCallerPermissionsToRequest;
+import static com.android.server.companion.PermissionsUtils.enforcePermissionsForAssociation;
import static com.android.server.companion.RolesUtils.isRoleHolder;
import static java.util.Objects.requireNonNull;
@@ -110,7 +110,7 @@
}
// 1. Enforce permissions and other requirements.
- enforceCallerPermissionsToRequest(mContext, request, packageName, userId);
+ enforcePermissionsForAssociation(mContext, request, packageName, userId);
mService.checkUsesFeature(packageName, userId);
// 2. Check if association can be created without launching UI (i.e. CDM needs NEITHER
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index e0cd472..b32d543 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -39,7 +39,8 @@
import static com.android.server.companion.PermissionsUtils.checkCallerCanManageAssociationsForPackage;
import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice;
import static com.android.server.companion.PermissionsUtils.enforceCallerCanInteractWithUserId;
-import static com.android.server.companion.PermissionsUtils.enforceCallerCanManagerCompanionDevice;
+import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
+import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageCompanionDevice;
import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation;
import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation;
@@ -401,15 +402,16 @@
Slog.i(LOG_TAG, "associate() "
+ "request=" + request + ", "
+ "package=u" + userId + "/" + packageName);
+ enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
+ "create associations");
+
mAssociationRequestsProcessor.process(request, packageName, userId, callback);
}
@Override
public List<AssociationInfo> getAssociations(String packageName, int userId) {
- if (!checkCallerCanManageAssociationsForPackage(getContext(), userId, packageName)) {
- throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
- + "permissions to get associations for u" + userId + "/" + packageName);
- }
+ enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
+ "get associations");
if (!checkCallerCanManageCompanionDevice(getContext())) {
// If the caller neither is system nor holds MANAGE_COMPANION_DEVICES: it needs to
@@ -424,7 +426,7 @@
@Override
public List<AssociationInfo> getAllAssociationsForUser(int userId) throws RemoteException {
enforceCallerCanInteractWithUserId(getContext(), userId);
- enforceCallerCanManagerCompanionDevice(getContext(), "getAllAssociationsForUser");
+ enforceCallerCanManageCompanionDevice(getContext(), "getAllAssociationsForUser");
return new ArrayList<>(
CompanionDeviceManagerService.this.getAllAssociationsForUser(userId));
@@ -434,7 +436,7 @@
public void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener,
int userId) {
enforceCallerCanInteractWithUserId(getContext(), userId);
- enforceCallerCanManagerCompanionDevice(getContext(),
+ enforceCallerCanManageCompanionDevice(getContext(),
"addOnAssociationsChangedListener");
//TODO: Implement.
@@ -621,7 +623,7 @@
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver)
throws RemoteException {
- enforceCallerCanManagerCompanionDevice(getContext(), "onShellCommand");
+ enforceCallerCanManageCompanionDevice(getContext(), "onShellCommand");
new CompanionDeviceShellCommand(CompanionDeviceManagerService.this)
.exec(this, in, out, err, args, callback, resultReceiver);
}
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java
index 45097f0..ea57089 100644
--- a/services/companion/java/com/android/server/companion/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java
@@ -24,6 +24,7 @@
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Binder.getCallingPid;
import static android.os.Binder.getCallingUid;
import static android.os.Process.SYSTEM_UID;
import static android.os.UserHandle.getCallingUserId;
@@ -37,11 +38,13 @@
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
import android.content.Context;
+import android.content.pm.PackageManagerInternal;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.ArrayMap;
import com.android.internal.app.IAppOpsService;
+import com.android.server.LocalServices;
import java.util.Map;
@@ -65,21 +68,19 @@
DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
}
- static void enforceCallerPermissionsToRequest(@NonNull Context context,
+ static void enforcePermissionsForAssociation(@NonNull Context context,
@NonNull AssociationRequest request, @NonNull String packageName,
@UserIdInt int userId) {
- enforceCallerCanInteractWithUserId(context, userId);
- enforceCallerIsSystemOr(userId, packageName);
-
- enforceRequestDeviceProfilePermissions(context, request.getDeviceProfile());
+ final int packageUid = getPackageUid(userId, packageName);
+ enforceRequestDeviceProfilePermissions(context, request.getDeviceProfile(), packageUid);
if (request.isSelfManaged()) {
- enforceRequestSelfManagedPermission(context);
+ enforceRequestSelfManagedPermission(context, packageUid);
}
}
static void enforceRequestDeviceProfilePermissions(
- @NonNull Context context, @Nullable String deviceProfile) {
+ @NonNull Context context, @Nullable String deviceProfile, int packageUid) {
// Device profile can be null.
if (deviceProfile == null) return;
@@ -100,14 +101,15 @@
}
final String permission = DEVICE_PROFILE_TO_PERMISSION.get(deviceProfile);
- if (context.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) {
+ if (context.checkPermission(permission, getCallingPid(), packageUid)
+ != PERMISSION_GRANTED) {
throw new SecurityException("Application must hold " + permission + " to associate "
+ "with a device with " + deviceProfile + " profile.");
}
}
- static void enforceRequestSelfManagedPermission(@NonNull Context context) {
- if (context.checkCallingOrSelfPermission(REQUEST_COMPANION_SELF_MANAGED)
+ static void enforceRequestSelfManagedPermission(@NonNull Context context, int packageUid) {
+ if (context.checkPermission(REQUEST_COMPANION_SELF_MANAGED, getCallingPid(), packageUid)
!= PERMISSION_GRANTED) {
throw new SecurityException("Application does not hold "
+ REQUEST_COMPANION_SELF_MANAGED);
@@ -159,13 +161,34 @@
return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED;
}
- static void enforceCallerCanManagerCompanionDevice(@NonNull Context context,
+ static void enforceCallerCanManageCompanionDevice(@NonNull Context context,
@Nullable String message) {
if (getCallingUid() == SYSTEM_UID) return;
context.enforceCallingPermission(MANAGE_COMPANION_DEVICES, message);
}
+ static void enforceCallerCanManageAssociationsForPackage(@NonNull Context context,
+ @UserIdInt int userId, @NonNull String packageName,
+ @Nullable String actionDescription) {
+ if (checkCallerCanManageAssociationsForPackage(context, userId, packageName)) return;
+
+ throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
+ + "permissions to "
+ + (actionDescription != null ? actionDescription : "manage associations")
+ + " for u" + userId + "/" + packageName);
+ }
+
+ /**
+ * Check if the caller is either:
+ * <ul>
+ * <li> the package itself
+ * <li> the System ({@link android.os.Process#SYSTEM_UID})
+ * <li> holds {@link Manifest.permission#MANAGE_COMPANION_DEVICES} and, if belongs to a
+ * different user, also holds {@link Manifest.permission#INTERACT_ACROSS_USERS}.
+ * </ul>
+ * @return whether the caller is one of the above.
+ */
static boolean checkCallerCanManageAssociationsForPackage(@NonNull Context context,
@UserIdInt int userId, @NonNull String packageName) {
if (checkCallerIsSystemOr(userId, packageName)) return true;
@@ -184,6 +207,11 @@
}
}
+ private static int getPackageUid(@UserIdInt int userId, @NonNull String packageName) {
+ return LocalServices.getService(PackageManagerInternal.class)
+ .getPackageUid(packageName, 0, userId);
+ }
+
private static IAppOpsService getAppOpsService() {
if (sAppOpsService == null) {
synchronized (PermissionsUtils.class) {
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index f3fad84..121a0bc 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -42,6 +42,7 @@
import android.os.Looper;
import android.os.PersistableBundle;
import android.os.Process;
+import android.os.storage.StorageManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
@@ -872,8 +873,8 @@
*
* @throws IOException if the request was unable to be fulfilled.
*/
- public abstract void freeStorage(String volumeUuid, long bytes, int storageFlags)
- throws IOException;
+ public abstract void freeStorage(String volumeUuid, long bytes,
+ @StorageManager.AllocateFlags int flags) throws IOException;
/** Returns {@code true} if the specified component is enabled and matches the given flags. */
public abstract boolean isEnabledAndMatches(@NonNull ParsedMainComponent component,
@@ -1261,5 +1262,6 @@
/**
* Reconcile all app data for the given user.
*/
- public abstract void reconcileAppsData(int userId, int storageFlags, boolean migrateAppsData);
+ public abstract void reconcileAppsData(int userId, @StorageManager.StorageFlags int flags,
+ boolean migrateAppsData);
}
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index b641377..71b463a 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -25,13 +25,14 @@
per-file *Binder* = file:/core/java/com/android/internal/os/BINDER_OWNERS
per-file *Bluetooth* = file:/core/java/android/bluetooth/OWNERS
per-file *Gnss* = file:/services/core/java/com/android/server/location/OWNERS
+per-file **IpSec* = file:/services/core/java/com/android/server/net/OWNERS
+per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
per-file *Location* = file:/services/core/java/com/android/server/location/OWNERS
per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
per-file *Storage* = file:/core/java/android/os/storage/OWNERS
per-file *TimeUpdate* = file:/core/java/android/app/timezone/OWNERS
per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationService/OWNERS
per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
-per-file IpSecService.java = file:/services/core/java/com/android/server/net/OWNERS
per-file MmsServiceBroker.java = file:/telephony/OWNERS
per-file NetIdManager.java = file:/services/core/java/com/android/server/net/OWNERS
per-file PackageWatchdog.java, RescueParty.java = file:/services/core/java/com/android/server/rollback/OWNERS
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index b7b4870..9180ef8 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -45,7 +45,6 @@
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.power.MeasuredEnergyStats;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import libcore.util.EmptyArray;
@@ -260,43 +259,6 @@
}
@Override
- public Future<?> scheduleReadProcStateCpuTimes(
- boolean onBattery, boolean onBatteryScreenOff, long delayMillis) {
- synchronized (mStats) {
- if (!mStats.trackPerProcStateCpuTimes()) {
- return null;
- }
- }
- synchronized (BatteryExternalStatsWorker.this) {
- if (!mExecutorService.isShutdown()) {
- return mExecutorService.schedule(PooledLambda.obtainRunnable(
- BatteryStatsImpl::updateProcStateCpuTimes,
- mStats, onBattery, onBatteryScreenOff).recycleOnUse(),
- delayMillis, TimeUnit.MILLISECONDS);
- }
- }
- return null;
- }
-
- @Override
- public Future<?> scheduleCopyFromAllUidsCpuTimes(
- boolean onBattery, boolean onBatteryScreenOff) {
- synchronized (mStats) {
- if (!mStats.trackPerProcStateCpuTimes()) {
- return null;
- }
- }
- synchronized (BatteryExternalStatsWorker.this) {
- if (!mExecutorService.isShutdown()) {
- return mExecutorService.submit(PooledLambda.obtainRunnable(
- BatteryStatsImpl::copyFromAllUidsCpuTimes,
- mStats, onBattery, onBatteryScreenOff).recycleOnUse());
- }
- }
- return null;
- }
-
- @Override
public Future<?> scheduleSyncDueToScreenStateChange(int flags, boolean onBattery,
boolean onBatteryScreenOff, int screenState, int[] perDisplayScreenStates) {
synchronized (BatteryExternalStatsWorker.this) {
@@ -491,7 +453,7 @@
}
if ((updateFlags & UPDATE_CPU) != 0) {
- mStats.copyFromAllUidsCpuTimes();
+ mStats.updateCpuTimesForAllUids();
}
// Clean up any UIDs if necessary.
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 80a8d63..1a89ae7 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2271,7 +2271,8 @@
// not the calling one.
appInfo.packageName = app.getHostingRecord().getDefiningPackageName();
appInfo.uid = uid;
- appZygote = new AppZygote(appInfo, uid, firstUid, lastUid);
+ int runtimeFlags = decideTaggingLevel(app);
+ appZygote = new AppZygote(appInfo, uid, firstUid, lastUid, runtimeFlags);
mAppZygotes.put(app.info.processName, uid, appZygote);
zygoteProcessList = new ArrayList<ProcessRecord>();
mAppZygoteProcesses.put(appZygote, zygoteProcessList);
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java
index b9cc992..8de515d 100644
--- a/services/core/java/com/android/server/appop/DiscreteRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java
@@ -134,7 +134,7 @@
private static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION
+ "," + OP_CAMERA + "," + OP_RECORD_AUDIO + "," + OP_PHONE_CALL_MICROPHONE + ","
+ OP_PHONE_CALL_CAMERA;
- private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofHours(24).toMillis();
+ private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis();
private static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis();
private static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION =
Duration.ofMinutes(1).toMillis();
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index ff451a3..e4ac7be 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -493,6 +493,12 @@
return isDeviceActiveForCommunication(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
}
+ /*package*/ boolean isDeviceConnected(@NonNull AudioDeviceAttributes device) {
+ synchronized (mDeviceStateLock) {
+ return mDeviceInventory.isDeviceConnected(device);
+ }
+ }
+
/*package*/ void setWiredDeviceConnectionState(int type,
@AudioService.ConnectionState int state, String address, String name,
String caller) {
@@ -502,6 +508,13 @@
}
}
+ /*package*/ void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
+ @AudioService.ConnectionState int state) {
+ synchronized (mDeviceStateLock) {
+ mDeviceInventory.setTestDeviceConnectionState(device, state);
+ }
+ }
+
/*package*/ static final class BleVolumeInfo {
final int mIndex;
final int mMaxIndex;
@@ -1002,7 +1015,8 @@
/*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
String deviceName) {
synchronized (mDeviceStateLock) {
- return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName);
+ return mDeviceInventory.handleDeviceConnection(connect, device, address, deviceName,
+ false /*for test*/);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index f32d3b5..a27e4b77 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -224,6 +224,7 @@
public final String mAddress;
public final String mName;
public final String mCaller;
+ public boolean mForTest = false;
/*package*/ WiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
String address, String name, String caller) {
@@ -521,7 +522,7 @@
}
if (!handleDeviceConnection(wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED,
- wdcs.mType, wdcs.mAddress, wdcs.mName)) {
+ wdcs.mType, wdcs.mAddress, wdcs.mName, wdcs.mForTest)) {
// change of connection state failed, bailout
mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed")
.record();
@@ -593,7 +594,7 @@
}
//------------------------------------------------------------
- //
+ // preferred device(s)
/*package*/ int setPreferredDevicesForStrategySync(int strategy,
@NonNull List<AudioDeviceAttributes> devices) {
@@ -674,16 +675,34 @@
mDevRoleCapturePresetDispatchers.unregister(dispatcher);
}
+ //-----------------------------------------------------------------------
+
+ /**
+ * Check if a device is in the list of connected devices
+ * @param device the device whose connection state is queried
+ * @return true if connected
+ */
+ // called with AudioDeviceBroker.mDeviceStateLock lock held
+ public boolean isDeviceConnected(@NonNull AudioDeviceAttributes device) {
+ final String key = DeviceInfo.makeDeviceListKey(device.getInternalType(),
+ device.getAddress());
+ synchronized (mDevicesLock) {
+ return (mConnectedDevices.get(key) != null);
+ }
+ }
+
/**
* Implements the communication with AudioSystem to (dis)connect a device in the native layers
* @param connect true if connection
* @param device the device type
* @param address the address of the device
* @param deviceName human-readable name of device
+ * @param isForTesting if true, not calling AudioSystem for the connection as this is
+ * just for testing
* @return false if an error was reported by AudioSystem
*/
/*package*/ boolean handleDeviceConnection(boolean connect, int device, String address,
- String deviceName) {
+ String deviceName, boolean isForTesting) {
if (AudioService.DEBUG_DEVICES) {
Slog.i(TAG, "handleDeviceConnection(" + connect + " dev:"
+ Integer.toHexString(device) + " address:" + address
@@ -706,9 +725,14 @@
Slog.i(TAG, "deviceInfo:" + di + " is(already)Connected:" + isConnected);
}
if (connect && !isConnected) {
- final int res = mAudioSystem.setDeviceConnectionState(device,
- AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
- AudioSystem.AUDIO_FORMAT_DEFAULT);
+ final int res;
+ if (isForTesting) {
+ res = AudioSystem.AUDIO_STATUS_OK;
+ } else {
+ res = mAudioSystem.setDeviceConnectionState(device,
+ AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
+ AudioSystem.AUDIO_FORMAT_DEFAULT);
+ }
if (res != AudioSystem.AUDIO_STATUS_OK) {
final String reason = "not connecting device 0x" + Integer.toHexString(device)
+ " due to command error " + res;
@@ -914,6 +938,15 @@
}
}
+ /*package*/ void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
+ @AudioService.ConnectionState int state) {
+ final WiredDeviceConnectionState connection = new WiredDeviceConnectionState(
+ device.getInternalType(), state, device.getAddress(),
+ "test device", "com.android.server.audio");
+ connection.mForTest = true;
+ onSetWiredDeviceConnectionState(connection);
+ }
+
//-------------------------------------------------------------------
// Internal utilities
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0f3b082..aa33644 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -35,6 +35,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
@@ -92,6 +93,7 @@
import android.media.IAudioService;
import android.media.ICapturePresetDevicesRoleDispatcher;
import android.media.ICommunicationDeviceDispatcher;
+import android.media.IMuteAwaitConnectionCallback;
import android.media.IPlaybackConfigDispatcher;
import android.media.IRecordingConfigDispatcher;
import android.media.IRingtonePlayer;
@@ -1027,7 +1029,8 @@
readUserRestrictions();
mPlaybackMonitor =
- new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
+ new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM],
+ device -> onMuteAwaitConnectionTimeout(device));
mPlaybackMonitor.registerPlaybackCallback(mVoicePlaybackActivityMonitor, true);
mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
@@ -1048,6 +1051,9 @@
mHasSpatializerEffect = SystemProperties.getBoolean("ro.audio.spatializer_enabled", false);
+ // monitor routing updates coming from native
+ mAudioSystem.setRoutingListener(this);
+
// done with service initialization, continue additional work in our Handler thread
queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES,
0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */);
@@ -1241,26 +1247,32 @@
initMinStreamVolumeWithoutModifyAudioSettings();
updateVibratorInfos();
+
+ synchronized (mSupportedSystemUsagesLock) {
+ AudioSystem.setSupportedSystemUsages(mSupportedSystemUsages);
+ }
}
//-----------------------------------------------------------------
// routing monitoring from AudioSystemAdapter
@Override
public void onRoutingUpdatedFromNative() {
- if (!mHasSpatializerEffect) {
- return;
- }
sendMsg(mAudioHandler,
MSG_ROUTING_UPDATED,
SENDMSG_REPLACE, 0, 0, null,
/*delay*/ 0);
}
- void monitorRoutingChanges(boolean enabled) {
- mAudioSystem.setRoutingListener(enabled ? this : null);
+ /**
+ * called when handling MSG_ROUTING_UPDATED
+ */
+ void onRoutingUpdatedFromAudioThread() {
+ if (mHasSpatializerEffect) {
+ mSpatializerHelper.onRoutingUpdated();
+ }
+ checkMuteAwaitConnection();
}
-
//-----------------------------------------------------------------
RoleObserver mRoleObserver;
@@ -1448,7 +1460,6 @@
if (mHasSpatializerEffect) {
mSpatializerHelper.reset(/* featureEnabled */ isSpatialAudioEnabled());
- monitorRoutingChanges(true);
}
onIndicateSystemReady();
@@ -3224,6 +3235,15 @@
}
}
+ private void enforceCallAudioInterceptionPermission() {
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.CALL_AUDIO_INTERCEPTION)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Missing CALL_AUDIO_INTERCEPTION permission");
+ }
+ }
+
+
/** @see AudioManager#setVolumeIndexForAttributes(attr, int, int) */
public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags,
String callingPackage, String attributionTag) {
@@ -4943,6 +4963,26 @@
mModeDispatchers.unregister(dispatcher);
}
+ /** @see AudioManager#isPstnCallAudioInterceptable() */
+ public boolean isPstnCallAudioInterceptable() {
+ enforceCallAudioInterceptionPermission();
+
+ boolean uplinkDeviceFound = false;
+ boolean downlinkDeviceFound = false;
+ AudioDeviceInfo[] devices = AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_ALL);
+ for (AudioDeviceInfo device : devices) {
+ if (device.getInternalType() == AudioSystem.DEVICE_OUT_TELEPHONY_TX) {
+ uplinkDeviceFound = true;
+ } else if (device.getInternalType() == AudioSystem.DEVICE_IN_TELEPHONY_RX) {
+ downlinkDeviceFound = true;
+ }
+ if (uplinkDeviceFound && downlinkDeviceFound) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/** @see AudioManager#setRttEnabled() */
@Override
public void setRttEnabled(boolean rttEnabled) {
@@ -6324,6 +6364,20 @@
mDeviceBroker.setWiredDeviceConnectionState(type, state, address, name, caller);
}
+ /** @see AudioManager#setTestDeviceConnectionState(AudioDeviceAttributes, boolean) */
+ public void setTestDeviceConnectionState(@NonNull AudioDeviceAttributes device,
+ boolean connected) {
+ Objects.requireNonNull(device);
+ enforceModifyAudioRoutingPermission();
+ mDeviceBroker.setTestDeviceConnectionState(device,
+ connected ? CONNECTION_STATE_CONNECTED : CONNECTION_STATE_DISCONNECTED);
+ // simulate a routing update from native
+ sendMsg(mAudioHandler,
+ MSG_ROUTING_UPDATED,
+ SENDMSG_REPLACE, 0, 0, null,
+ /*delay*/ 0);
+ }
+
/**
* @hide
* The states that can be used with AudioService.setBluetoothHearingAidDeviceConnectionState()
@@ -7619,7 +7673,6 @@
mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect);
if (mHasSpatializerEffect) {
mSpatializerHelper.setFeatureEnabled(isSpatialAudioEnabled());
- monitorRoutingChanges(true);
}
mAudioEventWakeLock.release();
break;
@@ -7758,7 +7811,7 @@
break;
case MSG_ROUTING_UPDATED:
- mSpatializerHelper.onRoutingUpdated();
+ onRoutingUpdatedFromAudioThread();
break;
case MSG_PERSIST_SPATIAL_AUDIO_ENABLED:
@@ -8169,7 +8222,10 @@
private void validateAudioAttributesUsage(@NonNull AudioAttributes audioAttributes) {
@AudioAttributes.AttributeUsage int usage = audioAttributes.getSystemUsage();
if (AudioAttributes.isSystemUsage(usage)) {
- if (callerHasPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)) {
+ if ((usage == AudioAttributes.USAGE_CALL_ASSISTANT
+ && (audioAttributes.getAllFlags() & AudioAttributes.FLAG_CALL_REDIRECTION) != 0
+ && callerHasPermission(Manifest.permission.CALL_AUDIO_INTERCEPTION))
+ || callerHasPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)) {
if (!isSupportedSystemUsage(usage)) {
throw new IllegalArgumentException(
"Unsupported usage " + AudioAttributes.usageToString(usage));
@@ -8183,8 +8239,12 @@
private boolean isValidAudioAttributesUsage(@NonNull AudioAttributes audioAttributes) {
@AudioAttributes.AttributeUsage int usage = audioAttributes.getSystemUsage();
if (AudioAttributes.isSystemUsage(usage)) {
- return callerHasPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
- && isSupportedSystemUsage(usage);
+ return isSupportedSystemUsage(usage)
+ && ((usage == AudioAttributes.USAGE_CALL_ASSISTANT
+ && (audioAttributes.getAllFlags()
+ & AudioAttributes.FLAG_CALL_REDIRECTION) != 0
+ && callerHasPermission(Manifest.permission.CALL_AUDIO_INTERCEPTION))
+ || callerHasPermission(Manifest.permission.MODIFY_AUDIO_ROUTING));
}
return true;
}
@@ -8572,6 +8632,171 @@
}
//==========================================================================================
+ private final Object mMuteAwaitConnectionLock = new Object();
+
+ /**
+ * The device that is expected to be connected soon, and causes players to be muted until
+ * its connection, or it times out.
+ * Null when no active muting command, or it has timed out.
+ */
+ @GuardedBy("mMuteAwaitConnectionLock")
+ private AudioDeviceAttributes mMutingExpectedDevice;
+ @GuardedBy("mMuteAwaitConnectionLock")
+ private @Nullable int[] mMutedUsagesAwaitingConnection;
+
+ /** @see AudioManager#muteAwaitConnection */
+ @SuppressLint("EmptyCatch") // callback exception caught inside dispatchMuteAwaitConnection
+ public void muteAwaitConnection(@NonNull int[] usages,
+ @NonNull AudioDeviceAttributes device, long timeOutMs) {
+ Objects.requireNonNull(usages);
+ Objects.requireNonNull(device);
+ enforceModifyAudioRoutingPermission();
+ if (timeOutMs <= 0 || usages.length == 0) {
+ throw new IllegalArgumentException("Invalid timeOutMs/usagesToMute");
+ }
+
+ if (mDeviceBroker.isDeviceConnected(device)) {
+ // not throwing an exception as there could be a race between a connection (server-side,
+ // notification of connection in flight) and a mute operation (client-side)
+ Log.i(TAG, "muteAwaitConnection ignored, device (" + device + ") already connected");
+ return;
+ }
+ synchronized (mMuteAwaitConnectionLock) {
+ if (mMutingExpectedDevice != null) {
+ Log.e(TAG, "muteAwaitConnection ignored, another in progress for device:"
+ + mMutingExpectedDevice);
+ throw new IllegalStateException("muteAwaitConnection already in progress");
+ }
+ mMutingExpectedDevice = device;
+ mMutedUsagesAwaitingConnection = usages;
+ mPlaybackMonitor.muteAwaitConnection(usages, device, timeOutMs);
+ }
+ dispatchMuteAwaitConnection(cb -> { try {
+ cb.dispatchOnMutedUntilConnection(device, usages); } catch (RemoteException e) { } });
+ }
+
+ /** @see AudioManager#getMutingExpectedDevice */
+ public @Nullable AudioDeviceAttributes getMutingExpectedDevice() {
+ enforceModifyAudioRoutingPermission();
+ synchronized (mMuteAwaitConnectionLock) {
+ return mMutingExpectedDevice;
+ }
+ }
+
+ /** @see AudioManager#cancelMuteAwaitConnection */
+ @SuppressLint("EmptyCatch") // callback exception caught inside dispatchMuteAwaitConnection
+ public void cancelMuteAwaitConnection(@NonNull AudioDeviceAttributes device) {
+ Objects.requireNonNull(device);
+ enforceModifyAudioRoutingPermission();
+ Log.i(TAG, "cancelMuteAwaitConnection for device:" + device);
+ final int[] mutedUsages;
+ synchronized (mMuteAwaitConnectionLock) {
+ if (mMutingExpectedDevice == null) {
+ // not throwing an exception as there could be a race between a timeout
+ // (server-side) and a cancel operation (client-side)
+ Log.i(TAG, "cancelMuteAwaitConnection ignored, no expected device");
+ return;
+ }
+ if (!device.equals(mMutingExpectedDevice)) {
+ Log.e(TAG, "cancelMuteAwaitConnection ignored, got " + device
+ + "] but expected device is" + mMutingExpectedDevice);
+ throw new IllegalStateException("cancelMuteAwaitConnection for wrong device");
+ }
+ mutedUsages = mMutedUsagesAwaitingConnection;
+ mMutingExpectedDevice = null;
+ mMutedUsagesAwaitingConnection = null;
+ mPlaybackMonitor.cancelMuteAwaitConnection();
+ }
+ dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent(
+ AudioManager.MuteAwaitConnectionCallback.EVENT_CANCEL, device, mutedUsages);
+ } catch (RemoteException e) { } });
+ }
+
+ final RemoteCallbackList<IMuteAwaitConnectionCallback> mMuteAwaitConnectionDispatchers =
+ new RemoteCallbackList<IMuteAwaitConnectionCallback>();
+
+ /** @see AudioManager#registerMuteAwaitConnectionCallback */
+ public void registerMuteAwaitConnectionDispatcher(@NonNull IMuteAwaitConnectionCallback cb,
+ boolean register) {
+ enforceModifyAudioRoutingPermission();
+ if (register) {
+ mMuteAwaitConnectionDispatchers.register(cb);
+ } else {
+ mMuteAwaitConnectionDispatchers.unregister(cb);
+ }
+ }
+
+ @SuppressLint("EmptyCatch") // callback exception caught inside dispatchMuteAwaitConnection
+ void checkMuteAwaitConnection() {
+ final AudioDeviceAttributes device;
+ final int[] mutedUsages;
+ synchronized (mMuteAwaitConnectionLock) {
+ if (mMutingExpectedDevice == null) {
+ return;
+ }
+ device = mMutingExpectedDevice;
+ mutedUsages = mMutedUsagesAwaitingConnection;
+ if (!mDeviceBroker.isDeviceConnected(device)) {
+ return;
+ }
+ mMutingExpectedDevice = null;
+ mMutedUsagesAwaitingConnection = null;
+ Log.i(TAG, "muteAwaitConnection device " + device + " connected, unmuting");
+ mPlaybackMonitor.cancelMuteAwaitConnection();
+ }
+ dispatchMuteAwaitConnection(cb -> { try { cb.dispatchOnUnmutedEvent(
+ AudioManager.MuteAwaitConnectionCallback.EVENT_CONNECTION, device, mutedUsages);
+ } catch (RemoteException e) { } });
+ }
+
+ /**
+ * Called by PlaybackActivityMonitor when the timeout hit for the mute on device connection
+ */
+ @SuppressLint("EmptyCatch") // callback exception caught inside dispatchMuteAwaitConnection
+ void onMuteAwaitConnectionTimeout(@NonNull AudioDeviceAttributes timedOutDevice) {
+ final int[] mutedUsages;
+ synchronized (mMuteAwaitConnectionLock) {
+ if (!timedOutDevice.equals(mMutingExpectedDevice)) {
+ return;
+ }
+ Log.i(TAG, "muteAwaitConnection timeout, clearing expected device "
+ + mMutingExpectedDevice);
+ mutedUsages = mMutedUsagesAwaitingConnection;
+ mMutingExpectedDevice = null;
+ mMutedUsagesAwaitingConnection = null;
+ }
+ dispatchMuteAwaitConnection(cb -> { try {
+ cb.dispatchOnUnmutedEvent(
+ AudioManager.MuteAwaitConnectionCallback.EVENT_TIMEOUT,
+ timedOutDevice, mutedUsages);
+ } catch (RemoteException e) { } });
+ }
+
+ private void dispatchMuteAwaitConnection(
+ java.util.function.Consumer<IMuteAwaitConnectionCallback> callback) {
+ final int nbDispatchers = mMuteAwaitConnectionDispatchers.beginBroadcast();
+ // lazy initialization as errors unlikely
+ ArrayList<IMuteAwaitConnectionCallback> errorList = null;
+ for (int i = 0; i < nbDispatchers; i++) {
+ try {
+ callback.accept(mMuteAwaitConnectionDispatchers.getBroadcastItem(i));
+ } catch (Exception e) {
+ if (errorList == null) {
+ errorList = new ArrayList<>(1);
+ }
+ errorList.add(mMuteAwaitConnectionDispatchers.getBroadcastItem(i));
+ }
+ }
+ if (errorList != null) {
+ for (IMuteAwaitConnectionCallback errorItem : errorList) {
+ mMuteAwaitConnectionDispatchers.unregister(errorItem);
+ }
+ }
+ mMuteAwaitConnectionDispatchers.finishBroadcast();
+ }
+
+
+ //==========================================================================================
// Device orientation
//==========================================================================================
/**
@@ -9578,6 +9803,7 @@
boolean requireValidProjection = false;
boolean requireCaptureAudioOrMediaOutputPerm = false;
boolean requireModifyRouting = false;
+ boolean requireCallAudioInterception = false;
ArrayList<AudioMix> voiceCommunicationCaptureMixes = null;
@@ -9618,7 +9844,10 @@
// otherwise MODIFY_AUDIO_ROUTING permission is required
if (mix.getRouteFlags() == mix.ROUTE_FLAG_LOOP_BACK_RENDER && projection != null) {
requireValidProjection |= true;
- } else {
+ } else if (mix.isForCallRedirection()) {
+ requireCallAudioInterception |= true;
+ } else if (mix.containsMatchAttributeRuleForUsage(
+ AudioAttributes.USAGE_VOICE_COMMUNICATION)) {
requireModifyRouting |= true;
}
}
@@ -9655,6 +9884,12 @@
return false;
}
+ if (requireCallAudioInterception
+ && !callerHasPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION)) {
+ Log.e(TAG, "Can not capture audio without CALL_AUDIO_INTERCEPTION");
+ return false;
+ }
+
return true;
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index b94cea4..b333ed2 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -17,9 +17,11 @@
package com.android.server.audio;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
import android.media.AudioSystem;
@@ -27,21 +29,27 @@
import android.media.PlayerBase;
import android.media.VolumeShaper;
import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
+import java.util.function.Consumer;
/**
* Class to receive and dispatch updates from AudioSystem about recording configurations.
@@ -54,6 +62,7 @@
/*package*/ static final boolean DEBUG = false;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1;
/*package*/ static final int VOLUME_SHAPER_SYSTEM_FADEOUT_ID = 2;
+ /*package*/ static final int VOLUME_SHAPER_SYSTEM_MUTE_AWAIT_CONNECTION_ID = 3;
private static final VolumeShaper.Configuration DUCK_VSHAPE =
new VolumeShaper.Configuration.Builder()
@@ -73,6 +82,18 @@
.createIfNeeded()
.build();
+ private static final long UNMUTE_DURATION_MS = 100;
+ private static final VolumeShaper.Configuration MUTE_AWAIT_CONNECTION_VSHAPE =
+ new VolumeShaper.Configuration.Builder()
+ .setId(VOLUME_SHAPER_SYSTEM_MUTE_AWAIT_CONNECTION_ID)
+ .setCurve(new float[] { 0.f, 1.f } /* times */,
+ new float[] { 1.f, 0.f } /* volumes */)
+ .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+ // even though we specify a duration, it's only used for the unmute,
+ // for muting this volume shaper is run with PLAY_SKIP_RAMP
+ .setDuration(UNMUTE_DURATION_MS)
+ .build();
+
// TODO support VolumeShaper on those players
private static final int[] UNDUCKABLE_PLAYER_TYPES = {
AudioPlaybackConfiguration.PLAYER_TYPE_AAUDIO,
@@ -90,6 +111,7 @@
private boolean mHasPublicClients = false;
private final Object mPlayerLock = new Object();
+ @GuardedBy("mPlayerLock")
private final HashMap<Integer, AudioPlaybackConfiguration> mPlayers =
new HashMap<Integer, AudioPlaybackConfiguration>();
@@ -97,12 +119,16 @@
private int mSavedAlarmVolume = -1;
private final int mMaxAlarmVolume;
private int mPrivilegedAlarmActiveCount = 0;
+ private final Consumer<AudioDeviceAttributes> mMuteAwaitConnectionTimeoutCb;
- PlaybackActivityMonitor(Context context, int maxAlarmVolume) {
+ PlaybackActivityMonitor(Context context, int maxAlarmVolume,
+ Consumer<AudioDeviceAttributes> muteTimeoutCallback) {
mContext = context;
mMaxAlarmVolume = maxAlarmVolume;
PlayMonitorClient.sListenerDeathMonitor = this;
AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
+ mMuteAwaitConnectionTimeoutCb = muteTimeoutCallback;
+ initEventHandler();
}
//=================================================================
@@ -170,6 +196,7 @@
sEventLogger.log(new NewPlayerEvent(apc));
synchronized(mPlayerLock) {
mPlayers.put(newPiid, apc);
+ maybeMutePlayerAwaitingConnection(apc);
}
return newPiid;
}
@@ -323,6 +350,7 @@
mPlayers.remove(new Integer(piid));
mDuckingManager.removeReleased(apc);
mFadingManager.removeReleased(apc);
+ mMutedPlayersAwaitingConnection.remove(Integer.valueOf(piid));
checkVolumeForPrivilegedAlarm(apc, AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
change = apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED,
AudioPlaybackConfiguration.PLAYER_DEVICEID_INVALID);
@@ -451,7 +479,7 @@
pw.println("\n faded out players piids:");
mFadingManager.dump(pw);
// players muted due to the device ringing or being in a call
- pw.print("\n muted player piids:");
+ pw.print("\n muted player piids due to call/ring:");
for (int piid : mMutedPlayers) {
pw.print(" " + piid);
}
@@ -462,6 +490,12 @@
pw.print(" " + uid);
}
pw.println("\n");
+ // muted players:
+ pw.print("\n muted players (piids) awaiting device connection: BL3 ####");
+ for (int piid : mMutedPlayersAwaitingConnection) {
+ pw.print(" " + piid);
+ }
+ pw.println("\n");
// log
sEventLogger.dump(pw);
}
@@ -1100,6 +1134,155 @@
}
}
+ private static final class MuteAwaitConnectionEvent extends AudioEventLogger.Event {
+ private final @NonNull int[] mUsagesToMute;
+
+ MuteAwaitConnectionEvent(@NonNull int[] usagesToMute) {
+ mUsagesToMute = usagesToMute;
+ }
+
+ @Override
+ public String eventToString() {
+ return "muteAwaitConnection muting usages " + Arrays.toString(mUsagesToMute);
+ }
+ }
+
static final AudioEventLogger sEventLogger = new AudioEventLogger(100,
"playback activity as reported through PlayerBase");
+
+ //==========================================================================================
+ // Mute conditional on device connection
+ //==========================================================================================
+ void muteAwaitConnection(@NonNull int[] usagesToMute,
+ @NonNull AudioDeviceAttributes dev, long timeOutMs) {
+ synchronized (mPlayerLock) {
+ mutePlayersExpectingDevice(usagesToMute);
+ // schedule timeout (remove previously scheduled first)
+ mEventHandler.removeMessages(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION);
+ mEventHandler.sendMessageDelayed(
+ mEventHandler.obtainMessage(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION, dev),
+ timeOutMs);
+ }
+ }
+
+ void cancelMuteAwaitConnection() {
+ synchronized (mPlayerLock) {
+ // cancel scheduled timeout, ignore device, only one expected device at a time
+ mEventHandler.removeMessages(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION);
+ // unmute immediately
+ unmutePlayersExpectingDevice();
+ }
+ }
+
+ /**
+ * List of the piids of the players that are muted until a specific audio device connects
+ */
+ @GuardedBy("mPlayerLock")
+ private final ArrayList<Integer> mMutedPlayersAwaitingConnection = new ArrayList<Integer>();
+
+ /**
+ * List of AudioAttributes usages to mute until a specific audio device connects
+ */
+ @GuardedBy("mPlayerLock")
+ private @Nullable int[] mMutedUsagesAwaitingConnection = null;
+
+ @GuardedBy("mPlayerLock")
+ private void mutePlayersExpectingDevice(@NonNull int[] usagesToMute) {
+ sEventLogger.log(new MuteAwaitConnectionEvent(usagesToMute));
+ mMutedUsagesAwaitingConnection = usagesToMute;
+ final Set<Integer> piidSet = mPlayers.keySet();
+ final Iterator<Integer> piidIterator = piidSet.iterator();
+ // find which players to mute
+ while (piidIterator.hasNext()) {
+ final Integer piid = piidIterator.next();
+ final AudioPlaybackConfiguration apc = mPlayers.get(piid);
+ if (apc == null) {
+ continue;
+ }
+ maybeMutePlayerAwaitingConnection(apc);
+ }
+ }
+
+ @GuardedBy("mPlayerLock")
+ private void maybeMutePlayerAwaitingConnection(@NonNull AudioPlaybackConfiguration apc) {
+ if (mMutedUsagesAwaitingConnection == null) {
+ return;
+ }
+ for (int usage : mMutedUsagesAwaitingConnection) {
+ if (usage == apc.getAudioAttributes().getUsage()) {
+ try {
+ sEventLogger.log((new AudioEventLogger.StringEvent(
+ "awaiting connection: muting piid:"
+ + apc.getPlayerInterfaceId()
+ + " uid:" + apc.getClientUid())).printLog(TAG));
+ apc.getPlayerProxy().applyVolumeShaper(
+ MUTE_AWAIT_CONNECTION_VSHAPE,
+ PLAY_CREATE_IF_NEEDED);
+ mMutedPlayersAwaitingConnection.add(apc.getPlayerInterfaceId());
+ } catch (Exception e) {
+ Log.e(TAG, "awaiting connection: error muting player "
+ + apc.getPlayerInterfaceId(), e);
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mPlayerLock")
+ private void unmutePlayersExpectingDevice() {
+ if (mMutedPlayersAwaitingConnection.isEmpty()) {
+ return;
+ }
+ for (int piid : mMutedPlayersAwaitingConnection) {
+ final AudioPlaybackConfiguration apc = mPlayers.get(piid);
+ if (apc == null) {
+ continue;
+ }
+ try {
+ sEventLogger.log(new AudioEventLogger.StringEvent(
+ "unmuting piid:" + piid).printLog(TAG));
+ apc.getPlayerProxy().applyVolumeShaper(MUTE_AWAIT_CONNECTION_VSHAPE,
+ VolumeShaper.Operation.REVERSE);
+ } catch (Exception e) {
+ Log.e(TAG, "Error unmuting player " + piid + " uid:"
+ + apc.getClientUid(), e);
+ }
+ }
+ mMutedPlayersAwaitingConnection.clear();
+ mMutedUsagesAwaitingConnection = null;
+ }
+
+ //=================================================================
+ // Message handling
+ private Handler mEventHandler;
+ private HandlerThread mEventThread;
+
+ /**
+ * timeout for a mute awaiting a device connection
+ * args:
+ * msg.obj: the audio device being expected
+ * type: AudioDeviceAttributes
+ */
+ private static final int MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION = 1;
+
+ private void initEventHandler() {
+ mEventThread = new HandlerThread(TAG);
+ mEventThread.start();
+ mEventHandler = new Handler(mEventThread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION:
+ Log.i(TAG, "Timeout for muting waiting for "
+ + (AudioDeviceAttributes) msg.obj + ", unmuting");
+ synchronized (mPlayerLock) {
+ unmutePlayersExpectingDevice();
+ }
+ mMuteAwaitConnectionTimeoutCb.accept((AudioDeviceAttributes) msg.obj);
+ break;
+ default:
+ break;
+ }
+ }
+ };
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index f42870b..758cf7a 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -1036,7 +1036,8 @@
promptInfo.setAuthenticators(authenticators);
return PreAuthInfo.create(mTrustManager, mDevicePolicyManager, mSettingObserver, mSensors,
- userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */);
+ userId, promptInfo, opPackageName, false /* checkDevicePolicyManager */,
+ getContext());
}
/**
@@ -1375,7 +1376,8 @@
try {
final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager,
mDevicePolicyManager, mSettingObserver, mSensors, userId, promptInfo,
- opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists());
+ opPackageName, promptInfo.isDisallowBiometricsIfPolicyExists(),
+ getContext());
final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
@@ -1383,8 +1385,11 @@
+ "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo
+ " requestId: " + requestId + " promptInfo.isIgnoreEnrollmentState: "
+ promptInfo.isIgnoreEnrollmentState());
-
- if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) {
+ // BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED is added so that BiometricPrompt can
+ // be shown for this case.
+ if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS
+ || preAuthStatus.second
+ == BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED) {
// If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but
// CREDENTIAL is requested and available, set the bundle to only request
// CREDENTIAL.
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index a5a3542..05c3f68 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -26,6 +26,8 @@
import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
+import android.content.Context;
+import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.PromptInfo;
@@ -59,6 +61,7 @@
static final int CREDENTIAL_NOT_ENROLLED = 9;
static final int BIOMETRIC_LOCKOUT_TIMED = 10;
static final int BIOMETRIC_LOCKOUT_PERMANENT = 11;
+ static final int BIOMETRIC_SENSOR_PRIVACY_ENABLED = 12;
@IntDef({AUTHENTICATOR_OK,
BIOMETRIC_NO_HARDWARE,
BIOMETRIC_DISABLED_BY_DEVICE_POLICY,
@@ -69,7 +72,8 @@
BIOMETRIC_NOT_ENABLED_FOR_APPS,
CREDENTIAL_NOT_ENROLLED,
BIOMETRIC_LOCKOUT_TIMED,
- BIOMETRIC_LOCKOUT_PERMANENT})
+ BIOMETRIC_LOCKOUT_PERMANENT,
+ BIOMETRIC_SENSOR_PRIVACY_ENABLED})
@Retention(RetentionPolicy.SOURCE)
@interface AuthenticatorStatus {}
@@ -84,13 +88,15 @@
final boolean credentialAvailable;
final boolean confirmationRequested;
final boolean ignoreEnrollmentState;
+ final int userId;
+ final Context context;
static PreAuthInfo create(ITrustManager trustManager,
DevicePolicyManager devicePolicyManager,
BiometricService.SettingObserver settingObserver,
List<BiometricSensor> sensors,
int userId, PromptInfo promptInfo, String opPackageName,
- boolean checkDevicePolicyManager)
+ boolean checkDevicePolicyManager, Context context)
throws RemoteException {
final boolean confirmationRequested = promptInfo.isConfirmationRequested();
@@ -116,14 +122,22 @@
devicePolicyManager, settingObserver, sensor, userId, opPackageName,
checkDevicePolicyManager, requestedStrength,
promptInfo.getAllowedSensorIds(),
- promptInfo.isIgnoreEnrollmentState());
+ promptInfo.isIgnoreEnrollmentState(),
+ context);
Slog.d(TAG, "Package: " + opPackageName
+ " Sensor ID: " + sensor.id
+ " Modality: " + sensor.modality
+ " Status: " + status);
- if (status == AUTHENTICATOR_OK) {
+ // A sensor with privacy enabled will still be eligible to
+ // authenticate with biometric prompt. This is so the framework can display
+ // a sensor privacy error message to users after briefly showing the
+ // Biometric Prompt.
+ //
+ // Note: if only a certain sensor is required and the privacy is enabled,
+ // canAuthenticate() will return false.
+ if (status == AUTHENTICATOR_OK || status == BIOMETRIC_SENSOR_PRIVACY_ENABLED) {
eligibleSensors.add(sensor);
} else {
ineligibleSensors.add(new Pair<>(sensor, status));
@@ -133,7 +147,7 @@
return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested,
- promptInfo.isIgnoreEnrollmentState());
+ promptInfo.isIgnoreEnrollmentState(), userId, context);
}
/**
@@ -149,7 +163,7 @@
BiometricSensor sensor, int userId, String opPackageName,
boolean checkDevicePolicyManager, int requestedStrength,
@NonNull List<Integer> requestedSensorIds,
- boolean ignoreEnrollmentState) {
+ boolean ignoreEnrollmentState, Context context) {
if (!requestedSensorIds.isEmpty() && !requestedSensorIds.contains(sensor.id)) {
return BIOMETRIC_NO_HARDWARE;
@@ -175,6 +189,16 @@
&& !ignoreEnrollmentState) {
return BIOMETRIC_NOT_ENROLLED;
}
+ final SensorPrivacyManager sensorPrivacyManager = context
+ .getSystemService(SensorPrivacyManager.class);
+
+ if (sensorPrivacyManager != null && sensor.modality == TYPE_FACE) {
+ if (sensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId)) {
+ return BIOMETRIC_SENSOR_PRIVACY_ENABLED;
+ }
+ }
+
final @LockoutTracker.LockoutMode int lockoutMode =
sensor.impl.getLockoutModeForUser(userId);
@@ -243,7 +267,8 @@
private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
boolean credentialRequested, List<BiometricSensor> eligibleSensors,
List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
- boolean confirmationRequested, boolean ignoreEnrollmentState) {
+ boolean confirmationRequested, boolean ignoreEnrollmentState, int userId,
+ Context context) {
mBiometricRequested = biometricRequested;
mBiometricStrengthRequested = biometricStrengthRequested;
this.credentialRequested = credentialRequested;
@@ -253,6 +278,8 @@
this.credentialAvailable = credentialAvailable;
this.confirmationRequested = confirmationRequested;
this.ignoreEnrollmentState = ignoreEnrollmentState;
+ this.userId = userId;
+ this.context = context;
}
private Pair<BiometricSensor, Integer> calculateErrorByPriority() {
@@ -280,15 +307,35 @@
private Pair<Integer, Integer> getInternalStatus() {
@AuthenticatorStatus final int status;
@BiometricAuthenticator.Modality int modality = TYPE_NONE;
+
+ final SensorPrivacyManager sensorPrivacyManager = context
+ .getSystemService(SensorPrivacyManager.class);
+
+ boolean cameraPrivacyEnabled = false;
+ if (sensorPrivacyManager != null) {
+ cameraPrivacyEnabled = sensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, userId);
+ }
+
if (mBiometricRequested && credentialRequested) {
if (credentialAvailable || !eligibleSensors.isEmpty()) {
- status = AUTHENTICATOR_OK;
- if (credentialAvailable) {
- modality |= TYPE_CREDENTIAL;
- }
for (BiometricSensor sensor : eligibleSensors) {
modality |= sensor.modality;
}
+
+ if (credentialAvailable) {
+ modality |= TYPE_CREDENTIAL;
+ status = AUTHENTICATOR_OK;
+ } else if (modality == TYPE_FACE && cameraPrivacyEnabled) {
+ // If the only modality requested is face, credential is unavailable,
+ // and the face sensor privacy is enabled then return
+ // BIOMETRIC_SENSOR_PRIVACY_ENABLED.
+ //
+ // Note: This sensor will still be eligible for calls to authenticate.
+ status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
+ } else {
+ status = AUTHENTICATOR_OK;
+ }
} else {
// Pick the first sensor error if it exists
if (!ineligibleSensors.isEmpty()) {
@@ -302,10 +349,18 @@
}
} else if (mBiometricRequested) {
if (!eligibleSensors.isEmpty()) {
- status = AUTHENTICATOR_OK;
- for (BiometricSensor sensor : eligibleSensors) {
- modality |= sensor.modality;
- }
+ for (BiometricSensor sensor : eligibleSensors) {
+ modality |= sensor.modality;
+ }
+ if (modality == TYPE_FACE && cameraPrivacyEnabled) {
+ // If the only modality requested is face and the privacy is enabled
+ // then return BIOMETRIC_SENSOR_PRIVACY_ENABLED.
+ //
+ // Note: This sensor will still be eligible for calls to authenticate.
+ status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
+ } else {
+ status = AUTHENTICATOR_OK;
+ }
} else {
// Pick the first sensor error if it exists
if (!ineligibleSensors.isEmpty()) {
@@ -326,9 +381,9 @@
Slog.e(TAG, "No authenticators requested");
status = BIOMETRIC_NO_HARDWARE;
}
-
Slog.d(TAG, "getCanAuthenticateInternal Modality: " + modality
+ " AuthenticatorStatus: " + status);
+
return new Pair<>(modality, status);
}
@@ -362,6 +417,7 @@
case CREDENTIAL_NOT_ENROLLED:
case BIOMETRIC_LOCKOUT_TIMED:
case BIOMETRIC_LOCKOUT_PERMANENT:
+ case BIOMETRIC_SENSOR_PRIVACY_ENABLED:
break;
case BIOMETRIC_DISABLED_BY_DEVICE_POLICY:
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 4f7c6b0..0e2582c 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -33,6 +33,7 @@
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENABLED_FOR_APPS;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NOT_ENROLLED;
import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE;
+import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_SENSOR_PRIVACY_ENABLED;
import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED;
import android.annotation.NonNull;
@@ -278,6 +279,9 @@
case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS;
break;
+ case BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED:
+ biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
+ break;
default:
Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode);
biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
@@ -337,7 +341,8 @@
case BIOMETRIC_LOCKOUT_PERMANENT:
return BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
-
+ case BIOMETRIC_SENSOR_PRIVACY_ENABLED:
+ return BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED;
case BIOMETRIC_DISABLED_BY_DEVICE_POLICY:
case BIOMETRIC_HARDWARE_NOT_DETECTED:
case BIOMETRIC_NOT_ENABLED_FOR_APPS:
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 97d791b..4131ae1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -21,6 +21,7 @@
import android.app.NotificationManager;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants;
@@ -56,6 +57,7 @@
@NonNull private final LockoutCache mLockoutCache;
@Nullable private final NotificationManager mNotificationManager;
@Nullable private ICancellationSignal mCancellationSignal;
+ @Nullable private SensorPrivacyManager mSensorPrivacyManager;
private final int[] mBiometricPromptIgnoreList;
private final int[] mBiometricPromptIgnoreListVendor;
@@ -81,6 +83,7 @@
mUsageStats = usageStats;
mLockoutCache = lockoutCache;
mNotificationManager = context.getSystemService(NotificationManager.class);
+ mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
final Resources resources = getContext().getResources();
mBiometricPromptIgnoreList = resources.getIntArray(
@@ -108,7 +111,16 @@
@Override
protected void startHalOperation() {
try {
- mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
+ if (mSensorPrivacyManager != null
+ && mSensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA,
+ getTargetUserId())) {
+ onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ } else {
+ mCancellationSignal = getFreshDaemon().authenticate(mOperationId);
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when requesting auth", e);
onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 2ef0911..2158dfe 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -19,6 +19,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.hardware.SensorPrivacyManager;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.face.ISession;
@@ -41,6 +43,7 @@
private final boolean mIsStrongBiometric;
@Nullable private ICancellationSignal mCancellationSignal;
+ @Nullable private SensorPrivacyManager mSensorPrivacyManager;
public FaceDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
@NonNull IBinder token, long requestId,
@@ -51,6 +54,7 @@
BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
+ mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
}
@Override
@@ -73,6 +77,14 @@
@Override
protected void startHalOperation() {
+ if (mSensorPrivacyManager != null
+ && mSensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) {
+ onError(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ return;
+ }
+
try {
mCancellationSignal = getFreshDaemon().detectInteraction();
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 40f2801..7548d28 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.SensorPrivacyManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants;
@@ -55,6 +56,7 @@
private final int[] mKeyguardIgnoreListVendor;
private int mLastAcquire;
+ private SensorPrivacyManager mSensorPrivacyManager;
FaceAuthenticationClient(@NonNull Context context,
@NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
@@ -71,6 +73,7 @@
isKeyguardBypassEnabled);
setRequestId(requestId);
mUsageStats = usageStats;
+ mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
final Resources resources = getContext().getResources();
mBiometricPromptIgnoreList = resources.getIntArray(
@@ -97,6 +100,15 @@
@Override
protected void startHalOperation() {
+
+ if (mSensorPrivacyManager != null
+ && mSensorPrivacyManager
+ .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, getTargetUserId())) {
+ onError(BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE, 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ return;
+ }
+
try {
getFreshDaemon().authenticate(mOperationId);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/communal/CommunalManagerService.java b/services/core/java/com/android/server/communal/CommunalManagerService.java
index 1196442..8d9b13e 100644
--- a/services/core/java/com/android/server/communal/CommunalManagerService.java
+++ b/services/core/java/com/android/server/communal/CommunalManagerService.java
@@ -84,6 +84,10 @@
@Override
public Intent intercept(ActivityInterceptorInfo info) {
if (!shouldIntercept(info.aInfo)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Activity allowed, not intercepting: "
+ + info.aInfo.getComponentName());
+ }
return null;
}
@@ -188,8 +192,17 @@
return true;
}
- return isChangeEnabled(ALLOW_COMMUNAL_MODE_WITH_USER_CONSENT, appInfo)
- && getUserEnabledApps().contains(appInfo.packageName);
+ if (!isChangeEnabled(ALLOW_COMMUNAL_MODE_WITH_USER_CONSENT, appInfo)) {
+ if (DEBUG) Slog.d(TAG, "App is not allowlisted: " + appInfo.packageName);
+ return false;
+ }
+
+ if (!getUserEnabledApps().contains(appInfo.packageName)) {
+ if (DEBUG) Slog.d(TAG, "App does not have user consent: " + appInfo.packageName);
+ return false;
+ }
+
+ return true;
}
private boolean isActiveDream(ApplicationInfo appInfo) {
@@ -219,6 +232,10 @@
final boolean showWhenLocked =
(activityInfo.flags & ActivityInfo.FLAG_SHOW_WHEN_LOCKED) != 0;
if (!showWhenLocked) {
+ if (DEBUG) {
+ Slog.d(TAG, "Activity does not contain showWhenLocked attribute: "
+ + activityInfo.getComponentName());
+ }
return true;
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index d0aa40b..b66c466 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -574,7 +574,13 @@
}
}
- private static final class Data {
+ /**
+ * Container class for all networkpolicy events data.
+ *
+ * Note: This class needs to be public for RingBuffer class to be able to create
+ * new instances of this.
+ */
+ public static final class Data {
public int type;
public long timeStamp;
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 6ee1981..1e2eb5d 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -162,7 +162,8 @@
* </ul>
*/
private @NonNull CompletableFuture<?> prepareAppData(@NonNull Installer.Batch batch,
- @Nullable AndroidPackage pkg, int previousAppId, int userId, int flags) {
+ @Nullable AndroidPackage pkg, int previousAppId, int userId,
+ @StorageManager.StorageFlags int flags) {
if (pkg == null) {
Slog.wtf(TAG, "Package was null!", new Throwable());
return CompletableFuture.completedFuture(null);
@@ -171,7 +172,8 @@
}
private void prepareAppDataAndMigrate(@NonNull Installer.Batch batch,
- @NonNull AndroidPackage pkg, int userId, int flags, boolean maybeMigrateAppData) {
+ @NonNull AndroidPackage pkg, int userId, @StorageManager.StorageFlags int flags,
+ boolean maybeMigrateAppData) {
prepareAppData(batch, pkg, Process.INVALID_UID, userId, flags).thenRun(() -> {
// Note: this code block is executed with the Installer lock
// already held, since it's invoked as a side-effect of
@@ -331,7 +333,8 @@
* correct for all installed apps on all mounted volumes.
*/
@NonNull
- public void reconcileAppsData(int userId, int flags, boolean migrateAppsData) {
+ public void reconcileAppsData(int userId, @StorageManager.StorageFlags int flags,
+ boolean migrateAppsData) {
final StorageManager storage = mInjector.getSystemService(StorageManager.class);
for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
final String volumeUuid = vol.getFsUuid();
@@ -342,7 +345,7 @@
}
@GuardedBy("mPm.mInstallLock")
- void reconcileAppsDataLI(String volumeUuid, int userId, int flags,
+ void reconcileAppsDataLI(String volumeUuid, int userId, @StorageManager.StorageFlags int flags,
boolean migrateAppData) {
reconcileAppsDataLI(volumeUuid, userId, flags, migrateAppData, false /* onlyCoreApps */);
}
@@ -359,8 +362,8 @@
* @return list of skipped non-core packages (if {@code onlyCoreApps} is true)
*/
@GuardedBy("mPm.mInstallLock")
- private List<String> reconcileAppsDataLI(String volumeUuid, int userId, int flags,
- boolean migrateAppData, boolean onlyCoreApps) {
+ private List<String> reconcileAppsDataLI(String volumeUuid, int userId,
+ @StorageManager.StorageFlags int flags, boolean migrateAppData, boolean onlyCoreApps) {
Slog.v(TAG, "reconcileAppsData for " + volumeUuid + " u" + userId + " 0x"
+ Integer.toHexString(flags) + " migrateAppData=" + migrateAppData);
List<String> result = onlyCoreApps ? new ArrayList<>() : null;
@@ -479,7 +482,7 @@
* can't wait for user to start
*/
public Future<?> fixAppsDataOnBoot() {
- final int storageFlags;
+ final @StorageManager.StorageFlags int storageFlags;
if (StorageManager.isFileEncryptedNativeOrEmulated()) {
storageFlags = StorageManager.FLAG_STORAGE_DE;
} else {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 80c2a7e..fce37a9 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2322,12 +2322,19 @@
}
}
- // It's implied that when a user requests installation, they want the app to be
- // installed and enabled. (This does not apply to USER_ALL, which here means only
- // install on users for which the app is already installed).
if (userId != UserHandle.USER_ALL) {
+ // It's implied that when a user requests installation, they want the app to
+ // be installed and enabled.
ps.setInstalled(true, userId);
ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
+ } else if (allUsers != null) {
+ // The caller explicitly specified INSTALL_ALL_USERS flag.
+ // Thus, updating the settings to install the app for all users.
+ for (int currentUserId : allUsers) {
+ ps.setInstalled(true, currentUserId);
+ ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId,
+ installerPackageName);
+ }
}
mPm.mSettings.addInstallerPackageNames(ps.getInstallSource());
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 26a5bbb..356d6c9 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -286,6 +286,7 @@
synchronized (mSessions) {
readSessionsLocked();
+ expireSessionsLocked();
reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL);
@@ -462,34 +463,7 @@
Slog.e(TAG, "Could not read session", e);
continue;
}
-
- final long age = System.currentTimeMillis() - session.createdMillis;
- final long timeSinceUpdate =
- System.currentTimeMillis() - session.getUpdatedMillis();
- final boolean valid;
- if (session.isStaged()) {
- if (timeSinceUpdate >= MAX_TIME_SINCE_UPDATE_MILLIS
- && session.isStagedAndInTerminalState()) {
- valid = false;
- } else {
- valid = true;
- }
- } else if (age >= MAX_AGE_MILLIS) {
- Slog.w(TAG, "Abandoning old session created at "
- + session.createdMillis);
- valid = false;
- } else {
- valid = true;
- }
-
- if (valid) {
- mSessions.put(session.sessionId, session);
- } else {
- // Since this is early during boot we don't send
- // any observer events about the session, but we
- // keep details around for dumpsys.
- addHistoricalSessionLocked(session);
- }
+ mSessions.put(session.sessionId, session);
mAllocatedSessions.put(session.sessionId, true);
}
}
@@ -509,6 +483,44 @@
}
@GuardedBy("mSessions")
+ private void expireSessionsLocked() {
+ SparseArray<PackageInstallerSession> tmp = mSessions.clone();
+ final int n = tmp.size();
+ for (int i = 0; i < n; ++i) {
+ PackageInstallerSession session = tmp.valueAt(i);
+ if (session.hasParentSessionId()) {
+ // Child sessions will be expired when handling parent sessions
+ continue;
+ }
+ final long age = System.currentTimeMillis() - session.createdMillis;
+ final long timeSinceUpdate = System.currentTimeMillis() - session.getUpdatedMillis();
+ final boolean valid;
+ if (session.isStaged()) {
+ valid = !session.isStagedAndInTerminalState()
+ || timeSinceUpdate < MAX_TIME_SINCE_UPDATE_MILLIS;
+ } else if (age >= MAX_AGE_MILLIS) {
+ Slog.w(TAG, "Abandoning old session created at "
+ + session.createdMillis);
+ valid = false;
+ } else {
+ valid = true;
+ }
+ if (!valid) {
+ // Remove expired sessions as well as child sessions if any
+ mSessions.remove(session.sessionId);
+ // Since this is early during boot we don't send
+ // any observer events about the session, but we
+ // keep details around for dumpsys.
+ addHistoricalSessionLocked(session);
+ for (PackageInstallerSession child : session.getChildSessions()) {
+ mSessions.remove(child.sessionId);
+ addHistoricalSessionLocked(child);
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mSessions")
private void addHistoricalSessionLocked(PackageInstallerSession session) {
CharArrayWriter writer = new CharArrayWriter();
IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 55a0c96..28204ea 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -706,9 +706,10 @@
if (mCommitted.get()) {
mStagingManager.abortCommittedSession(this);
}
- destroyInternal();
+ destroy();
dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
- maybeCleanUpChildSessions();
+ maybeFinishChildSessions(INSTALL_FAILED_ABORTED,
+ "Session was abandoned because the parent session is abandoned");
};
if (mStageDirInUse) {
// Pre-reboot verification is ongoing, not safe to clean up the session yet.
@@ -2131,6 +2132,7 @@
destroy();
// Dispatch message to remove session from PackageInstallerService.
dispatchSessionFinished(error, msg, null);
+ maybeFinishChildSessions(error, msg);
}
}
@@ -3646,20 +3648,19 @@
throw new SecurityException("Must be sealed to accept permissions");
}
+ PackageInstallerSession root = hasParentSessionId()
+ ? mSessionProvider.getSession(getParentSessionId()) : this;
+
if (accepted) {
// Mark and kick off another install pass
synchronized (mLock) {
mPermissionsManuallyAccepted = true;
}
-
- PackageInstallerSession root =
- (hasParentSessionId())
- ? mSessionProvider.getSession(getParentSessionId())
- : this;
root.mHandler.obtainMessage(MSG_INSTALL).sendToTarget();
} else {
- destroyInternal();
- dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
+ root.destroy();
+ root.dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
+ root.maybeFinishChildSessions(INSTALL_FAILED_ABORTED, "User rejected permissions");
}
}
@@ -3710,27 +3711,12 @@
}
/**
- * Cleans up the relevant stored files and information of all child sessions.
- * <p>Cleaning up the stored files and session information is necessary for
- * preventing the orphan children sessions.
- * <ol>
- * <li>To call {@link #destroyInternal()} cleans up the stored files.</li>
- * <li>To call {@link #dispatchSessionFinished(int, String, Bundle)} to trigger the
- * procedure to clean up the information in PackageInstallerService.</li>
- * </ol></p>
+ * Calls dispatchSessionFinished() on all child sessions with the given error code and
+ * error message to prevent orphaned child sessions.
*/
- private void maybeCleanUpChildSessions() {
- if (!isMultiPackage()) {
- return;
- }
-
- final List<PackageInstallerSession> childSessions = getChildSessions();
- final int size = childSessions.size();
- for (int i = 0; i < size; ++i) {
- final PackageInstallerSession session = childSessions.get(i);
- session.destroyInternal();
- session.dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned"
- + " because the parent session is abandoned", null);
+ private void maybeFinishChildSessions(int returnCode, String msg) {
+ for (PackageInstallerSession child : getChildSessions()) {
+ child.dispatchSessionFinished(returnCode, msg, null);
}
}
@@ -3742,10 +3728,12 @@
if (LOGD) Slog.d(TAG, "Ignoring abandon for staging files are in use");
return;
}
- destroyInternal();
+ mDestroyed = true;
}
+ destroy();
dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
- maybeCleanUpChildSessions();
+ maybeFinishChildSessions(INSTALL_FAILED_ABORTED,
+ "Session was abandoned because the parent session is abandoned");
}
private void assertNotChild(String cookie) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 333d3a4..fa23000 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2741,13 +2741,13 @@
@Override
public void freeStorageAndNotify(final String volumeUuid, final long freeStorageSize,
- final int storageFlags, final IPackageDataObserver observer) {
+ final @StorageManager.AllocateFlags int flags, final IPackageDataObserver observer) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CLEAR_APP_CACHE, null);
mHandler.post(() -> {
boolean success = false;
try {
- freeStorage(volumeUuid, freeStorageSize, storageFlags);
+ freeStorage(volumeUuid, freeStorageSize, flags);
success = true;
} catch (IOException e) {
Slog.w(TAG, e);
@@ -2764,13 +2764,13 @@
@Override
public void freeStorage(final String volumeUuid, final long freeStorageSize,
- final int storageFlags, final IntentSender pi) {
+ final @StorageManager.AllocateFlags int flags, final IntentSender pi) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CLEAR_APP_CACHE, TAG);
mHandler.post(() -> {
boolean success = false;
try {
- freeStorage(volumeUuid, freeStorageSize, storageFlags);
+ freeStorage(volumeUuid, freeStorageSize, flags);
success = true;
} catch (IOException e) {
Slog.w(TAG, e);
@@ -2789,7 +2789,8 @@
* Blocking call to clear various types of cached data across the system
* until the requested bytes are available.
*/
- public void freeStorage(String volumeUuid, long bytes, int storageFlags) throws IOException {
+ public void freeStorage(String volumeUuid, long bytes,
+ @StorageManager.AllocateFlags int flags) throws IOException {
final StorageManager storage = mInjector.getSystemService(StorageManager.class);
final File file = storage.findPathForUuid(volumeUuid);
if (file.getUsableSpace() >= bytes) return;
@@ -2797,8 +2798,7 @@
if (mEnableFreeCacheV2) {
final boolean internalVolume = Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL,
volumeUuid);
- final boolean aggressive = (storageFlags
- & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0;
+ final boolean aggressive = (flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0;
// 1. Pre-flight to determine if we have any chance to succeed
// 2. Consider preloaded data (after 1w honeymoon, unless aggressive)
@@ -8311,9 +8311,9 @@
}
@Override
- public void freeStorage(String volumeUuid, long bytes, int storageFlags)
- throws IOException {
- PackageManagerService.this.freeStorage(volumeUuid, bytes, storageFlags);
+ public void freeStorage(String volumeUuid, long bytes,
+ @StorageManager.AllocateFlags int flags) throws IOException {
+ PackageManagerService.this.freeStorage(volumeUuid, bytes, flags);
}
@Override
@@ -8695,7 +8695,8 @@
}
@Override
- public void reconcileAppsData(int userId, int flags, boolean migrateAppsData) {
+ public void reconcileAppsData(int userId, @StorageManager.StorageFlags int flags,
+ boolean migrateAppsData) {
PackageManagerService.this.mAppDataHelper.reconcileAppsData(userId, flags,
migrateAppsData);
}
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 328a55f..0f3b4bc 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -143,8 +143,9 @@
UserManager.DISALLOW_CAMERA_TOGGLE,
UserManager.DISALLOW_CHANGE_WIFI_STATE,
UserManager.DISALLOW_WIFI_TETHERING,
- UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI
-
+ UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
+ UserManager.DISALLOW_WIFI_DIRECT,
+ UserManager.DISALLOW_ADD_WIFI_CONFIG
});
public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet(
@@ -190,7 +191,9 @@
UserManager.DISALLOW_MICROPHONE_TOGGLE,
UserManager.DISALLOW_CAMERA_TOGGLE,
UserManager.DISALLOW_CHANGE_WIFI_STATE,
- UserManager.DISALLOW_WIFI_TETHERING
+ UserManager.DISALLOW_WIFI_TETHERING,
+ UserManager.DISALLOW_WIFI_DIRECT,
+ UserManager.DISALLOW_ADD_WIFI_CONFIG
);
/**
@@ -227,7 +230,9 @@
UserManager.DISALLOW_CONFIG_DATE_TIME,
UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
UserManager.DISALLOW_CHANGE_WIFI_STATE,
- UserManager.DISALLOW_WIFI_TETHERING
+ UserManager.DISALLOW_WIFI_TETHERING,
+ UserManager.DISALLOW_WIFI_DIRECT,
+ UserManager.DISALLOW_ADD_WIFI_CONFIG
);
/**
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 232ea09..4eae939 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -64,6 +64,7 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -139,6 +140,8 @@
private int mCurrentUserId;
private boolean mTracingEnabled;
+ private final TileRequestTracker mTileRequestTracker;
+
private final SparseArray<UiState> mDisplayUiState = new SparseArray<>();
@GuardedBy("mLock")
private IUdfpsHbmListener mUdfpsHbmListener;
@@ -245,6 +248,8 @@
mActivityTaskManager = LocalServices.getService(ActivityTaskManagerInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+
+ mTileRequestTracker = new TileRequestTracker(mContext);
}
@Override
@@ -1765,11 +1770,26 @@
mCurrentRequestAddTilePackages.put(packageName, currentTime);
}
+ if (mTileRequestTracker.shouldBeDenied(userId, componentName)) {
+ if (clearTileAddRequest(packageName)) {
+ try {
+ callback.onTileRequest(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "requestAddTile - callback", e);
+ }
+ }
+ return;
+ }
+
IAddTileResultCallback proxyCallback = new IAddTileResultCallback.Stub() {
@Override
public void onTileRequest(int i) {
if (i == StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED) {
i = StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED;
+ } else if (i == StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED) {
+ mTileRequestTracker.addDenial(userId, componentName);
+ } else if (i == StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED) {
+ mTileRequestTracker.resetRequests(userId, componentName);
}
if (clearTileAddRequest(packageName)) {
try {
@@ -1961,6 +1981,8 @@
pw.println(" " + requests.get(i) + ",");
}
pw.println(" ]");
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ mTileRequestTracker.dump(fd, ipw.increaseIndent(), args);
}
}
diff --git a/services/core/java/com/android/server/statusbar/TileRequestTracker.java b/services/core/java/com/android/server/statusbar/TileRequestTracker.java
new file mode 100644
index 0000000..d5ace3f7
--- /dev/null
+++ b/services/core/java/com/android/server/statusbar/TileRequestTracker.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2021 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.statusbar;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.SparseArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+
+/**
+ * Tracks user denials of requests from {@link StatusBarManagerService#requestAddTile}.
+ *
+ * After a certain number of denials for a particular pair (user,ComponentName), requests will be
+ * auto-denied without showing a dialog to the user.
+ */
+public class TileRequestTracker {
+
+ @VisibleForTesting
+ static final int MAX_NUM_DENIALS = 3;
+
+ private final Context mContext;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final SparseArrayMap<ComponentName, Integer> mTrackingMap = new SparseArrayMap<>();
+ @GuardedBy("mLock")
+ private final ArraySet<ComponentName> mComponentsToRemove = new ArraySet<>();
+
+ private final BroadcastReceiver mUninstallReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ return;
+ }
+
+ Uri data = intent.getData();
+ String packageName = data.getEncodedSchemeSpecificPart();
+
+ if (!intent.hasExtra(Intent.EXTRA_UID)) {
+ return;
+ }
+ int userId = UserHandle.getUserId(intent.getIntExtra(Intent.EXTRA_UID, -1));
+ synchronized (mLock) {
+ mComponentsToRemove.clear();
+ final int elementsForUser = mTrackingMap.numElementsForKey(userId);
+ final int userKeyIndex = mTrackingMap.indexOfKey(userId);
+ for (int compKeyIndex = 0; compKeyIndex < elementsForUser; compKeyIndex++) {
+ ComponentName c = mTrackingMap.keyAt(userKeyIndex, compKeyIndex);
+ if (c.getPackageName().equals(packageName)) {
+ mComponentsToRemove.add(c);
+ }
+ }
+ final int compsToRemoveNum = mComponentsToRemove.size();
+ for (int i = 0; i < compsToRemoveNum; i++) {
+ ComponentName c = mComponentsToRemove.valueAt(i);
+ mTrackingMap.delete(userId, c);
+ }
+ }
+ }
+ };
+
+ TileRequestTracker(Context context) {
+ mContext = context;
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
+ intentFilter.addDataScheme("package");
+ mContext.registerReceiverAsUser(mUninstallReceiver, UserHandle.ALL, intentFilter, null,
+ null);
+ }
+
+ /**
+ * Return whether this combination of {@code userId} and {@link ComponentName} should be
+ * auto-denied.
+ */
+ boolean shouldBeDenied(int userId, ComponentName componentName) {
+ synchronized (mLock) {
+ return mTrackingMap.getOrDefault(userId, componentName, 0) >= MAX_NUM_DENIALS;
+ }
+ }
+
+ /**
+ * Add a new denial instance for a given {@code userId} and {@link ComponentName}.
+ */
+ void addDenial(int userId, ComponentName componentName) {
+ synchronized (mLock) {
+ int current = mTrackingMap.getOrDefault(userId, componentName, 0);
+ mTrackingMap.add(userId, componentName, current + 1);
+ }
+ }
+
+ /**
+ * Reset the number of denied request for a given {@code userId} and {@link ComponentName}.
+ */
+ void resetRequests(int userId, ComponentName componentName) {
+ synchronized (mLock) {
+ mTrackingMap.delete(userId, componentName);
+ }
+ }
+
+ void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
+ pw.println("TileRequestTracker:");
+ pw.increaseIndent();
+ synchronized (mLock) {
+ mTrackingMap.forEach((user, componentName, value) -> {
+ pw.println("user=" + user + ", " + componentName.toShortString() + ": " + value);
+ });
+ }
+ pw.decreaseIndent();
+ }
+}
diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java
index 65907f1..417177f 100644
--- a/services/core/java/com/android/server/timedetector/ServerFlags.java
+++ b/services/core/java/com/android/server/timedetector/ServerFlags.java
@@ -35,6 +35,7 @@
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
+import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -179,8 +180,13 @@
public static final @DeviceConfigKey String KEY_ENHANCED_METRICS_COLLECTION_ENABLED =
"enhanced_metrics_collection_enabled";
+ /**
+ * The registered listeners and the keys to trigger on. The value is explicitly a HashSet to
+ * ensure O(1) lookup performance when working out whether a listener should trigger.
+ */
@GuardedBy("mListeners")
- private final ArrayMap<ConfigurationChangeListener, Set<String>> mListeners = new ArrayMap<>();
+ private final ArrayMap<ConfigurationChangeListener, HashSet<String>> mListeners =
+ new ArrayMap<>();
private static final Object SLOCK = new Object();
@@ -207,18 +213,29 @@
private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) {
synchronized (mListeners) {
- for (Map.Entry<ConfigurationChangeListener, Set<String>> listenerEntry
+ for (Map.Entry<ConfigurationChangeListener, HashSet<String>> listenerEntry
: mListeners.entrySet()) {
- if (intersects(listenerEntry.getValue(), properties.getKeyset())) {
+ // It's unclear which set of the following two Sets is going to be larger in the
+ // average case: monitoredKeys will be a subset of the set of possible keys, but
+ // only changed keys are reported. Because we guarantee the type / lookup behavior
+ // of the monitoredKeys by making that a HashSet, that is used as the haystack Set,
+ // while the changed keys is treated as the needles Iterable. At the time of
+ // writing, properties.getKeyset() actually returns a HashSet, so iteration isn't
+ // super efficient and the use of HashSet for monitoredKeys may be redundant, but
+ // neither set will be enormous.
+ HashSet<String> monitoredKeys = listenerEntry.getValue();
+ Iterable<String> modifiedKeys = properties.getKeyset();
+ if (containsAny(monitoredKeys, modifiedKeys)) {
listenerEntry.getKey().onChange();
}
}
}
}
- private static boolean intersects(@NonNull Set<String> one, @NonNull Set<String> two) {
- for (String toFind : one) {
- if (two.contains(toFind)) {
+ private static boolean containsAny(
+ @NonNull Set<String> haystack, @NonNull Iterable<String> needles) {
+ for (String needle : needles) {
+ if (haystack.contains(needle)) {
return true;
}
}
@@ -237,8 +254,11 @@
Objects.requireNonNull(listener);
Objects.requireNonNull(keys);
+ // Make a defensive copy and use a well-defined Set implementation to provide predictable
+ // performance on the lookup.
+ HashSet<String> keysCopy = new HashSet<>(keys);
synchronized (mListeners) {
- mListeners.put(listener, keys);
+ mListeners.put(listener, keysCopy);
}
}
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
index 7f7d01c..5f14000 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
@@ -25,7 +25,6 @@
import android.content.Context;
import android.os.Build;
import android.os.SystemProperties;
-import android.util.ArraySet;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -35,7 +34,6 @@
import java.time.Instant;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -65,11 +63,10 @@
Long.max(android.os.Environment.getRootDirectory().lastModified(), Build.TIME));
/** Device config keys that affect the {@link TimeDetectorService}. */
- private static final Set<String> SERVER_FLAGS_KEYS_TO_WATCH = Collections.unmodifiableSet(
- new ArraySet<>(new String[] {
- KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE,
- KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE,
- }));
+ private static final Set<String> SERVER_FLAGS_KEYS_TO_WATCH = Set.of(
+ KEY_TIME_DETECTOR_LOWER_BOUND_MILLIS_OVERRIDE,
+ KEY_TIME_DETECTOR_ORIGIN_PRIORITIES_OVERRIDE
+ );
private static final Object SLOCK = new Object();
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index b452d90..ae52912 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -36,7 +36,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.util.ArraySet;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -45,7 +44,6 @@
import java.time.Duration;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -59,33 +57,31 @@
/**
* Device config keys that can affect the content of {@link ConfigurationInternal}.
*/
- private static final Set<String> CONFIGURATION_INTERNAL_SERVER_FLAGS_KEYS_TO_WATCH =
- Collections.unmodifiableSet(new ArraySet<>(new String[] {
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_RUN_IN_BACKGROUND_ENABLED,
- ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
- ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED,
- }));
+ private static final Set<String> CONFIGURATION_INTERNAL_SERVER_FLAGS_KEYS_TO_WATCH = Set.of(
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_RUN_IN_BACKGROUND_ENABLED,
+ ServerFlags.KEY_ENHANCED_METRICS_COLLECTION_ENABLED,
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
+ ServerFlags.KEY_TIME_ZONE_DETECTOR_TELEPHONY_FALLBACK_SUPPORTED
+ );
/**
* Device config keys that can affect {@link
* com.android.server.timezonedetector.location.LocationTimeZoneManagerService} behavior.
*/
- private static final Set<String> LOCATION_TIME_ZONE_MANAGER_SERVER_FLAGS_KEYS_TO_WATCH =
- Collections.unmodifiableSet(new ArraySet<>(new String[] {
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_RUN_IN_BACKGROUND_ENABLED,
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
- ServerFlags.KEY_PRIMARY_LTZP_MODE_OVERRIDE,
- ServerFlags.KEY_SECONDARY_LTZP_MODE_OVERRIDE,
- ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS,
- ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
- ServerFlags.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS,
- ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS
- }));
+ private static final Set<String> LOCATION_TIME_ZONE_MANAGER_SERVER_FLAGS_KEYS_TO_WATCH = Set.of(
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_FEATURE_SUPPORTED,
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_RUN_IN_BACKGROUND_ENABLED,
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_DEFAULT,
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_SETTING_ENABLED_OVERRIDE,
+ ServerFlags.KEY_PRIMARY_LTZP_MODE_OVERRIDE,
+ ServerFlags.KEY_SECONDARY_LTZP_MODE_OVERRIDE,
+ ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_MILLIS,
+ ServerFlags.KEY_LTZP_INITIALIZATION_TIMEOUT_FUZZ_MILLIS,
+ ServerFlags.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS,
+ ServerFlags.KEY_LOCATION_TIME_ZONE_DETECTION_UNCERTAINTY_DELAY_MILLIS
+ );
private static final Duration DEFAULT_LTZP_INITIALIZATION_TIMEOUT = Duration.ofMinutes(5);
private static final Duration DEFAULT_LTZP_INITIALIZATION_TIMEOUT_FUZZ = Duration.ofMinutes(1);
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 0436460..6628802 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -37,6 +37,7 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
@@ -52,6 +53,9 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
/**
* This class provides a system service that manages the TV tuner resources.
@@ -64,6 +68,8 @@
public static final int INVALID_CLIENT_ID = -1;
private static final int MAX_CLIENT_PRIORITY = 1000;
+ private static final long INVALID_THREAD_ID = -1;
+ private static final long TRMS_LOCK_TIMEOUT = 500;
// Map of the registered client profiles
private Map<Integer, ClientProfile> mClientProfiles = new HashMap<>();
@@ -94,6 +100,12 @@
// Used to synchronize the access to the service.
private final Object mLock = new Object();
+ private final ReentrantLock mLockForTRMSLock = new ReentrantLock();
+ private final Condition mTunerApiLockReleasedCV = mLockForTRMSLock.newCondition();
+ private int mTunerApiLockHolder = INVALID_CLIENT_ID;
+ private long mTunerApiLockHolderThreadId = INVALID_THREAD_ID;
+ private int mTunerApiLockNestedCount = 0;
+
public TunerResourceManagerService(@Nullable Context context) {
super(context);
}
@@ -511,6 +523,20 @@
}
@Override
+ public boolean acquireLock(int clientId, long clientThreadId) {
+ enforceTrmAccessPermission("acquireLock");
+ // this must not be locked with mLock
+ return acquireLockInternal(clientId, clientThreadId, TRMS_LOCK_TIMEOUT);
+ }
+
+ @Override
+ public boolean releaseLock(int clientId) {
+ enforceTrmAccessPermission("releaseLock");
+ // this must not be locked with mLock
+ return releaseLockInternal(clientId, TRMS_LOCK_TIMEOUT, false, false);
+ }
+
+ @Override
protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -1194,6 +1220,187 @@
return true;
}
+ // Return value is guaranteed to be positive
+ private long getElapsedTime(long begin) {
+ long now = SystemClock.uptimeMillis();
+ long elapsed;
+ if (now >= begin) {
+ elapsed = now - begin;
+ } else {
+ elapsed = Long.MAX_VALUE - begin + now;
+ if (elapsed < 0) {
+ elapsed = Long.MAX_VALUE;
+ }
+ }
+ return elapsed;
+ }
+
+ private boolean lockForTunerApiLock(int clientId, long timeoutMS, String callerFunction) {
+ try {
+ if (mLockForTRMSLock.tryLock(timeoutMS, TimeUnit.MILLISECONDS)) {
+ return true;
+ } else {
+ Slog.e(TAG, "FAILED to lock mLockForTRMSLock in " + callerFunction
+ + ", clientId:" + clientId + ", timeoutMS:" + timeoutMS
+ + ", mTunerApiLockHolder:" + mTunerApiLockHolder);
+ return false;
+ }
+ } catch (InterruptedException ie) {
+ Slog.e(TAG, "exception thrown in " + callerFunction + ":" + ie);
+ if (mLockForTRMSLock.isHeldByCurrentThread()) {
+ mLockForTRMSLock.unlock();
+ }
+ return false;
+ }
+ }
+
+ private boolean acquireLockInternal(int clientId, long clientThreadId, long timeoutMS) {
+ long begin = SystemClock.uptimeMillis();
+
+ // Grab lock
+ if (!lockForTunerApiLock(clientId, timeoutMS, "acquireLockInternal()")) {
+ return false;
+ }
+
+ try {
+ boolean available = mTunerApiLockHolder == INVALID_CLIENT_ID;
+ boolean nestedSelf = (clientId == mTunerApiLockHolder)
+ && (clientThreadId == mTunerApiLockHolderThreadId);
+ boolean recovery = false;
+
+ // Allow same thread to grab the lock multiple times
+ while (!available && !nestedSelf) {
+ // calculate how much time is left before timeout
+ long leftOverMS = timeoutMS - getElapsedTime(begin);
+ if (leftOverMS <= 0) {
+ Slog.e(TAG, "FAILED:acquireLockInternal(" + clientId + ", " + clientThreadId
+ + ", " + timeoutMS + ") - timed out, but will grant the lock to "
+ + "the callee by stealing it from the current holder:"
+ + mTunerApiLockHolder + "(" + mTunerApiLockHolderThreadId + "), "
+ + "who likely failed to call releaseLock(), "
+ + "to prevent this from becoming an unrecoverable error");
+ // This should not normally happen, but there sometimes are cases where
+ // in-flight tuner API execution gets scheduled even after binderDied(),
+ // which can leave the in-flight execution dissappear/stopped in between
+ // acquireLock and releaseLock
+ recovery = true;
+ break;
+ }
+
+ // Cond wait for left over time
+ mTunerApiLockReleasedCV.await(leftOverMS, TimeUnit.MILLISECONDS);
+
+ // Check the availability for "spurious wakeup"
+ // The case that was confirmed is that someone else can acquire this in between
+ // signal() and wakup from the above await()
+ available = mTunerApiLockHolder == INVALID_CLIENT_ID;
+
+ if (!available) {
+ Slog.w(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId + ", "
+ + timeoutMS + ") - woken up from cond wait, but " + mTunerApiLockHolder
+ + "(" + mTunerApiLockHolderThreadId + ") is already holding the lock. "
+ + "Going to wait again if timeout hasn't reached yet");
+ }
+ }
+
+ // Will always grant unless exception is thrown (or lock is already held)
+ if (available || recovery) {
+ if (DEBUG) {
+ Slog.d(TAG, "SUCCESS:acquireLockInternal(" + clientId + ", " + clientThreadId
+ + ", " + timeoutMS + ")");
+ }
+
+ if (mTunerApiLockNestedCount != 0) {
+ Slog.w(TAG, "Something is wrong as nestedCount(" + mTunerApiLockNestedCount
+ + ") is not zero. Will overriding it to 1 anyways");
+ }
+
+ // set the caller to be the holder
+ mTunerApiLockHolder = clientId;
+ mTunerApiLockHolderThreadId = clientThreadId;
+ mTunerApiLockNestedCount = 1;
+ } else if (nestedSelf) {
+ // Increment the nested count so releaseLockInternal won't signal prematuredly
+ mTunerApiLockNestedCount++;
+ if (DEBUG) {
+ Slog.d(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId
+ + ", " + timeoutMS + ") - nested count incremented to "
+ + mTunerApiLockNestedCount);
+ }
+ } else {
+ Slog.e(TAG, "acquireLockInternal(" + clientId + ", " + clientThreadId
+ + ", " + timeoutMS + ") - should not reach here");
+ }
+ // return true in "recovery" so callee knows that the deadlock is possible
+ // only when the return value is false
+ return (available || nestedSelf || recovery);
+ } catch (InterruptedException ie) {
+ Slog.e(TAG, "exception thrown in acquireLockInternal(" + clientId + ", "
+ + clientThreadId + ", " + timeoutMS + "):" + ie);
+ return false;
+ } finally {
+ if (mLockForTRMSLock.isHeldByCurrentThread()) {
+ mLockForTRMSLock.unlock();
+ }
+ }
+ }
+
+ private boolean releaseLockInternal(int clientId, long timeoutMS,
+ boolean ignoreNestedCount, boolean suppressError) {
+ // Grab lock first
+ if (!lockForTunerApiLock(clientId, timeoutMS, "releaseLockInternal()")) {
+ return false;
+ }
+
+ try {
+ if (mTunerApiLockHolder == clientId) {
+ // Should always reach here unless called from binderDied()
+ mTunerApiLockNestedCount--;
+ if (ignoreNestedCount || mTunerApiLockNestedCount <= 0) {
+ if (DEBUG) {
+ Slog.d(TAG, "SUCCESS:releaseLockInternal(" + clientId + ", " + timeoutMS
+ + ", " + ignoreNestedCount + ", " + suppressError
+ + ") - signaling!");
+ }
+ // Reset the current holder and signal
+ mTunerApiLockHolder = INVALID_CLIENT_ID;
+ mTunerApiLockHolderThreadId = INVALID_THREAD_ID;
+ mTunerApiLockNestedCount = 0;
+ mTunerApiLockReleasedCV.signal();
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS
+ + ", " + ignoreNestedCount + ", " + suppressError
+ + ") - NOT signaling because nested count is not zero ("
+ + mTunerApiLockNestedCount + ")");
+ }
+ }
+ return true;
+ } else if (mTunerApiLockHolder == INVALID_CLIENT_ID) {
+ if (!suppressError) {
+ Slog.w(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS
+ + ") - called while there is no current holder");
+ }
+ // No need to do anything.
+ // Shouldn't reach here unless called from binderDied()
+ return false;
+ } else {
+ if (!suppressError) {
+ Slog.e(TAG, "releaseLockInternal(" + clientId + ", " + timeoutMS
+ + ") - called while someone else:" + mTunerApiLockHolder
+ + "is the current holder");
+ }
+ // Cannot reset the holder Id because it reaches here when called
+ // from binderDied()
+ return false;
+ }
+ } finally {
+ if (mLockForTRMSLock.isHeldByCurrentThread()) {
+ mLockForTRMSLock.unlock();
+ }
+ }
+ }
+
@VisibleForTesting
protected class ResourcesReclaimListenerRecord implements IBinder.DeathRecipient {
private final IResourcesReclaimListener mListener;
@@ -1206,10 +1413,15 @@
@Override
public void binderDied() {
- synchronized (mLock) {
- if (checkClientExists(mClientId)) {
- removeClientProfile(mClientId);
+ try {
+ synchronized (mLock) {
+ if (checkClientExists(mClientId)) {
+ removeClientProfile(mClientId);
+ }
}
+ } finally {
+ // reset the tuner API lock
+ releaseLockInternal(mClientId, TRMS_LOCK_TIMEOUT, true, true);
}
}
@@ -1247,6 +1459,13 @@
protected boolean reclaimResource(int reclaimingClientId,
@TunerResourceManager.TunerResourceType int resourceType) {
+ // Allowing this because:
+ // 1) serialization of resource reclaim is required in the current design
+ // 2) the outgoing transaction is handled by the system app (with
+ // android.Manifest.permission.TUNER_RESOURCE_ACCESS), which goes through full
+ // Google certification
+ Binder.allowBlockingForCurrentThread();
+
// Reclaim all the resources of the share owners of the frontend that is used by the current
// resource reclaimed client.
ClientProfile profile = getClientProfile(reclaimingClientId);
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 584530c..8b80b4a 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -687,6 +687,7 @@
mUnderlyingNetworkController =
mDeps.newUnderlyingNetworkController(
mVcnContext,
+ mConnectionConfig,
subscriptionGroup,
mLastSnapshot,
mUnderlyingNetworkControllerCallback);
@@ -2376,11 +2377,12 @@
/** Builds a new UnderlyingNetworkController. */
public UnderlyingNetworkController newUnderlyingNetworkController(
VcnContext vcnContext,
+ VcnGatewayConnectionConfig connectionConfig,
ParcelUuid subscriptionGroup,
TelephonySubscriptionSnapshot snapshot,
UnderlyingNetworkControllerCallback callback) {
return new UnderlyingNetworkController(
- vcnContext, subscriptionGroup, snapshot, callback);
+ vcnContext, connectionConfig, subscriptionGroup, snapshot, callback);
}
/** Builds a new IkeSession. */
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
index bea8ae9..7b26fe0 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkPriorityClassifier.java
@@ -15,25 +15,36 @@
*/
package com.android.server.vcn.routeselection;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK;
import static com.android.server.VcnManagementService.LOCAL_LOG;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnCellUnderlyingNetworkPriority;
import android.net.vcn.VcnManager;
+import android.net.vcn.VcnUnderlyingNetworkPriority;
+import android.net.vcn.VcnWifiUnderlyingNetworkPriority;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.util.Slog;
-import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
+import java.util.LinkedHashSet;
import java.util.Set;
/** @hide */
@@ -56,52 +67,20 @@
*/
@VisibleForTesting(visibility = Visibility.PRIVATE)
static final int WIFI_EXIT_RSSI_THRESHOLD_DEFAULT = -74;
- /** Priority for any cellular network for which the subscription is listed as opportunistic */
- @VisibleForTesting(visibility = Visibility.PRIVATE)
- static final int PRIORITY_OPPORTUNISTIC_CELLULAR = 0;
- /** Priority for any WiFi network which is in use, and satisfies the in-use RSSI threshold */
- @VisibleForTesting(visibility = Visibility.PRIVATE)
- static final int PRIORITY_WIFI_IN_USE = 1;
- /** Priority for any WiFi network which satisfies the prospective-network RSSI threshold */
- @VisibleForTesting(visibility = Visibility.PRIVATE)
- static final int PRIORITY_WIFI_PROSPECTIVE = 2;
- /** Priority for any standard macro cellular network */
- @VisibleForTesting(visibility = Visibility.PRIVATE)
- static final int PRIORITY_MACRO_CELLULAR = 3;
+
/** Priority for any other networks (including unvalidated, etc) */
@VisibleForTesting(visibility = Visibility.PRIVATE)
static final int PRIORITY_ANY = Integer.MAX_VALUE;
- private static final SparseArray<String> PRIORITY_TO_STRING_MAP = new SparseArray<>();
-
- static {
- PRIORITY_TO_STRING_MAP.put(
- PRIORITY_OPPORTUNISTIC_CELLULAR, "PRIORITY_OPPORTUNISTIC_CELLULAR");
- PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_IN_USE, "PRIORITY_WIFI_IN_USE");
- PRIORITY_TO_STRING_MAP.put(PRIORITY_WIFI_PROSPECTIVE, "PRIORITY_WIFI_PROSPECTIVE");
- PRIORITY_TO_STRING_MAP.put(PRIORITY_MACRO_CELLULAR, "PRIORITY_MACRO_CELLULAR");
- PRIORITY_TO_STRING_MAP.put(PRIORITY_ANY, "PRIORITY_ANY");
- }
-
- /**
- * Gives networks a priority class, based on the following priorities:
- *
- * <ol>
- * <li>Opportunistic cellular
- * <li>Carrier WiFi, signal strength >= WIFI_ENTRY_RSSI_THRESHOLD_DEFAULT
- * <li>Carrier WiFi, active network + signal strength >= WIFI_EXIT_RSSI_THRESHOLD_DEFAULT
- * <li>Macro cellular
- * <li>Any others
- * </ol>
- */
- static int calculatePriorityClass(
+ /** Gives networks a priority class, based on configured VcnGatewayConnectionConfig */
+ public static int calculatePriorityClass(
+ VcnContext vcnContext,
UnderlyingNetworkRecord networkRecord,
+ LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities,
ParcelUuid subscriptionGroup,
TelephonySubscriptionSnapshot snapshot,
UnderlyingNetworkRecord currentlySelected,
PersistableBundle carrierConfig) {
- final NetworkCapabilities caps = networkRecord.networkCapabilities;
-
// mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED
if (networkRecord.isBlocked) {
@@ -109,8 +88,167 @@
return PRIORITY_ANY;
}
- if (caps.hasTransport(TRANSPORT_CELLULAR)
- && isOpportunistic(snapshot, caps.getSubscriptionIds())) {
+ if (snapshot == null) {
+ logWtf("Got null snapshot");
+ return PRIORITY_ANY;
+ }
+
+ int priorityIndex = 0;
+ for (VcnUnderlyingNetworkPriority nwPriority : underlyingNetworkPriorities) {
+ if (checkMatchesPriorityRule(
+ vcnContext,
+ nwPriority,
+ networkRecord,
+ subscriptionGroup,
+ snapshot,
+ currentlySelected,
+ carrierConfig)) {
+ return priorityIndex;
+ }
+ priorityIndex++;
+ }
+ return PRIORITY_ANY;
+ }
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static boolean checkMatchesPriorityRule(
+ VcnContext vcnContext,
+ VcnUnderlyingNetworkPriority networkPriority,
+ UnderlyingNetworkRecord networkRecord,
+ ParcelUuid subscriptionGroup,
+ TelephonySubscriptionSnapshot snapshot,
+ UnderlyingNetworkRecord currentlySelected,
+ PersistableBundle carrierConfig) {
+ // TODO: Check Network Quality reported by metric monitors/probers.
+
+ final NetworkCapabilities caps = networkRecord.networkCapabilities;
+ if (!networkPriority.allowMetered() && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)) {
+ return false;
+ }
+
+ if (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST)) {
+ return true;
+ }
+
+ if (networkPriority instanceof VcnWifiUnderlyingNetworkPriority) {
+ return checkMatchesWifiPriorityRule(
+ (VcnWifiUnderlyingNetworkPriority) networkPriority,
+ networkRecord,
+ currentlySelected,
+ carrierConfig);
+ }
+
+ if (networkPriority instanceof VcnCellUnderlyingNetworkPriority) {
+ return checkMatchesCellPriorityRule(
+ vcnContext,
+ (VcnCellUnderlyingNetworkPriority) networkPriority,
+ networkRecord,
+ subscriptionGroup,
+ snapshot);
+ }
+
+ logWtf(
+ "Got unknown VcnUnderlyingNetworkPriority class: "
+ + networkPriority.getClass().getSimpleName());
+ return false;
+ }
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static boolean checkMatchesWifiPriorityRule(
+ VcnWifiUnderlyingNetworkPriority networkPriority,
+ UnderlyingNetworkRecord networkRecord,
+ UnderlyingNetworkRecord currentlySelected,
+ PersistableBundle carrierConfig) {
+ final NetworkCapabilities caps = networkRecord.networkCapabilities;
+
+ if (!caps.hasTransport(TRANSPORT_WIFI)) {
+ return false;
+ }
+
+ // TODO: Move the Network Quality check to the network metric monitor framework.
+ if (networkPriority.getNetworkQuality()
+ > getWifiQuality(networkRecord, currentlySelected, carrierConfig)) {
+ return false;
+ }
+
+ if (networkPriority.getSsid() != null && networkPriority.getSsid() != caps.getSsid()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static int getWifiQuality(
+ UnderlyingNetworkRecord networkRecord,
+ UnderlyingNetworkRecord currentlySelected,
+ PersistableBundle carrierConfig) {
+ final NetworkCapabilities caps = networkRecord.networkCapabilities;
+ final boolean isSelectedNetwork =
+ currentlySelected != null
+ && networkRecord.network.equals(currentlySelected.network);
+
+ if (isSelectedNetwork
+ && caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)) {
+ return NETWORK_QUALITY_OK;
+ }
+
+ if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) {
+ return NETWORK_QUALITY_OK;
+ }
+
+ return NETWORK_QUALITY_ANY;
+ }
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static boolean checkMatchesCellPriorityRule(
+ VcnContext vcnContext,
+ VcnCellUnderlyingNetworkPriority networkPriority,
+ UnderlyingNetworkRecord networkRecord,
+ ParcelUuid subscriptionGroup,
+ TelephonySubscriptionSnapshot snapshot) {
+ final NetworkCapabilities caps = networkRecord.networkCapabilities;
+
+ if (!caps.hasTransport(TRANSPORT_CELLULAR)) {
+ return false;
+ }
+
+ final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+ ((TelephonyNetworkSpecifier) caps.getNetworkSpecifier());
+ if (telephonyNetworkSpecifier == null) {
+ logWtf("Got null NetworkSpecifier");
+ return false;
+ }
+
+ final int subId = telephonyNetworkSpecifier.getSubscriptionId();
+ final TelephonyManager subIdSpecificTelephonyMgr =
+ vcnContext
+ .getContext()
+ .getSystemService(TelephonyManager.class)
+ .createForSubscriptionId(subId);
+
+ if (!networkPriority.getAllowedPlmnIds().isEmpty()) {
+ final String plmnId = subIdSpecificTelephonyMgr.getNetworkOperator();
+ if (!networkPriority.getAllowedPlmnIds().contains(plmnId)) {
+ return false;
+ }
+ }
+
+ if (!networkPriority.getAllowedSpecificCarrierIds().isEmpty()) {
+ final int carrierId = subIdSpecificTelephonyMgr.getSimSpecificCarrierId();
+ if (!networkPriority.getAllowedSpecificCarrierIds().contains(carrierId)) {
+ return false;
+ }
+ }
+
+ if (!networkPriority.allowRoaming() && !caps.hasCapability(NET_CAPABILITY_NOT_ROAMING)) {
+ return false;
+ }
+
+ if (networkPriority.requireOpportunistic()) {
+ if (!isOpportunistic(snapshot, caps.getSubscriptionIds())) {
+ return false;
+ }
+
// If this carrier is the active data provider, ensure that opportunistic is only
// ever prioritized if it is also the active data subscription. This ensures that
// if an opportunistic subscription is still in the process of being switched to,
@@ -121,36 +259,15 @@
// Allow the following two cases:
// 1. Active subId is NOT in the group that this VCN is supporting
// 2. This opportunistic subscription is for the active subId
- if (!snapshot.getAllSubIdsInGroup(subscriptionGroup)
+ if (snapshot.getAllSubIdsInGroup(subscriptionGroup)
.contains(SubscriptionManager.getActiveDataSubscriptionId())
- || caps.getSubscriptionIds()
+ && !caps.getSubscriptionIds()
.contains(SubscriptionManager.getActiveDataSubscriptionId())) {
- return PRIORITY_OPPORTUNISTIC_CELLULAR;
+ return false;
}
}
- if (caps.hasTransport(TRANSPORT_WIFI)) {
- if (caps.getSignalStrength() >= getWifiExitRssiThreshold(carrierConfig)
- && currentlySelected != null
- && networkRecord.network.equals(currentlySelected.network)) {
- return PRIORITY_WIFI_IN_USE;
- }
-
- if (caps.getSignalStrength() >= getWifiEntryRssiThreshold(carrierConfig)) {
- return PRIORITY_WIFI_PROSPECTIVE;
- }
- }
-
- // Disallow opportunistic subscriptions from matching PRIORITY_MACRO_CELLULAR, as might
- // be the case when Default Data SubId (CBRS) != Active Data SubId (MACRO), as might be
- // the case if the Default Data SubId does not support certain services (eg voice
- // calling)
- if (caps.hasTransport(TRANSPORT_CELLULAR)
- && !isOpportunistic(snapshot, caps.getSubscriptionIds())) {
- return PRIORITY_MACRO_CELLULAR;
- }
-
- return PRIORITY_ANY;
+ return true;
}
static boolean isOpportunistic(
@@ -185,10 +302,6 @@
return WIFI_EXIT_RSSI_THRESHOLD_DEFAULT;
}
- static String priorityClassToString(int priorityClass) {
- return PRIORITY_TO_STRING_MAP.get(priorityClass, "unknown");
- }
-
private static void logWtf(String msg) {
Slog.wtf(TAG, msg);
LOCAL_LOG.log(TAG + " WTF: " + msg);
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index 071c7a6..cd124ee 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -32,6 +32,8 @@
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnUnderlyingNetworkPriority;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.ParcelUuid;
@@ -68,6 +70,7 @@
@NonNull private static final String TAG = UnderlyingNetworkController.class.getSimpleName();
@NonNull private final VcnContext mVcnContext;
+ @NonNull private final VcnGatewayConnectionConfig mConnectionConfig;
@NonNull private final ParcelUuid mSubscriptionGroup;
@NonNull private final UnderlyingNetworkControllerCallback mCb;
@NonNull private final Dependencies mDeps;
@@ -91,24 +94,22 @@
public UnderlyingNetworkController(
@NonNull VcnContext vcnContext,
+ @NonNull VcnGatewayConnectionConfig connectionConfig,
@NonNull ParcelUuid subscriptionGroup,
@NonNull TelephonySubscriptionSnapshot snapshot,
@NonNull UnderlyingNetworkControllerCallback cb) {
- this(
- vcnContext,
- subscriptionGroup,
- snapshot,
- cb,
- new Dependencies());
+ this(vcnContext, connectionConfig, subscriptionGroup, snapshot, cb, new Dependencies());
}
private UnderlyingNetworkController(
@NonNull VcnContext vcnContext,
+ @NonNull VcnGatewayConnectionConfig connectionConfig,
@NonNull ParcelUuid subscriptionGroup,
@NonNull TelephonySubscriptionSnapshot snapshot,
@NonNull UnderlyingNetworkControllerCallback cb,
@NonNull Dependencies deps) {
mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext");
+ mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig");
mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup");
mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot");
mCb = Objects.requireNonNull(cb, "Missing cb");
@@ -399,6 +400,8 @@
TreeSet<UnderlyingNetworkRecord> sorted =
new TreeSet<>(
UnderlyingNetworkRecord.getComparator(
+ mVcnContext,
+ mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
mSubscriptionGroup,
mLastSnapshot,
mCurrentRecord,
@@ -495,12 +498,31 @@
pw.println(
"Currently selected: " + (mCurrentRecord == null ? null : mCurrentRecord.network));
+ pw.println("VcnUnderlyingNetworkPriority list:");
+ pw.increaseIndent();
+ int index = 0;
+ for (VcnUnderlyingNetworkPriority priority :
+ mConnectionConfig.getVcnUnderlyingNetworkPriorities()) {
+ pw.println("Priority index: " + index);
+ priority.dump(pw);
+ index++;
+ }
+ pw.decreaseIndent();
+ pw.println();
+
pw.println("Underlying networks:");
pw.increaseIndent();
if (mRouteSelectionCallback != null) {
for (UnderlyingNetworkRecord record :
mRouteSelectionCallback.getSortedUnderlyingNetworks()) {
- record.dump(pw, mSubscriptionGroup, mLastSnapshot, mCurrentRecord, mCarrierConfig);
+ record.dump(
+ mVcnContext,
+ pw,
+ mConnectionConfig.getVcnUnderlyingNetworkPriorities(),
+ mSubscriptionGroup,
+ mLastSnapshot,
+ mCurrentRecord,
+ mCarrierConfig);
}
}
pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
index 65c69de..27ba854 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkRecord.java
@@ -21,6 +21,7 @@
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.vcn.VcnUnderlyingNetworkPriority;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
@@ -28,8 +29,10 @@
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
import java.util.Comparator;
+import java.util.LinkedHashSet;
import java.util.Objects;
/**
@@ -73,22 +76,64 @@
}
static Comparator<UnderlyingNetworkRecord> getComparator(
+ VcnContext vcnContext,
+ LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities,
ParcelUuid subscriptionGroup,
TelephonySubscriptionSnapshot snapshot,
UnderlyingNetworkRecord currentlySelected,
PersistableBundle carrierConfig) {
return (left, right) -> {
- return Integer.compare(
+ final int leftIndex =
NetworkPriorityClassifier.calculatePriorityClass(
- left, subscriptionGroup, snapshot, currentlySelected, carrierConfig),
+ vcnContext,
+ left,
+ underlyingNetworkPriorities,
+ subscriptionGroup,
+ snapshot,
+ currentlySelected,
+ carrierConfig);
+ final int rightIndex =
NetworkPriorityClassifier.calculatePriorityClass(
- right, subscriptionGroup, snapshot, currentlySelected, carrierConfig));
+ vcnContext,
+ right,
+ underlyingNetworkPriorities,
+ subscriptionGroup,
+ snapshot,
+ currentlySelected,
+ carrierConfig);
+
+ // In the case of networks in the same priority class, prioritize based on other
+ // criteria (eg. actively selected network, link metrics, etc)
+ if (leftIndex == rightIndex) {
+ // TODO: Improve the strategy of network selection when both UnderlyingNetworkRecord
+ // fall into the same priority class.
+ if (isSelected(left, currentlySelected)) {
+ return -1;
+ }
+ if (isSelected(left, currentlySelected)) {
+ return 1;
+ }
+ }
+ return Integer.compare(leftIndex, rightIndex);
};
}
+ private static boolean isSelected(
+ UnderlyingNetworkRecord recordToCheck, UnderlyingNetworkRecord currentlySelected) {
+ if (currentlySelected == null) {
+ return false;
+ }
+ if (currentlySelected.network == recordToCheck.network) {
+ return true;
+ }
+ return false;
+ }
+
/** Dumps the state of this record for logging and debugging purposes. */
void dump(
+ VcnContext vcnContext,
IndentingPrintWriter pw,
+ LinkedHashSet<VcnUnderlyingNetworkPriority> underlyingNetworkPriorities,
ParcelUuid subscriptionGroup,
TelephonySubscriptionSnapshot snapshot,
UnderlyingNetworkRecord currentlySelected,
@@ -96,15 +141,17 @@
pw.println("UnderlyingNetworkRecord:");
pw.increaseIndent();
- final int priorityClass =
+ final int priorityIndex =
NetworkPriorityClassifier.calculatePriorityClass(
- this, subscriptionGroup, snapshot, currentlySelected, carrierConfig);
- pw.println(
- "Priority class: "
- + NetworkPriorityClassifier.priorityClassToString(priorityClass)
- + " ("
- + priorityClass
- + ")");
+ vcnContext,
+ this,
+ underlyingNetworkPriorities,
+ subscriptionGroup,
+ snapshot,
+ currentlySelected,
+ carrierConfig);
+
+ pw.println("Priority index:" + priorityIndex);
pw.println("mNetwork: " + network);
pw.println("mNetworkCapabilities: " + networkCapabilities);
pw.println("mLinkProperties: " + linkProperties);
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 03d6590..d5abe4f 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -1426,21 +1426,21 @@
}
@TransitionType int getKeyguardTransition() {
- // In case we unocclude Keyguard and occlude it again, meaning that we never actually
- // unoccclude/occlude Keyguard, but just run a normal transition.
- final int occludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_UNOCCLUDE);
- if (occludeIndex != -1
- && occludeIndex < mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_OCCLUDE)) {
+ if (mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_GOING_AWAY) != -1) {
+ return TRANSIT_KEYGUARD_GOING_AWAY;
+ }
+ final int unoccludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_UNOCCLUDE);
+ final int occludeIndex = mNextAppTransitionRequests.indexOf(TRANSIT_KEYGUARD_OCCLUDE);
+ // No keyguard related transition requests.
+ if (unoccludeIndex == -1 && occludeIndex == -1) {
return TRANSIT_NONE;
}
-
- for (int i = 0; i < mNextAppTransitionRequests.size(); ++i) {
- final @TransitionType int transit = mNextAppTransitionRequests.get(i);
- if (isKeyguardTransit(transit)) {
- return transit;
- }
+ // In case we unocclude Keyguard and occlude it again, meaning that we never actually
+ // unoccclude/occlude Keyguard, but just run a normal transition.
+ if (unoccludeIndex != -1 && unoccludeIndex < occludeIndex) {
+ return TRANSIT_NONE;
}
- return TRANSIT_NONE;
+ return unoccludeIndex != -1 ? TRANSIT_KEYGUARD_UNOCCLUDE : TRANSIT_KEYGUARD_OCCLUDE;
}
@TransitionType int getFirstAppTransition() {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 71ab5b6..4768b27 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -45,7 +45,6 @@
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
@@ -846,20 +845,6 @@
}
/**
- * Only trusted overlays are allowed to use FLAG_SLIPPERY.
- */
- static int sanitizeFlagSlippery(int flags, int privateFlags, String name) {
- if ((flags & FLAG_SLIPPERY) == 0) {
- return flags;
- }
- if ((privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0) {
- return flags;
- }
- Slog.w(TAG, "Removing FLAG_SLIPPERY for non-trusted overlay " + name);
- return flags & ~FLAG_SLIPPERY;
- }
-
- /**
* Sanitize the layout parameters coming from a client. Allows the policy
* to do things like ensure that windows of a specific type can't take
* input focus.
@@ -942,8 +927,6 @@
} else if (mRoundedCornerWindow == win) {
mRoundedCornerWindow = null;
}
-
- attrs.flags = sanitizeFlagSlippery(attrs.flags, attrs.privateFlags, win.getName());
}
/**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index f0b55cb..038699b 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1440,11 +1440,11 @@
}
/** Called when an {@link ActivityRecord} is added as a descendant */
- void onDescendantActivityAdded(boolean hadChild, int activityType, ActivityRecord r) {
+ void onDescendantActivityAdded(boolean hadActivity, int activityType, ActivityRecord r) {
warnForNonLeafTask("onDescendantActivityAdded");
// Only set this based on the first activity
- if (!hadChild) {
+ if (!hadActivity) {
if (r.getActivityType() == ACTIVITY_TYPE_UNDEFINED) {
// Normally non-standard activity type for the activity record will be set when the
// object is created, however we delay setting the standard application type until
@@ -4534,14 +4534,15 @@
}
super.setWindowingMode(windowingMode);
- // Try reparent pinned activity back to its original task after onConfigurationChanged
- // cascade finishes. This is done on Task level instead of
- // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit PiP,
- // we set final windowing mode on the ActivityRecord first and then on its Task when
- // the exit PiP transition finishes. Meanwhile, the exit transition is always
- // performed on its original task, reparent immediately in ActivityRecord breaks it.
- if (currentMode == WINDOWING_MODE_PINNED) {
- if (topActivity != null && topActivity.getLastParentBeforePip() != null) {
+ if (currentMode == WINDOWING_MODE_PINNED && topActivity != null) {
+ // Try reparent pinned activity back to its original task after
+ // onConfigurationChanged cascade finishes. This is done on Task level instead of
+ // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit
+ // PiP, we set final windowing mode on the ActivityRecord first and then on its
+ // Task when the exit PiP transition finishes. Meanwhile, the exit transition is
+ // always performed on its original task, reparent immediately in ActivityRecord
+ // breaks it.
+ if (topActivity.getLastParentBeforePip() != null) {
// Do not reparent if the pinned task is in removal, indicated by the
// force hidden flag.
if (!isForceHidden()) {
@@ -4554,6 +4555,11 @@
}
}
}
+ // Resume app-switches-allowed flag when exiting from pinned mode since
+ // it does not follow the ActivityStarter path.
+ if (topActivity.shouldBeVisible()) {
+ mAtmService.resumeAppSwitches();
+ }
}
if (creating) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 59a5cdf..fdaa2fc 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -101,6 +101,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -1666,8 +1667,8 @@
boolean isAddingActivity = child.asActivityRecord() != null;
final Task task = isAddingActivity ? getTask() : null;
- // If this task had any child before we added this one.
- boolean taskHadChild = task != null && task.hasChild();
+ // If this task had any activity before we added this one.
+ boolean taskHadActivity = task != null && task.getActivity(Objects::nonNull) != null;
// getActivityType() looks at the top child, so we need to read the type before adding
// a new child in case the new child is on top and UNDEFINED.
final int activityType = task != null ? task.getActivityType() : ACTIVITY_TYPE_UNDEFINED;
@@ -1676,7 +1677,7 @@
if (isAddingActivity && task != null) {
child.asActivityRecord().inHistory = true;
- task.onDescendantActivityAdded(taskHadChild, activityType, child.asActivityRecord());
+ task.onDescendantActivityAdded(taskHadActivity, activityType, child.asActivityRecord());
}
}
@@ -2186,14 +2187,13 @@
TaskFragmentInfo getTaskFragmentInfo() {
List<IBinder> childActivities = new ArrayList<>();
for (int i = 0; i < getChildCount(); i++) {
- WindowContainer wc = getChildAt(i);
- if (mTaskFragmentOrganizerUid != INVALID_UID
- && wc.asActivityRecord() != null
- && wc.asActivityRecord().info.processName.equals(
- mTaskFragmentOrganizerProcessName)
- && wc.asActivityRecord().getUid() == mTaskFragmentOrganizerUid) {
+ final WindowContainer wc = getChildAt(i);
+ final ActivityRecord ar = wc.asActivityRecord();
+ if (mTaskFragmentOrganizerUid != INVALID_UID && ar != null
+ && ar.info.processName.equals(mTaskFragmentOrganizerProcessName)
+ && ar.getUid() == mTaskFragmentOrganizerUid && !ar.finishing) {
// Only includes Activities that belong to the organizer process for security.
- childActivities.add(wc.asActivityRecord().token);
+ childActivities.add(ar.token);
}
}
final Point positionInParent = new Point();
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 29c27f9..c7fdefc 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -24,6 +24,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.content.res.Configuration;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -579,4 +580,26 @@
event.mException);
}
}
+
+ // TODO(b/204399167): change to push the embedded state to the client side
+ @Override
+ public boolean isActivityEmbedded(IBinder activityToken) {
+ synchronized (mGlobalLock) {
+ final ActivityRecord activity = ActivityRecord.forTokenLocked(activityToken);
+ if (activity == null) {
+ return false;
+ }
+ final TaskFragment taskFragment = activity.getOrganizedTaskFragment();
+ if (taskFragment == null) {
+ return false;
+ }
+ final Task parentTask = taskFragment.getTask();
+ if (parentTask != null) {
+ final Rect taskBounds = parentTask.getBounds();
+ final Rect taskFragBounds = taskFragment.getBounds();
+ return !taskBounds.equals(taskFragBounds) && taskBounds.contains(taskFragBounds);
+ }
+ return false;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e986fd2..58860de 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1652,6 +1652,7 @@
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
+ attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
win.setRequestedVisibilities(requestedVisibilities);
res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
@@ -2207,6 +2208,7 @@
if (attrs != null) {
displayPolicy.adjustWindowParamsLw(win, attrs);
win.mToken.adjustWindowParams(win, attrs);
+ attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), uid, pid);
int disableFlags =
(attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility) & DISABLE_MASK;
if (disableFlags != 0 && !hasStatusBarPermission(pid, uid)) {
@@ -8220,6 +8222,23 @@
}
/**
+ * You need ALLOW_SLIPPERY_TOUCHES permission to be able to set FLAG_SLIPPERY.
+ */
+ private int sanitizeFlagSlippery(int flags, String windowName, int callingUid, int callingPid) {
+ if ((flags & FLAG_SLIPPERY) == 0) {
+ return flags;
+ }
+ final int permissionResult = mContext.checkPermission(
+ android.Manifest.permission.ALLOW_SLIPPERY_TOUCHES, callingPid, callingUid);
+ if (permissionResult != PackageManager.PERMISSION_GRANTED) {
+ Slog.w(TAG, "Removing FLAG_SLIPPERY from '" + windowName
+ + "' because it doesn't have ALLOW_SLIPPERY_TOUCHES permission");
+ return flags & ~FLAG_SLIPPERY;
+ }
+ return flags;
+ }
+
+ /**
* Assigns an InputChannel to a SurfaceControl and configures it to receive
* touch input according to it's on-screen geometry.
*
@@ -8258,7 +8277,7 @@
h.setWindowToken(window);
h.name = name;
- flags = DisplayPolicy.sanitizeFlagSlippery(flags, privateFlags, name);
+ flags = sanitizeFlagSlippery(flags, name, callingUid, callingPid);
final int sanitizedFlags = flags & (LayoutParams.FLAG_NOT_TOUCHABLE
| FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE);
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 4504853..06f5aed 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -37,10 +37,7 @@
#include <android/hardware/gnss/measurement_corrections/1.0/IMeasurementCorrections.h>
#include <android/hardware/gnss/measurement_corrections/1.1/IMeasurementCorrections.h>
#include <android/hardware/gnss/visibility_control/1.0/IGnssVisibilityControl.h>
-#include <arpa/inet.h>
#include <binder/IServiceManager.h>
-#include <linux/in.h>
-#include <linux/in6.h>
#include <nativehelper/JNIHelp.h>
#include <pthread.h>
#include <string.h>
@@ -52,6 +49,7 @@
#include "android_runtime/AndroidRuntime.h"
#include "android_runtime/Log.h"
+#include "gnss/AGnss.h"
#include "gnss/GnssAntennaInfoCallback.h"
#include "gnss/GnssBatching.h"
#include "gnss/GnssConfiguration.h"
@@ -69,7 +67,6 @@
static jmethodID method_reportLocation;
static jmethodID method_reportStatus;
static jmethodID method_reportSvStatus;
-static jmethodID method_reportAGpsStatus;
static jmethodID method_reportNmea;
static jmethodID method_setTopHalCapabilities;
static jmethodID method_setGnssYearOfHardware;
@@ -163,10 +160,6 @@
using IGnssAntennaInfo = android::hardware::gnss::V2_1::IGnssAntennaInfo;
using IAGnssRil_V1_0 = android::hardware::gnss::V1_0::IAGnssRil;
using IAGnssRil_V2_0 = android::hardware::gnss::V2_0::IAGnssRil;
-using IAGnss_V1_0 = android::hardware::gnss::V1_0::IAGnss;
-using IAGnss_V2_0 = android::hardware::gnss::V2_0::IAGnss;
-using IAGnssCallback_V1_0 = android::hardware::gnss::V1_0::IAGnssCallback;
-using IAGnssCallback_V2_0 = android::hardware::gnss::V2_0::IAGnssCallback;
using IMeasurementCorrections_V1_0 = android::hardware::gnss::measurement_corrections::V1_0::IMeasurementCorrections;
using IMeasurementCorrections_V1_1 = android::hardware::gnss::measurement_corrections::V1_1::IMeasurementCorrections;
@@ -182,6 +175,7 @@
using android::hardware::gnss::IGnssPowerIndication;
using android::hardware::gnss::IGnssPowerIndicationCallback;
using android::hardware::gnss::PsdsType;
+using IAGnssAidl = android::hardware::gnss::IAGnss;
using IGnssAidl = android::hardware::gnss::IGnss;
using IGnssCallbackAidl = android::hardware::gnss::IGnssCallback;
using IGnssBatchingAidl = android::hardware::gnss::IGnssBatching;
@@ -215,8 +209,6 @@
sp<IGnssXtra> gnssXtraIface = nullptr;
sp<IAGnssRil_V1_0> agnssRilIface = nullptr;
sp<IAGnssRil_V2_0> agnssRilIface_V2_0 = nullptr;
-sp<IAGnss_V1_0> agnssIface = nullptr;
-sp<IAGnss_V2_0> agnssIface_V2_0 = nullptr;
sp<IGnssDebug_V1_0> gnssDebugIface = nullptr;
sp<IGnssDebug_V2_0> gnssDebugIface_V2_0 = nullptr;
sp<IGnssNi> gnssNiIface = nullptr;
@@ -231,6 +223,7 @@
std::unique_ptr<android::gnss::GnssNavigationMessageInterface> gnssNavigationMessageIface = nullptr;
std::unique_ptr<android::gnss::GnssBatchingInterface> gnssBatchingIface = nullptr;
std::unique_ptr<android::gnss::GnssGeofenceInterface> gnssGeofencingIface = nullptr;
+std::unique_ptr<android::gnss::AGnssInterface> agnssIface = nullptr;
#define WAKE_LOCK_NAME "GPS"
@@ -255,34 +248,6 @@
return value ? JNI_TRUE : JNI_FALSE;
}
-struct ScopedJniString {
- ScopedJniString(JNIEnv* env, jstring javaString) : mEnv(env), mJavaString(javaString) {
- mNativeString = mEnv->GetStringUTFChars(mJavaString, nullptr);
- }
-
- ~ScopedJniString() {
- if (mNativeString != nullptr) {
- mEnv->ReleaseStringUTFChars(mJavaString, mNativeString);
- }
- }
-
- const char* c_str() const {
- return mNativeString;
- }
-
- operator hidl_string() const {
- return hidl_string(mNativeString);
- }
-
-private:
- ScopedJniString(const ScopedJniString&) = delete;
- ScopedJniString& operator=(const ScopedJniString&) = delete;
-
- JNIEnv* mEnv;
- jstring mJavaString;
- const char* mNativeString;
-};
-
static GnssLocation_V1_0 createGnssLocation_V1_0(
jint gnssLocationFlags, jdouble latitudeDegrees, jdouble longitudeDegrees,
jdouble altitudeMeters, jfloat speedMetersPerSec, jfloat bearingDegrees,
@@ -806,122 +771,6 @@
}
/*
- * AGnssCallback_V1_0 implements callback methods required by the IAGnssCallback 1.0 interface.
- */
-struct AGnssCallback_V1_0 : public IAGnssCallback_V1_0 {
- // Methods from ::android::hardware::gps::V1_0::IAGnssCallback follow.
- Return<void> agnssStatusIpV6Cb(
- const IAGnssCallback_V1_0::AGnssStatusIpV6& agps_status) override;
-
- Return<void> agnssStatusIpV4Cb(
- const IAGnssCallback_V1_0::AGnssStatusIpV4& agps_status) override;
- private:
- jbyteArray convertToIpV4(uint32_t ip);
-};
-
-Return<void> AGnssCallback_V1_0::agnssStatusIpV6Cb(
- const IAGnssCallback_V1_0::AGnssStatusIpV6& agps_status) {
- JNIEnv* env = getJniEnv();
- jbyteArray byteArray = nullptr;
-
- byteArray = env->NewByteArray(16);
- if (byteArray != nullptr) {
- env->SetByteArrayRegion(byteArray, 0, 16,
- (const jbyte*)(agps_status.ipV6Addr.data()));
- } else {
- ALOGE("Unable to allocate byte array for IPv6 address.");
- }
-
- IF_ALOGD() {
- // log the IP for reference in case there is a bogus value pushed by HAL
- char str[INET6_ADDRSTRLEN];
- inet_ntop(AF_INET6, agps_status.ipV6Addr.data(), str, INET6_ADDRSTRLEN);
- ALOGD("AGPS IP is v6: %s", str);
- }
-
- jsize byteArrayLength = byteArray != nullptr ? env->GetArrayLength(byteArray) : 0;
- ALOGV("Passing AGPS IP addr: size %d", byteArrayLength);
- env->CallVoidMethod(mCallbacksObj, method_reportAGpsStatus,
- agps_status.type, agps_status.status, byteArray);
-
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
-
- if (byteArray) {
- env->DeleteLocalRef(byteArray);
- }
-
- return Void();
-}
-
-Return<void> AGnssCallback_V1_0::agnssStatusIpV4Cb(
- const IAGnssCallback_V1_0::AGnssStatusIpV4& agps_status) {
- JNIEnv* env = getJniEnv();
- jbyteArray byteArray = nullptr;
-
- uint32_t ipAddr = agps_status.ipV4Addr;
- byteArray = convertToIpV4(ipAddr);
-
- IF_ALOGD() {
- /*
- * log the IP for reference in case there is a bogus value pushed by
- * HAL.
- */
- char str[INET_ADDRSTRLEN];
- inet_ntop(AF_INET, &ipAddr, str, INET_ADDRSTRLEN);
- ALOGD("AGPS IP is v4: %s", str);
- }
-
- jsize byteArrayLength =
- byteArray != nullptr ? env->GetArrayLength(byteArray) : 0;
- ALOGV("Passing AGPS IP addr: size %d", byteArrayLength);
- env->CallVoidMethod(mCallbacksObj, method_reportAGpsStatus,
- agps_status.type, agps_status.status, byteArray);
-
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
-
- if (byteArray) {
- env->DeleteLocalRef(byteArray);
- }
- return Void();
-}
-
-jbyteArray AGnssCallback_V1_0::convertToIpV4(uint32_t ip) {
- if (INADDR_NONE == ip) {
- return nullptr;
- }
-
- JNIEnv* env = getJniEnv();
- jbyteArray byteArray = env->NewByteArray(4);
- if (byteArray == nullptr) {
- ALOGE("Unable to allocate byte array for IPv4 address");
- return nullptr;
- }
-
- jbyte ipv4[4];
- ALOGV("Converting IPv4 address byte array (net_order) %x", ip);
- memcpy(ipv4, &ip, sizeof(ipv4));
- env->SetByteArrayRegion(byteArray, 0, 4, (const jbyte*)ipv4);
- return byteArray;
-}
-
-/*
- * AGnssCallback_V2_0 implements callback methods required by the IAGnssCallback 2.0 interface.
- */
-struct AGnssCallback_V2_0 : public IAGnssCallback_V2_0 {
- // Methods from ::android::hardware::gps::V2_0::IAGnssCallback follow.
- Return<void> agnssStatusCb(IAGnssCallback_V2_0::AGnssType type,
- IAGnssCallback_V2_0::AGnssStatusValue status) override;
-};
-
-Return<void> AGnssCallback_V2_0::agnssStatusCb(IAGnssCallback_V2_0::AGnssType type,
- IAGnssCallback_V2_0::AGnssStatusValue status) {
- JNIEnv* env = getJniEnv();
- env->CallVoidMethod(mCallbacksObj, method_reportAGpsStatus, type, status, nullptr);
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
- return Void();
-}
-
-/*
* AGnssRilCallback implements the callback methods required by the AGnssRil
* interface.
*/
@@ -990,7 +839,6 @@
"(ZLandroid/location/Location;)V");
method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V");
method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "(I[I[F[F[F[F[F)V");
- method_reportAGpsStatus = env->GetMethodID(clazz, "reportAGpsStatus", "(II[B)V");
method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");
method_setTopHalCapabilities = env->GetMethodID(clazz, "setTopHalCapabilities", "(I)V");
method_setGnssYearOfHardware = env->GetMethodID(clazz, "setGnssYearOfHardware", "(I)V");
@@ -1075,8 +923,10 @@
gnss::GnssAntennaInfo_class_init_once(env, clazz);
gnss::GnssBatching_class_init_once(env, clazz);
gnss::GnssConfiguration_class_init_once(env);
+ gnss::GnssGeofence_class_init_once(env, clazz);
gnss::GnssMeasurement_class_init_once(env, clazz);
gnss::GnssNavigationMessage_class_init_once(env, clazz);
+ gnss::AGnss_class_init_once(env, clazz);
gnss::Utils_class_init_once(env);
}
@@ -1147,19 +997,21 @@
}
}
- if (gnssHal_V2_0 != nullptr) {
+ if (gnssHalAidl != nullptr) {
+ sp<IAGnssAidl> agnssAidl;
+ auto status = gnssHalAidl->getExtensionAGnss(&agnssAidl);
+ if (checkAidlStatus(status, "Unable to get a handle to AGnss interface.")) {
+ agnssIface = std::make_unique<gnss::AGnss>(agnssAidl);
+ }
+ } else if (gnssHal_V2_0 != nullptr) {
auto agnss_V2_0 = gnssHal_V2_0->getExtensionAGnss_2_0();
- if (!agnss_V2_0.isOk()) {
- ALOGD("Unable to get a handle to AGnss_V2_0");
- } else {
- agnssIface_V2_0 = agnss_V2_0;
+ if (checkHidlReturn(agnss_V2_0, "Unable to get a handle to AGnss_V2_0")) {
+ agnssIface = std::make_unique<gnss::AGnss_V2_0>(agnss_V2_0);
}
} else if (gnssHal != nullptr) {
auto agnss_V1_0 = gnssHal->getExtensionAGnss();
- if (!agnss_V1_0.isOk()) {
- ALOGD("Unable to get a handle to AGnss");
- } else {
- agnssIface = agnss_V1_0;
+ if (checkHidlReturn(agnss_V1_0, "Unable to get a handle to AGnss_V1_0")) {
+ agnssIface = std::make_unique<gnss::AGnss_V1_0>(agnss_V1_0);
}
}
@@ -1452,16 +1304,9 @@
}
}
- // Set IAGnss.hal callback.
- if (agnssIface_V2_0 != nullptr) {
- sp<IAGnssCallback_V2_0> aGnssCbIface = new AGnssCallback_V2_0();
- auto agnssStatus = agnssIface_V2_0->setCallback(aGnssCbIface);
- checkHidlReturn(agnssStatus, "IAGnss 2.0 setCallback() failed.");
- } else if (agnssIface != nullptr) {
- sp<IAGnssCallback_V1_0> aGnssCbIface = new AGnssCallback_V1_0();
- auto agnssStatus = agnssIface->setCallback(aGnssCbIface);
- checkHidlReturn(agnssStatus, "IAGnss setCallback() failed.");
- } else {
+ // Set IAGnss callback.
+ if (agnssIface == nullptr ||
+ !agnssIface->setCallback(std::make_unique<gnss::AGnssCallback>())) {
ALOGI("Unable to initialize IAGnss interface.");
}
@@ -1737,61 +1582,6 @@
env->ReleasePrimitiveArrayCritical(data, bytes, JNI_ABORT);
}
-struct AGnssDispatcher {
- static void dataConnOpen(sp<IAGnss_V1_0> agnssIface, JNIEnv* env, jstring apn, jint apnIpType);
- static void dataConnOpen(sp<IAGnss_V2_0> agnssIface_V2_0, JNIEnv* env, jlong networkHandle,
- jstring apn, jint apnIpType);
-
- template <class T>
- static void dataConnClosed(sp<T> agnssIface);
-
- template <class T>
- static void dataConnFailed(sp<T> agnssIface);
-
- template <class T, class U>
- static void setServer(sp<T> agnssIface, JNIEnv* env, jint type, jstring hostname, jint port);
-
-private:
- AGnssDispatcher() = delete;
-};
-
-void AGnssDispatcher::dataConnOpen(sp<IAGnss_V1_0> agnssIface, JNIEnv* env, jstring apn,
- jint apnIpType) {
- ScopedJniString jniApn{env, apn};
- auto result = agnssIface->dataConnOpen(jniApn,
- static_cast<IAGnss_V1_0::ApnIpType>(apnIpType));
- checkHidlReturn(result, "IAGnss dataConnOpen() failed. APN and its IP type not set.");
-}
-
-void AGnssDispatcher::dataConnOpen(sp<IAGnss_V2_0> agnssIface_V2_0, JNIEnv* env,
- jlong networkHandle, jstring apn, jint apnIpType) {
- ScopedJniString jniApn{env, apn};
- auto result = agnssIface_V2_0->dataConnOpen(static_cast<uint64_t>(networkHandle), jniApn,
- static_cast<IAGnss_V2_0::ApnIpType>(apnIpType));
- checkHidlReturn(result, "IAGnss 2.0 dataConnOpen() failed. APN and its IP type not set.");
-}
-
-template<class T>
-void AGnssDispatcher::dataConnClosed(sp<T> agnssIface) {
- auto result = agnssIface->dataConnClosed();
- checkHidlReturn(result, "IAGnss dataConnClosed() failed.");
-}
-
-template<class T>
-void AGnssDispatcher::dataConnFailed(sp<T> agnssIface) {
- auto result = agnssIface->dataConnFailed();
- checkHidlReturn(result, "IAGnss dataConnFailed() failed.");
-}
-
-template <class T, class U>
-void AGnssDispatcher::setServer(sp<T> agnssIface, JNIEnv* env, jint type, jstring hostname,
- jint port) {
- ScopedJniString jniHostName{env, hostname};
- auto result = agnssIface->setServer(static_cast<typename U::AGnssType>(type),
- jniHostName, port);
- checkHidlReturn(result, "IAGnss setServer() failed. Host name and port not set.");
-}
-
static void android_location_GnssNetworkConnectivityHandler_agps_data_conn_open(
JNIEnv* env, jobject /* obj */, jlong networkHandle, jstring apn, jint apnIpType) {
if (apn == nullptr) {
@@ -1799,10 +1589,8 @@
return;
}
- if (agnssIface_V2_0 != nullptr) {
- AGnssDispatcher::dataConnOpen(agnssIface_V2_0, env, networkHandle, apn, apnIpType);
- } else if (agnssIface != nullptr) {
- AGnssDispatcher::dataConnOpen(agnssIface, env, apn, apnIpType);
+ if (agnssIface != nullptr) {
+ agnssIface->dataConnOpen(env, networkHandle, apn, apnIpType);
} else {
ALOGE("%s: IAGnss interface not available.", __func__);
return;
@@ -1811,10 +1599,8 @@
static void android_location_GnssNetworkConnectivityHandler_agps_data_conn_closed(JNIEnv* /* env */,
jobject /* obj */) {
- if (agnssIface_V2_0 != nullptr) {
- AGnssDispatcher::dataConnClosed(agnssIface_V2_0);
- } else if (agnssIface != nullptr) {
- AGnssDispatcher::dataConnClosed(agnssIface);
+ if (agnssIface != nullptr) {
+ agnssIface->dataConnClosed();
} else {
ALOGE("%s: IAGnss interface not available.", __func__);
return;
@@ -1823,10 +1609,8 @@
static void android_location_GnssNetworkConnectivityHandler_agps_data_conn_failed(JNIEnv* /* env */,
jobject /* obj */) {
- if (agnssIface_V2_0 != nullptr) {
- AGnssDispatcher::dataConnFailed(agnssIface_V2_0);
- } else if (agnssIface != nullptr) {
- AGnssDispatcher::dataConnFailed(agnssIface);
+ if (agnssIface != nullptr) {
+ agnssIface->dataConnFailed();
} else {
ALOGE("%s: IAGnss interface not available.", __func__);
return;
@@ -1835,12 +1619,8 @@
static void android_location_gnss_hal_GnssNative_set_agps_server(JNIEnv* env, jclass, jint type,
jstring hostname, jint port) {
- if (agnssIface_V2_0 != nullptr) {
- AGnssDispatcher::setServer<IAGnss_V2_0, IAGnssCallback_V2_0>(agnssIface_V2_0, env, type,
- hostname, port);
- } else if (agnssIface != nullptr) {
- AGnssDispatcher::setServer<IAGnss_V1_0, IAGnssCallback_V1_0>(agnssIface, env, type,
- hostname, port);
+ if (agnssIface != nullptr) {
+ agnssIface->setServer(env, type, hostname, port);
} else {
ALOGE("%s: IAGnss interface not available.", __func__);
return;
diff --git a/services/core/jni/gnss/AGnss.cpp b/services/core/jni/gnss/AGnss.cpp
new file mode 100644
index 0000000..00403d6
--- /dev/null
+++ b/services/core/jni/gnss/AGnss.cpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+// Define LOG_TAG before <log/log.h> to overwrite the default value.
+#define LOG_TAG "AGnssJni"
+
+#include "AGnss.h"
+
+#include "Utils.h"
+
+using android::hardware::gnss::IAGnss;
+using IAGnss_V1_0 = android::hardware::gnss::V1_0::IAGnss;
+using IAGnss_V2_0 = android::hardware::gnss::V2_0::IAGnss;
+using IAGnssCallback_V1_0 = android::hardware::gnss::V1_0::IAGnssCallback;
+using IAGnssCallback_V2_0 = android::hardware::gnss::V2_0::IAGnssCallback;
+using AGnssType = android::hardware::gnss::IAGnssCallback::AGnssType;
+
+namespace android::gnss {
+
+// Implementation of AGnss (AIDL HAL)
+
+AGnss::AGnss(const sp<IAGnss>& iAGnss) : mIAGnss(iAGnss) {
+ assert(mIAGnss != nullptr);
+}
+
+jboolean AGnss::setCallback(const std::unique_ptr<AGnssCallback>& callback) {
+ auto status = mIAGnss->setCallback(callback->getAidl());
+ return checkAidlStatus(status, "IAGnssAidl setCallback() failed.");
+}
+
+jboolean AGnss::dataConnOpen(JNIEnv* env, jlong networkHandle, jstring apn, jint apnIpType) {
+ ScopedJniString jniApn{env, apn};
+ auto status = mIAGnss->dataConnOpen(networkHandle, String16(jniApn.c_str()),
+ static_cast<IAGnss::ApnIpType>(apnIpType));
+ return checkAidlStatus(status,
+ "IAGnssAidl dataConnOpen() failed. APN and its IP type not set.");
+}
+
+jboolean AGnss::dataConnClosed() {
+ auto status = mIAGnss->dataConnClosed();
+ return checkAidlStatus(status, "IAGnssAidl dataConnClosed() failed.");
+}
+
+jboolean AGnss::dataConnFailed() {
+ auto status = mIAGnss->dataConnFailed();
+ return checkAidlStatus(status, "IAGnssAidl dataConnFailed() failed.");
+}
+
+jboolean AGnss::setServer(JNIEnv* env, jint type, jstring hostname, jint port) {
+ ScopedJniString jniHostName{env, hostname};
+ auto status =
+ mIAGnss->setServer(static_cast<AGnssType>(type), String16(jniHostName.c_str()), port);
+ return checkAidlStatus(status, "IAGnssAidl setServer() failed. Host name and port not set.");
+}
+
+// Implementation of AGnss_V1_0
+
+AGnss_V1_0::AGnss_V1_0(const sp<IAGnss_V1_0>& iAGnss) : mIAGnss_V1_0(iAGnss) {
+ assert(mIAGnss_V1_0 != nullptr);
+}
+
+jboolean AGnss_V1_0::setCallback(const std::unique_ptr<AGnssCallback>& callback) {
+ auto result = mIAGnss_V1_0->setCallback(callback->getV1_0());
+ return checkHidlReturn(result, "IAGnss_V1_0 setCallback() failed.");
+}
+
+jboolean AGnss_V1_0::dataConnOpen(JNIEnv* env, jlong, jstring apn, jint apnIpType) {
+ ScopedJniString jniApn{env, apn};
+ auto result =
+ mIAGnss_V1_0->dataConnOpen(jniApn, static_cast<IAGnss_V1_0::ApnIpType>(apnIpType));
+ return checkHidlReturn(result,
+ "IAGnss_V1_0 dataConnOpen() failed. APN and its IP type not set.");
+}
+
+jboolean AGnss_V1_0::dataConnClosed() {
+ auto result = mIAGnss_V1_0->dataConnClosed();
+ return checkHidlReturn(result, "IAGnss_V1_0 dataConnClosed() failed.");
+}
+
+jboolean AGnss_V1_0::dataConnFailed() {
+ auto result = mIAGnss_V1_0->dataConnFailed();
+ return checkHidlReturn(result, "IAGnss_V1_0 dataConnFailed() failed.");
+}
+
+jboolean AGnss_V1_0::setServer(JNIEnv* env, jint type, jstring hostname, jint port) {
+ ScopedJniString jniHostName{env, hostname};
+ auto result = mIAGnss_V1_0->setServer(static_cast<IAGnssCallback_V1_0::AGnssType>(type),
+ jniHostName, port);
+ return checkHidlReturn(result, "IAGnss_V1_0 setServer() failed. Host name and port not set.");
+}
+
+// Implementation of AGnss_V2_0
+
+AGnss_V2_0::AGnss_V2_0(const sp<IAGnss_V2_0>& iAGnss) : mIAGnss_V2_0(iAGnss) {
+ assert(mIAGnss_V2_0 != nullptr);
+}
+
+jboolean AGnss_V2_0::setCallback(const std::unique_ptr<AGnssCallback>& callback) {
+ auto result = mIAGnss_V2_0->setCallback(callback->getV2_0());
+ return checkHidlReturn(result, "IAGnss_V2_0 setCallback() failed.");
+}
+
+jboolean AGnss_V2_0::dataConnOpen(JNIEnv* env, jlong networkHandle, jstring apn, jint apnIpType) {
+ ScopedJniString jniApn{env, apn};
+ auto result = mIAGnss_V2_0->dataConnOpen(static_cast<uint64_t>(networkHandle), jniApn,
+ static_cast<IAGnss_V2_0::ApnIpType>(apnIpType));
+ return checkHidlReturn(result,
+ "IAGnss_V2_0 dataConnOpen() failed. APN and its IP type not set.");
+}
+
+jboolean AGnss_V2_0::dataConnClosed() {
+ auto result = mIAGnss_V2_0->dataConnClosed();
+ return checkHidlReturn(result, "IAGnss_V2_0 dataConnClosed() failed.");
+}
+
+jboolean AGnss_V2_0::dataConnFailed() {
+ auto result = mIAGnss_V2_0->dataConnFailed();
+ return checkHidlReturn(result, "IAGnss_V2_0 dataConnFailed() failed.");
+}
+
+jboolean AGnss_V2_0::setServer(JNIEnv* env, jint type, jstring hostname, jint port) {
+ ScopedJniString jniHostName{env, hostname};
+ auto result = mIAGnss_V2_0->setServer(static_cast<IAGnssCallback_V2_0::AGnssType>(type),
+ jniHostName, port);
+ return checkHidlReturn(result, "IAGnss_V2_0 setServer() failed. Host name and port not set.");
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/AGnss.h b/services/core/jni/gnss/AGnss.h
new file mode 100644
index 0000000..2828b82
--- /dev/null
+++ b/services/core/jni/gnss/AGnss.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_AGNSS_H
+#define _ANDROID_SERVER_GNSS_AGNSS_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/BnAGnss.h>
+#include <log/log.h>
+
+#include "AGnssCallback.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+class AGnssInterface {
+public:
+ virtual ~AGnssInterface() {}
+ virtual jboolean setCallback(const std::unique_ptr<AGnssCallback>& callback) = 0;
+ virtual jboolean dataConnOpen(JNIEnv* env, jlong networkHandle, jstring apn,
+ jint apnIpType) = 0;
+ virtual jboolean dataConnClosed() = 0;
+ virtual jboolean dataConnFailed() = 0;
+ virtual jboolean setServer(JNIEnv* env, jint type, jstring hostname, jint port) = 0;
+};
+
+class AGnss : public AGnssInterface {
+public:
+ AGnss(const sp<android::hardware::gnss::IAGnss>& iAGnss);
+ jboolean setCallback(const std::unique_ptr<AGnssCallback>& callback) override;
+ jboolean dataConnOpen(JNIEnv* env, jlong networkHandle, jstring apn, jint apnIpType) override;
+ jboolean dataConnClosed() override;
+ jboolean dataConnFailed() override;
+ jboolean setServer(JNIEnv* env, jint type, jstring hostname, jint port) override;
+
+private:
+ const sp<android::hardware::gnss::IAGnss> mIAGnss;
+};
+
+class AGnss_V1_0 : public AGnssInterface {
+public:
+ AGnss_V1_0(const sp<android::hardware::gnss::V1_0::IAGnss>& iAGnss);
+ jboolean setCallback(const std::unique_ptr<AGnssCallback>& callback) override;
+ jboolean dataConnOpen(JNIEnv* env, jlong, jstring apn, jint apnIpType) override;
+ jboolean dataConnClosed() override;
+ jboolean dataConnFailed() override;
+ jboolean setServer(JNIEnv* env, jint type, jstring hostname, jint port) override;
+
+private:
+ const sp<android::hardware::gnss::V1_0::IAGnss> mIAGnss_V1_0;
+};
+
+class AGnss_V2_0 : public AGnssInterface {
+public:
+ AGnss_V2_0(const sp<android::hardware::gnss::V2_0::IAGnss>& iAGnss);
+ jboolean setCallback(const std::unique_ptr<AGnssCallback>& callback) override;
+ jboolean dataConnOpen(JNIEnv* env, jlong networkHandle, jstring apn, jint apnIpType) override;
+ jboolean dataConnClosed() override;
+ jboolean dataConnFailed() override;
+ jboolean setServer(JNIEnv* env, jint type, jstring hostname, jint port) override;
+
+private:
+ const sp<android::hardware::gnss::V2_0::IAGnss> mIAGnss_V2_0;
+};
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_AGNSS_H
diff --git a/services/core/jni/gnss/AGnssCallback.cpp b/services/core/jni/gnss/AGnssCallback.cpp
new file mode 100644
index 0000000..466cdfa
--- /dev/null
+++ b/services/core/jni/gnss/AGnssCallback.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#define LOG_TAG "AGnssCbJni"
+
+#include "AGnssCallback.h"
+
+namespace android::gnss {
+
+using binder::Status;
+using hardware::Return;
+using hardware::Void;
+using IAGnssCallback_V1_0 = android::hardware::gnss::V1_0::IAGnssCallback;
+using IAGnssCallback_V2_0 = android::hardware::gnss::V2_0::IAGnssCallback;
+
+namespace {
+
+jmethodID method_reportAGpsStatus;
+
+}
+
+void AGnss_class_init_once(JNIEnv* env, jclass clazz) {
+ method_reportAGpsStatus = env->GetMethodID(clazz, "reportAGpsStatus", "(II[B)V");
+}
+
+Status AGnssCallbackAidl::agnssStatusCb(AGnssType type, AGnssStatusValue status) {
+ AGnssCallbackUtil::agnssStatusCbImpl(type, status);
+ return Status::ok();
+}
+
+Return<void> AGnssCallback_V1_0::agnssStatusIpV6Cb(
+ const IAGnssCallback_V1_0::AGnssStatusIpV6& agps_status) {
+ JNIEnv* env = getJniEnv();
+ jbyteArray byteArray = nullptr;
+
+ byteArray = env->NewByteArray(16);
+ if (byteArray != nullptr) {
+ env->SetByteArrayRegion(byteArray, 0, 16, (const jbyte*)(agps_status.ipV6Addr.data()));
+ } else {
+ ALOGE("Unable to allocate byte array for IPv6 address.");
+ }
+
+ IF_ALOGD() {
+ // log the IP for reference in case there is a bogus value pushed by HAL
+ char str[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, agps_status.ipV6Addr.data(), str, INET6_ADDRSTRLEN);
+ ALOGD("AGPS IP is v6: %s", str);
+ }
+
+ jsize byteArrayLength = byteArray != nullptr ? env->GetArrayLength(byteArray) : 0;
+ ALOGV("Passing AGPS IP addr: size %d", byteArrayLength);
+ env->CallVoidMethod(mCallbacksObj, method_reportAGpsStatus, agps_status.type,
+ agps_status.status, byteArray);
+
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+
+ if (byteArray) {
+ env->DeleteLocalRef(byteArray);
+ }
+
+ return Void();
+}
+
+Return<void> AGnssCallback_V1_0::agnssStatusIpV4Cb(
+ const IAGnssCallback_V1_0::AGnssStatusIpV4& agps_status) {
+ JNIEnv* env = getJniEnv();
+ jbyteArray byteArray = nullptr;
+
+ uint32_t ipAddr = agps_status.ipV4Addr;
+ byteArray = convertToIpV4(ipAddr);
+
+ IF_ALOGD() {
+ /*
+ * log the IP for reference in case there is a bogus value pushed by
+ * HAL.
+ */
+ char str[INET_ADDRSTRLEN];
+ inet_ntop(AF_INET, &ipAddr, str, INET_ADDRSTRLEN);
+ ALOGD("AGPS IP is v4: %s", str);
+ }
+
+ jsize byteArrayLength = byteArray != nullptr ? env->GetArrayLength(byteArray) : 0;
+ ALOGV("Passing AGPS IP addr: size %d", byteArrayLength);
+ env->CallVoidMethod(mCallbacksObj, method_reportAGpsStatus, agps_status.type,
+ agps_status.status, byteArray);
+
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+
+ if (byteArray) {
+ env->DeleteLocalRef(byteArray);
+ }
+ return Void();
+}
+
+jbyteArray AGnssCallback_V1_0::convertToIpV4(uint32_t ip) {
+ if (INADDR_NONE == ip) {
+ return nullptr;
+ }
+
+ JNIEnv* env = getJniEnv();
+ jbyteArray byteArray = env->NewByteArray(4);
+ if (byteArray == nullptr) {
+ ALOGE("Unable to allocate byte array for IPv4 address");
+ return nullptr;
+ }
+
+ jbyte ipv4[4];
+ ALOGV("Converting IPv4 address byte array (net_order) %x", ip);
+ memcpy(ipv4, &ip, sizeof(ipv4));
+ env->SetByteArrayRegion(byteArray, 0, 4, (const jbyte*)ipv4);
+ return byteArray;
+}
+
+Return<void> AGnssCallback_V2_0::agnssStatusCb(IAGnssCallback_V2_0::AGnssType type,
+ IAGnssCallback_V2_0::AGnssStatusValue status) {
+ AGnssCallbackUtil::agnssStatusCbImpl(type, status);
+ return Void();
+}
+
+} // namespace android::gnss
diff --git a/services/core/jni/gnss/AGnssCallback.h b/services/core/jni/gnss/AGnssCallback.h
new file mode 100644
index 0000000..e9bb471
--- /dev/null
+++ b/services/core/jni/gnss/AGnssCallback.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef _ANDROID_SERVER_GNSS_AGNSSCALLBACK_H
+#define _ANDROID_SERVER_GNSS_AGNSSCALLBACK_H
+
+#pragma once
+
+#ifndef LOG_TAG
+#error LOG_TAG must be defined before including this file.
+#endif
+
+#include <android/hardware/gnss/1.0/IAGnss.h>
+#include <android/hardware/gnss/2.0/IAGnss.h>
+#include <android/hardware/gnss/BnAGnssCallback.h>
+#include <arpa/inet.h>
+#include <log/log.h>
+
+#include <vector>
+
+#include "Utils.h"
+#include "jni.h"
+
+namespace android::gnss {
+
+namespace {
+
+extern jmethodID method_reportAGpsStatus;
+
+}
+
+void AGnss_class_init_once(JNIEnv* env, jclass clazz);
+
+/*
+ * AGnssCallbackAidl class implements the callback methods required by the
+ * android::hardware::gnss::IAGnss interface.
+ */
+class AGnssCallbackAidl : public android::hardware::gnss::BnAGnssCallback {
+public:
+ binder::Status agnssStatusCb(AGnssType type, AGnssStatusValue status) override;
+};
+
+/*
+ * AGnssCallback_V1_0 implements callback methods required by the IAGnssCallback 1.0 interface.
+ */
+class AGnssCallback_V1_0 : public android::hardware::gnss::V1_0::IAGnssCallback {
+public:
+ // Methods from ::android::hardware::gps::V1_0::IAGnssCallback follow.
+ hardware::Return<void> agnssStatusIpV6Cb(
+ const android::hardware::gnss::V1_0::IAGnssCallback::AGnssStatusIpV6& agps_status)
+ override;
+
+ hardware::Return<void> agnssStatusIpV4Cb(
+ const android::hardware::gnss::V1_0::IAGnssCallback::AGnssStatusIpV4& agps_status)
+ override;
+
+private:
+ jbyteArray convertToIpV4(uint32_t ip);
+};
+
+/*
+ * AGnssCallback_V2_0 implements callback methods required by the IAGnssCallback 2.0 interface.
+ */
+class AGnssCallback_V2_0 : public android::hardware::gnss::V2_0::IAGnssCallback {
+public:
+ // Methods from ::android::hardware::gps::V2_0::IAGnssCallback follow.
+ hardware::Return<void> agnssStatusCb(
+ android::hardware::gnss::V2_0::IAGnssCallback::AGnssType type,
+ android::hardware::gnss::V2_0::IAGnssCallback::AGnssStatusValue status) override;
+};
+
+class AGnssCallback {
+public:
+ AGnssCallback() {}
+ sp<AGnssCallbackAidl> getAidl() {
+ if (callbackAidl == nullptr) {
+ callbackAidl = sp<AGnssCallbackAidl>::make();
+ }
+ return callbackAidl;
+ }
+
+ sp<AGnssCallback_V1_0> getV1_0() {
+ if (callbackV1_0 == nullptr) {
+ callbackV1_0 = sp<AGnssCallback_V1_0>::make();
+ }
+ return callbackV1_0;
+ }
+
+ sp<AGnssCallback_V2_0> getV2_0() {
+ if (callbackV2_0 == nullptr) {
+ callbackV2_0 = sp<AGnssCallback_V2_0>::make();
+ }
+ return callbackV2_0;
+ }
+
+private:
+ sp<AGnssCallbackAidl> callbackAidl;
+ sp<AGnssCallback_V1_0> callbackV1_0;
+ sp<AGnssCallback_V2_0> callbackV2_0;
+};
+
+struct AGnssCallbackUtil {
+ template <class T, class U>
+ static void agnssStatusCbImpl(const T& type, const U& status);
+
+private:
+ AGnssCallbackUtil() = delete;
+};
+
+template <class T, class U>
+void AGnssCallbackUtil::agnssStatusCbImpl(const T& type, const U& status) {
+ ALOGD("%s. type: %d, status:%d", __func__, static_cast<int32_t>(type),
+ static_cast<int32_t>(status));
+ JNIEnv* env = getJniEnv();
+ env->CallVoidMethod(mCallbacksObj, method_reportAGpsStatus, type, status, nullptr);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+}
+
+} // namespace android::gnss
+
+#endif // _ANDROID_SERVER_GNSS_AGNSSCALLBACK_H
\ No newline at end of file
diff --git a/services/core/jni/gnss/Android.bp b/services/core/jni/gnss/Android.bp
index ac50bfa..e49e81c 100644
--- a/services/core/jni/gnss/Android.bp
+++ b/services/core/jni/gnss/Android.bp
@@ -23,6 +23,8 @@
],
srcs: [
+ "AGnss.cpp",
+ "AGnssCallback.cpp",
"GnssAntennaInfoCallback.cpp",
"GnssBatching.cpp",
"GnssBatchingCallback.cpp",
diff --git a/services/core/jni/gnss/Utils.h b/services/core/jni/gnss/Utils.h
index 1bd69c4..2640a77 100644
--- a/services/core/jni/gnss/Utils.h
+++ b/services/core/jni/gnss/Utils.h
@@ -196,6 +196,30 @@
JNIEnv* mEnv = nullptr;
};
+struct ScopedJniString {
+ ScopedJniString(JNIEnv* env, jstring javaString) : mEnv(env), mJavaString(javaString) {
+ mNativeString = mEnv->GetStringUTFChars(mJavaString, nullptr);
+ }
+
+ ~ScopedJniString() {
+ if (mNativeString != nullptr) {
+ mEnv->ReleaseStringUTFChars(mJavaString, mNativeString);
+ }
+ }
+
+ const char* c_str() const { return mNativeString; }
+
+ operator hardware::hidl_string() const { return hardware::hidl_string(mNativeString); }
+
+private:
+ ScopedJniString(const ScopedJniString&) = delete;
+ ScopedJniString& operator=(const ScopedJniString&) = delete;
+
+ JNIEnv* mEnv;
+ jstring mJavaString;
+ const char* mNativeString;
+};
+
JNIEnv* getJniEnv();
template <class T>
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 98a7b5e..bfceeda 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9008,42 +9008,48 @@
return;
}
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
+
final CallerIdentity caller = getCallerIdentity();
- if (userHandle != mOwners.getDeviceOwnerUserId() && !mOwners.hasProfileOwner(userHandle)
- && getManagedUserId(userHandle) == -1
- && newState != STATE_USER_UNMANAGED) {
- // No managed device, user or profile, so setting provisioning state makes no sense.
- throw new IllegalStateException("Not allowed to change provisioning state unless a "
- + "device or profile owner is set.");
- }
+ final long id = mInjector.binderClearCallingIdentity();
+ try {
+ if (userHandle != mOwners.getDeviceOwnerUserId() && !mOwners.hasProfileOwner(userHandle)
+ && getManagedUserId(userHandle) == -1
+ && newState != STATE_USER_UNMANAGED) {
+ // No managed device, user or profile, so setting provisioning state makes no sense.
+ throw new IllegalStateException("Not allowed to change provisioning state unless a "
+ + "device or profile owner is set.");
+ }
- synchronized (getLockObject()) {
- boolean transitionCheckNeeded = true;
+ synchronized (getLockObject()) {
+ boolean transitionCheckNeeded = true;
- // Calling identity/permission checks.
- if (isAdb(caller)) {
- // ADB shell can only move directly from un-managed to finalized as part of directly
- // setting profile-owner or device-owner.
- if (getUserProvisioningState(userHandle) !=
- DevicePolicyManager.STATE_USER_UNMANAGED
- || newState != DevicePolicyManager.STATE_USER_SETUP_FINALIZED) {
- throw new IllegalStateException("Not allowed to change provisioning state "
- + "unless current provisioning state is unmanaged, and new state is "
- + "finalized.");
+ // Calling identity/permission checks.
+ if (isAdb(caller)) {
+ // ADB shell can only move directly from un-managed to finalized as part of
+ // directly setting profile-owner or device-owner.
+ if (getUserProvisioningState(userHandle)
+ != DevicePolicyManager.STATE_USER_UNMANAGED
+ || newState != DevicePolicyManager.STATE_USER_SETUP_FINALIZED) {
+ throw new IllegalStateException("Not allowed to change provisioning state "
+ + "unless current provisioning state is unmanaged, and new state"
+ + "is finalized.");
+ }
+ transitionCheckNeeded = false;
}
- transitionCheckNeeded = false;
- } else {
- Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS));
- }
- final DevicePolicyData policyData = getUserData(userHandle);
- if (transitionCheckNeeded) {
- // Optional state transition check for non-ADB case.
- checkUserProvisioningStateTransition(policyData.mUserProvisioningState, newState);
+ final DevicePolicyData policyData = getUserData(userHandle);
+ if (transitionCheckNeeded) {
+ // Optional state transition check for non-ADB case.
+ checkUserProvisioningStateTransition(policyData.mUserProvisioningState,
+ newState);
+ }
+ policyData.mUserProvisioningState = newState;
+ saveSettingsLocked(userHandle);
}
- policyData.mUserProvisioningState = newState;
- saveSettingsLocked(userHandle);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(id);
}
}
diff --git a/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java b/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
index bf4eeae..8561651 100644
--- a/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
@@ -43,13 +43,13 @@
import android.platform.test.annotations.Presubmit;
import android.util.Log;
-import com.android.internal.backup.IBackupTransport;
import com.android.server.backup.BackupManagerService;
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.testing.TransportData;
import com.android.server.backup.testing.TransportTestUtils;
import com.android.server.backup.testing.TransportTestUtils.TransportMock;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.testing.shadows.ShadowSlog;
@@ -75,7 +75,7 @@
@Mock private UserBackupManagerService mBackupManagerService;
@Mock private TransportManager mTransportManager;
@Mock private OnTaskFinishedListener mListener;
- @Mock private IBackupTransport mTransportBinder;
+ @Mock private BackupTransportClient mTransportClient;
@Mock private IBackupObserver mObserver;
@Mock private AlarmManager mAlarmManager;
@Mock private PendingIntent mRunInitIntent;
@@ -101,19 +101,19 @@
@Test
public void testRun_callsTransportCorrectly() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+ configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_OK);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
- verify(mTransportBinder).initializeDevice();
- verify(mTransportBinder).finishBackup();
+ verify(mTransportClient).initializeDevice();
+ verify(mTransportClient).finishBackup();
}
@Test
public void testRun_callsBackupManagerCorrectly() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+ configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_OK);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -127,7 +127,7 @@
@Test
public void testRun_callsObserverAndListenerCorrectly() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+ configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_OK);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -140,13 +140,13 @@
@Test
public void testRun_whenInitializeDeviceFails() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+ configureTransport(mTransportClient, TRANSPORT_ERROR, 0);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
- verify(mTransportBinder).initializeDevice();
- verify(mTransportBinder, never()).finishBackup();
+ verify(mTransportClient).initializeDevice();
+ verify(mTransportClient, never()).finishBackup();
verify(mBackupManagerService)
.recordInitPending(true, mTransportName, mTransport.transportDirName);
}
@@ -155,7 +155,7 @@
public void testRun_whenInitializeDeviceFails_callsObserverAndListenerCorrectly()
throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+ configureTransport(mTransportClient, TRANSPORT_ERROR, 0);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -168,7 +168,7 @@
@Test
public void testRun_whenInitializeDeviceFails_schedulesAlarm() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+ configureTransport(mTransportClient, TRANSPORT_ERROR, 0);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -179,13 +179,13 @@
@Test
public void testRun_whenFinishBackupFails() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+ configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_ERROR);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
- verify(mTransportBinder).initializeDevice();
- verify(mTransportBinder).finishBackup();
+ verify(mTransportClient).initializeDevice();
+ verify(mTransportClient).finishBackup();
verify(mBackupManagerService)
.recordInitPending(true, mTransportName, mTransport.transportDirName);
}
@@ -193,7 +193,7 @@
@Test
public void testRun_whenFinishBackupFails_callsObserverAndListenerCorrectly() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+ configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_ERROR);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -206,7 +206,7 @@
@Test
public void testRun_whenFinishBackupFails_logs() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+ configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_ERROR);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -219,7 +219,7 @@
@Test
public void testRun_whenInitializeDeviceFails_logs() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+ configureTransport(mTransportClient, TRANSPORT_ERROR, 0);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -232,7 +232,7 @@
@Test
public void testRun_whenFinishBackupFails_schedulesAlarm() throws Exception {
setUpTransport(mTransport);
- configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+ configureTransport(mTransportClient, TRANSPORT_OK, TRANSPORT_ERROR);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
performInitializeTask.run();
@@ -327,7 +327,7 @@
List<TransportMock> transportMocks =
setUpTransports(mTransportManager, transport1, transport2);
String registeredTransportName = transport2.transportName;
- IBackupTransport registeredTransport = transportMocks.get(1).transport;
+ BackupTransportClient registeredTransport = transportMocks.get(1).transport;
TransportConnection
registeredTransportConnection = transportMocks.get(1).mTransportConnection;
PerformInitializeTask performInitializeTask =
@@ -357,7 +357,7 @@
@Test
public void testRun_whenTransportThrowsDeadObjectException() throws Exception {
TransportMock transportMock = setUpTransport(mTransport);
- IBackupTransport transport = transportMock.transport;
+ BackupTransportClient transport = transportMock.transport;
TransportConnection transportConnection = transportMock.mTransportConnection;
when(transport.initializeDevice()).thenThrow(DeadObjectException.class);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
@@ -380,7 +380,7 @@
}
private void configureTransport(
- IBackupTransport transportMock, int initializeDeviceStatus, int finishBackupStatus)
+ BackupTransportClient transportMock, int initializeDeviceStatus, int finishBackupStatus)
throws Exception {
when(transportMock.initializeDevice()).thenReturn(initializeDeviceStatus);
when(transportMock.finishBackup()).thenReturn(finishBackupStatus);
@@ -389,7 +389,7 @@
private TransportMock setUpTransport(TransportData transport) throws Exception {
TransportMock transportMock =
TransportTestUtils.setUpTransport(mTransportManager, transport);
- mTransportBinder = transportMock.transport;
+ mTransportClient = transportMock.transport;
return transportMock;
}
}
diff --git a/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java
index ce44f06..8131ac4 100644
--- a/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java
+++ b/services/robotests/backup/src/com/android/server/backup/testing/TransportTestUtils.java
@@ -34,8 +34,8 @@
import android.content.pm.ServiceInfo;
import android.os.RemoteException;
-import com.android.internal.backup.IBackupTransport;
import com.android.server.backup.TransportManager;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.transport.TransportNotAvailableException;
import com.android.server.backup.transport.TransportNotRegisteredException;
@@ -160,7 +160,7 @@
when(transportConnectionMock.getTransportComponent()).thenReturn(transportComponent);
if (status == TransportStatus.REGISTERED_AVAILABLE) {
// Transport registered and available
- IBackupTransport transportMock = mockTransportBinder(transport);
+ BackupTransportClient transportMock = mockTransportBinder(transport);
when(transportConnectionMock.connectOrThrow(any())).thenReturn(transportMock);
when(transportConnectionMock.connect(any())).thenReturn(transportMock);
@@ -179,8 +179,9 @@
}
}
- private static IBackupTransport mockTransportBinder(TransportData transport) throws Exception {
- IBackupTransport transportBinder = mock(IBackupTransport.class);
+ private static BackupTransportClient mockTransportBinder(TransportData transport)
+ throws Exception {
+ BackupTransportClient transportBinder = mock(BackupTransportClient.class);
try {
when(transportBinder.name()).thenReturn(transport.transportName);
when(transportBinder.transportDirName()).thenReturn(transport.transportDirName);
@@ -199,12 +200,12 @@
public static class TransportMock {
public final TransportData transportData;
@Nullable public final TransportConnection mTransportConnection;
- @Nullable public final IBackupTransport transport;
+ @Nullable public final BackupTransportClient transport;
private TransportMock(
TransportData transportData,
@Nullable TransportConnection transportConnection,
- @Nullable IBackupTransport transport) {
+ @Nullable BackupTransportClient transport) {
this.transportData = transportData;
this.mTransportConnection = transportConnection;
this.transport = transport;
diff --git a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
index de4aec6..6a82f16 100644
--- a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java
@@ -84,9 +84,11 @@
@Mock private TransportConnectionListener mTransportConnectionListener;
@Mock private TransportConnectionListener mTransportConnectionListener2;
@Mock private IBackupTransport.Stub mTransportBinder;
+
@UserIdInt private int mUserId;
private TransportStats mTransportStats;
private TransportConnection mTransportConnection;
+ private BackupTransportClient mTransportClient;
private ComponentName mTransportComponent;
private String mTransportString;
private Intent mBindIntent;
@@ -116,6 +118,7 @@
"1",
"caller",
new Handler(mainLooper));
+ mTransportClient = new BackupTransportClient(mTransportBinder);
when(mContext.bindServiceAsUser(
eq(mBindIntent),
@@ -156,7 +159,8 @@
mShadowMainLooper.runToEndOfTasks();
verify(mTransportConnectionListener)
- .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection));
+ .onTransportConnectionResult(any(BackupTransportClient.class),
+ eq(mTransportConnection));
}
@Test
@@ -169,9 +173,11 @@
connection.onServiceConnected(mTransportComponent, mTransportBinder);
mShadowMainLooper.runToEndOfTasks();
verify(mTransportConnectionListener)
- .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection));
+ .onTransportConnectionResult(any(BackupTransportClient.class),
+ eq(mTransportConnection));
verify(mTransportConnectionListener2)
- .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection));
+ .onTransportConnectionResult(any(BackupTransportClient.class),
+ eq(mTransportConnection));
}
@Test
@@ -184,7 +190,8 @@
mShadowMainLooper.runToEndOfTasks();
verify(mTransportConnectionListener2)
- .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection));
+ .onTransportConnectionResult(any(BackupTransportClient.class),
+ eq(mTransportConnection));
}
@Test
@@ -312,10 +319,10 @@
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
connection.onServiceConnected(mTransportComponent, mTransportBinder);
- IBackupTransport transportBinder =
+ BackupTransportClient transportClient =
runInWorkerThread(() -> mTransportConnection.connect("caller2"));
- assertThat(transportBinder).isNotNull();
+ assertThat(transportClient).isNotNull();
}
@Test
@@ -325,10 +332,10 @@
connection.onServiceConnected(mTransportComponent, mTransportBinder);
connection.onServiceDisconnected(mTransportComponent);
- IBackupTransport transportBinder =
+ BackupTransportClient transportClient =
runInWorkerThread(() -> mTransportConnection.connect("caller2"));
- assertThat(transportBinder).isNull();
+ assertThat(transportClient).isNull();
}
@Test
@@ -337,10 +344,10 @@
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
connection.onBindingDied(mTransportComponent);
- IBackupTransport transportBinder =
+ BackupTransportClient transportClient =
runInWorkerThread(() -> mTransportConnection.connect("caller2"));
- assertThat(transportBinder).isNull();
+ assertThat(transportClient).isNull();
}
@Test
@@ -354,17 +361,17 @@
doAnswer(
invocation -> {
TransportConnectionListener listener = invocation.getArgument(0);
- listener.onTransportConnectionResult(mTransportBinder,
+ listener.onTransportConnectionResult(mTransportClient,
transportConnection);
return null;
})
.when(transportConnection)
.connectAsync(any(), any());
- IBackupTransport transportBinder =
+ BackupTransportClient transportClient =
runInWorkerThread(() -> transportConnection.connect("caller"));
- assertThat(transportBinder).isNotNull();
+ assertThat(transportClient).isNotNull();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index aa7d6aa..c36e1a8 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -31,13 +31,13 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import com.android.internal.backup.IBackupTransport;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.params.BackupParams;
+import com.android.server.backup.transport.BackupTransportClient;
import com.android.server.backup.transport.TransportConnection;
import com.android.server.backup.utils.BackupEligibilityRules;
@@ -57,9 +57,8 @@
@Mock IBackupManagerMonitor mBackupManagerMonitor;
@Mock IBackupObserver mBackupObserver;
@Mock PackageManager mPackageManager;
- @Mock
- TransportConnection mTransportConnection;
- @Mock IBackupTransport mBackupTransport;
+ @Mock TransportConnection mTransportConnection;
+ @Mock BackupTransportClient mBackupTransport;
@Mock BackupEligibilityRules mBackupEligibilityRules;
diff --git a/services/tests/servicestests/src/com/android/server/backup/internal/LifecycleOperationStorageTest.java b/services/tests/servicestests/src/com/android/server/backup/internal/LifecycleOperationStorageTest.java
index c079c2d..948accc 100644
--- a/services/tests/servicestests/src/com/android/server/backup/internal/LifecycleOperationStorageTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/internal/LifecycleOperationStorageTest.java
@@ -28,7 +28,7 @@
import com.android.server.backup.OperationStorage.OpState;
import com.android.server.backup.OperationStorage.OpType;
-import com.google.common.collect.ImmutableSet;
+import com.google.android.collect.Sets;
import org.junit.After;
import org.junit.Before;
@@ -45,14 +45,15 @@
private static final int USER_ID = 0;
private static final int TOKEN_1 = 1;
private static final int TOKEN_2 = 2;
+ private static final int TOKEN_3 = 3;
private static final long RESULT = 123L;
private static final String PKG_FOO = "com.android.foo";
private static final String PKG_BAR = "com.android.bar";
private static final String PKG_BAZ = "com.android.baz";
- private static final ImmutableSet<String> MULTIPLE_PKG = ImmutableSet.of(PKG_FOO);
- private static final ImmutableSet<String> MULTIPLE_PKGS_1 = ImmutableSet.of(PKG_FOO, PKG_BAR);
- private static final ImmutableSet<String> MULTIPLE_PKGS_2 = ImmutableSet.of(PKG_BAR, PKG_BAZ);
+ private static final Set<String> MULTIPLE_PKG = Sets.newHashSet(PKG_FOO);
+ private static final Set<String> MULTIPLE_PKGS_1 = Sets.newHashSet(PKG_FOO, PKG_BAR);
+ private static final Set<String> MULTIPLE_PKGS_2 = Sets.newHashSet(PKG_BAR, PKG_BAZ);
@Mock private BackupRestoreTask mCallback;
private LifecycleOperationStorage mOpStorage;
@@ -72,6 +73,7 @@
Set<Integer> tokens = mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT);
+ assertThat(mOpStorage.numOperations()).isEqualTo(1);
assertThat(tokens).isEqualTo(only(TOKEN_1));
}
@@ -85,7 +87,8 @@
Set<Integer> stateAcknowledgedTokens =
mOpStorage.operationTokensForOpState(OpState.ACKNOWLEDGED);
- assertThat(typeWaitTokens).isEqualTo(ImmutableSet.of(TOKEN_1, TOKEN_2));
+ assertThat(mOpStorage.numOperations()).isEqualTo(2);
+ assertThat(typeWaitTokens).isEqualTo(Sets.newHashSet(TOKEN_1, TOKEN_2));
assertThat(statePendingTokens).isEqualTo(only(TOKEN_1));
assertThat(stateAcknowledgedTokens).isEqualTo(only(TOKEN_2));
}
@@ -97,6 +100,7 @@
Set<Integer> tokens = mOpStorage.operationTokensForPackage(PKG_FOO);
+ assertThat(mOpStorage.numOperations()).isEqualTo(1);
assertThat(tokens).isEqualTo(only(TOKEN_1));
}
@@ -111,8 +115,9 @@
Set<Integer> tokensBar = mOpStorage.operationTokensForPackage(PKG_BAR);
Set<Integer> tokensBaz = mOpStorage.operationTokensForPackage(PKG_BAZ);
+ assertThat(mOpStorage.numOperations()).isEqualTo(2);
assertThat(tokensFoo).isEqualTo(only(TOKEN_1));
- assertThat(tokensBar).isEqualTo(ImmutableSet.of(TOKEN_1, TOKEN_2));
+ assertThat(tokensBar).isEqualTo(Sets.newHashSet(TOKEN_1, TOKEN_2));
assertThat(tokensBaz).isEqualTo(only(TOKEN_2));
}
@@ -123,6 +128,7 @@
Set<Integer> typeWaitTokens = mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT);
Set<Integer> statePendingTokens = mOpStorage.operationTokensForOpState(OpState.PENDING);
+ assertThat(mOpStorage.numOperations()).isEqualTo(1);
assertThat(typeWaitTokens).isEqualTo(only(TOKEN_2));
assertThat(statePendingTokens).isEqualTo(only(TOKEN_2));
@@ -131,6 +137,7 @@
typeWaitTokens = mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT);
statePendingTokens = mOpStorage.operationTokensForOpState(OpState.PENDING);
+ assertThat(mOpStorage.numOperations()).isEqualTo(0);
assertThat(typeWaitTokens).isEmpty();
assertThat(statePendingTokens).isEmpty();
}
@@ -148,12 +155,25 @@
Set<Integer> tokensBar = mOpStorage.operationTokensForPackage(PKG_BAR);
Set<Integer> tokensBaz = mOpStorage.operationTokensForPackage(PKG_BAZ);
+ assertThat(mOpStorage.numOperations()).isEqualTo(1);
assertThat(tokensFoo).isEqualTo(only(TOKEN_1));
assertThat(tokensBar).isEqualTo(only(TOKEN_1));
assertThat(tokensBaz).isEmpty();
}
@Test
+ public void testIsBackupOperationInProgress() throws Exception {
+ mOpStorage.registerOperation(TOKEN_1, OpState.ACKNOWLEDGED, mCallback, OpType.RESTORE_WAIT);
+ assertThat(mOpStorage.isBackupOperationInProgress()).isFalse();
+
+ mOpStorage.registerOperation(TOKEN_2, OpState.TIMEOUT, mCallback, OpType.BACKUP_WAIT);
+ assertThat(mOpStorage.isBackupOperationInProgress()).isFalse();
+
+ mOpStorage.registerOperation(TOKEN_3, OpState.PENDING, mCallback, OpType.BACKUP);
+ assertThat(mOpStorage.isBackupOperationInProgress()).isTrue();
+ }
+
+ @Test
public void testOnOperationComplete_pendingAdvancesState_invokesCallback() throws Exception {
mOpStorage.registerOperation(TOKEN_1, OpState.PENDING, mCallback, OpType.BACKUP_WAIT);
@@ -169,6 +189,6 @@
}
private Set<Integer> only(Integer val) {
- return ImmutableSet.of(val);
+ return Sets.newHashSet(val);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index b3f7587..b255a35 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -302,6 +302,65 @@
testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null));
}
+ // TODO (b/208484275) : Enable these tests
+ // @Test
+ // public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception {
+ // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class);
+ // when(manager
+ // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt()))
+ // .thenReturn(false);
+ // when(mContext.getSystemService(SensorPrivacyManager.class))
+ // .thenReturn(manager);
+ // setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+ // mock(IBiometricAuthenticator.class));
+ // final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG);
+ // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false);
+ // assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult());
+ // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) {
+ // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState());
+ // }
+ // }
+
+ // @Test
+ // public void testPreAuth_cannotAuthAndPrivacyEnabled() throws Exception {
+ // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class);
+ // when(manager
+ // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt()))
+ // .thenReturn(true);
+ // when(mContext.getSystemService(SensorPrivacyManager.class))
+ // .thenReturn(manager);
+ // setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+ // mock(IBiometricAuthenticator.class));
+ // final PromptInfo promptInfo = createPromptInfo(Authenticators.BIOMETRIC_STRONG);
+ // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false);
+ // assertEquals(BiometricManager.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED,
+ // preAuthInfo.getCanAuthenticateResult());
+ // // Even though canAuth returns privacy enabled, we should still be able to authenticate.
+ // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) {
+ // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState());
+ // }
+ // }
+
+ // @Test
+ // public void testPreAuth_canAuthAndPrivacyEnabledCredentialEnabled() throws Exception {
+ // SensorPrivacyManager manager = ExtendedMockito.mock(SensorPrivacyManager.class);
+ // when(manager
+ // .isSensorPrivacyEnabled(SensorPrivacyManager.Sensors.CAMERA, anyInt()))
+ // .thenReturn(true);
+ // when(mContext.getSystemService(SensorPrivacyManager.class))
+ // .thenReturn(manager);
+ // setupFace(1 /* id */, false /* confirmationAlwaysRequired */,
+ // mock(IBiometricAuthenticator.class));
+ // final PromptInfo promptInfo =
+ // createPromptInfo(Authenticators.BIOMETRIC_STRONG
+ // | Authenticators. DEVICE_CREDENTIAL);
+ // final PreAuthInfo preAuthInfo = createPreAuthInfo(mSensors, 0, promptInfo, false);
+ // assertEquals(BiometricManager.BIOMETRIC_SUCCESS, preAuthInfo.getCanAuthenticateResult());
+ // for (BiometricSensor sensor : preAuthInfo.eligibleSensors) {
+ // assertEquals(BiometricSensor.STATE_UNKNOWN, sensor.getSensorState());
+ // }
+ // }
+
private void testInvokesCancel(Consumer<AuthSession> sessionConsumer) throws RemoteException {
final IBiometricAuthenticator faceAuthenticator = mock(IBiometricAuthenticator.class);
@@ -331,7 +390,8 @@
userId,
promptInfo,
TEST_PACKAGE,
- checkDevicePolicyManager);
+ checkDevicePolicyManager,
+ mContext);
}
private AuthSession createAuthSession(List<BiometricSensor> sensors,
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/NoBroadcastContextWrapper.java b/services/tests/servicestests/src/com/android/server/statusbar/NoBroadcastContextWrapper.java
new file mode 100644
index 0000000..f172279
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/statusbar/NoBroadcastContextWrapper.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2021 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.statusbar;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.testing.TestableContext;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+
+/**
+ * {@link ContextWrapper} that doesn't register {@link BroadcastReceiver}.
+ *
+ * Instead, it keeps a list of the registrations for querying.
+ */
+class NoBroadcastContextWrapper extends TestableContext {
+
+ ArrayList<BroadcastReceiverRegistration> mRegistrationList =
+ new ArrayList<>();
+
+ NoBroadcastContextWrapper(Context context) {
+ super(context);
+ }
+
+ @Override
+ public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter) {
+ return registerReceiver(receiver, filter, 0);
+ }
+
+ @Override
+ public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter,
+ int flags) {
+ return registerReceiver(receiver, filter, null, null, flags);
+ }
+
+ @Override
+ public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter,
+ @Nullable String broadcastPermission, @Nullable Handler scheduler) {
+ return registerReceiver(receiver, filter, broadcastPermission, scheduler, 0);
+ }
+
+ @Override
+ public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter,
+ @Nullable String broadcastPermission, @Nullable Handler scheduler, int flags) {
+ return registerReceiverAsUser(receiver, getUser(), filter, broadcastPermission, scheduler,
+ flags);
+ }
+
+ @Nullable
+ @Override
+ public Intent registerReceiverForAllUsers(@Nullable BroadcastReceiver receiver,
+ @NonNull IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler) {
+ return registerReceiverForAllUsers(receiver, filter, broadcastPermission, scheduler, 0);
+ }
+
+ @Nullable
+ @Override
+ public Intent registerReceiverForAllUsers(@Nullable BroadcastReceiver receiver,
+ @NonNull IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler, int flags) {
+ return registerReceiverAsUser(receiver, UserHandle.ALL, filter, broadcastPermission,
+ scheduler, flags);
+ }
+
+ @Override
+ public Intent registerReceiverAsUser(@Nullable BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler) {
+ return registerReceiverAsUser(receiver, user, filter, broadcastPermission,
+ scheduler, 0);
+ }
+
+ @Override
+ public Intent registerReceiverAsUser(@Nullable BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler, int flags) {
+ BroadcastReceiverRegistration reg = new BroadcastReceiverRegistration(
+ receiver, user, filter, broadcastPermission, scheduler, flags
+ );
+ mRegistrationList.add(reg);
+ return null;
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ mRegistrationList.removeIf((reg) -> reg.mReceiver == receiver);
+ }
+
+ static class BroadcastReceiverRegistration {
+ final BroadcastReceiver mReceiver;
+ final UserHandle mUser;
+ final IntentFilter mIntentFilter;
+ final String mBroadcastPermission;
+ final Handler mHandler;
+ final int mFlags;
+
+ BroadcastReceiverRegistration(BroadcastReceiver receiver, UserHandle user,
+ IntentFilter intentFilter, String broadcastPermission, Handler handler, int flags) {
+ mReceiver = receiver;
+ mUser = user;
+ mIntentFilter = intentFilter;
+ mBroadcastPermission = broadcastPermission;
+ mHandler = handler;
+ mFlags = flags;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
new file mode 100644
index 0000000..c293b5e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -0,0 +1,659 @@
+/*
+ * Copyright (C) 2021 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.statusbar;
+
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.ActivityManagerInternal;
+import android.app.StatusBarManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Icon;
+import android.hardware.display.DisplayManager;
+import android.os.Binder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.quicksettings.TileService;
+import android.testing.TestableContext;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.statusbar.IAddTileResultCallback;
+import com.android.internal.statusbar.IStatusBar;
+import com.android.server.LocalServices;
+import com.android.server.policy.GlobalActionsProvider;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class StatusBarManagerServiceTest {
+
+ private static final String TEST_PACKAGE = "test_pkg";
+ private static final String TEST_SERVICE = "test_svc";
+ private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PACKAGE,
+ TEST_SERVICE);
+ private static final CharSequence APP_NAME = "AppName";
+ private static final CharSequence TILE_LABEL = "Tile label";
+
+ @Rule
+ public final TestableContext mContext =
+ new NoBroadcastContextWrapper(InstrumentationRegistry.getContext());
+
+ @Mock
+ private ActivityTaskManagerInternal mActivityTaskManagerInternal;
+ @Mock
+ private PackageManagerInternal mPackageManagerInternal;
+ @Mock
+ private ActivityManagerInternal mActivityManagerInternal;
+ @Mock
+ private ApplicationInfo mApplicationInfo;
+ @Mock
+ private IStatusBar.Stub mMockStatusBar;
+ @Captor
+ private ArgumentCaptor<IAddTileResultCallback> mAddTileResultCallbackCaptor;
+
+ private Icon mIcon;
+ private StatusBarManagerService mStatusBarManagerService;
+
+ @BeforeClass
+ public static void oneTimeInitialization() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+ LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInternal);
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInternal);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
+
+ when(mMockStatusBar.asBinder()).thenReturn(mMockStatusBar);
+ when(mApplicationInfo.loadLabel(any())).thenReturn(APP_NAME);
+
+ mStatusBarManagerService = new StatusBarManagerService(mContext);
+ LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+ LocalServices.removeServiceForTest(GlobalActionsProvider.class);
+
+ mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(
+ mStatusBarManagerService);
+
+ mStatusBarManagerService.registerStatusBar(mMockStatusBar);
+
+ mIcon = Icon.createWithResource(mContext, android.R.drawable.btn_plus);
+ }
+
+ @Test
+ public void testHandleIncomingUserCalled() {
+ int fakeUser = 17;
+ try {
+ mStatusBarManagerService.requestAddTile(
+ TEST_COMPONENT,
+ TILE_LABEL,
+ mIcon,
+ fakeUser,
+ new Callback()
+ );
+ fail("Should have SecurityException from uid check");
+ } catch (SecurityException e) {
+ verify(mActivityManagerInternal).handleIncomingUser(
+ eq(Binder.getCallingPid()),
+ eq(Binder.getCallingUid()),
+ eq(fakeUser),
+ eq(false),
+ eq(ActivityManagerInternal.ALLOW_NON_FULL),
+ anyString(),
+ eq(TEST_PACKAGE)
+ );
+ }
+ }
+
+ @Test
+ public void testCheckUid_pass() {
+ when(mPackageManagerInternal.getPackageUid(TEST_PACKAGE, 0, mContext.getUserId()))
+ .thenReturn(Binder.getCallingUid());
+ try {
+ mStatusBarManagerService.requestAddTile(
+ TEST_COMPONENT,
+ TILE_LABEL,
+ mIcon,
+ mContext.getUserId(),
+ new Callback()
+ );
+ } catch (SecurityException e) {
+ fail("No SecurityException should be thrown");
+ }
+ }
+
+ @Test
+ public void testCheckUid_pass_differentUser() {
+ int otherUserUid = UserHandle.getUid(17, UserHandle.getAppId(Binder.getCallingUid()));
+ when(mPackageManagerInternal.getPackageUid(TEST_PACKAGE, 0, mContext.getUserId()))
+ .thenReturn(otherUserUid);
+ try {
+ mStatusBarManagerService.requestAddTile(
+ TEST_COMPONENT,
+ TILE_LABEL,
+ mIcon,
+ mContext.getUserId(),
+ new Callback()
+ );
+ } catch (SecurityException e) {
+ fail("No SecurityException should be thrown");
+ }
+ }
+
+ @Test
+ public void testCheckUid_fail() {
+ when(mPackageManagerInternal.getPackageUid(TEST_PACKAGE, 0, mContext.getUserId()))
+ .thenReturn(Binder.getCallingUid() + 1);
+ try {
+ mStatusBarManagerService.requestAddTile(
+ TEST_COMPONENT,
+ TILE_LABEL,
+ mIcon,
+ mContext.getUserId(),
+ new Callback()
+ );
+ fail("Should throw SecurityException");
+ } catch (SecurityException e) {
+ // pass
+ }
+ }
+
+ @Test
+ public void testCurrentUser_fail() {
+ mockUidCheck();
+ int user = 0;
+ when(mActivityManagerInternal.getCurrentUserId()).thenReturn(user + 1);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_NOT_CURRENT_USER,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testCurrentUser_pass() {
+ mockUidCheck();
+ int user = 0;
+ when(mActivityManagerInternal.getCurrentUserId()).thenReturn(user);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertNotEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_NOT_CURRENT_USER,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testValidComponent_fail_noComponentFound() {
+ int user = 10;
+ mockUidCheck();
+ mockCurrentUserCheck(user);
+ IntentMatcher im = new IntentMatcher(
+ new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ eq(user), anyInt())).thenReturn(null);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT, callback.mUserResponse);
+ }
+
+ @Test
+ public void testValidComponent_fail_notEnabled() {
+ int user = 10;
+ mockUidCheck();
+ mockCurrentUserCheck(user);
+
+ ResolveInfo r = makeResolveInfo();
+ r.serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE;
+
+ IntentMatcher im = new IntentMatcher(
+ new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ eq(user), anyInt())).thenReturn(r);
+ when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
+ Binder.getCallingUid(), user)).thenReturn(
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT, callback.mUserResponse);
+ }
+
+ @Test
+ public void testValidComponent_fail_noPermission() {
+ int user = 10;
+ mockUidCheck();
+ mockCurrentUserCheck(user);
+
+ ResolveInfo r = makeResolveInfo();
+
+ IntentMatcher im = new IntentMatcher(
+ new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ eq(user), anyInt())).thenReturn(r);
+ when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
+ Binder.getCallingUid(), user)).thenReturn(
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT, callback.mUserResponse);
+ }
+
+ @Test
+ public void testValidComponent_fail_notExported() {
+ int user = 10;
+ mockUidCheck();
+ mockCurrentUserCheck(user);
+
+ ResolveInfo r = makeResolveInfo();
+ r.serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE;
+ r.serviceInfo.exported = false;
+
+ IntentMatcher im = new IntentMatcher(
+ new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ eq(user), anyInt())).thenReturn(r);
+ when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
+ Binder.getCallingUid(), user)).thenReturn(
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT, callback.mUserResponse);
+ }
+
+ @Test
+ public void testValidComponent_pass() {
+ int user = 10;
+ mockUidCheck();
+ mockCurrentUserCheck(user);
+
+ ResolveInfo r = makeResolveInfo();
+ r.serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE;
+ r.serviceInfo.exported = true;
+
+ IntentMatcher im = new IntentMatcher(
+ new Intent(TileService.ACTION_QS_TILE).setComponent(TEST_COMPONENT));
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ eq(user), anyInt())).thenReturn(r);
+ when(mPackageManagerInternal.getComponentEnabledSetting(TEST_COMPONENT,
+ Binder.getCallingUid(), user)).thenReturn(
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertNotEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testAppInForeground_fail() {
+ int user = 10;
+ mockUidCheck();
+ mockCurrentUserCheck(user);
+ mockComponentInfo(user);
+
+ when(mActivityManagerInternal.getUidProcessState(Binder.getCallingUid())).thenReturn(
+ PROCESS_STATE_FOREGROUND_SERVICE);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_APP_NOT_IN_FOREGROUND,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testAppInForeground_pass() {
+ int user = 10;
+ mockUidCheck();
+ mockCurrentUserCheck(user);
+ mockComponentInfo(user);
+
+ when(mActivityManagerInternal.getUidProcessState(Binder.getCallingUid())).thenReturn(
+ PROCESS_STATE_TOP);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertNotEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_APP_NOT_IN_FOREGROUND,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testRequestToStatusBar() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
+ new Callback());
+
+ verify(mMockStatusBar).requestAddTile(
+ eq(TEST_COMPONENT),
+ eq(APP_NAME),
+ eq(TILE_LABEL),
+ eq(mIcon),
+ any()
+ );
+ }
+
+ @Test
+ public void testRequestInProgress_samePackage() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
+ new Callback());
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_REQUEST_IN_PROGRESS,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testRequestInProgress_differentPackage() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+ ComponentName otherComponent = new ComponentName("a", "b");
+ mockUidCheck(otherComponent.getPackageName());
+ mockComponentInfo(user, otherComponent);
+
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
+ new Callback());
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(otherComponent, TILE_LABEL, mIcon, user, callback);
+
+ assertNotEquals(StatusBarManager.TILE_ADD_REQUEST_ERROR_REQUEST_IN_PROGRESS,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testResponseForwardedToCallback_tileAdded() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ verify(mMockStatusBar).requestAddTile(
+ eq(TEST_COMPONENT),
+ eq(APP_NAME),
+ eq(TILE_LABEL),
+ eq(mIcon),
+ mAddTileResultCallbackCaptor.capture()
+ );
+
+ mAddTileResultCallbackCaptor.getValue().onTileRequest(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED);
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED, callback.mUserResponse);
+ }
+
+ @Test
+ public void testResponseForwardedToCallback_tileNotAdded() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ verify(mMockStatusBar).requestAddTile(
+ eq(TEST_COMPONENT),
+ eq(APP_NAME),
+ eq(TILE_LABEL),
+ eq(mIcon),
+ mAddTileResultCallbackCaptor.capture()
+ );
+
+ mAddTileResultCallbackCaptor.getValue().onTileRequest(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED);
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testResponseForwardedToCallback_tileAlreadyAdded() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ verify(mMockStatusBar).requestAddTile(
+ eq(TEST_COMPONENT),
+ eq(APP_NAME),
+ eq(TILE_LABEL),
+ eq(mIcon),
+ mAddTileResultCallbackCaptor.capture()
+ );
+
+ mAddTileResultCallbackCaptor.getValue().onTileRequest(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED);
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testResponseForwardedToCallback_dialogDismissed() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ verify(mMockStatusBar).requestAddTile(
+ eq(TEST_COMPONENT),
+ eq(APP_NAME),
+ eq(TILE_LABEL),
+ eq(mIcon),
+ mAddTileResultCallbackCaptor.capture()
+ );
+
+ mAddTileResultCallbackCaptor.getValue().onTileRequest(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED);
+ // This gets translated to TILE_NOT_ADDED
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testInstaDenialAfterManyDenials() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+
+ for (int i = 0; i < TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
+ new Callback());
+
+ verify(mMockStatusBar, times(i + 1)).requestAddTile(
+ eq(TEST_COMPONENT),
+ eq(APP_NAME),
+ eq(TILE_LABEL),
+ eq(mIcon),
+ mAddTileResultCallbackCaptor.capture()
+ );
+ mAddTileResultCallbackCaptor.getValue().onTileRequest(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED);
+ }
+
+ Callback callback = new Callback();
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user, callback);
+
+ // Only called MAX_NUM_DENIALS times
+ verify(mMockStatusBar, times(TileRequestTracker.MAX_NUM_DENIALS)).requestAddTile(
+ any(),
+ any(),
+ any(),
+ any(),
+ mAddTileResultCallbackCaptor.capture()
+ );
+ assertEquals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED,
+ callback.mUserResponse);
+ }
+
+ @Test
+ public void testDialogDismissalNotCountingAgainstDenials() throws RemoteException {
+ int user = 10;
+ mockEverything(user);
+
+ for (int i = 0; i < TileRequestTracker.MAX_NUM_DENIALS * 2; i++) {
+ mStatusBarManagerService.requestAddTile(TEST_COMPONENT, TILE_LABEL, mIcon, user,
+ new Callback());
+
+ verify(mMockStatusBar, times(i + 1)).requestAddTile(
+ eq(TEST_COMPONENT),
+ eq(APP_NAME),
+ eq(TILE_LABEL),
+ eq(mIcon),
+ mAddTileResultCallbackCaptor.capture()
+ );
+ mAddTileResultCallbackCaptor.getValue().onTileRequest(
+ StatusBarManager.TILE_ADD_REQUEST_RESULT_DIALOG_DISMISSED);
+ }
+ }
+
+ private void mockUidCheck() {
+ mockUidCheck(TEST_PACKAGE);
+ }
+
+ private void mockUidCheck(String packageName) {
+ when(mPackageManagerInternal.getPackageUid(eq(packageName), anyInt(), anyInt()))
+ .thenReturn(Binder.getCallingUid());
+ }
+
+ private void mockCurrentUserCheck(int user) {
+ when(mActivityManagerInternal.getCurrentUserId()).thenReturn(user);
+ }
+
+ private void mockComponentInfo(int user) {
+ mockComponentInfo(user, TEST_COMPONENT);
+ }
+
+ private ResolveInfo makeResolveInfo() {
+ ResolveInfo r = new ResolveInfo();
+ r.serviceInfo = new ServiceInfo();
+ r.serviceInfo.applicationInfo = mApplicationInfo;
+ return r;
+ }
+
+ private void mockComponentInfo(int user, ComponentName componentName) {
+ ResolveInfo r = makeResolveInfo();
+ r.serviceInfo.exported = true;
+ r.serviceInfo.permission = Manifest.permission.BIND_QUICK_SETTINGS_TILE;
+
+ IntentMatcher im = new IntentMatcher(
+ new Intent(TileService.ACTION_QS_TILE).setComponent(componentName));
+ when(mPackageManagerInternal.resolveService(argThat(im), nullable(String.class), eq(0),
+ eq(user), anyInt())).thenReturn(r);
+ when(mPackageManagerInternal.getComponentEnabledSetting(componentName,
+ Binder.getCallingUid(), user)).thenReturn(
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ }
+
+ private void mockProcessState() {
+ when(mActivityManagerInternal.getUidProcessState(Binder.getCallingUid())).thenReturn(
+ PROCESS_STATE_TOP);
+ }
+
+ private void mockEverything(int user) {
+ mockUidCheck();
+ mockCurrentUserCheck(user);
+ mockComponentInfo(user);
+ mockProcessState();
+ }
+
+ private static class Callback extends IAddTileResultCallback.Stub {
+ int mUserResponse = -1;
+
+ @Override
+ public void onTileRequest(int userResponse) throws RemoteException {
+ if (mUserResponse != -1) {
+ throw new IllegalStateException(
+ "Setting response to " + userResponse + " but it already has "
+ + mUserResponse);
+ }
+ mUserResponse = userResponse;
+ }
+ }
+
+ private static class IntentMatcher implements ArgumentMatcher<Intent> {
+ private final Intent mIntent;
+
+ IntentMatcher(Intent intent) {
+ mIntent = intent;
+ }
+
+ @Override
+ public boolean matches(Intent argument) {
+ return argument != null && argument.filterEquals(mIntent);
+ }
+
+ @Override
+ public String toString() {
+ return "Expected: " + mIntent;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/TileRequestTrackerTest.java b/services/tests/servicestests/src/com/android/server/statusbar/TileRequestTrackerTest.java
new file mode 100644
index 0000000..dac6df9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/statusbar/TileRequestTrackerTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2021 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.statusbar;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class TileRequestTrackerTest {
+
+ private static final String TEST_PACKAGE = "test_pkg";
+ private static final String TEST_SERVICE = "test_svc";
+ private static final String TEST_SERVICE_OTHER = "test_svc_other";
+ private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PACKAGE,
+ TEST_SERVICE);
+ private static final ComponentName TEST_COMPONENT_OTHER = new ComponentName(TEST_PACKAGE,
+ TEST_SERVICE_OTHER);
+ private static final ComponentName TEST_COMPONENT_OTHER_PACKAGE = new ComponentName("other",
+ TEST_SERVICE);
+ private static final int USER_ID = 0;
+ private static final int USER_ID_OTHER = 10;
+ private static final int APP_UID = 12345;
+ private static final int USER_UID = UserHandle.getUid(USER_ID, APP_UID);
+ private static final int USER_OTHER_UID = UserHandle.getUid(USER_ID_OTHER, APP_UID);
+
+ @Rule
+ public final NoBroadcastContextWrapper mContext =
+ new NoBroadcastContextWrapper(InstrumentationRegistry.getContext());
+
+ private TileRequestTracker mTileRequestTracker;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mTileRequestTracker = new TileRequestTracker(mContext);
+ }
+
+ @Test
+ public void testBroadcastReceiverRegistered() {
+ NoBroadcastContextWrapper.BroadcastReceiverRegistration reg = getReceiverRegistration();
+
+ assertEquals(UserHandle.ALL, reg.mUser);
+ assertNull(reg.mBroadcastPermission);
+ assertNotNull(reg.mReceiver);
+
+ IntentFilter filter = reg.mIntentFilter;
+ assertEquals(2, filter.countActions());
+ assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_REMOVED));
+ assertTrue(filter.hasAction(Intent.ACTION_PACKAGE_DATA_CLEARED));
+ assertTrue(filter.hasDataScheme("package"));
+ }
+
+ @Test
+ public void testNoDenialsFromStart() {
+ // Certainly not an exhaustive test
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID_OTHER, TEST_COMPONENT));
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT_OTHER));
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID_OTHER, TEST_COMPONENT_OTHER));
+ }
+
+ @Test
+ public void testNoDenialBeforeMax() {
+ for (int i = 1; i < TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ }
+
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+ }
+
+ @Test
+ public void testDenialOnMax() {
+ for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ }
+ assertTrue(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+ }
+
+ @Test
+ public void testDenialPerUser() {
+ for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ }
+
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID_OTHER, TEST_COMPONENT));
+ }
+
+ @Test
+ public void testDenialPerComponent() {
+ for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ }
+
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT_OTHER));
+ }
+
+ @Test
+ public void testPackageUninstallRemovesDenials_allComponents() {
+ for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT_OTHER);
+ }
+
+ Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+ intent.putExtra(Intent.EXTRA_UID, USER_UID);
+ intent.setData(Uri.parse("package:" + TEST_PACKAGE));
+ getReceiverRegistration().mReceiver.onReceive(mContext, intent);
+
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT_OTHER));
+ }
+
+ @Test
+ public void testPackageUninstallRemoveDenials_differentUsers() {
+ for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ mTileRequestTracker.addDenial(USER_ID_OTHER, TEST_COMPONENT);
+ }
+
+ Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+ intent.putExtra(Intent.EXTRA_UID, USER_OTHER_UID);
+ intent.setData(Uri.parse("package:" + TEST_PACKAGE));
+ getReceiverRegistration().mReceiver.onReceive(mContext, intent);
+
+ // User 0 package was not removed
+ assertTrue(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+ // User 10 package was removed
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID_OTHER, TEST_COMPONENT));
+ }
+
+ @Test
+ public void testPackageUninstallRemoveDenials_differentPackages() {
+ for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT_OTHER_PACKAGE);
+ }
+
+ Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+ intent.putExtra(Intent.EXTRA_UID, USER_UID);
+ intent.setData(Uri.parse("package:" + TEST_PACKAGE));
+ getReceiverRegistration().mReceiver.onReceive(mContext, intent);
+
+ // Package TEST_PACKAGE removed
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+ // Package "other" not removed
+ assertTrue(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT_OTHER_PACKAGE));
+ }
+
+ @Test
+ public void testPackageUpdateDoesntRemoveDenials() {
+ for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ }
+
+ Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+ intent.putExtra(Intent.EXTRA_REPLACING, true);
+ intent.putExtra(Intent.EXTRA_UID, USER_UID);
+ intent.setData(Uri.parse("package:" + TEST_PACKAGE));
+ getReceiverRegistration().mReceiver.onReceive(mContext, intent);
+
+ assertTrue(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+ }
+
+ @Test
+ public void testClearPackageDataRemovesDenials() {
+ for (int i = 1; i <= TileRequestTracker.MAX_NUM_DENIALS; i++) {
+ mTileRequestTracker.addDenial(USER_ID, TEST_COMPONENT);
+ }
+
+ Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED);
+ intent.putExtra(Intent.EXTRA_UID, USER_UID);
+ intent.setData(Uri.parse("package:" + TEST_PACKAGE));
+ getReceiverRegistration().mReceiver.onReceive(mContext, intent);
+
+ assertFalse(mTileRequestTracker.shouldBeDenied(USER_ID, TEST_COMPONENT));
+ }
+
+ private NoBroadcastContextWrapper.BroadcastReceiverRegistration getReceiverRegistration() {
+ assertEquals(1, mContext.mRegistrationList.size());
+ return mContext.mRegistrationList.get(0);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index 4dcd633..add4cda 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -62,12 +62,15 @@
private static final String LOG_TAG = "SystemConfigTest";
private SystemConfig mSysConfig;
+ private File mFooJar;
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
@Before
public void setUp() throws Exception {
mSysConfig = new SystemConfigTestClass();
+ mFooJar = createTempFile(
+ mTemporaryFolder.getRoot().getCanonicalFile(), "foo.jar", "JAR");
}
/**
@@ -340,7 +343,7 @@
"<permissions>\n"
+ " <library \n"
+ " name=\"foo\"\n"
- + " file=\"foo.jar\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ " on-bootclasspath-before=\"10\"\n"
+ " on-bootclasspath-since=\"20\"\n"
+ " />\n\n"
@@ -362,7 +365,7 @@
"<permissions>\n"
+ " <updatable-library \n"
+ " name=\"foo\"\n"
- + " file=\"foo.jar\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ " on-bootclasspath-before=\"10\"\n"
+ " on-bootclasspath-since=\"20\"\n"
+ " />\n\n"
@@ -384,7 +387,7 @@
"<permissions>\n"
+ " <library \n"
+ " name=\"foo\"\n"
- + " file=\"foo.jar\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ " min-device-sdk=\"30\"\n"
+ " />\n\n"
+ " </permissions>";
@@ -402,7 +405,7 @@
"<permissions>\n"
+ " <library \n"
+ " name=\"foo\"\n"
- + " file=\"foo.jar\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ " min-device-sdk=\"" + Build.VERSION.SDK_INT + "\"\n"
+ " />\n\n"
+ " </permissions>";
@@ -420,7 +423,7 @@
"<permissions>\n"
+ " <library \n"
+ " name=\"foo\"\n"
- + " file=\"foo.jar\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ " min-device-sdk=\"" + (Build.VERSION.SDK_INT + 1) + "\"\n"
+ " />\n\n"
+ " </permissions>";
@@ -438,7 +441,7 @@
"<permissions>\n"
+ " <library \n"
+ " name=\"foo\"\n"
- + " file=\"foo.jar\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ " max-device-sdk=\"30\"\n"
+ " />\n\n"
+ " </permissions>";
@@ -456,7 +459,7 @@
"<permissions>\n"
+ " <library \n"
+ " name=\"foo\"\n"
- + " file=\"foo.jar\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ " max-device-sdk=\"" + Build.VERSION.SDK_INT + "\"\n"
+ " />\n\n"
+ " </permissions>";
@@ -474,7 +477,7 @@
"<permissions>\n"
+ " <library \n"
+ " name=\"foo\"\n"
- + " file=\"foo.jar\"\n"
+ + " file=\"" + mFooJar + "\"\n"
+ " max-device-sdk=\"" + (Build.VERSION.SDK_INT + 1) + "\"\n"
+ " />\n\n"
+ " </permissions>";
@@ -507,7 +510,7 @@
* @param folder pre-existing subdirectory of mTemporaryFolder to put the file
* @param fileName name of the file (e.g. filename.xml) to create
* @param contents contents to write to the file
- * @return the folder containing the newly created file (not the file itself!)
+ * @return the newly created file
*/
private File createTempFile(File folder, String fileName, String contents)
throws IOException {
@@ -523,13 +526,13 @@
Log.d(LOG_TAG, input.nextLine());
}
- return folder;
+ return file;
}
private void assertFooIsOnlySharedLibrary() {
assertThat(mSysConfig.getSharedLibraries().size()).isEqualTo(1);
SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo");
assertThat(entry.name).isEqualTo("foo");
- assertThat(entry.filename).isEqualTo("foo.jar");
+ assertThat(entry.filename).isEqualTo(mFooJar.toString());
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index c0959d3..2ea7fda 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -22,6 +22,9 @@
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
@@ -92,6 +95,7 @@
final ActivityRecord activity = createActivityRecord(dc);
mDc.prepareAppTransition(TRANSIT_OPEN);
+ mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE);
mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY);
mDc.mOpeningApps.add(activity);
assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
@@ -102,6 +106,22 @@
}
@Test
+ public void testKeyguardUnoccludeOcclude() {
+ final DisplayContent dc = createNewDisplay(Display.STATE_ON);
+ final ActivityRecord activity = createActivityRecord(dc);
+
+ mDc.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE);
+ mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE);
+ mDc.mOpeningApps.add(activity);
+ assertEquals(TRANSIT_NONE,
+ AppTransitionController.getTransitCompatType(mDc.mAppTransition,
+ mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
+ mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
+ null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
+
+ }
+
+ @Test
public void testKeyguardKeep() {
final DisplayContent dc = createNewDisplay(Display.STATE_ON);
final ActivityRecord activity = createActivityRecord(dc);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 1fab89e..f728324 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3811,14 +3811,21 @@
}
/**
- * Returns the phone number for the given {@code subId} and {@code source},
+ * Returns the phone number for the given {@code subscriptionId} and {@code source},
* or an empty string if not available.
*
- * <p>Requires Permission:
- * {@link android.Manifest.permission#READ_PHONE_NUMBERS READ_PHONE_NUMBERS}, or
- * READ_PRIVILEGED_PHONE_STATE permission (can only be granted to apps preloaded on device),
- * or that the calling app has carrier privileges
- * (see {@link TelephonyManager#hasCarrierPrivileges}).
+ * <p>General apps that need to know the phone number should use {@link #getPhoneNumber(int)}
+ * instead. This API may be suitable specific apps that needs to know the phone number from
+ * a specific source. For example, a carrier app needs to know exactly what's on
+ * {@link #PHONE_NUMBER_SOURCE_UICC UICC} and decide if the previously set phone number
+ * of source {@link #PHONE_NUMBER_SOURCE_CARRIER carrier} should be updated.
+ *
+ * <p>Note the assumption is that one subscription (which usually means one SIM) has
+ * only one phone number. The multiple sources backup each other so hopefully at least one
+ * is availavle. For example, for a carrier that doesn't typically set phone numbers
+ * on {@link #PHONE_NUMBER_SOURCE_UICC UICC}, the source {@link #PHONE_NUMBER_SOURCE_IMS IMS}
+ * may provide one. Or, a carrier may decide to provide the phone number via source
+ * {@link #PHONE_NUMBER_SOURCE_CARRIER carrier} if neither source UICC nor IMS is available.
*
* @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID}
* for the default one.
@@ -3831,10 +3838,10 @@
* @see #PHONE_NUMBER_SOURCE_CARRIER
* @see #PHONE_NUMBER_SOURCE_IMS
*/
- @SuppressAutoDoc // No support for carrier privileges (b/72967236)
@RequiresPermission(anyOf = {
android.Manifest.permission.READ_PHONE_NUMBERS,
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ "carrier privileges",
})
@NonNull
public String getPhoneNumber(int subscriptionId, @PhoneNumberSource int source) {
@@ -3863,16 +3870,14 @@
* Returns the phone number for the given {@code subId}, or an empty string if
* not available.
*
+ * <p>This API is suitable for general apps that needs to know the phone number.
+ * For specific apps that needs to know the phone number provided by a specific source,
+ * {@link #getPhoneNumber(int, int)} may be suitable.
+ *
* <p>This API is built up on {@link #getPhoneNumber(int, int)}, but picks
* from available sources in the following order: {@link #PHONE_NUMBER_SOURCE_CARRIER}
* > {@link #PHONE_NUMBER_SOURCE_UICC} > {@link #PHONE_NUMBER_SOURCE_IMS}.
*
- * <p>Requires Permission:
- * {@link android.Manifest.permission#READ_PHONE_NUMBERS READ_PHONE_NUMBERS}, or
- * READ_PRIVILEGED_PHONE_STATE permission (can only be granted to apps preloaded on device),
- * or that the calling app has carrier privileges
- * (see {@link TelephonyManager#hasCarrierPrivileges}).
- *
* @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID}
* for the default one.
* @return the phone number, or an empty string if not available.
@@ -3880,10 +3885,10 @@
* @throws SecurityException if the caller doesn't have permissions required.
* @see #getPhoneNumber(int, int)
*/
- @SuppressAutoDoc // No support for carrier privileges (b/72967236)
@RequiresPermission(anyOf = {
android.Manifest.permission.READ_PHONE_NUMBERS,
android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ "carrier privileges",
})
@NonNull
public String getPhoneNumber(int subscriptionId) {
@@ -3908,8 +3913,8 @@
* {@link #PHONE_NUMBER_SOURCE_CARRIER carrier}.
* Sets an empty string to remove the previously set phone number.
*
- * <p>Requires Permission: the calling app has carrier privileges
- * (see {@link TelephonyManager#hasCarrierPrivileges}).
+ * <p>The API is suitable for carrier apps to provide a phone number, for example when
+ * it's not possible to update {@link #PHONE_NUMBER_SOURCE_UICC UICC} directly.
*
* @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID}
* for the default one.
@@ -3918,6 +3923,7 @@
* @throws NullPointerException if {@code number} is {@code null}.
* @throws SecurityException if the caller doesn't have permissions required.
*/
+ @RequiresPermission("carrier privileges")
public void setCarrierPhoneNumber(int subscriptionId, @NonNull String number) {
if (subscriptionId == DEFAULT_SUBSCRIPTION_ID) {
subscriptionId = getDefaultSubscriptionId();
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 5f21e87..0267b68 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -117,6 +117,7 @@
import com.android.internal.telephony.ISub;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.IUpdateAvailableNetworksCallback;
+import com.android.internal.telephony.IccLogicalChannelRequest;
import com.android.internal.telephony.OperatorInfo;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.RILConstants;
@@ -6774,8 +6775,13 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.iccOpenLogicalChannelByPort(slotIndex, DEFAULT_PORT_INDEX,
- getOpPackageName(), aid, p2);
+ IccLogicalChannelRequest request = new IccLogicalChannelRequest();
+ request.slotIndex = slotIndex;
+ request.aid = aid;
+ request.p2 = p2;
+ request.callingPackage = getOpPackageName();
+ request.binder = new Binder();
+ return telephony.iccOpenLogicalChannel(request);
}
} catch (RemoteException ex) {
} catch (NullPointerException ex) {
@@ -6842,8 +6848,15 @@
public IccOpenLogicalChannelResponse iccOpenLogicalChannel(int subId, String AID, int p2) {
try {
ITelephony telephony = getITelephony();
- if (telephony != null)
- return telephony.iccOpenLogicalChannel(subId, getOpPackageName(), AID, p2);
+ if (telephony != null) {
+ IccLogicalChannelRequest request = new IccLogicalChannelRequest();
+ request.subId = subId;
+ request.callingPackage = getOpPackageName();
+ request.aid = AID;
+ request.p2 = p2;
+ request.binder = new Binder();
+ return telephony.iccOpenLogicalChannel(request);
+ }
} catch (RemoteException ex) {
} catch (NullPointerException ex) {
}
@@ -6873,8 +6886,10 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- return telephony.iccCloseLogicalChannelByPort(slotIndex, DEFAULT_PORT_INDEX,
- channel);
+ IccLogicalChannelRequest request = new IccLogicalChannelRequest();
+ request.slotIndex = slotIndex;
+ request.channel = channel;
+ return telephony.iccCloseLogicalChannel(request);
}
} catch (RemoteException ex) {
} catch (NullPointerException ex) {
@@ -6917,8 +6932,12 @@
public boolean iccCloseLogicalChannel(int subId, int channel) {
try {
ITelephony telephony = getITelephony();
- if (telephony != null)
- return telephony.iccCloseLogicalChannel(subId, channel);
+ if (telephony != null) {
+ IccLogicalChannelRequest request = new IccLogicalChannelRequest();
+ request.subId = subId;
+ request.channel = channel;
+ return telephony.iccCloseLogicalChannel(request);
+ }
} catch (RemoteException ex) {
} catch (NullPointerException ex) {
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 00b57e6..d5c9ec4 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -67,6 +67,7 @@
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.internal.telephony.IBooleanConsumer;
import com.android.internal.telephony.ICallForwardingInfoCallback;
+import com.android.internal.telephony.IccLogicalChannelRequest;
import com.android.internal.telephony.IImsStateCallback;
import com.android.internal.telephony.IIntegerConsumer;
import com.android.internal.telephony.INumberVerificationCallback;
@@ -584,59 +585,24 @@
void setCellInfoListRate(int rateInMillis);
/**
- * Opens a logical channel to the ICC card using the physical slot index and port index.
- *
- * Input parameters equivalent to TS 27.007 AT+CCHO command.
- *
- * @param slotIndex The physical slot index of the target ICC card
- * @param portIndex The unique index referring to a port belonging to the SIM slot
- * @param callingPackage the name of the package making the call.
- * @param AID Application id. See ETSI 102.221 and 101.220.
- * @param p2 P2 parameter (described in ISO 7816-4).
- * @return an IccOpenLogicalChannelResponse object.
- */
- IccOpenLogicalChannelResponse iccOpenLogicalChannelByPort(
- int slotIndex, int portIndex, String callingPackage, String AID, int p2);
-
- /**
* Opens a logical channel to the ICC card.
*
* Input parameters equivalent to TS 27.007 AT+CCHO command.
*
- * @param subId The subscription to use.
- * @param callingPackage the name of the package making the call.
- * @param AID Application id. See ETSI 102.221 and 101.220.
- * @param p2 P2 parameter (described in ISO 7816-4).
+ * @param request the parcelable used to indicate how to open the logical channel.
* @return an IccOpenLogicalChannelResponse object.
*/
- IccOpenLogicalChannelResponse iccOpenLogicalChannel(
- int subId, String callingPackage, String AID, int p2);
-
- /**
- * Closes a previously opened logical channel to the ICC card using the physical slot index and port index.
- *
- * Input parameters equivalent to TS 27.007 AT+CCHC command.
- *
- * @param slotIndex The physical slot index of the target ICC card
- * @param portIndex The unique index referring to a port belonging to the SIM slot
- * @param channel is the channel id to be closed as returned by a
- * successful iccOpenLogicalChannel.
- * @return true if the channel was closed successfully.
- */
- boolean iccCloseLogicalChannelByPort(int slotIndex, int portIndex, int channel);
+ IccOpenLogicalChannelResponse iccOpenLogicalChannel(in IccLogicalChannelRequest request);
/**
* Closes a previously opened logical channel to the ICC card.
*
* Input parameters equivalent to TS 27.007 AT+CCHC command.
*
- * @param subId The subscription to use.
- * @param channel is the channel id to be closed as returned by a
- * successful iccOpenLogicalChannel.
+ * @param request the parcelable used to indicate how to close the logical channel.
* @return true if the channel was closed successfully.
*/
- @UnsupportedAppUsage(trackingBug = 171933273)
- boolean iccCloseLogicalChannel(int subId, int channel);
+ boolean iccCloseLogicalChannel(in IccLogicalChannelRequest request);
/**
* Transmit an APDU to the ICC card over a logical channel using the physical slot index and port index.
diff --git a/telephony/java/com/android/internal/telephony/IccLogicalChannelRequest.aidl b/telephony/java/com/android/internal/telephony/IccLogicalChannelRequest.aidl
new file mode 100644
index 0000000..a84e752
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IccLogicalChannelRequest.aidl
@@ -0,0 +1,52 @@
+/*
+** Copyright 2007, 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.telephony;
+
+import android.os.IBinder;
+
+/**
+ * A request to open or close a logical channel to the ICC card.
+ *
+ * @hide
+ */
+@JavaDerive(toString=true, equals=true)
+parcelable IccLogicalChannelRequest {
+
+ /** Subscription id. */
+ int subId = -1;
+
+ /** Physical slot index of the ICC card. */
+ int slotIndex = -1;
+
+ /** The unique index referring to a port belonging to the ICC card slot. */
+ int portIndex = 0;
+
+ /** Package name for the calling app, used only when open channel. */
+ @nullable String callingPackage;
+
+ /** Application id, used only when open channel. */
+ @nullable String aid;
+
+ /** The P2 parameter described in ISO 7816-4, used only when open channel. */
+ int p2 = 0;
+
+ /** Channel number */
+ int channel = -1;
+
+ /** A IBinder object for server side to check if the request client is still living. */
+ @nullable IBinder binder;
+}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index 8a0af2d..5628321 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -179,7 +179,7 @@
doReturn(mUnderlyingNetworkController)
.when(mDeps)
- .newUnderlyingNetworkController(any(), any(), any(), any());
+ .newUnderlyingNetworkController(any(), any(), any(), any(), any());
doReturn(mWakeLock)
.when(mDeps)
.newWakeLock(eq(mContext), eq(PowerManager.PARTIAL_WAKE_LOCK), any());
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
new file mode 100644
index 0000000..2e1aab6
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkPriorityClassifierTest.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2021 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.vcn.routeselection;
+
+import static android.net.vcn.VcnUnderlyingNetworkPriority.NETWORK_QUALITY_OK;
+
+import static com.android.server.vcn.VcnTestUtils.setupSystemService;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.PRIORITY_ANY;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.calculatePriorityClass;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesCellPriorityRule;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesPriorityRule;
+import static com.android.server.vcn.routeselection.NetworkPriorityClassifier.checkMatchesWifiPriorityRule;
+import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnCellUnderlyingNetworkPriority;
+import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnManager;
+import android.net.vcn.VcnWifiUnderlyingNetworkPriority;
+import android.os.ParcelUuid;
+import android.os.PersistableBundle;
+import android.os.test.TestLooper;
+import android.telephony.TelephonyManager;
+import android.util.ArraySet;
+
+import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import com.android.server.vcn.VcnContext;
+import com.android.server.vcn.VcnNetworkProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Set;
+import java.util.UUID;
+
+public class NetworkPriorityClassifierTest {
+ private static final String SSID = "TestWifi";
+ private static final String SSID_OTHER = "TestWifiOther";
+ private static final String PLMN_ID = "123456";
+ private static final String PLMN_ID_OTHER = "234567";
+
+ private static final int SUB_ID = 1;
+ private static final int WIFI_RSSI = -60;
+ private static final int WIFI_RSSI_HIGH = -50;
+ private static final int WIFI_RSSI_LOW = -80;
+ private static final int CARRIER_ID = 1;
+ private static final int CARRIER_ID_OTHER = 2;
+
+ private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0));
+
+ private static final NetworkCapabilities WIFI_NETWORK_CAPABILITIES =
+ new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .setSignalStrength(WIFI_RSSI)
+ .setSsid(SSID)
+ .build();
+
+ private static final TelephonyNetworkSpecifier TEL_NETWORK_SPECIFIER =
+ new TelephonyNetworkSpecifier.Builder().setSubscriptionId(SUB_ID).build();
+ private static final NetworkCapabilities CELL_NETWORK_CAPABILITIES =
+ new NetworkCapabilities.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .setSubscriptionIds(Set.of(SUB_ID))
+ .setNetworkSpecifier(TEL_NETWORK_SPECIFIER)
+ .build();
+
+ private static final LinkProperties LINK_PROPERTIES = getLinkPropertiesWithName("test_iface");
+
+ @Mock private Network mNetwork;
+ @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot;
+ @Mock private TelephonyManager mTelephonyManager;
+
+ private TestLooper mTestLooper;
+ private VcnContext mVcnContext;
+ private UnderlyingNetworkRecord mWifiNetworkRecord;
+ private UnderlyingNetworkRecord mCellNetworkRecord;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ final Context mockContext = mock(Context.class);
+ mTestLooper = new TestLooper();
+ mVcnContext =
+ spy(
+ new VcnContext(
+ mockContext,
+ mTestLooper.getLooper(),
+ mock(VcnNetworkProvider.class),
+ false /* isInTestMode */));
+ doNothing().when(mVcnContext).ensureRunningOnLooperThread();
+
+ mWifiNetworkRecord =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ WIFI_NETWORK_CAPABILITIES,
+ LINK_PROPERTIES,
+ false /* isBlocked */);
+
+ mCellNetworkRecord =
+ new UnderlyingNetworkRecord(
+ mNetwork,
+ CELL_NETWORK_CAPABILITIES,
+ LINK_PROPERTIES,
+ false /* isBlocked */);
+
+ setupSystemService(
+ mockContext, mTelephonyManager, Context.TELEPHONY_SERVICE, TelephonyManager.class);
+ when(mTelephonyManager.createForSubscriptionId(SUB_ID)).thenReturn(mTelephonyManager);
+ when(mTelephonyManager.getNetworkOperator()).thenReturn(PLMN_ID);
+ when(mTelephonyManager.getSimSpecificCarrierId()).thenReturn(CARRIER_ID);
+ }
+
+ @Test
+ public void testMatchWithoutNotMeteredBit() {
+ final VcnWifiUnderlyingNetworkPriority wifiNetworkPriority =
+ new VcnWifiUnderlyingNetworkPriority.Builder()
+ .setNetworkQuality(NETWORK_QUALITY_OK)
+ .setAllowMetered(false /* allowMetered */)
+ .build();
+
+ assertFalse(
+ checkMatchesPriorityRule(
+ mVcnContext,
+ wifiNetworkPriority,
+ mWifiNetworkRecord,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ null /* currentlySelecetd */,
+ null /* carrierConfig */));
+ }
+
+ private void verifyMatchWifi(
+ boolean isSelectedNetwork, PersistableBundle carrierConfig, boolean expectMatch) {
+ final VcnWifiUnderlyingNetworkPriority wifiNetworkPriority =
+ new VcnWifiUnderlyingNetworkPriority.Builder()
+ .setNetworkQuality(NETWORK_QUALITY_OK)
+ .setAllowMetered(true /* allowMetered */)
+ .build();
+ final UnderlyingNetworkRecord selectedNetworkRecord =
+ isSelectedNetwork ? mWifiNetworkRecord : null;
+ assertEquals(
+ expectMatch,
+ checkMatchesWifiPriorityRule(
+ wifiNetworkPriority,
+ mWifiNetworkRecord,
+ selectedNetworkRecord,
+ carrierConfig));
+ }
+
+ @Test
+ public void testMatchSelectedWifi() {
+ verifyMatchWifi(
+ true /* isSelectedNetwork */, null /* carrierConfig */, true /* expectMatch */);
+ }
+
+ @Test
+ public void testMatchSelectedWifiBelowRssiThreshold() {
+ final PersistableBundle carrierConfig = new PersistableBundle();
+ carrierConfig.putInt(
+ VcnManager.VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY, WIFI_RSSI_HIGH);
+ carrierConfig.putInt(
+ VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, WIFI_RSSI_HIGH);
+
+ verifyMatchWifi(true /* isSelectedNetwork */, carrierConfig, false /* expectMatch */);
+ }
+
+ @Test
+ public void testMatchUnselectedWifi() {
+ verifyMatchWifi(
+ false /* isSelectedNetwork */, null /* carrierConfig */, true /* expectMatch */);
+ }
+
+ @Test
+ public void testMatchUnselectedWifiBelowRssiThreshold() {
+ final PersistableBundle carrierConfig = new PersistableBundle();
+ carrierConfig.putInt(
+ VcnManager.VCN_NETWORK_SELECTION_WIFI_ENTRY_RSSI_THRESHOLD_KEY, WIFI_RSSI_HIGH);
+
+ verifyMatchWifi(false /* isSelectedNetwork */, carrierConfig, false /* expectMatch */);
+ }
+
+ private void verifyMatchWifiWithSsid(boolean useMatchedSsid, boolean expectMatch) {
+ final String nwPrioritySsid = useMatchedSsid ? SSID : SSID_OTHER;
+ final VcnWifiUnderlyingNetworkPriority wifiNetworkPriority =
+ new VcnWifiUnderlyingNetworkPriority.Builder()
+ .setNetworkQuality(NETWORK_QUALITY_OK)
+ .setAllowMetered(true /* allowMetered */)
+ .setSsid(nwPrioritySsid)
+ .build();
+
+ assertEquals(
+ expectMatch,
+ checkMatchesWifiPriorityRule(
+ wifiNetworkPriority,
+ mWifiNetworkRecord,
+ null /* currentlySelecetd */,
+ null /* carrierConfig */));
+ }
+
+ @Test
+ public void testMatchWifiWithSsid() {
+ verifyMatchWifiWithSsid(true /* useMatchedSsid */, true /* expectMatch */);
+ }
+
+ @Test
+ public void testMatchWifiFailWithWrongSsid() {
+ verifyMatchWifiWithSsid(false /* useMatchedSsid */, false /* expectMatch */);
+ }
+
+ private static VcnCellUnderlyingNetworkPriority.Builder getCellNetworkPriorityBuilder() {
+ return new VcnCellUnderlyingNetworkPriority.Builder()
+ .setNetworkQuality(NETWORK_QUALITY_OK)
+ .setAllowMetered(true /* allowMetered */)
+ .setAllowRoaming(true /* allowRoaming */);
+ }
+
+ @Test
+ public void testMatchMacroCell() {
+ assertTrue(
+ checkMatchesCellPriorityRule(
+ mVcnContext,
+ getCellNetworkPriorityBuilder().build(),
+ mCellNetworkRecord,
+ SUB_GROUP,
+ mSubscriptionSnapshot));
+ }
+
+ @Test
+ public void testMatchOpportunisticCell() {
+ final VcnCellUnderlyingNetworkPriority opportunisticCellNetworkPriority =
+ getCellNetworkPriorityBuilder()
+ .setRequireOpportunistic(true /* requireOpportunistic */)
+ .build();
+
+ when(mSubscriptionSnapshot.isOpportunistic(SUB_ID)).thenReturn(true);
+ when(mSubscriptionSnapshot.getAllSubIdsInGroup(SUB_GROUP)).thenReturn(new ArraySet<>());
+
+ assertTrue(
+ checkMatchesCellPriorityRule(
+ mVcnContext,
+ opportunisticCellNetworkPriority,
+ mCellNetworkRecord,
+ SUB_GROUP,
+ mSubscriptionSnapshot));
+ }
+
+ private void verifyMatchMacroCellWithAllowedPlmnIds(
+ boolean useMatchedPlmnId, boolean expectMatch) {
+ final String networkPriorityPlmnId = useMatchedPlmnId ? PLMN_ID : PLMN_ID_OTHER;
+ final VcnCellUnderlyingNetworkPriority networkPriority =
+ getCellNetworkPriorityBuilder()
+ .setAllowedPlmnIds(Set.of(networkPriorityPlmnId))
+ .build();
+
+ assertEquals(
+ expectMatch,
+ checkMatchesCellPriorityRule(
+ mVcnContext,
+ networkPriority,
+ mCellNetworkRecord,
+ SUB_GROUP,
+ mSubscriptionSnapshot));
+ }
+
+ @Test
+ public void testMatchMacroCellWithAllowedPlmnIds() {
+ verifyMatchMacroCellWithAllowedPlmnIds(true /* useMatchedPlmnId */, true /* expectMatch */);
+ }
+
+ @Test
+ public void testMatchMacroCellFailWithDisallowedPlmnIds() {
+ verifyMatchMacroCellWithAllowedPlmnIds(
+ false /* useMatchedPlmnId */, false /* expectMatch */);
+ }
+
+ private void verifyMatchMacroCellWithAllowedSpecificCarrierIds(
+ boolean useMatchedCarrierId, boolean expectMatch) {
+ final int networkPriorityCarrierId = useMatchedCarrierId ? CARRIER_ID : CARRIER_ID_OTHER;
+ final VcnCellUnderlyingNetworkPriority networkPriority =
+ getCellNetworkPriorityBuilder()
+ .setAllowedSpecificCarrierIds(Set.of(networkPriorityCarrierId))
+ .build();
+
+ assertEquals(
+ expectMatch,
+ checkMatchesCellPriorityRule(
+ mVcnContext,
+ networkPriority,
+ mCellNetworkRecord,
+ SUB_GROUP,
+ mSubscriptionSnapshot));
+ }
+
+ @Test
+ public void testMatchMacroCellWithAllowedSpecificCarrierIds() {
+ verifyMatchMacroCellWithAllowedSpecificCarrierIds(
+ true /* useMatchedCarrierId */, true /* expectMatch */);
+ }
+
+ @Test
+ public void testMatchMacroCellFailWithDisallowedSpecificCarrierIds() {
+ verifyMatchMacroCellWithAllowedSpecificCarrierIds(
+ false /* useMatchedCarrierId */, false /* expectMatch */);
+ }
+
+ @Test
+ public void testMatchWifiFailWithoutNotRoamingBit() {
+ final VcnCellUnderlyingNetworkPriority networkPriority =
+ getCellNetworkPriorityBuilder().setAllowRoaming(false /* allowRoaming */).build();
+
+ assertFalse(
+ checkMatchesCellPriorityRule(
+ mVcnContext,
+ networkPriority,
+ mCellNetworkRecord,
+ SUB_GROUP,
+ mSubscriptionSnapshot));
+ }
+
+ private void verifyCalculatePriorityClass(
+ UnderlyingNetworkRecord networkRecord, int expectedIndex) {
+ final int priorityIndex =
+ calculatePriorityClass(
+ mVcnContext,
+ networkRecord,
+ VcnGatewayConnectionConfig.DEFAULT_UNDERLYING_NETWORK_PRIORITIES,
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ null /* currentlySelected */,
+ null /* carrierConfig */);
+
+ assertEquals(expectedIndex, priorityIndex);
+ }
+
+ @Test
+ public void testCalculatePriorityClass() throws Exception {
+ verifyCalculatePriorityClass(mCellNetworkRecord, 2);
+ }
+
+ @Test
+ public void testCalculatePriorityClassFailToMatchAny() throws Exception {
+ final NetworkCapabilities nc =
+ new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .setSignalStrength(WIFI_RSSI_LOW)
+ .setSsid(SSID)
+ .build();
+ final UnderlyingNetworkRecord wifiNetworkRecord =
+ new UnderlyingNetworkRecord(mNetwork, nc, LINK_PROPERTIES, false /* isBlocked */);
+
+ verifyCalculatePriorityClass(wifiNetworkRecord, PRIORITY_ANY);
+ }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
index c954cb8..fad9669 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
@@ -42,6 +42,7 @@
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.TelephonyNetworkSpecifier;
+import android.net.vcn.VcnGatewayConnectionConfigTest;
import android.os.ParcelUuid;
import android.os.test.TestLooper;
import android.telephony.CarrierConfigManager;
@@ -145,7 +146,11 @@
mUnderlyingNetworkController =
new UnderlyingNetworkController(
- mVcnContext, SUB_GROUP, mSubscriptionSnapshot, mNetworkControllerCb);
+ mVcnContext,
+ VcnGatewayConnectionConfigTest.buildTestConfig(),
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mNetworkControllerCb);
}
private void resetVcnContext() {
@@ -153,7 +158,8 @@
doNothing().when(mVcnContext).ensureRunningOnLooperThread();
}
- private static LinkProperties getLinkPropertiesWithName(String iface) {
+ // Package private for use in NetworkPriorityClassifierTest
+ static LinkProperties getLinkPropertiesWithName(String iface) {
LinkProperties linkProperties = new LinkProperties();
linkProperties.setInterfaceName(iface);
return linkProperties;
@@ -182,7 +188,11 @@
true /* isInTestMode */);
new UnderlyingNetworkController(
- vcnContext, SUB_GROUP, mSubscriptionSnapshot, mNetworkControllerCb);
+ vcnContext,
+ VcnGatewayConnectionConfigTest.buildTestConfig(),
+ SUB_GROUP,
+ mSubscriptionSnapshot,
+ mNetworkControllerCb);
verify(cm)
.registerNetworkCallback(
@@ -345,6 +355,17 @@
return verifyRegistrationOnAvailableAndGetCallback(INITIAL_NETWORK_CAPABILITIES);
}
+ private static NetworkCapabilities buildResponseNwCaps(
+ NetworkCapabilities requestNetworkCaps, Set<Integer> netCapsSubIds) {
+ final TelephonyNetworkSpecifier telephonyNetworkSpecifier =
+ new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(netCapsSubIds.iterator().next())
+ .build();
+ return new NetworkCapabilities.Builder(requestNetworkCaps)
+ .setNetworkSpecifier(telephonyNetworkSpecifier)
+ .build();
+ }
+
private UnderlyingNetworkListener verifyRegistrationOnAvailableAndGetCallback(
NetworkCapabilities networkCapabilities) {
verify(mConnectivityManager)
@@ -355,14 +376,17 @@
UnderlyingNetworkListener cb = mUnderlyingNetworkListenerCaptor.getValue();
cb.onAvailable(mNetwork);
- cb.onCapabilitiesChanged(mNetwork, networkCapabilities);
+
+ final NetworkCapabilities responseNetworkCaps =
+ buildResponseNwCaps(networkCapabilities, INITIAL_SUB_IDS);
+ cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
cb.onLinkPropertiesChanged(mNetwork, INITIAL_LINK_PROPERTIES);
cb.onBlockedStatusChanged(mNetwork, false /* isFalse */);
UnderlyingNetworkRecord expectedRecord =
new UnderlyingNetworkRecord(
mNetwork,
- networkCapabilities,
+ responseNetworkCaps,
INITIAL_LINK_PROPERTIES,
false /* isBlocked */);
verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
@@ -373,12 +397,14 @@
public void testRecordTrackerCallbackNotifiedForNetworkCapabilitiesChange() {
UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
- cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES);
+ final NetworkCapabilities responseNetworkCaps =
+ buildResponseNwCaps(UPDATED_NETWORK_CAPABILITIES, UPDATED_SUB_IDS);
+ cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
UnderlyingNetworkRecord expectedRecord =
new UnderlyingNetworkRecord(
mNetwork,
- UPDATED_NETWORK_CAPABILITIES,
+ responseNetworkCaps,
INITIAL_LINK_PROPERTIES,
false /* isBlocked */);
verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
@@ -393,7 +419,7 @@
UnderlyingNetworkRecord expectedRecord =
new UnderlyingNetworkRecord(
mNetwork,
- INITIAL_NETWORK_CAPABILITIES,
+ buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS),
UPDATED_LINK_PROPERTIES,
false /* isBlocked */);
verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
@@ -403,19 +429,21 @@
public void testRecordTrackerCallbackNotifiedForNetworkSuspended() {
UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
- cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES);
+ final NetworkCapabilities responseNetworkCaps =
+ buildResponseNwCaps(SUSPENDED_NETWORK_CAPABILITIES, UPDATED_SUB_IDS);
+ cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
UnderlyingNetworkRecord expectedRecord =
new UnderlyingNetworkRecord(
mNetwork,
- SUSPENDED_NETWORK_CAPABILITIES,
+ responseNetworkCaps,
INITIAL_LINK_PROPERTIES,
false /* isBlocked */);
verify(mNetworkControllerCb, times(1))
.onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
// onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't
// change.
- cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES);
+ cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
verify(mNetworkControllerCb, times(1))
.onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
}
@@ -425,19 +453,21 @@
UnderlyingNetworkListener cb =
verifyRegistrationOnAvailableAndGetCallback(SUSPENDED_NETWORK_CAPABILITIES);
- cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES);
+ final NetworkCapabilities responseNetworkCaps =
+ buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS);
+ cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
UnderlyingNetworkRecord expectedRecord =
new UnderlyingNetworkRecord(
mNetwork,
- INITIAL_NETWORK_CAPABILITIES,
+ responseNetworkCaps,
INITIAL_LINK_PROPERTIES,
false /* isBlocked */);
verify(mNetworkControllerCb, times(1))
.onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
// onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't
// change.
- cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES);
+ cb.onCapabilitiesChanged(mNetwork, responseNetworkCaps);
verify(mNetworkControllerCb, times(1))
.onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
}
@@ -451,7 +481,7 @@
UnderlyingNetworkRecord expectedRecord =
new UnderlyingNetworkRecord(
mNetwork,
- INITIAL_NETWORK_CAPABILITIES,
+ buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS),
INITIAL_LINK_PROPERTIES,
true /* isBlocked */);
verify(mNetworkControllerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
@@ -470,7 +500,8 @@
public void testRecordTrackerCallbackIgnoresDuplicateRecord() {
UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
- cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES);
+ cb.onCapabilitiesChanged(
+ mNetwork, buildResponseNwCaps(INITIAL_NETWORK_CAPABILITIES, INITIAL_SUB_IDS));
// Verify no more calls to the UnderlyingNetworkControllerCallback when the
// UnderlyingNetworkRecord does not actually change
@@ -482,7 +513,8 @@
UnderlyingNetworkListener cb = verifyRegistrationOnAvailableAndGetCallback();
mUnderlyingNetworkController.teardown();
- cb.onCapabilitiesChanged(mNetwork, UPDATED_NETWORK_CAPABILITIES);
+ cb.onCapabilitiesChanged(
+ mNetwork, buildResponseNwCaps(UPDATED_NETWORK_CAPABILITIES, INITIAL_SUB_IDS));
// Verify that the only call was during onAvailable()
verify(mNetworkControllerCb, times(1)).onSelectedUnderlyingNetworkChanged(any());