diff options
325 files changed, 10370 insertions, 3577 deletions
diff --git a/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java b/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java index 973e996a17c3..762b16c23ef7 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.content.Context; 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 class SQLiteDatabasePerfTest { 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 @@ public class SQLiteDatabasePerfTest { 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 @@ public class SQLiteDatabasePerfTest { } } + /** + * 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 @@ public class SQLiteDatabasePerfTest { } } + /** + * 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 @@ public class SQLiteDatabasePerfTest { 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 @@ public class SQLiteDatabasePerfTest { 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 @@ public class SQLiteDatabasePerfTest { mDatabase.setTransactionSuccessful(); mDatabase.endTransaction(); } + + private void recreateTestDatabase(String journalMode, String syncMode) { + mDatabase.close(); + mContext.deleteDatabase(DB_NAME); + createOrOpenTestDatabase(journalMode, syncMode); + } } diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 14cf6b46cdb1..c706a3a54c38 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -3343,7 +3343,7 @@ public class DeviceIdleController extends SystemService if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE"); resetLightIdleManagementLocked(); scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT, - mConstants.FLEX_TIME_SHORT); + mConstants.FLEX_TIME_SHORT, true); EventLogTags.writeDeviceIdleLight(mLightState, "no activity"); } } @@ -3424,7 +3424,7 @@ public class DeviceIdleController extends SystemService mLightState = LIGHT_STATE_PRE_IDLE; EventLogTags.writeDeviceIdleLight(mLightState, reason); scheduleLightAlarmLocked(mConstants.LIGHT_PRE_IDLE_TIMEOUT, - mConstants.FLEX_TIME_SHORT); + mConstants.FLEX_TIME_SHORT, true); break; } // Nothing active, fall through to immediately idle. @@ -3443,7 +3443,7 @@ public class DeviceIdleController extends SystemService } } mMaintenanceStartTime = 0; - scheduleLightAlarmLocked(mNextLightIdleDelay, mNextLightIdleDelayFlex); + scheduleLightAlarmLocked(mNextLightIdleDelay, mNextLightIdleDelayFlex, false); mNextLightIdleDelay = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT, (long) (mNextLightIdleDelay * mConstants.LIGHT_IDLE_FACTOR)); mNextLightIdleDelayFlex = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT_FLEX, @@ -3467,7 +3467,7 @@ public class DeviceIdleController extends SystemService } else if (mCurLightIdleBudget > mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET) { mCurLightIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET; } - scheduleLightAlarmLocked(mCurLightIdleBudget, mConstants.FLEX_TIME_SHORT); + scheduleLightAlarmLocked(mCurLightIdleBudget, mConstants.FLEX_TIME_SHORT, true); if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE."); mLightState = LIGHT_STATE_IDLE_MAINTENANCE; @@ -3478,7 +3478,8 @@ public class DeviceIdleController extends SystemService // We'd like to do maintenance, but currently don't have network // connectivity... let's try to wait until the network comes back. // We'll only wait for another full idle period, however, and then give up. - scheduleLightAlarmLocked(mNextLightIdleDelay, mNextLightIdleDelayFlex / 2); + scheduleLightAlarmLocked(mNextLightIdleDelay, + mNextLightIdleDelayFlex / 2, true); if (DEBUG) Slog.d(TAG, "Moved to LIGHT_WAITING_FOR_NETWORK."); mLightState = LIGHT_STATE_WAITING_FOR_NETWORK; EventLogTags.writeDeviceIdleLight(mLightState, reason); @@ -4034,17 +4035,22 @@ public class DeviceIdleController extends SystemService } @GuardedBy("this") - void scheduleLightAlarmLocked(long delay, long flex) { + void scheduleLightAlarmLocked(long delay, long flex, boolean wakeup) { if (DEBUG) { Slog.d(TAG, "scheduleLightAlarmLocked(" + delay - + (mConstants.USE_WINDOW_ALARMS ? "/" + flex : "") + ")"); + + (mConstants.USE_WINDOW_ALARMS ? "/" + flex : "") + + ", wakeup=" + wakeup + ")"); } mNextLightAlarmTime = SystemClock.elapsedRealtime() + delay; if (mConstants.USE_WINDOW_ALARMS) { - mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, mNextLightAlarmTime, flex, + mAlarmManager.setWindow( + wakeup ? AlarmManager.ELAPSED_REALTIME_WAKEUP : AlarmManager.ELAPSED_REALTIME, + mNextLightAlarmTime, flex, "DeviceIdleController.light", mLightAlarmListener, mHandler); } else { - mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, mNextLightAlarmTime, + mAlarmManager.set( + wakeup ? AlarmManager.ELAPSED_REALTIME_WAKEUP : AlarmManager.ELAPSED_REALTIME, + mNextLightAlarmTime, "DeviceIdleController.light", mLightAlarmListener, mHandler); } } diff --git a/core/api/current.txt b/core/api/current.txt index 44f86131ed54..cc8b638d2e59 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -6749,6 +6749,7 @@ package android.app { field public static final int START_STICKY = 1; // 0x1 field public static final int START_STICKY_COMPATIBILITY = 0; // 0x0 field public static final int STOP_FOREGROUND_DETACH = 2; // 0x2 + field @Deprecated public static final int STOP_FOREGROUND_LEGACY = 0; // 0x0 field public static final int STOP_FOREGROUND_REMOVE = 1; // 0x1 } @@ -11442,6 +11443,7 @@ package android.content { field public static final String ACTION_VIEW_LOCUS = "android.intent.action.VIEW_LOCUS"; field @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE) public static final String ACTION_VIEW_PERMISSION_USAGE = "android.intent.action.VIEW_PERMISSION_USAGE"; field @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE) public static final String ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD = "android.intent.action.VIEW_PERMISSION_USAGE_FOR_PERIOD"; + field @RequiresPermission("android.permission.MANAGE_SENSOR_PRIVACY") public static final String ACTION_VIEW_SAFETY_HUB = "android.intent.action.VIEW_SAFETY_HUB"; field public static final String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND"; field @Deprecated public static final String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED"; field public static final String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH"; @@ -14374,11 +14376,21 @@ package android.database.sqlite { 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 { @@ -32491,6 +32503,7 @@ package android.os { 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"; @@ -32546,6 +32559,7 @@ package android.os { 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"; @@ -42978,8 +42992,8 @@ package android.telephony { 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); @@ -42991,7 +43005,7 @@ package android.telephony { 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); @@ -44676,6 +44690,7 @@ package android.text { 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 @@ -45805,8 +45820,10 @@ package android.text.style { 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); @@ -45824,6 +45841,17 @@ package android.text.style { method public void writeToParcel(android.os.Parcel, int); } + public final class SuggestionRangeSpan extends android.text.style.CharacterStyle implements android.text.ParcelableSpan { + ctor public SuggestionRangeSpan(); + method public int describeContents(); + method public int getBackgroundColor(); + method public int getSpanTypeId(); + method public void setBackgroundColor(int); + method public void updateDrawState(@NonNull android.text.TextPaint); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.text.style.SuggestionRangeSpan> CREATOR; + } + public class SuggestionSpan extends android.text.style.CharacterStyle implements android.text.ParcelableSpan { ctor public SuggestionSpan(android.content.Context, String[], int); ctor public SuggestionSpan(java.util.Locale, String[], int); @@ -51318,6 +51346,7 @@ package android.view.accessibility { 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(); @@ -51334,6 +51363,7 @@ package android.view.accessibility { 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 @@ -51349,6 +51379,10 @@ package android.view.accessibility { 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 0bbc80c55af6..b8ce02e72571 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -79,7 +79,7 @@ package android.content { } 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 90506531ad44..dd951b4e71d3 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -75,6 +75,7 @@ package android { 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 @@ package android.app.admin { } 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 @@ package android.app.admin { 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 @@ package android.app.admin { 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"; @@ -3296,6 +3314,7 @@ package android.hardware { method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int); method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void removeSensorPrivacyListener(int, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener); + method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, boolean); } public static interface SensorPrivacyManager.OnSensorPrivacyChangedListener { @@ -5401,17 +5420,21 @@ package android.media { 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); @@ -5419,7 +5442,10 @@ package android.media { 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); @@ -5439,6 +5465,7 @@ package android.media { 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 @@ -5458,6 +5485,15 @@ package android.media { 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); } @@ -7584,7 +7620,7 @@ package android.media.tv.tuner.frontend { 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(); } @@ -7592,7 +7628,7 @@ package android.media.tv.tuner.frontend { 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 6b4d773c01c9..aa791aadfdfe 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -437,7 +437,6 @@ package android.app { 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 @@ package android.app.admin { 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 @@ -1067,13 +1051,13 @@ package android.hardware { public final class SensorPrivacyManager { method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, int, boolean); - method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyForProfileGroup(int, int, boolean); } public static class SensorPrivacyManager.Sources { field public static final int DIALOG = 3; // 0x3 field public static final int OTHER = 5; // 0x5 field public static final int QS_TILE = 1; // 0x1 + field public static final int SAFETY_HUB = 6; // 0x6 field public static final int SETTINGS = 2; // 0x2 field public static final int SHELL = 4; // 0x4 } @@ -1445,6 +1429,8 @@ package android.media { 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 @@ package android.media { 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/Service.java b/core/java/android/app/Service.java index be6a31e7f145..7635138f6cdd 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -315,6 +315,16 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac private static final String TAG = "Service"; /** + * Selector for {@link #stopForeground(int)}: equivalent to passing {@code false} + * to the legacy API {@link #stopForeground(boolean)}. + * + * @deprecated Use {@link #STOP_FOREGROUND_DETACH} instead. The legacy + * behavior was inconsistent, leading to bugs around unpredictable results. + */ + @Deprecated + public static final int STOP_FOREGROUND_LEGACY = 0; + + /** * Selector for {@link #stopForeground(int)}: if supplied, the notification previously * supplied to {@link #startForeground} will be cancelled and removed from display. */ @@ -329,6 +339,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac /** @hide */ @IntDef(flag = false, prefix = { "STOP_FOREGROUND_" }, value = { + STOP_FOREGROUND_LEGACY, STOP_FOREGROUND_REMOVE, STOP_FOREGROUND_DETACH }) @@ -795,15 +806,17 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac * Legacy version of {@link #stopForeground(int)}. * @param removeNotification If true, the {@link #STOP_FOREGROUND_REMOVE} * selector will be passed to {@link #stopForeground(int)}; otherwise - * {@code zero} will be passed. + * {@link #STOP_FOREGROUND_LEGACY} will be passed. * @see #stopForeground(int) * @see #startForeground(int, Notification) * - * @deprecated use {@link #stopForeground(int)} instead. + * @deprecated call {@link #stopForeground(int)} and pass either + * {@link #STOP_FOREGROUND_REMOVE} or {@link #STOP_FOREGROUND_DETACH} + * explicitly instead. */ @Deprecated public final void stopForeground(boolean removeNotification) { - stopForeground(removeNotification ? STOP_FOREGROUND_REMOVE : 0); + stopForeground(removeNotification ? STOP_FOREGROUND_REMOVE : STOP_FOREGROUND_LEGACY); } /** diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 48ceef06ea98..2392c9aa185c 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -626,6 +626,9 @@ public class StatusBarManager { * 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 61bf9b308875..772492dbfd5e 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -1489,18 +1489,27 @@ public class WallpaperManager { 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 @@ public class WallpaperManager { 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 ee74bb875a35..ba2828370534 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2158,13 +2158,24 @@ public class DevicePolicyManager { /** * 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 @@ public class DevicePolicyManager { * * @hide */ - @TestApi + @SystemApi public static final int CODE_HAS_DEVICE_OWNER = 1; /** @@ -2186,7 +2197,7 @@ public class DevicePolicyManager { * * @hide */ - @TestApi + @SystemApi public static final int CODE_USER_HAS_PROFILE_OWNER = 2; /** @@ -2196,7 +2207,7 @@ public class DevicePolicyManager { * * @hide */ - @TestApi + @SystemApi public static final int CODE_USER_NOT_RUNNING = 3; /** @@ -2207,7 +2218,7 @@ public class DevicePolicyManager { * * @hide */ - @TestApi + @SystemApi public static final int CODE_USER_SETUP_COMPLETED = 4; /** @@ -2215,7 +2226,7 @@ public class DevicePolicyManager { * * @hide */ - @TestApi + @SystemApi public static final int CODE_NONSYSTEM_USER_EXISTS = 5; /** @@ -2223,7 +2234,7 @@ public class DevicePolicyManager { * * @hide */ - @TestApi + @SystemApi public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; /** @@ -2233,7 +2244,7 @@ public class DevicePolicyManager { * * @hide */ - @TestApi + @SystemApi public static final int CODE_NOT_SYSTEM_USER = 7; /** @@ -2244,7 +2255,7 @@ public class DevicePolicyManager { * * @hide */ - @TestApi + @SystemApi public static final int CODE_HAS_PAIRED = 8; /** @@ -2256,7 +2267,7 @@ public class DevicePolicyManager { * @see {@link PackageManager#FEATURE_MANAGED_USERS} * @hide */ - @TestApi + @SystemApi public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; /** @@ -2268,7 +2279,7 @@ public class DevicePolicyManager { * * @hide */ - @TestApi + @SystemApi public static final int CODE_SYSTEM_USER = 10; /** @@ -2279,7 +2290,7 @@ public class DevicePolicyManager { * * @hide */ - @TestApi + @SystemApi public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; /** @@ -2289,7 +2300,6 @@ public class DevicePolicyManager { * @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 @@ public class DevicePolicyManager { * * @hide */ - @TestApi + @SystemApi public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; /** @@ -2323,7 +2333,7 @@ public class DevicePolicyManager { * * @hide */ - @TestApi + @SystemApi public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; /** @@ -2334,8 +2344,8 @@ public class DevicePolicyManager { */ @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 @@ public class DevicePolicyManager { * @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 @@ public class DevicePolicyManager { * * @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 @@ public class DevicePolicyManager { } /** + * 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/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java index 0589f4a3b2bb..fb8a83bfc0ef 100644 --- a/core/java/android/app/slice/SliceProvider.java +++ b/core/java/android/app/slice/SliceProvider.java @@ -299,7 +299,7 @@ public abstract class SliceProvider extends ContentProvider { * @see #getCallingPackage() */ public @NonNull PendingIntent onCreatePermissionRequest(Uri sliceUri) { - return createPermissionIntent(getContext(), sliceUri, getCallingPackage()); + return createPermissionPendingIntent(getContext(), sliceUri, getCallingPackage()); } @Override @@ -508,7 +508,17 @@ public abstract class SliceProvider extends ContentProvider { /** * @hide */ - public static PendingIntent createPermissionIntent(Context context, Uri sliceUri, + public static PendingIntent createPermissionPendingIntent(Context context, Uri sliceUri, + String callingPackage) { + return PendingIntent.getActivity(context, 0, + createPermissionIntent(context, sliceUri, callingPackage), + PendingIntent.FLAG_IMMUTABLE); + } + + /** + * @hide + */ + public static Intent createPermissionIntent(Context context, Uri sliceUri, String callingPackage) { Intent intent = new Intent(SliceManager.ACTION_REQUEST_SLICE_PERMISSION); intent.setComponent(ComponentName.unflattenFromString(context.getResources().getString( @@ -518,8 +528,7 @@ public abstract class SliceProvider extends ContentProvider { // Unique pending intent. intent.setData(sliceUri.buildUpon().appendQueryParameter("package", callingPackage) .build()); - - return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE); + return intent; } /** diff --git a/core/java/android/companion/OWNERS b/core/java/android/companion/OWNERS index 54b35fc32f62..004f66caed7b 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/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 0aa442bfc503..dabc603bc47f 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -22,5 +22,16 @@ package android.companion.virtual; * @hide */ interface IVirtualDevice { + + /** + * Returns the association ID for this virtual device. + * + * @see AssociationInfo#getId() + */ + int getAssociationId(); + + /** + * Closes the virtual device and frees all associated resources. + */ void close(); } diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl index 91e717d719a9..2dfa2021fdfe 100644 --- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl +++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl @@ -25,5 +25,14 @@ import android.companion.virtual.IVirtualDevice; */ interface IVirtualDeviceManager { - IVirtualDevice createVirtualDevice(); + /** + * Creates a virtual device that can be used to create virtual displays and stream contents. + * + * @param token The binder token created by the caller of this API. + * @param packageName The package name of the caller. Implementation of this method must verify + * that this belongs to the calling UID. + * @param associationId The association ID as returned by {@link AssociationInfo#getId()} from + * CDM. Virtual devices must have a corresponding association with CDM in order to be created. + */ + IVirtualDevice createVirtualDevice(in IBinder token, String packageName, int associationId); } diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 6187de57a216..590b10887c7f 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -21,7 +21,9 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.companion.AssociationInfo; import android.content.Context; +import android.os.Binder; import android.os.RemoteException; /** @@ -49,14 +51,18 @@ public final class VirtualDeviceManager { /** * Creates a virtual device. * + * @param associationId The association ID as returned by {@link AssociationInfo#getId()} from + * Companion Device Manager. Virtual devices must have a corresponding association with CDM in + * order to be created. * @hide */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @Nullable - public VirtualDevice createVirtualDevice() { - // TODO(b/194949534): Add CDM association ID here and unhide this API + public VirtualDevice createVirtualDevice(int associationId) { + // TODO(b/194949534): Unhide this API try { - IVirtualDevice virtualDevice = mService.createVirtualDevice(); + IVirtualDevice virtualDevice = mService.createVirtualDevice( + new Binder(), mContext.getPackageName(), associationId); return new VirtualDevice(mContext, virtualDevice); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/core/java/android/content/AttributionSource.aidl b/core/java/android/content/AttributionSource.aidl index 10d5c274ae91..7554cb24b41f 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 1d4d30d87560..f335ae483bf7 100644 --- a/core/java/android/content/BroadcastReceiver.java +++ b/core/java/android/content/BroadcastReceiver.java @@ -27,6 +27,7 @@ import android.os.Build; 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 @@ public abstract class BroadcastReceiver { boolean mAbortBroadcast; @UnsupportedAppUsage boolean mFinished; + String mReceiverClassName; /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) @@ -219,6 +221,12 @@ public abstract class BroadcastReceiver { * 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 abstract class BroadcastReceiver { 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 adae5996372a..184acb1a81ef 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -2684,7 +2684,8 @@ public abstract class ContentResolver implements ContentInterface { * @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/Intent.java b/core/java/android/content/Intent.java index a36d532c9919..18237a26659f 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -18,6 +18,7 @@ package android.content; import static android.content.ContentProvider.maybeAddUserId; +import android.Manifest; import android.accessibilityservice.AccessibilityService; import android.annotation.AnyRes; import android.annotation.BroadcastBehavior; @@ -2041,6 +2042,21 @@ public class Intent implements Parcelable, Cloneable { "android.intent.action.VIEW_PERMISSION_USAGE_FOR_PERIOD"; /** + * Activity action: Launch the Safety Hub UI. + * + * <p> + * Input: Nothing. + * </p> + * <p> + * Output: Nothing. + * </p> + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) + public static final String ACTION_VIEW_SAFETY_HUB = + "android.intent.action.VIEW_SAFETY_HUB"; + + /** * Activity action: Launch UI to manage a default app. * <p> * Input: {@link #EXTRA_ROLE_NAME} specifies the role of the default app which will be managed diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 58f20efe2e7c..45d6ddd1dc21 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1017,12 +1017,12 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * 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 191167067ddb..3bf5f310fd31 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3651,6 +3651,14 @@ public abstract class PackageManager { 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 a6f2e4083cb9..632d8e5e291c 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -1923,6 +1923,25 @@ public class Resources { } } } + + @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 5bc235f0eeba..6c0735692db9 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 @@ public final class StringBlock implements Closeable { 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 1db948aba7c6..ccb7cf19d0b1 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -143,7 +143,6 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { throw new AssertionError(); // Not possible, the native code won't return it. } mCloseGuard.open("close"); - recordNewWindow(Binder.getCallingPid(), mWindowPtr); } /** @@ -191,7 +190,6 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { mCloseGuard.close(); } if (mWindowPtr != 0) { - recordClosingOfWindow(mWindowPtr); nativeDispose(mWindowPtr); mWindowPtr = 0; } @@ -746,64 +744,6 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { 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 7e61b48b2547..328858b260ac 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -26,14 +26,13 @@ import android.os.OperationCanceledException; 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 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen 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 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } setPageSize(); setForeignKeyModeFromConfiguration(); - setWalModeFromConfiguration(); + setJournalFromConfiguration(); + setSyncModeFromConfiguration(); setJournalSizeLimit(); setAutoCheckpointInterval(); setLocaleFromConfiguration(); @@ -334,30 +334,19 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } } - 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 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen * 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 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen + 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 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } } - 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 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen // 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 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen 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 852f8f2a4204..d3ad6bb27b3c 100644 --- a/core/java/android/database/sqlite/SQLiteConnectionPool.java +++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java @@ -106,7 +106,11 @@ public final class SQLiteConnectionPool implements Closeable { @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 @@ public final class SQLiteConnectionPool implements Closeable { 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 @@ public final class SQLiteConnectionPool implements Closeable { } void onStatementExecuted(long executionTimeMs) { - mTotalExecutionTimeCounter.addAndGet(executionTimeMs); + mTotalStatementsTime.addAndGet(executionTimeMs); + mTotalStatementsCount.incrementAndGet(); } // Can't throw. @@ -1037,8 +1045,7 @@ public final class SQLiteConnectionPool implements Closeable { } 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 @@ public final class SQLiteConnectionPool implements Closeable { 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 final class SQLiteConnectionPool implements Closeable { } } + 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 9684ece5d9c1..0d0615a28af3 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -20,6 +20,8 @@ import android.annotation.IntDef; 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.EventLog; 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 final class SQLiteDatabase extends SQLiteClosable { */ 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 @@ public final class SQLiteDatabase extends SQLiteClosable { } } 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 @@ public final class SQLiteDatabase extends SQLiteClosable { 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 @@ public final class SQLiteDatabase extends SQLiteClosable { 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 @@ public final class SQLiteDatabase extends SQLiteClosable { synchronized (mLock) { throwIfNotOpenLocked(); - return (mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0; + return mConfigurationLocked.resolveJournalMode().equalsIgnoreCase( + SQLiteDatabase.JOURNAL_MODE_WAL); } } @@ -2309,6 +2475,20 @@ public final class SQLiteDatabase extends SQLiteClosable { 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 @@ public final class SQLiteDatabase extends SQLiteClosable { // 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 @@ public final class SQLiteDatabase extends SQLiteClosable { } } - 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 @@ public final class SQLiteDatabase extends SQLiteClosable { /** * 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 @@ public final class SQLiteDatabase extends SQLiteClosable { 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 21c21c902fed..830584314039 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 @@ public final class SQLiteDatabaseConfiguration { * 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 @@ public final class SQLiteDatabaseConfiguration { 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 @@ public final class SQLiteDatabaseConfiguration { 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 1afa0f8d7090..b84a8d24d42a 100644 --- a/core/java/android/database/sqlite/SQLiteDebug.java +++ b/core/java/android/database/sqlite/SQLiteDebug.java @@ -70,7 +70,8 @@ public final class SQLiteDebug { /** * 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 5e2875d02d90..28964fdd5bf6 100644 --- a/core/java/android/database/sqlite/SQLiteGlobal.java +++ b/core/java/android/database/sqlite/SQLiteGlobal.java @@ -84,7 +84,7 @@ public final class SQLiteGlobal { /** * 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 @@ public final class SQLiteGlobal { /** * 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 @@ public final class SQLiteGlobal { /** * 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/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java index 3c524b2052f7..7074a2c8a60d 100644 --- a/core/java/android/hardware/SensorPrivacyManager.java +++ b/core/java/android/hardware/SensorPrivacyManager.java @@ -41,6 +41,7 @@ import com.android.internal.annotations.GuardedBy; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -137,6 +138,11 @@ public final class SensorPrivacyManager { public static final int OTHER = SensorPrivacyToggleSourceProto.OTHER; /** + * Constant for SAFETY_HUB. + */ + public static final int SAFETY_HUB = SensorPrivacyToggleSourceProto.SAFETY_HUB; + + /** * Source for toggling sensors * * @hide @@ -146,7 +152,8 @@ public final class SensorPrivacyManager { SETTINGS, DIALOG, SHELL, - OTHER + OTHER, + SAFETY_HUB }) @Retention(RetentionPolicy.SOURCE) public @interface Source {} @@ -409,6 +416,31 @@ public final class SensorPrivacyManager { * * @hide */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) + public void setSensorPrivacy(@Sensors.Sensor int sensor, + boolean enable) { + setSensorPrivacy(resolveSourceFromCurrentContext(), sensor, enable, + UserHandle.USER_CURRENT); + } + + private @Sources.Source int resolveSourceFromCurrentContext() { + String packageName = mContext.getOpPackageName(); + if (Objects.equals(packageName, + mContext.getPackageManager().getPermissionControllerPackageName())) { + return Sources.SAFETY_HUB; + } + return Sources.OTHER; + } + + /** + * Sets sensor privacy to the specified state for an individual sensor. + * + * @param sensor the sensor which to change the state for + * @param enable the state to which sensor privacy should be set. + * + * @hide + */ @TestApi @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(@Sources.Source int source, @Sensors.Sensor int sensor, @@ -445,7 +477,6 @@ public final class SensorPrivacyManager { * * @hide */ - @TestApi @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyForProfileGroup(@Sources.Source int source, @Sensors.Sensor int sensor, boolean enable) { diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java index 43ef33e1f420..28046c56b9f8 100644 --- a/core/java/android/hardware/biometrics/BiometricConstants.java +++ b/core/java/android/hardware/biometrics/BiometricConstants.java @@ -151,6 +151,12 @@ public interface BiometricConstants { 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 fe43c83d17f1..fd46f243874b 100644 --- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java +++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java @@ -69,7 +69,7 @@ public interface BiometricFaceConstants { 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 f55bcd31feb7..b989488f9015 100644 --- a/core/java/android/net/OWNERS +++ b/core/java/android/net/OWNERS @@ -2,5 +2,6 @@ set noparent 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 6b33e4f45c42..50a6bfc065d7 100644 --- a/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java +++ b/core/java/android/net/vcn/VcnCellUnderlyingNetworkPriority.java @@ -29,6 +29,7 @@ import android.telephony.TelephonyManager; 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 @@ public final class VcnCellUnderlyingNetworkPriority extends VcnUnderlyingNetwork && 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 de4ada2dbc26..31e38c0b7574 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -160,7 +160,9 @@ public final class VcnGatewayConnectionConfig { 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 82f6ae72b43c..551f75772b9a 100644 --- a/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java +++ b/core/java/android/net/vcn/VcnUnderlyingNetworkPriority.java @@ -21,8 +21,10 @@ import android.annotation.IntDef; 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 @@ public abstract class VcnUnderlyingNetworkPriority { /** @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 @@ public abstract class VcnUnderlyingNetworkPriority { && 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 fc7e7e2c4e41..2ba916927705 100644 --- a/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java +++ b/core/java/android/net/vcn/VcnWifiUnderlyingNetworkPriority.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.os.PersistableBundle; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IndentingPrintWriter; import java.util.Objects; @@ -81,6 +82,12 @@ public final class VcnWifiUnderlyingNetworkPriority extends VcnUnderlyingNetwork 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 74b814ea4159..c8b4226ecae0 100644 --- a/core/java/android/os/AppZygote.java +++ b/core/java/android/os/AppZygote.java @@ -45,6 +45,8 @@ public class AppZygote { // 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 @@ public class AppZygote { 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 @@ public class AppZygote { 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 53484d2a0618..584f3c43911b 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -634,7 +634,7 @@ public abstract class BatteryStats implements Parcelable { */ 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 @@ public abstract class BatteryStats implements Parcelable { * 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 @@ public abstract class BatteryStats implements Parcelable { "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 b3416e98147e..8292f26a8c6b 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -67,6 +67,9 @@ public class GraphicsEnvironment { 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 @@ public class GraphicsEnvironment { 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 bc6dbd8c098f..29d4d78c125d 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -327,6 +327,43 @@ public class UserManager { "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 @@ public class UserManager { 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 627e09eadd26..adf7955cedb1 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -274,6 +274,15 @@ public class StorageManager { 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 5036abc66a0b..eef1ff7c7703 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -2956,7 +2956,10 @@ public final class ContactsContract { */ @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 @@ public final class ContactsContract { */ @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 2093b3f39a33..49a211f08bde 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -965,6 +965,22 @@ public final class Settings { 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> @@ -16670,12 +16686,6 @@ public final class Settings { "alt_bypass_wifi_requirement_time_millis"; /** - * Whether or not Up/Down Gestures are enabled. - * @hide - */ - public static final String UPDOWN_GESTURES_ENABLED = "updown_gestures_enabled"; - - /** * Whether the setup was skipped. * @hide */ diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index b80b01f5a64a..ae12132d49a1 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -858,9 +858,17 @@ class HtmlToSpannedConverter implements ContentHandler { } 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 @@ class HtmlToSpannedConverter implements ContentHandler { // 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 7f903b6fda66..dfe4119d1067 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 interface InputType { */ 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 bdfa700215f8..176a0685e19b 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 @@ import android.text.TextUtils; 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 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { * 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 class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { */ public StyleSpan(@NonNull Parcel src) { mStyle = src.readInt(); + mFontWeightAdjustment = src.readInt(); } @Override @@ -91,6 +112,7 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { @Override public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeInt(mStyle); + dest.writeInt(mFontWeightAdjustment); } /** @@ -100,17 +122,27 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { 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 @@ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { 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/text/style/SuggestionRangeSpan.java b/core/java/android/text/style/SuggestionRangeSpan.java index 2b04a7ac2ab8..1eee99aaac62 100644 --- a/core/java/android/text/style/SuggestionRangeSpan.java +++ b/core/java/android/text/style/SuggestionRangeSpan.java @@ -16,8 +16,9 @@ package android.text.style; -import android.compat.annotation.UnsupportedAppUsage; +import android.annotation.NonNull; import android.os.Parcel; +import android.os.Parcelable; import android.text.ParcelableSpan; import android.text.TextPaint; import android.text.TextUtils; @@ -25,30 +26,40 @@ import android.text.TextUtils; /** * A SuggestionRangeSpan is used to show which part of an EditText is affected by a suggestion * popup window. - * - * @hide */ -public class SuggestionRangeSpan extends CharacterStyle implements ParcelableSpan { +public final class SuggestionRangeSpan extends CharacterStyle implements ParcelableSpan { private int mBackgroundColor; - @UnsupportedAppUsage public SuggestionRangeSpan() { // 0 is a fully transparent black. Has to be set using #setBackgroundColor mBackgroundColor = 0; } - @UnsupportedAppUsage - public SuggestionRangeSpan(Parcel src) { + /** @hide */ + public SuggestionRangeSpan(@NonNull Parcel src) { mBackgroundColor = src.readInt(); } + public static final @NonNull Parcelable.Creator<SuggestionRangeSpan> + CREATOR = new Parcelable.Creator<SuggestionRangeSpan>() { + @Override + public SuggestionRangeSpan createFromParcel(Parcel source) { + return new SuggestionRangeSpan(source); + } + + @Override + public SuggestionRangeSpan[] newArray(int size) { + return new SuggestionRangeSpan[size]; + } + }; + @Override public int describeContents() { return 0; } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } @@ -67,13 +78,16 @@ public class SuggestionRangeSpan extends CharacterStyle implements ParcelableSpa return TextUtils.SUGGESTION_RANGE_SPAN; } - @UnsupportedAppUsage public void setBackgroundColor(int backgroundColor) { mBackgroundColor = backgroundColor; } + public int getBackgroundColor() { + return mBackgroundColor; + } + @Override - public void updateDrawState(TextPaint tp) { + public void updateDrawState(@NonNull TextPaint tp) { tp.bgColor = mBackgroundColor; } } diff --git a/core/java/android/util/LocalLog.java b/core/java/android/util/LocalLog.java index 8c4dcb3b28e2..cd077e1f756c 100644 --- a/core/java/android/util/LocalLog.java +++ b/core/java/android/util/LocalLog.java @@ -22,6 +22,7 @@ import android.os.SystemClock; 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 @@ public final class LocalLog { 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 ba436e16288e..3c0597cac851 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -948,9 +948,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall // 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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } 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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall 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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall 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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall 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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } 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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall // 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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } 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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall applyOrMergeTransaction(mRtTransaction, frameNumber); } } + + public Transaction getTransaction() { + return mPositionChangedTransaction; + } } private SurfaceViewPositionUpdateListener mPositionListener = null; @@ -1651,6 +1655,11 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @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 dc617270cbf9..7a33507e9d78 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -273,6 +273,9 @@ public final class AccessibilityManager { 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 @@ public final class AccessibilityManager { } /** + * 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 @@ public final class AccessibilityManager { } /** + * 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 @@ public final class AccessibilityManager { 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 @@ public final class AccessibilityManager { notifyHighTextContrastStateChanged(); } + if (wasAudioDescriptionByDefaultRequested + != audioDescriptionEnabled) { + notifyAudioDescriptionbyDefaultStateChanged(); + } + updateAccessibilityTracingState(stateFlags); } @@ -1688,15 +1740,20 @@ public final class AccessibilityManager { /** * 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 @@ public final class AccessibilityManager { } /** + * 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 74ca91376621..4cbd477d807a 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -92,6 +92,7 @@ public class EditorInfo implements InputType, Parcelable { * 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 class EditorInfo implements InputType, Parcelable { public int describeContents() { return 0; } -}
\ No newline at end of file +} diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 29425b5364a6..96952089ef51 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -69,6 +69,7 @@ import android.text.SpanWatcher; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; +import android.text.SpannedString; import android.text.StaticLayout; import android.text.TextUtils; import android.text.method.KeyListener; @@ -4120,8 +4121,15 @@ public class Editor { mSuggestionRangeSpan.setBackgroundColor( (underlineColor & 0x00FFFFFF) + (newAlpha << 24)); } + boolean sendAccessibilityEvent = mTextView.isVisibleToAccessibility(); + CharSequence beforeText = sendAccessibilityEvent + ? new SpannedString(spannable, true) : null; spannable.setSpan(mSuggestionRangeSpan, spanUnionStart, spanUnionEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + if (sendAccessibilityEvent) { + mTextView.sendAccessibilityEventTypeViewTextChanged( + beforeText, spanUnionStart, spanUnionEnd); + } mSuggestionsAdapter.notifyDataSetChanged(); return true; diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl index 1ad0452dd837..4399207fcc27 100644 --- a/core/java/android/window/ITaskFragmentOrganizerController.aidl +++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl @@ -44,4 +44,10 @@ interface ITaskFragmentOrganizerController { * 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 7e7d37083b5b..9c2fde04e4d2 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -216,4 +216,17 @@ public class TaskFragmentOrganizer extends WindowOrganizer { 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/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index aba43d859db4..c2224b4ab9d1 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -18,6 +18,7 @@ package com.android.internal.os; 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 @@ public class BatteryStatsImpl extends BatteryStats { MeasuredEnergyStats.POWER_BUCKET_CPU, }; - @GuardedBy("this") - public boolean mPerProcStateCpuTimesAvailable = true; + // 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; - /** - * 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(); + public boolean mPerProcStateCpuTimesAvailable = true; @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 @@ public class BatteryStatsImpl extends BatteryStats { } /** - * 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; - } + @GuardedBy("this") + @SuppressWarnings("GuardedBy") // errorprone false positive on getProcStateTimeCounter + @VisibleForTesting + public void updateProcStateCpuTimesLocked(int uid, long timestampMs) { + if (!initKernelSingleUidTimeReaderLocked()) { + return; + } - if (mPendingUids.size() == 0) { - return; - } - uidStates = mPendingUids.clone(); - mPendingUids.clear(); - } - 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); - LongArrayMultiStateCounter onBatteryCounter = - u.getProcStateTimeCounter().getCounter(); - LongArrayMultiStateCounter onBatteryScreenOffCounter = - u.getProcStateScreenOffTimeCounter().getCounter(); + mNumSingleUidCpuTimeReads++; - onBatteryCounter.setState(procState, timestampMs); - mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, timestampMs); + LongArrayMultiStateCounter onBatteryCounter = + u.getProcStateTimeCounter().getCounter(); + LongArrayMultiStateCounter onBatteryScreenOffCounter = + u.getProcStateScreenOffTimeCounter().getCounter(); - onBatteryScreenOffCounter.setState(procState, timestampMs); - mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, timestampMs); + mKernelSingleUidTimeReader.addDelta(uid, onBatteryCounter, 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); - } + 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); } } - - onBatteryCounter.setState(u.mProcessState, timestampMs); - onBatteryScreenOffCounter.setState(u.mProcessState, timestampMs); } } @@ -542,24 +481,17 @@ public class BatteryStatsImpl extends BatteryStats { } } - 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 @@ public class BatteryStatsImpl extends BatteryStats { // 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { return null; } + @GuardedBy("mBsi") private void ensureMultiStateCounters() { if (mProcStateTimeMs != null) { return; @@ -8440,31 +8347,26 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { processState); } + @GuardedBy("mBsi") @Override public long getGnssMeasuredBatteryConsumptionUC() { return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_GNSS); @@ -9553,7 +9456,7 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { } 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 @@ public class BatteryStatsImpl extends BatteryStats { // 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { } @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 @@ public class BatteryStatsImpl extends BatteryStats { } 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 @@ public class BatteryStatsImpl extends BatteryStats { /** 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 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public boolean trackPerProcStateCpuTimes() { - return mConstants.TRACK_CPU_TIMES_BY_PROC_STATE && mPerProcStateCpuTimesAvailable; + return mCpuUidFreqTimeReader.isFastCpuTimesReader(); } @GuardedBy("this") @@ -15747,8 +15639,6 @@ public class BatteryStatsImpl extends BatteryStats { @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 class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 class BatteryStatsImpl extends BatteryStats { } 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { // 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 @@ public class BatteryStatsImpl extends BatteryStats { } 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 @@ public class BatteryStatsImpl extends BatteryStats { } 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/DmabufInfoReader.java b/core/java/com/android/internal/os/KernelAllocationStats.java index 786a6eedf343..1c3f8b0bf095 100644 --- a/core/java/com/android/internal/os/DmabufInfoReader.java +++ b/core/java/com/android/internal/os/KernelAllocationStats.java @@ -18,9 +18,9 @@ package com.android.internal.os; import android.annotation.Nullable; -/** Wrapper around libdmabufinfo. */ -public final class DmabufInfoReader { - private DmabufInfoReader() {} +/** JNI wrapper around libmeminfo for kernel memory allocation stats (dmabufs, gpu driver). */ +public final class KernelAllocationStats { + private KernelAllocationStats() {} /** Process dma-buf stats. */ public static final class ProcessDmabuf { @@ -47,5 +47,19 @@ public final class DmabufInfoReader { * stats could not be read. */ @Nullable - public static native ProcessDmabuf getProcessStats(int pid); + public static native ProcessDmabuf getDmabufAllocations(int pid); + + /** Pid to gpu memory size. */ + public static final class ProcessGpuMem { + public final int pid; + public final int gpuMemoryKb; + + ProcessGpuMem(int pid, int gpuMemoryKb) { + this.pid = pid; + this.gpuMemoryKb = gpuMemoryKb; + } + } + + /** Return list of pid to gpu memory size. */ + public static native ProcessGpuMem[] getGpuAllocations(); } diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java index faeb8fc237ec..c801be0ce3e7 100644 --- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java +++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java @@ -620,6 +620,10 @@ public abstract class KernelCpuUidTimeReader<T> { } 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 424632fe82eb..6541b14b9070 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -270,8 +270,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind 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 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mSemiTransparentBarColor = context.getResources().getColor( R.color.system_bar_background_semi_transparent, null /* theme */); - updateAvailableWidth(); - setWindow(window); updateLogTag(params); @@ -697,7 +693,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind @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 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind 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 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind updateDecorCaptionStatus(newConfig); - updateAvailableWidth(); initializeElevation(); } @@ -2616,12 +2614,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind 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 be5dc0039ac6..d66f4614e0e8 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -870,7 +870,8 @@ public class SystemConfig { 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 @@ public class SystemConfig { ? 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.bp b/core/jni/Android.bp index 1a1a8badf82b..da628635af36 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -210,8 +210,8 @@ cc_library_shared { "com_android_internal_content_om_OverlayConfig.cpp", "com_android_internal_net_NetworkUtilsInternal.cpp", "com_android_internal_os_ClassLoaderFactory.cpp", - "com_android_internal_os_DmabufInfoReader.cpp", "com_android_internal_os_FuseAppLoop.cpp", + "com_android_internal_os_KernelAllocationStats.cpp", "com_android_internal_os_KernelCpuBpfTracking.cpp", "com_android_internal_os_KernelCpuTotalBpfMapReader.cpp", "com_android_internal_os_KernelCpuUidBpfMapReader.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 18e85b6eb0be..04fafb441a28 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -197,8 +197,8 @@ extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env); extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env); extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env); -extern int register_com_android_internal_os_DmabufInfoReader(JNIEnv* env); extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env); +extern int register_com_android_internal_os_KernelAllocationStats(JNIEnv* env); extern int register_com_android_internal_os_KernelCpuBpfTracking(JNIEnv* env); extern int register_com_android_internal_os_KernelCpuTotalBpfMapReader(JNIEnv* env); extern int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env); @@ -1655,8 +1655,8 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_security_Scrypt), REG_JNI(register_com_android_internal_content_F2fsUtils), REG_JNI(register_com_android_internal_content_NativeLibraryHelper), - REG_JNI(register_com_android_internal_os_DmabufInfoReader), REG_JNI(register_com_android_internal_os_FuseAppLoop), + REG_JNI(register_com_android_internal_os_KernelAllocationStats), REG_JNI(register_com_android_internal_os_KernelCpuBpfTracking), REG_JNI(register_com_android_internal_os_KernelCpuTotalBpfMapReader), REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader), diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index a059dd6e52e2..78e5adc2ccd1 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 void nativeApplyPendingTransactions(JNIEnv* env, jclass clazz, jlong ptr, 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 f1ae268f4c16..423ef7cd980b 100644 --- a/core/jni/android_media_AudioAttributes.cpp +++ b/core/jni/android_media_AudioAttributes.cpp @@ -58,7 +58,7 @@ static struct { jmethodID setSystemUsage; jmethodID setInternalCapturePreset; jmethodID setContentType; - jmethodID setFlags; + jmethodID replaceFlags; jmethodID addTag; } gAudioAttributesBuilderMethods; @@ -130,7 +130,7 @@ static jint nativeAudioAttributesToJavaAudioAttributes( 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 @@ int register_android_media_AudioAttributes(JNIEnv *env) 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/jni/com_android_internal_os_DmabufInfoReader.cpp b/core/jni/com_android_internal_os_DmabufInfoReader.cpp deleted file mode 100644 index 4b0a6ac5b6c4..000000000000 --- a/core/jni/com_android_internal_os_DmabufInfoReader.cpp +++ /dev/null @@ -1,60 +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. - */ - -#include <dmabufinfo/dmabufinfo.h> -#include "core_jni_helpers.h" - -namespace android { - -static jobject DmabufInfoReader_getProcessStats(JNIEnv *env, jobject, jint pid) { - std::vector<dmabufinfo::DmaBuffer> buffers; - if (!dmabufinfo::ReadDmaBufMapRefs(pid, &buffers)) { - return nullptr; - } - jint mappedSize = 0; - jint mappedCount = buffers.size(); - for (const auto &buffer : buffers) { - mappedSize += buffer.size(); - } - mappedSize /= 1024; - - jint retainedSize = -1; - jint retainedCount = -1; - if (dmabufinfo::ReadDmaBufFdRefs(pid, &buffers)) { - retainedCount = buffers.size(); - retainedSize = 0; - for (const auto &buffer : buffers) { - retainedSize += buffer.size(); - } - retainedSize /= 1024; - } - - jclass clazz = FindClassOrDie(env, "com/android/internal/os/DmabufInfoReader$ProcessDmabuf"); - jmethodID constructID = GetMethodIDOrDie(env, clazz, "<init>", "(IIII)V"); - return env->NewObject(clazz, constructID, retainedSize, retainedCount, mappedSize, mappedCount); -} - -static const JNINativeMethod methods[] = { - {"getProcessStats", "(I)Lcom/android/internal/os/DmabufInfoReader$ProcessDmabuf;", - (void *)DmabufInfoReader_getProcessStats}, -}; - -int register_com_android_internal_os_DmabufInfoReader(JNIEnv *env) { - return RegisterMethodsOrDie(env, "com/android/internal/os/DmabufInfoReader", methods, - NELEM(methods)); -} - -} // namespace android diff --git a/core/jni/com_android_internal_os_KernelAllocationStats.cpp b/core/jni/com_android_internal_os_KernelAllocationStats.cpp new file mode 100644 index 000000000000..e0a24430e739 --- /dev/null +++ b/core/jni/com_android_internal_os_KernelAllocationStats.cpp @@ -0,0 +1,97 @@ +/* + * 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. + */ +#include <dmabufinfo/dmabufinfo.h> +#include <jni.h> +#include <meminfo/sysmeminfo.h> + +#include "core_jni_helpers.h" + +namespace { +static jclass gProcessDmabufClazz; +static jmethodID gProcessDmabufCtor; +static jclass gProcessGpuMemClazz; +static jmethodID gProcessGpuMemCtor; +} // namespace + +namespace android { + +static jobject KernelAllocationStats_getDmabufAllocations(JNIEnv *env, jobject, jint pid) { + std::vector<dmabufinfo::DmaBuffer> buffers; + if (!dmabufinfo::ReadDmaBufMapRefs(pid, &buffers)) { + return nullptr; + } + jint mappedSize = 0; + jint mappedCount = buffers.size(); + for (const auto &buffer : buffers) { + mappedSize += buffer.size(); + } + mappedSize /= 1024; + + jint retainedSize = -1; + jint retainedCount = -1; + if (dmabufinfo::ReadDmaBufFdRefs(pid, &buffers)) { + retainedCount = buffers.size(); + retainedSize = 0; + for (const auto &buffer : buffers) { + retainedSize += buffer.size(); + } + retainedSize /= 1024; + } + return env->NewObject(gProcessDmabufClazz, gProcessDmabufCtor, retainedSize, retainedCount, + mappedSize, mappedCount); +} + +static jobject KernelAllocationStats_getGpuAllocations(JNIEnv *env) { + std::unordered_map<uint32_t, uint64_t> out; + meminfo::ReadPerProcessGpuMem(&out); + jobjectArray result = env->NewObjectArray(out.size(), gProcessGpuMemClazz, nullptr); + if (result == NULL) { + jniThrowRuntimeException(env, "Cannot create result array"); + return nullptr; + } + int idx = 0; + for (const auto &entry : out) { + jobject pidStats = + env->NewObject(gProcessGpuMemClazz, gProcessGpuMemCtor, entry.first, entry.second); + env->SetObjectArrayElement(result, idx, pidStats); + env->DeleteLocalRef(pidStats); + ++idx; + } + return result; +} + +static const JNINativeMethod methods[] = { + {"getDmabufAllocations", "(I)Lcom/android/internal/os/KernelAllocationStats$ProcessDmabuf;", + (void *)KernelAllocationStats_getDmabufAllocations}, + {"getGpuAllocations", "()[Lcom/android/internal/os/KernelAllocationStats$ProcessGpuMem;", + (void *)KernelAllocationStats_getGpuAllocations}, +}; + +int register_com_android_internal_os_KernelAllocationStats(JNIEnv *env) { + int res = RegisterMethodsOrDie(env, "com/android/internal/os/KernelAllocationStats", methods, + NELEM(methods)); + jclass clazz = + FindClassOrDie(env, "com/android/internal/os/KernelAllocationStats$ProcessDmabuf"); + gProcessDmabufClazz = MakeGlobalRefOrDie(env, clazz); + gProcessDmabufCtor = GetMethodIDOrDie(env, gProcessDmabufClazz, "<init>", "(IIII)V"); + + clazz = FindClassOrDie(env, "com/android/internal/os/KernelAllocationStats$ProcessGpuMem"); + gProcessGpuMemClazz = MakeGlobalRefOrDie(env, clazz); + gProcessGpuMemCtor = GetMethodIDOrDie(env, gProcessGpuMemClazz, "<init>", "(II)V"); + return res; +} + +} // namespace android diff --git a/core/proto/android/hardware/sensorprivacy.proto b/core/proto/android/hardware/sensorprivacy.proto index d52af5c6fe67..81d849e7c6e8 100644 --- a/core/proto/android/hardware/sensorprivacy.proto +++ b/core/proto/android/hardware/sensorprivacy.proto @@ -78,6 +78,7 @@ message SensorPrivacyToggleSourceProto { DIALOG = 3; SHELL = 4; OTHER = 5; + SAFETY_HUB = 6; } // Source for which sensor privacy was toggled. diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 29f13c91577b..6e2c80710516 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1532,6 +1532,7 @@ android:label="@string/permlab_postNotification" android:description="@string/permdesc_postNotification" android:protectionLevel="dangerous" /> + <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- ====================================================================== --> <!-- REMOVED PERMISSIONS --> @@ -3508,7 +3509,7 @@ <!-- Allows an application to read or write the secure system settings. <p>Not for use by third-party applications. --> <permission android:name="android.permission.WRITE_SECURE_SETTINGS" - android:protectionLevel="signature|privileged|development|role" /> + android:protectionLevel="signature|privileged|development|role|installer" /> <!-- Allows an application to retrieve state dump information from system services. <p>Not for use by third-party applications. --> @@ -4694,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" @@ -5815,7 +5823,7 @@ <!-- @SystemApi Allows sensor privacy to be modified. @hide --> <permission android:name="android.permission.MANAGE_SENSOR_PRIVACY" - android:protectionLevel="internal|role" /> + android:protectionLevel="internal|role|installer" /> <!-- @SystemApi Allows sensor privacy changes to be observed. @hide --> @@ -5862,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 697ec204d745..60766459f1cb 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 650bd3dd9d85..769e667a2b25 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 ccb487e31801..134235d5b1aa 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 6471492c9663..c3d707cd7596 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothLeAudioCodecConfigTest.java @@ -45,7 +45,6 @@ public class BluetoothLeAudioCodecConfigTest extends TestCase { 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 8ec33bf0e1bb..c1a45c47539d 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 static org.mockito.Mockito.when; 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 @@ import org.mockito.MockitoAnnotations; @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 @@ public class BatteryStatsImplTest { @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 @@ public class BatteryStatsImplTest { 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]); + } + + 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)); + } } - mBatteryStatsImpl.updateProcStateCpuTimes(true, false); // Obtain initial CPU time-in-freq counts final long[][] cpuTimes = { @@ -117,24 +123,14 @@ public class BatteryStatsImplTest { {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 @@ public class BatteryStatsImplTest { {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 @@ public class BatteryStatsImplTest { } // 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 @@ public class BatteryStatsImplTest { {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 @@ public class BatteryStatsImplTest { {3049509483598l, 4597834, 377654, 94589035, 7854}, {9493, 784, 99895, 8974893, 9879843} }; + + 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); + 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); } - addPendingUids(testUids, testProcStates); - 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); - 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 @@ public class BatteryStatsImplTest { } @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 @@ public class BatteryStatsImplTest { 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 @@ public class BatteryStatsImplTest { } mMockClock.realtime += 1000; - mBatteryStatsImpl.copyFromAllUidsCpuTimes(true, false); - verifyNoPendingUids(); + mBatteryStatsImpl.updateCpuTimesForAllUids(); final long[] timeInFreqs = new long[NUM_CPU_FREQS]; @@ -411,9 +403,7 @@ public class BatteryStatsImplTest { 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 @@ public class BatteryStatsImplTest { 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 @@ public class BatteryStatsImplTest { 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 @@ public class BatteryStatsImplTest { 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 @@ public class BatteryStatsImplTest { 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 441e85d4bca5..9172d349ead3 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; 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.Process; 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 @@ public class BstatsCpuTimesValidationTest { 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 @@ public class BstatsCpuTimesValidationTest { 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 1ae30dbb4457..d5b0f0a58e8e 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 @@ public class CpuPowerCalculatorTest { 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 @@ public class CpuPowerCalculatorTest { @Test public void testTimerBasedModel_byProcessState() { - mStatsRule.getBatteryStats().setTrackingCpuByProcStateEnabled(true); - when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true); when(mMockCpuUidFreqTimeReader.allUidTimesAvailable()).thenReturn(true); @@ -311,7 +311,7 @@ public class CpuPowerCalculatorTest { }).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 @@ public class CpuPowerCalculatorTest { }).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 @@ public class CpuPowerCalculatorTest { }).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 d16689cfc560..e4c83f175a71 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 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { // 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 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { 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 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { } @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 0a2670a3b242..60cb9d321f02 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -154,6 +154,7 @@ applications that come with the platform <permission name="android.permission.PACKAGE_USAGE_STATS" /> <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" /> <permission name="android.permission.MODIFY_AUDIO_ROUTING" /> + <permission name="android.permission.WRITE_SECURE_SETTINGS" /> </privapp-permissions> <privapp-permissions package="com.android.phone"> @@ -494,6 +495,8 @@ applications that come with the platform <!-- 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" /> @@ -514,6 +517,8 @@ applications that come with the platform <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 8844370f9b4f..8d3eadb8496d 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -27,8 +27,9 @@ public final class BLASTBufferQueue { // 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 @@ public final class BLASTBufferQueue { /** 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 fe6c7ba3b24c..af19bd0a79ac 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 class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ 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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen 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 8c8ef92b80dc..46bdf6d0e689 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.Animator; 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 @@ import java.util.function.BiFunction; 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 a1a53bc93781..4d2d0551d828 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 @@ class TaskFragmentContainer { 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 Binary files differindex d6678bf9b320..f54ab08d8a8a 100644 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ b/libs/WindowManager/Jetpack/window-extensions-release.aar 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 0fbdf90fd9d5..8467cc5fc591 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 @@ public class PipDismissTargetHandler implements ViewTreeObserver.OnPreDrawListen 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 82e827398bb7..da4bbe81a3e6 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 @@ public class PipMenuView extends FrameLayout { 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 e30e6c537c93..d1feee4803c1 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 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 e904f6a9e22c..0683a25ecd41 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 @@ package com.android.wm.shell.splitscreen; 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.internal.policy.ScreenDecorationsUtils; 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 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange * 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 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange 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 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange 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 1a3e42cbe04c..20a94750021d 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 @@ class AppPairsTestPairPrimaryAndSecondaryApps( @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 32109764bb0e..2a53bef8cf2e 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 @@ class SetRequestedOrientationWhilePinnedTest( super.statusBarLayerRotatesScales() } - @Postsubmit + @Presubmit @Test fun pipWindowInsideDisplay() { testSpec.assertWmStart { @@ -112,7 +111,7 @@ class SetRequestedOrientationWhilePinnedTest( } } - @Postsubmit + @Presubmit @Test fun pipLayerInsideDisplay() { testSpec.assertLayersStart { @@ -126,7 +125,7 @@ class SetRequestedOrientationWhilePinnedTest( 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 85f6789c3435..fb6300c8a4bf 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 @@ public class StageCoordinatorTests extends ShellTestCase { when(mSplitLayout.getBounds1()).thenReturn(mBounds1); when(mSplitLayout.getBounds2()).thenReturn(mBounds2); + when(mSplitLayout.isLandscape()).thenReturn(false); } @Test @@ -168,8 +169,9 @@ public class StageCoordinatorTests extends ShellTestCase { 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 @@ public class StageCoordinatorTests extends ShellTestCase { 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/aidl/android/media/audio/common/AudioPort.aidl b/media/aidl/android/media/audio/common/AudioPort.aidl index 8e1c5afe3fe3..84675e3a00a6 100644 --- a/media/aidl/android/media/audio/common/AudioPort.aidl +++ b/media/aidl/android/media/audio/common/AudioPort.aidl @@ -18,7 +18,6 @@ package android.media.audio.common; import android.media.audio.common.AudioGain; import android.media.audio.common.AudioIoFlags; -import android.media.audio.common.AudioPortConfig; import android.media.audio.common.AudioPortExt; import android.media.audio.common.AudioProfile; import android.media.audio.common.ExtraAudioDescriptor; @@ -33,7 +32,7 @@ import android.media.audio.common.ExtraAudioDescriptor; @VintfStability parcelable AudioPort { /** - * Unique identifier of the port within this HAL service. + * Unique identifier of the port within a HAL module. */ int id; /** @@ -57,8 +56,6 @@ parcelable AudioPort { ExtraAudioDescriptor[] extraAudioDescriptors; /** Gain controllers. */ AudioGain[] gains; - /** Current audio port configuration. */ - AudioPortConfig activeConfig; /** Extra parameters depending on the port role. */ AudioPortExt ext; } diff --git a/media/aidl/android/media/audio/common/AudioPortConfig.aidl b/media/aidl/android/media/audio/common/AudioPortConfig.aidl index e3a9374822d2..2702b147e7f4 100644 --- a/media/aidl/android/media/audio/common/AudioPortConfig.aidl +++ b/media/aidl/android/media/audio/common/AudioPortConfig.aidl @@ -33,10 +33,15 @@ import android.media.audio.common.Int; @VintfStability parcelable AudioPortConfig { /** - * Port unique ID. This field is set to a non-zero value when it is needed - * to select a previously reported port and apply new configuration to it. + * Port config unique ID. This field is set to a non-zero value when it is + * needed to select a previously reported port config and apply new + * configuration to it. */ int id; + /** + * The ID of the AudioPort instance this configuration applies to. + */ + int portId; /** Sample rate in Hz. Can be left unspecified. */ @nullable Int sampleRate; /** Channel mask. Can be left unspecified. */ diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPort.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPort.aidl index 8f563b3b2894..970bbc06890f 100644 --- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPort.aidl +++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPort.aidl @@ -41,6 +41,5 @@ parcelable AudioPort { android.media.audio.common.AudioIoFlags flags; android.media.audio.common.ExtraAudioDescriptor[] extraAudioDescriptors; android.media.audio.common.AudioGain[] gains; - android.media.audio.common.AudioPortConfig activeConfig; android.media.audio.common.AudioPortExt ext; } diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortConfig.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortConfig.aidl index 78967b43961c..18e6406117dc 100644 --- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortConfig.aidl +++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioPortConfig.aidl @@ -36,6 +36,7 @@ package android.media.audio.common; @JavaDerive(equals=true, toString=true) @VintfStability parcelable AudioPortConfig { int id; + int portId; @nullable android.media.audio.common.Int sampleRate; @nullable android.media.audio.common.AudioChannelLayout channelMask; @nullable android.media.audio.common.AudioFormatDescription format; diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java index 54252b5b712a..9993ce9dec72 100644 --- a/media/java/android/media/AudioAttributes.java +++ b/media/java/android/media/AudioAttributes.java @@ -481,13 +481,20 @@ public final class AudioAttributes implements Parcelable { */ 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 @@ public final class AudioAttributes implements Parcelable { 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 final class AudioAttributes implements Parcelable { 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 @@ public final class AudioAttributes implements Parcelable { } /** + * @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 @@ public final class AudioAttributes implements Parcelable { 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 faae9a5bd4c9..d721291ad78f 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -88,7 +88,8 @@ import java.util.Objects; 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 @@ public class AudioManager { } /** + * 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 @@ public class AudioManager { return getDeviceInfoFromTypeAndAddress(deviceType, null); } - /** + /** * @hide * Returns an {@link AudioDeviceInfo} corresponding to a connected device of the type and * address provided. @@ -7692,6 +7710,506 @@ public class AudioManager { } } + + /** + * @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 7c6ae28bdd30..e76bb42560f6 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -32,6 +32,7 @@ import android.content.AttributionSource.ScopedParcelState; 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.annotation.RetentionPolicy; 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 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, // 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 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, 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 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, 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 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, 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 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, /** * @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 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, .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 0450a808df35..ea692b940c81 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -26,6 +26,9 @@ import android.annotation.RequiresPermission; 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 @@ public class AudioTrack extends PlayerBase */ @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE; + private AudioPolicy mAudioPolicy; + //-------------------------------- // Used exclusively by native code //-------------------- @@ -1032,6 +1037,7 @@ public class AudioTrack extends PlayerBase 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 @@ public class AudioTrack extends PlayerBase } /** + * 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 @@ public class AudioTrack extends PlayerBase .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 @@ public class AudioTrack extends PlayerBase } /** + * 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 @@ public class AudioTrack extends PlayerBase } 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 35191bde8ac4..afcbc5769cf0 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -32,6 +32,7 @@ import android.media.IAudioRoutesObserver; 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 @@ interface IAudioService { 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/tv/TsRequest.aidl b/media/java/android/media/IMuteAwaitConnectionCallback.aidl index 0abc7ecec769..77fc02960d5f 100644 --- a/media/java/android/media/tv/TsRequest.aidl +++ b/media/java/android/media/IMuteAwaitConnectionCallback.aidl @@ -14,6 +14,18 @@ * limitations under the License. */ -package android.media.tv; +package android.media; -parcelable TsRequest; +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 8080aad6dcc2..f85bdee18967 100644 --- a/media/java/android/media/audiopolicy/AudioMix.java +++ b/media/java/android/media/audiopolicy/AudioMix.java @@ -241,6 +241,11 @@ public class AudioMix { } /** @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 611229080e4c..c91275941200 100644 --- a/media/java/android/media/audiopolicy/AudioMixingRule.java +++ b/media/java/android/media/audiopolicy/AudioMixingRule.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; 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 @@ public class AudioMixingRule { } /** @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 3e8d76ac7551..0f08d79bf3bb 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -588,6 +588,10 @@ public class AudioPolicy { 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 @@ public class AudioPolicy { 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 @@ public class AudioPolicy { } } + 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 @@ public class AudioPolicy { .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 @@ public class AudioPolicy { } 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 4d8eda1a100b..c439356ef184 100644 --- a/media/java/android/media/tv/BroadcastInfoRequest.java +++ b/media/java/android/media/tv/BroadcastInfoRequest.java @@ -23,19 +23,35 @@ import android.annotation.NonNull; /** @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 @@ public abstract class BroadcastInfoRequest implements Parcelable { } }; - int requestId; + protected final int mType; + protected final int mRequestId; + protected final int mOption; + + protected BroadcastInfoRequest(int type, int requestId, int option) { + mType = type; + mRequestId = requestId; + mOption = option; + } - public BroadcastInfoRequest(int requestId) { - this.requestId = requestId; + protected BroadcastInfoRequest(int type, Parcel source) { + mType = type; + mRequestId = source.readInt(); + mOption = source.readInt(); } - protected BroadcastInfoRequest(Parcel source) { - requestId = source.readInt(); + public int getType() { + return mType; } public int getRequestId() { - return requestId; + return mRequestId; + } + + public int getOption() { + return mOption; } @Override @@ -66,6 +96,8 @@ public abstract class BroadcastInfoRequest implements Parcelable { @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 fe4e8b7f1d0a..288f2f97e3b9 100644 --- a/media/java/android/media/tv/BroadcastInfoResponse.java +++ b/media/java/android/media/tv/BroadcastInfoResponse.java @@ -22,12 +22,37 @@ import android.os.Parcelable; 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 @@ public final class BroadcastInfoResponse implements Parcelable { } }; - int requestId; + protected final int mType; + protected final int mRequestId; + protected final int mSequence; + protected final int mResponseResult; + + protected BroadcastInfoResponse(int type, int requestId, int sequence, int responseResult) { + mType = type; + mRequestId = requestId; + mSequence = sequence; + mResponseResult = responseResult; + } - public BroadcastInfoResponse(int requestId) { - this.requestId = requestId; + protected BroadcastInfoResponse(int type, Parcel source) { + mType = type; + mRequestId = source.readInt(); + mSequence = source.readInt(); + mResponseResult = source.readInt(); } - private BroadcastInfoResponse(Parcel source) { - requestId = 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 @@ public final class BroadcastInfoResponse implements Parcelable { @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 000000000000..e7a0595d1ce9 --- /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 000000000000..f2e47505ad15 --- /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 000000000000..3bdfb956c889 --- /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 000000000000..0e444b8f1d5a --- /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 000000000000..d46e6fcf1240 --- /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 000000000000..3e8e90901b71 --- /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 000000000000..1c8f96560a59 --- /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 000000000000..09399c298722 --- /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 000000000000..027b73513a26 --- /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 000000000000..54322158ff60 --- /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 000000000000..a6d3e3996825 --- /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.java b/media/java/android/media/tv/TsRequest.java index 3690d05e9b95..141f3ac91828 100644 --- a/media/java/android/media/tv/TsRequest.java +++ b/media/java/android/media/tv/TsRequest.java @@ -22,6 +22,8 @@ import android.os.Parcelable; /** @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 @@ public class TsRequest extends BroadcastInfoRequest implements Parcelable { } }; - 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 000000000000..e30ff54af4aa --- /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 000000000000..845641d4d6ca --- /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 000000000000..3181b08c62f7 --- /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 73a821e09f20..94de7fa17ae7 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.List; 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 @@ public class Tuner implements AutoCloseable { 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 @@ public class Tuner implements AutoCloseable { 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 @@ public class Tuner implements AutoCloseable { /** @hide */ public List<Integer> getFrontendIds() { - return nativeGetFrontendIds(); + mFrontendLock.lock(); + try { + return nativeGetFrontendIds(); + } finally { + mFrontendLock.unlock(); + } } /** @@ -426,13 +438,20 @@ public class Tuner implements AutoCloseable { * @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 @@ public class Tuner implements AutoCloseable { */ @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; - } - if (mLnb != null) { - mLnb.close(); - } - if (mFrontendCiCamHandle != null) { - int result = nativeUnlinkCiCam(mFrontendCiCamId); - if (result == RESULT_SUCCESS) { - mTunerResourceManager.releaseCiCam(mFrontendCiCamHandle, mClientId); - mFrontendCiCamId = null; - mFrontendCiCamHandle = null; + } finally { + mFrontendLock.unlock(); + } + + 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 @@ public class Tuner implements AutoCloseable { mDescramblers.clear(); } } + synchronized (mFilters) { if (!mFilters.isEmpty()) { for (WeakReference<Filter> weakFilter : mFilters) { @@ -554,13 +597,19 @@ public class Tuner implements AutoCloseable { 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 @@ public class Tuner implements AutoCloseable { */ @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 @@ public class Tuner implements AutoCloseable { */ @Result public int cancelTuning() { - return nativeStopTune(); + mFrontendLock.lock(); + try { + return nativeStopTune(); + } finally { + mFrontendLock.unlock(); + } } /** @@ -824,33 +887,41 @@ public class Tuner implements AutoCloseable { @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 @@ public class Tuner implements AutoCloseable { */ @Result public int cancelScanning() { - 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; + 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; + } + } finally { + mFrontendLock.unlock(); } } @@ -903,7 +979,12 @@ public class Tuner implements AutoCloseable { */ @Result private int setLnb(@NonNull Lnb lnb) { - return nativeSetLnb(lnb); + mLnbLock.lock(); + try { + return nativeSetLnb(lnb); + } finally { + mLnbLock.unlock(); + } } /** @@ -929,10 +1010,15 @@ public class Tuner implements AutoCloseable { */ @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 @@ public class Tuner implements AutoCloseable { * @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 @@ public class Tuner implements AutoCloseable { * @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 @@ public class Tuner implements AutoCloseable { */ @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 @@ public class Tuner implements AutoCloseable { * {@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 @@ public class Tuner implements AutoCloseable { */ @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 @@ public class Tuner implements AutoCloseable { */ @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 @@ public class Tuner implements AutoCloseable { */ @Nullable public FrontendInfo getFrontendInfo() { - if (!checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) { - return null; - } - if (mFrontend == null) { - throw new IllegalStateException("frontend is not initialized"); - } - if (mFrontendInfo == null) { - mFrontendInfo = getFrontendInfoById(mFrontend.mId); + 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(); } - return mFrontendInfo; } /** @@ -1114,7 +1251,12 @@ public class Tuner implements AutoCloseable { /** @hide */ public FrontendInfo getFrontendInfoById(int id) { - return nativeGetFrontendInfo(id); + mFrontendLock.lock(); + try { + return nativeGetFrontendInfo(id); + } finally { + mFrontendLock.unlock(); + } } /** @@ -1125,7 +1267,12 @@ public class Tuner implements AutoCloseable { */ @Nullable public DemuxCapabilities getDemuxCapabilities() { - return nativeGetDemuxCapabilities(); + mDemuxLock.lock(); + try { + return nativeGetDemuxCapabilities(); + } finally { + mDemuxLock.unlock(); + } } private void onFrontendEvent(int eventType) { @@ -1417,32 +1564,37 @@ public class Tuner implements AutoCloseable { 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 @@ public class Tuner implements AutoCloseable { */ @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; - } - if (checkResource(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB) && mLnb != null) { - mLnb.setCallback(executor, cb, this); - setLnb(mLnb); - 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(); } - return null; } /** @@ -1483,20 +1641,25 @@ public class Tuner implements AutoCloseable { @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 @@ public class Tuner implements AutoCloseable { */ @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 @@ public class Tuner implements AutoCloseable { @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 @@ public class Tuner implements AutoCloseable { @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 @@ public class Tuner implements AutoCloseable { @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 @@ public class Tuner implements AutoCloseable { 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 @@ public class Tuner implements AutoCloseable { 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 @@ public class Tuner implements AutoCloseable { 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"); + } + } + + // 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(); + } } - return true; } /* 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 403bfa716702..89512a05674b 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 @@ public class IsdbtFrontendSettings extends FrontendSettings { 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 @@ public class IsdbtFrontendSettings extends FrontendSettings { 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 @@ public class IsdbtFrontendSettings extends FrontendSettings { 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 @@ public class IsdbtFrontendSettings extends FrontendSettings { 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 @@ public class IsdbtFrontendSettings extends FrontendSettings { @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 244fd0e796aa..fe611c711273 100644 --- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java +++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java @@ -320,6 +320,48 @@ public class TunerResourceManager { } /** + * 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 7bc50586fb37..5f3582046d15 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 @@ interface ITunerResourceManager { * @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 4c1ed45c5f3d..ded9652622a7 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -2961,7 +2961,7 @@ static FrontendSettings getIsdbtFrontendSettings(JNIEnv *env, const jobject& set 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 9d3ce348f013..cc7bb4a3ff81 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 02475dd1ea0c..ec8d7798e17e 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.LocalSocket; 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 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback { 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 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback { 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 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback { 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 76ecea726b78..497107dbf989 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.IBinder; 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 @@ public class NsdService extends INsdManager.Stub { 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 @@ public class NsdService extends INsdManager.Stub { 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 @@ public class NsdService extends INsdManager.Stub { } @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 class NsdService extends INsdManager.Stub { } 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 @@ public class NsdService extends INsdManager.Stub { } } - 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 @@ public class NsdService extends INsdManager.Stub { 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 @@ public class NsdService extends INsdManager.Stub { } } } - - /** - * 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 2272a375fb83..2624a419e4a4 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 6362882e2332..157a54e3573d 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 cb858c85e888..6d5615dfe52f 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 @@ public class MainSwitchBar extends LinearLayout implements CompoundButton.OnChec 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 ff00fb3282b1..0f340239cd24 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.Os; 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.TextUtils; 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 @@ public class DeviceInfoUtils { 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 @@ public class DeviceInfoUtils { 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 @@ public class DeviceInfoUtils { 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 389892ed15e4..8ac4e38677fa 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 @@ public class BluetoothEventManager { } @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 8b17be1e8ec9..dee68948a16e 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 @@ public class EnableZenModeDialog { @VisibleForTesting protected Context mContext; private final int mThemeResId; + private final boolean mCancelIsNeutral; @VisibleForTesting protected TextView mZenAlarmWarning; @VisibleForTesting @@ -101,8 +102,13 @@ public class EnableZenModeDialog { } 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 @@ public class EnableZenModeDialog { 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 @@ public class EnableZenModeDialog { } }); + 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/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java index bf0dc7bce5f9..1343895ed93d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java @@ -261,8 +261,6 @@ public class WifiStatusTracker { private void updateWifiState() { state = mWifiManager.getWifiState(); enabled = state == WifiManager.WIFI_STATE_ENABLED; - isCarrierMerged = false; - subId = 0; } private void updateRssi(int newRssi) { 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 4bff78f4af2c..bee466d39c23 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 @@ public class BluetoothEventManagerTest { /* 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 @@ public class BluetoothEventManagerTest { /* 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 @@ public class BluetoothEventManagerTest { @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 @@ public class BluetoothEventManagerTest { @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 @@ public class BluetoothEventManagerTest { @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 @@ public class BluetoothEventManagerTest { @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 class BluetoothEventManagerTest { 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 class BluetoothEventManagerTest { 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 class BluetoothEventManagerTest { 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 @@ public class BluetoothEventManagerTest { @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 @@ public class BluetoothEventManagerTest { @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 @@ public class BluetoothEventManagerTest { @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 @@ public class BluetoothEventManagerTest { @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/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml index 04d3f9486354..c5bea2ea6791 100644 --- a/packages/SettingsProvider/AndroidManifest.xml +++ b/packages/SettingsProvider/AndroidManifest.xml @@ -3,6 +3,8 @@ coreApp="true" android:sharedUserId="android.uid.system"> + <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> + <application android:allowClearUserData="false" android:label="@string/app_label" android:process="system" diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index 07b24f0a1eb9..7732da40aac2 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -272,9 +272,6 @@ <integer name="def_wearable_offChargerWifiUsageLimitMinutes">120</integer> - <!-- Default enabled state of accelerometer-based up/down gestures. --> - <bool name="def_wearable_upDownGesturesEnabled">false</bool> - <!-- Whether to enable mute when off body by default. --> <bool name="def_wearable_muteWhenOffBodyEnabled">true</bool> diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 8da9e35a2ab2..08e491db7247 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -205,7 +205,6 @@ public class GlobalSettingsValidators { VALIDATORS.put( Global.Wearable.ALT_BYPASS_WIFI_REQUIREMENT_TIME_MILLIS, ANY_INTEGER_VALIDATOR); - VALIDATORS.put(Global.Wearable.UPDOWN_GESTURES_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put( Global.Wearable.SETUP_SKIPPED, new DiscreteValueValidator( diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index b3470700bb5c..1e2d4e9ac93a 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.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT; 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; @@ -1346,6 +1347,13 @@ public class SettingsProvider extends ContentProvider { // Anyone can get the global settings, so no security checks. for (int i = 0; i < nameCount; i++) { String name = names.get(i); + try { + enforceSettingReadable(name, SETTINGS_TYPE_GLOBAL, + UserHandle.getCallingUserId()); + } catch (SecurityException e) { + // Caller doesn't have permission to read this setting + continue; + } Setting setting = settingsState.getSettingLocked(name); appendSettingToCursor(result, setting); } @@ -1523,6 +1531,13 @@ public class SettingsProvider extends ContentProvider { continue; } + try { + enforceSettingReadable(name, SETTINGS_TYPE_SECURE, callingUserId); + } catch (SecurityException e) { + // Caller doesn't have permission to read this setting + continue; + } + // As of Android O, the SSAID is read from an app-specific entry in table // SETTINGS_FILE_SSAID, unless accessed by a system process. final Setting setting; @@ -1785,7 +1800,12 @@ public class SettingsProvider extends ContentProvider { for (int i = 0; i < nameCount; i++) { String name = names.get(i); - + try { + enforceSettingReadable(name, SETTINGS_TYPE_SYSTEM, callingUserId); + } catch (SecurityException e) { + // Caller doesn't have permission to read this setting + continue; + } // Determine the owning user as some profile settings are cloned from the parent. final int owningUserId = resolveOwningUserIdForSystemSettingLocked(callingUserId, name); @@ -3604,7 +3624,7 @@ public class SettingsProvider extends ContentProvider { } private final class UpgradeController { - private static final int SETTINGS_VERSION = 207; + private static final int SETTINGS_VERSION = 208; private final int mUserId; @@ -5242,11 +5262,6 @@ public class SettingsProvider extends ContentProvider { initGlobalSettingsDefaultValForWearLocked( Global.Wearable.ALT_BYPASS_WIFI_REQUIREMENT_TIME_MILLIS, 0L); initGlobalSettingsDefaultValForWearLocked( - Global.Wearable.UPDOWN_GESTURES_ENABLED, - getContext() - .getResources() - .getBoolean(R.bool.def_wearable_upDownGesturesEnabled)); - initGlobalSettingsDefaultValForWearLocked( Global.Wearable.SETUP_SKIPPED, Global.Wearable.SETUP_SKIPPED_UNKNOWN); initGlobalSettingsDefaultValForWearLocked( Global.Wearable.LAST_CALL_FORWARD_ACTION, @@ -5458,6 +5473,30 @@ public class SettingsProvider extends ContentProvider { 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/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index cd85904589a6..f0f2c0fef65c 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -611,7 +611,6 @@ public class SettingsBackupTest { Settings.Global.Wearable.AUTO_WIFI, Settings.Global.Wearable.WIFI_POWER_SAVE, Settings.Global.Wearable.ALT_BYPASS_WIFI_REQUIREMENT_TIME_MILLIS, - Settings.Global.Wearable.UPDOWN_GESTURES_ENABLED, Settings.Global.Wearable.SETUP_SKIPPED, Settings.Global.Wearable.LAST_CALL_FORWARD_ACTION, Settings.Global.Wearable.STEM_1_TYPE, diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 867ab3cfbc69..262cf536eff5 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -256,6 +256,7 @@ <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" /> <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" /> <uses-permission android:name="android.permission.STATUS_BAR" /> + <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- Permission needed to rename bugreport notifications (so they're not shown as Shell) --> <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" /> <uses-permission android:name="android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE" /> @@ -570,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" /> @@ -599,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 f2eff05a16f1..5c19b41904f8 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 --> @@ -588,11 +589,7 @@ android:theme="@style/Theme.SystemUI.Dialog.Alert" android:finishOnCloseSystemDialogs="true" android:excludeFromRecents="true" - android:exported="true"> - <intent-filter> - <action android:name="com.android.intent.action.REQUEST_SLICE_PERMISSION" /> - </intent-filter> - </activity> + android:exported="true" /> <!-- platform logo easter egg activity --> <activity 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 620dd4891b9b..000000000000 --- 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 a268abce0c27..000000000000 --- 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 fb80f1c131ad..a0d335db92d6 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.ActivityTaskManager 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.SyncRtSurfaceTransactionApplier 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 @@ private const val TAG = "ActivityLaunchAnimator" * 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 const val LAUNCH_TIMEOUT = 1000L + private val ANIMATION_DELAY_NAV_FADE_IN = + TIMINGS.totalDuration - ANIMATION_DURATION_NAV_FADE_IN - private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f) + 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 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 @@ class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) { 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 @@ class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) { 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 @@ class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) { .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 de82ebdc6b1c..066e169dcde3 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.Animator 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.service.dreams.IDreamManager 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 @@ private const val TAG = "DialogLaunchAnimator" * 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 @@ class DialogLaunchAnimator( 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 @@ class DialogLaunchAnimator( } private class AnimatedDialog( - private val context: Context, private val launchAnimator: LaunchAnimator, private val dreamManager: IDreamManager, @@ -174,10 +181,16 @@ private class AnimatedDialog( 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 @@ private class AnimatedDialog( // 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 @@ private class AnimatedDialog( // 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) - 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 + // 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) + } - // 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 + /** + * 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 + } + + // 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 class AnimatedDialog( private fun onDialogDismissed() { if (Looper.myLooper() != Looper.getMainLooper()) { - context.mainExecutor.execute { onDialogDismissed() } + dialog.context.mainExecutor.execute { onDialogDismissed() } return } @@ -556,25 +587,12 @@ private class AnimatedDialog( .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 3bf6c5ebd091..ebe96ebf2988 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.Log 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 @@ class LaunchAnimator @JvmOverloads constructor( * 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 @@ class LaunchAnimator @JvmOverloads constructor( 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 @@ class LaunchAnimator @JvmOverloads constructor( // 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 @@ class LaunchAnimator @JvmOverloads constructor( // 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 @@ class LaunchAnimator @JvmOverloads constructor( // 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 @@ class LaunchAnimator @JvmOverloads constructor( // 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 e9bd638dfbf4..e80cfafdd71a 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 4fc38a813e8a..16010430df11 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 50267fda0b25..000000000000 --- 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 d9ae777ef8a1..000000000000 --- 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 d49454fa1504..000000000000 --- 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 665b4564ebf1..a47299d6f854 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 59a31e8f6136..000000000000 --- 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 1e0ce0026d8e..46cbc253175e 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"/> + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="match_parent"> - <ImageView - android:id="@+id/icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content"/> + <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/title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Title"/> + <ImageView + android:id="@+id/icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:contentDescription="@null" /> - <TextView - android:id="@+id/subtitle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Subtitle"/> + <TextView + android:id="@+id/title" + style="@style/TextAppearance.AuthCredential.Title" + android:layout_width="wrap_content" + 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"/> + <TextView + android:id="@+id/subtitle" + style="@style/TextAppearance.AuthCredential.Subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_weight="1"/> + <TextView + android:id="@+id/description" + style="@style/TextAppearance.AuthCredential.Description" + 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"/> + </LinearLayout> - <TextView - android:id="@+id/error" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Error"/> + <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> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_weight="5"/> + </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 4939ea2c99ee..470298ea0b13 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"> - - <FrameLayout - android:layout_width="wrap_content" - android:layout_height="0dp" - android:layout_weight="1" - style="@style/LockPatternContainerStyle"> - - <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"/> + <TextView + android:id="@+id/subtitle" + style="@style/TextAppearance.AuthCredential.Subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/description" + style="@style/TextAppearance.AuthCredential.Description" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/auth_credential_header" + android:gravity="center" + android:orientation="vertical" + android:paddingBottom="16dp" + android:paddingTop="60dp"> + + <FrameLayout + style="@style/LockPatternContainerStyle" + android:layout_width="wrap_content" + 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> + </FrameLayout> - <TextView - android:id="@+id/error" + </LinearLayout> + + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - style="@style/TextAppearance.AuthCredential.Error"/> + 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> + </LinearLayout> - <Space - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_weight="1"/> + </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 e90a6446c47b..75fa66be86d7 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 843770220996..b7265b951387 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 e43a149a6cd9..f57d65aef8af 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 c43428508e8d..9d4c2c3b8379 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 11865a52e9d8..8cad5b3581a4 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 c09658b28927..c7350a1eee8b 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 43254aa5c538..99508a5fc73a 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -588,9 +588,11 @@ <string name="quick_settings_camera_label">Camera access</string> <!-- QuickSettings: Microphone [CHAR LIMIT=NONE] --> <string name="quick_settings_mic_label">Mic access</string> - <!-- QuickSettings: Camera or microphone access is allowed [CHAR LIMIT=NONE] --> + <!-- QuickSettings: Camera or microphone access is allowed. If you update this string, please + update the string "available" in packages/modules/Permission/PermissionController[CHAR LIMIT=NONE] --> <string name="quick_settings_camera_mic_available">Available</string> - <!-- QuickSettings: Camera or microphone access is blocked [CHAR LIMIT=NONE] --> + <!-- QuickSettings: Camera or microphone access is blocked. If you update this string, please + update the string "blocked" in packages/modules/Permission/PermissionController [CHAR LIMIT=NONE] --> <string name="quick_settings_camera_mic_blocked">Blocked</string> <!-- QuickSettings: Media device [CHAR LIMIT=NONE] --> <string name="quick_settings_media_device_label">Media device</string> @@ -632,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> @@ -2048,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 1a4f3e24fde3..01f0c1236bdc 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 e46b6f12e4a3..ea93a3b9375e 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 @@ fun createUnfoldTransitionProgressProvider( 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 90f5998053b8..51eae573f040 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.SpringAnimation 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 @@ import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener * - doesn't handle postures */ internal class PhysicsBasedUnfoldTransitionProgressProvider( - private val handler: Handler, private val foldStateProvider: FoldStateProvider ) : UnfoldTransitionProgressProvider, @@ -51,8 +50,6 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider) } - private val timeoutRunnable = TimeoutRunnable() - private var isTransitionRunning = false private var isAnimatedCancelRunning = false @@ -92,7 +89,7 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( 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 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( 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 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( } private fun cancelTransition(endValue: Float, animate: Boolean) { - handler.removeCallbacks(timeoutRunnable) - if (isTransitionRunning && animate) { isAnimatedCancelRunning = true springAnimation.animateToFinalPosition(endValue) @@ -175,8 +174,6 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( } springAnimation.start() - - handler.postDelayed(timeoutRunnable, TRANSITION_TIMEOUT_MILLIS) } override fun addCallback(listener: TransitionProgressListener) { @@ -187,13 +184,6 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( 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 @@ internal class PhysicsBasedUnfoldTransitionProgressProvider( 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 35e2b30d0a39..6d9631c12430 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 @@ class DeviceFoldStateProvider( 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 @@ class DeviceFoldStateProvider( @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 @@ class DeviceFoldStateProvider( 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 @@ class DeviceFoldStateProvider( onHingeAngle(angle) } } + + private inner class TimeoutRunnable : Runnable { + + override fun run() { + notifyFoldUpdate(FOLD_UPDATE_ABORTED) + } + } +} + +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 START_CLOSING_THRESHOLD_DEGREES = 95f -private const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
\ No newline at end of file +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 643ece353522..bffebcd4512b 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 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> { 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 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> { 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 ba6771644db1..237ca71027b5 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.PackageManager; 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLockPatternUtils = lockPatternUtils; mAuthController = authController; dumpManager.registerDumpable(getClass().getName(), this); + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); mHandler = new Handler(mainLooper) { @Override @@ -2517,6 +2533,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab // 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/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index dd162d057464..1f5303fba0a4 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -90,7 +90,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private static final int sLockIconRadiusPx = (int) (sDefaultDensity * 36); private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); - private static final long LONG_PRESS_TIMEOUT = 150L; // milliseconds + private static final long LONG_PRESS_TIMEOUT = 200L; // milliseconds @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @NonNull private final KeyguardViewController mKeyguardViewController; @@ -683,6 +683,17 @@ public class LockIconViewController extends ViewController<LockIconView> impleme if (mOnGestureDetectedRunnable != null) { mOnGestureDetectedRunnable.run(); } + + if (mVibrator != null) { + // play device entry haptic (same as biometric success haptic) + mVibrator.vibrate( + Process.myUid(), + getContext().getOpPackageName(), + UdfpsController.EFFECT_CLICK, + "lock-icon-device-entry", + TOUCH_VIBRATION_ATTRIBUTES); + } + mKeyguardViewController.showBouncer(/* scrim */ true); } diff --git a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java index 47adffc216a5..45077d2333b6 100644 --- a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java @@ -50,10 +50,13 @@ public class SlicePermissionActivity extends Activity implements OnClickListener protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // Verify intent is valid mUri = getIntent().getParcelableExtra(SliceProvider.EXTRA_BIND_URI); mCallingPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PKG); - if (mUri == null) { - Log.e(TAG, SliceProvider.EXTRA_BIND_URI + " wasn't provided"); + if (mUri == null + || !SliceProvider.SLICE_TYPE.equals(getContentResolver().getType(mUri)) + || !SliceManager.ACTION_REQUEST_SLICE_PERMISSION.equals(getIntent().getAction())) { + Log.e(TAG, "Intent is not valid"); finish(); return; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 29e5574830a8..6edf2a87f903 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.IntentFilter; 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 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba 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 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps; @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser; + private SensorPrivacyManager mSensorPrivacyManager; private class BiometricTaskStackListener extends TaskStackListener { @Override @@ -492,6 +495,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); context.registerReceiver(mBroadcastReceiver, filter); + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); } private void updateFingerprintLocation() { @@ -642,10 +646,16 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba 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 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba ? 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/communal/conditions/CommunalConditionsMonitor.java b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalConditionsMonitor.java index 4f772da48768..1197816e24d2 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalConditionsMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalConditionsMonitor.java @@ -16,139 +16,27 @@ package com.android.systemui.communal.conditions; -import static com.android.systemui.communal.dagger.CommunalModule.COMMUNAL_CONDITIONS; -import android.util.Log; +import static com.android.systemui.communal.dagger.CommunalModule.COMMUNAL_CONDITIONS; -import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.statusbar.policy.CallbackController; +import com.android.systemui.util.condition.Condition; +import com.android.systemui.util.condition.Monitor; -import org.jetbrains.annotations.NotNull; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; /** - * {@link CommunalConditionsMonitor} takes in a set of conditions, monitors whether all of them have - * been fulfilled, and informs any registered listeners. + * A concrete implementation of {@Monitor} with conditions for monitoring when communal mode should + * be enabled. */ @SysUISingleton -public class CommunalConditionsMonitor implements - CallbackController<CommunalConditionsMonitor.Callback> { - private final String mTag = getClass().getSimpleName(); - - private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>(); - - // Set of all conditions that need to be monitored. - private final Set<CommunalCondition> mConditions; - - // Map of values of each condition. - private final HashMap<CommunalCondition, Boolean> mConditionsMap = new HashMap<>(); - - // Whether all conditions have been met. - private boolean mAllConditionsMet = false; - - // Whether the monitor has started listening for all the conditions. - private boolean mHaveConditionsStarted = false; - - // Callback for when each condition has been updated. - private final CommunalCondition.Callback mConditionCallback = (condition, isConditionMet) -> { - mConditionsMap.put(condition, isConditionMet); - - final boolean newAllConditionsMet = !mConditionsMap.containsValue(false); - - if (newAllConditionsMet == mAllConditionsMet) { - return; - } - - if (shouldLog()) Log.d(mTag, "all conditions met: " + newAllConditionsMet); - mAllConditionsMet = newAllConditionsMet; - - // Updates all callbacks. - final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator(); - while (iterator.hasNext()) { - final Callback callback = iterator.next().get(); - if (callback == null) { - iterator.remove(); - } else { - callback.onConditionsChanged(mAllConditionsMet); - } - } - }; - +public class CommunalConditionsMonitor extends Monitor { @Inject public CommunalConditionsMonitor( - @Named(COMMUNAL_CONDITIONS) Set<CommunalCondition> communalConditions) { - mConditions = communalConditions; - - // Initializes the conditions map and registers a callback for each condition. - mConditions.forEach((condition -> mConditionsMap.put(condition, false))); - } - - @Override - public void addCallback(@NotNull Callback callback) { - if (shouldLog()) Log.d(mTag, "adding callback"); - mCallbacks.add(new WeakReference<>(callback)); - - // Updates the callback immediately. - callback.onConditionsChanged(mAllConditionsMet); - - if (!mHaveConditionsStarted) { - if (shouldLog()) Log.d(mTag, "starting all conditions"); - mConditions.forEach(condition -> condition.addCallback(mConditionCallback)); - mHaveConditionsStarted = true; - } - } - - @Override - public void removeCallback(@NotNull Callback callback) { - if (shouldLog()) Log.d(mTag, "removing callback"); - final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator(); - while (iterator.hasNext()) { - final Callback cb = iterator.next().get(); - if (cb == null || cb == callback) { - iterator.remove(); - } - } - - if (mCallbacks.isEmpty() && mHaveConditionsStarted) { - if (shouldLog()) Log.d(mTag, "stopping all conditions"); - mConditions.forEach(condition -> condition.removeCallback(mConditionCallback)); - - mAllConditionsMet = false; - mHaveConditionsStarted = false; - } - } - - /** - * Force updates each condition to the value provided. - */ - @VisibleForTesting - public void overrideAllConditionsMet(boolean value) { - mConditions.forEach(condition -> condition.updateCondition(value)); - } - - private boolean shouldLog() { - return Log.isLoggable(mTag, Log.DEBUG); - } - - /** - * Callback that receives updates of whether all conditions have been fulfilled. - */ - public interface Callback { - /** - * Triggered when the fulfillment of all conditions have been met. - * - * @param allConditionsMet True if all conditions have been fulfilled. False if none or - * only partial conditions have been fulfilled. - */ - void onConditionsChanged(boolean allConditionsMet); + @Named(COMMUNAL_CONDITIONS) Set<Condition> communalConditions) { + super(communalConditions); } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalSettingCondition.java b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalSettingCondition.java index 1616b18172a9..25519d0f96c7 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalSettingCondition.java +++ b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalSettingCondition.java @@ -23,6 +23,7 @@ import android.provider.Settings; import androidx.annotation.MainThread; +import com.android.systemui.util.condition.Condition; import com.android.systemui.util.settings.SecureSettings; import javax.inject.Inject; @@ -30,7 +31,7 @@ import javax.inject.Inject; /** * Monitors the communal setting, and informs any listeners with updates. */ -public class CommunalSettingCondition extends CommunalCondition { +public class CommunalSettingCondition extends Condition { private final SecureSettings mSecureSettings; private final ContentObserver mCommunalSettingContentObserver; diff --git a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalTrustedNetworkCondition.java b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalTrustedNetworkCondition.java index e4692dbba1ca..2d59e1390083 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalTrustedNetworkCondition.java +++ b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalTrustedNetworkCondition.java @@ -31,6 +31,7 @@ import android.util.Log; import androidx.annotation.NonNull; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.util.condition.Condition; import com.android.systemui.util.settings.SecureSettings; import java.util.Arrays; @@ -42,7 +43,7 @@ import javax.inject.Inject; * Monitors Wi-Fi connections and triggers callback, if any, when the device is connected to and * disconnected from a trusted network. */ -public class CommunalTrustedNetworkCondition extends CommunalCondition { +public class CommunalTrustedNetworkCondition extends Condition { private final String mTag = getClass().getSimpleName(); private final ConnectivityManager mConnectivityManager; private final ContentObserver mTrustedNetworksObserver; diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java index a0986660ebdc..f27ae344eb24 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java @@ -28,12 +28,12 @@ import androidx.annotation.Nullable; import com.android.systemui.R; import com.android.systemui.communal.CommunalSource; import com.android.systemui.communal.PackageObserver; -import com.android.systemui.communal.conditions.CommunalCondition; import com.android.systemui.communal.conditions.CommunalSettingCondition; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.idle.AmbientLightModeMonitor; import com.android.systemui.idle.LightSensorEventsDebounceAlgorithm; import com.android.systemui.idle.dagger.IdleViewComponent; +import com.android.systemui.util.condition.Condition; import java.util.Collections; import java.util.HashSet; @@ -99,7 +99,7 @@ public interface CommunalModule { @Provides @ElementsIntoSet @Named(COMMUNAL_CONDITIONS) - static Set<CommunalCondition> provideCommunalConditions( + static Set<Condition> provideCommunalConditions( CommunalSettingCondition communalSettingCondition) { return new HashSet<>(Collections.singletonList(communalSettingCondition)); } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 97533c90adba..d9f66631b973 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -49,6 +49,8 @@ public class Flags { public static final BooleanFlag NOTIFICATION_PIPELINE_DEVELOPER_LOGGING = new BooleanFlag(103, false); + public static final ResourceBooleanFlag NOTIFICATION_SHADE_DRAG = + new ResourceBooleanFlag(104, R.bool.config_enableNotificationShadeDrag); /***************************************/ // 200 - keyguard/lockscreen diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java index 10878dcc2474..c46ffa077746 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java @@ -108,7 +108,18 @@ public class FragmentHostManager { return p; } - public FragmentHostManager addTagListener(String tag, FragmentListener listener) { + /** + * Add a {@link FragmentListener} for a given tag + * + * @param tag string identifier for the fragment + * @param listener the listener to register + * + * @return this + */ + public FragmentHostManager addTagListener( + @NonNull String tag, + @NonNull FragmentListener listener + ) { ArrayList<FragmentListener> listeners = mListeners.get(tag); if (listeners == null) { listeners = new ArrayList<>(); 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 f74fbf45157f..7f5744c9472b 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 abstract class MediaOutputBaseDialog extends SystemUIDialog implements 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 012eda745976..56317c68c2fd 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.MediaSessionManager; 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.bluetooth.LocalBluetoothManager; 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 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { 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 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback { 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/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index c8551141f96e..3e2da7204c05 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java @@ -114,7 +114,8 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { @Override public int getTilesHeight() { - TileLayout tileLayout = mPages.get(getCurrentItem()); + // Use the first page as that is the maximum height we need to show. + TileLayout tileLayout = mPages.get(0); if (tileLayout == null) { return 0; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index eddc206db231..d470fa242f71 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 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr } 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 abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr /** */ 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 7ba9cc22bec9..a2577d6e7f60 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 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements 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 83506b22548b..bb274588e1e2 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 @@ public class DndTile extends QSTileImpl<BooleanState> { 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 @@ public class DndTile extends QSTileImpl<BooleanState> { } 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 26c89ff83076..e3f085cfbe36 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 @@ public class InternetDialog extends SystemUIDialog implements 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 @@ public class InternetDialog extends SystemUIDialog implements protected WifiEntry mConnectedWifiEntry; @VisibleForTesting protected int mWifiEntriesCount; + @VisibleForTesting + protected boolean mHasMoreEntry; // Wi-Fi scanning progress bar protected boolean mIsProgressBarVisible; @@ -157,7 +159,9 @@ public class InternetDialog extends SystemUIDialog implements 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 @@ public class InternetDialog extends SystemUIDialog implements } 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 @@ public class InternetDialog extends SystemUIDialog implements } 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 @@ public class InternetDialog extends SystemUIDialog implements @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 6f63a08c6c0f..1fee1b430073 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 @@ public class InternetDialogController implements AccessPointController.AccessPoi 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 @@ public class InternetDialogController implements AccessPointController.AccessPoi 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 @@ public class InternetDialogController implements AccessPointController.AccessPoi mWifiEntriesCount = wifiEntries.size(); if (mCallback != null) { - mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry); + mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry, hasMoreEntry); } } @@ -1060,7 +1069,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi 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 c50365f1bf38..71c5fad5322e 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 @@ import com.android.systemui.statusbar.phone.SystemUIDialog 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 @@ class SensorUseDialog( 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 b0071d92481d..dae375ad7cc7 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 @@ class SensorUseStartedActivity @Inject constructor( 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 @@ class SensorUseStartedActivity @Inject constructor( } } - mDialog = SensorUseDialog(this, sensor, this) + mDialog = SensorUseDialog(this, sensor, this, this) mDialog!!.show() } @@ -212,4 +212,8 @@ class SensorUseStartedActivity @Inject constructor( .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 5437ce63475e..d6125ce7aa65 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.NotificationChannel; 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.dagger.qualifiers.Main; 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 @@ import java.util.List; 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 @@ public class NotificationListener extends NotificationListenerWithPlugins { 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 @@ public class NotificationListener extends NotificationListenerWithPlugins { 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 @@ public class NotificationListener extends NotificationListenerWithPlugins { 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 class NotificationListener extends NotificationListenerWithPlugins { 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 @@ public class NotificationListener extends NotificationListenerWithPlugins { 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 c4fadff16c09..4551807499ab 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 @@ public class UserUtil { 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 @@ public class UserUtil { @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/connectivity/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java index f8f7b7f8f83a..89fe24ff91c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiSignalController.java @@ -82,7 +82,7 @@ public class WifiSignalController extends SignalController<WifiState, IconGroup> @Override public void notifyListeners(SignalCallback callback) { if (mCurrentState.isCarrierMerged) { - if (mCurrentState.isDefault) { + if (mCurrentState.isDefault || !mNetworkController.isRadioOn()) { notifyListenersForCarrierWifi(callback); } } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt new file mode 100644 index 000000000000..55d549ddf60b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt @@ -0,0 +1,78 @@ +/* + * 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.systemui.statusbar.core + +import android.app.Fragment +import com.android.systemui.R +import com.android.systemui.fragments.FragmentHostManager +import com.android.systemui.statusbar.phone.PhoneStatusBarView +import com.android.systemui.statusbar.phone.PhoneStatusBarViewController +import com.android.systemui.statusbar.phone.dagger.StatusBarComponent +import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope +import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment +import com.android.systemui.statusbar.window.StatusBarWindowController +import java.lang.IllegalStateException +import javax.inject.Inject + +/** + * Responsible for creating the StatusBar window and initializing the root components of that window + * (see [CollapsedStatusBarFragment]) + */ +@StatusBarScope +class StatusBarInitializer @Inject constructor( + private val windowController: StatusBarWindowController +) { + + var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null + + /** + * Creates the status bar window and root views, and initializes the component + */ + fun initializeStatusBar( + sbComponent: StatusBarComponent + ) { + windowController.fragmentHostManager.addTagListener( + CollapsedStatusBarFragment.TAG, + object : FragmentHostManager.FragmentListener { + override fun onFragmentViewCreated(tag: String, fragment: Fragment) { + val statusBarFragmentComponent = (fragment as CollapsedStatusBarFragment) + .statusBarFragmentComponent ?: throw IllegalStateException() + val statusBarView = statusBarFragmentComponent.phoneStatusBarView + val sbViewController = + statusBarFragmentComponent.phoneStatusBarViewController + + statusBarViewUpdatedListener + ?.onStatusBarViewUpdated(statusBarView, sbViewController) + } + + override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) { + // nop + } + }).fragmentManager + .beginTransaction() + .replace(R.id.status_bar_container, + sbComponent.createCollapsedStatusBarFragment(), + CollapsedStatusBarFragment.TAG) + .commit() + } + + interface OnStatusBarViewUpdatedListener { + fun onStatusBarViewUpdated( + statusBarView: PhoneStatusBarView, + statusBarViewController: PhoneStatusBarViewController + ) + } +} 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 4c7ee1775241..d574cdabced6 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 android.service.dreams.IDreamManager; 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 @@ public interface StatusBarDependenciesModule { 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 @@ public interface StatusBarDependenciesModule { */ @Provides @SysUISingleton - static LaunchAnimator provideLaunchAnimator(Context context) { - return new LaunchAnimator(context); - } - - /** - */ - @Provides - @SysUISingleton - static ActivityLaunchAnimator provideActivityLaunchAnimator(LaunchAnimator launchAnimator) { - return new ActivityLaunchAnimator(launchAnimator); + static ActivityLaunchAnimator provideActivityLaunchAnimator() { + return new ActivityLaunchAnimator(); } /** */ @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 64a73054c434..349b1918a504 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 @@ package com.android.systemui.statusbar.notification 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 @@ class ExpandAnimationParameters( } 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 8797335fa22d..ffe6e4b79e0b 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setQsExpansionFraction(float qsExpansionFraction) { + boolean footerAffected = mQsExpansionFraction != qsExpansionFraction + && (mQsExpansionFraction == 1 || qsExpansionFraction == 1); mQsExpansionFraction = qsExpansionFraction; updateUseRoundedRectClipping(); @@ -4777,6 +4780,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index 8a7cf360a254..ac62522eb8f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.phone; -import android.graphics.Point; import android.graphics.Rect; import android.view.View; @@ -59,9 +58,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar private final NotificationIconAreaController mNotificationIconAreaController; private final HeadsUpManagerPhone mHeadsUpManager; private final NotificationStackScrollLayoutController mStackScrollerController; - private final View mCenteredIconView; - private final View mClockView; - private final View mOperatorNameView; + private final DarkIconDispatcher mDarkIconDispatcher; private final NotificationPanelViewController mNotificationPanelViewController; private final Consumer<ExpandableNotificationRow> @@ -71,6 +68,11 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar private final StatusBarStateController mStatusBarStateController; private final CommandQueue mCommandQueue; private final NotificationWakeUpCoordinator mWakeUpCoordinator; + + private View mCenteredIconView; + private View mClockView; + private View mOperatorNameView; + @VisibleForTesting float mExpandedHeight; @VisibleForTesting @@ -85,7 +87,6 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar } }; private boolean mAnimationsEnabled = true; - Point mPoint; private KeyguardStateController mKeyguardStateController; @Inject 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 4651e8446059..c68d39b97355 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 @@ public class NotificationListenerWithPlugins extends NotificationListenerService @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 @@ public class NotificationListenerWithPlugins extends NotificationListenerService 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 4053d842f40d..a64e579ce72e 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.keyguard.dagger.KeyguardUserSwitcherComponent; 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 @@ public class NotificationPanelViewController extends PanelViewController { */ 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; @@ -780,6 +782,7 @@ public class NotificationPanelViewController extends PanelViewController { InteractionJankMonitor interactionJankMonitor, QsFrameTranslateController qsFrameTranslateController) { super(view, + featureFlags, falsingManager, dozeLog, keyguardStateController, @@ -3785,8 +3788,8 @@ public class NotificationPanelViewController extends PanelViewController { } 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/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index 6a00591ea0e9..9af79a94285b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -53,6 +53,8 @@ import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.classifier.Classifier; import com.android.systemui.doze.DozeLog; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -212,6 +214,7 @@ public abstract class PanelViewController { public PanelViewController( PanelView view, + FeatureFlags featureFlags, FalsingManager falsingManager, DozeLog dozeLog, KeyguardStateController keyguardStateController, @@ -270,8 +273,7 @@ public abstract class PanelViewController { mBounceInterpolator = new BounceInterpolator(); mFalsingManager = falsingManager; mDozeLog = dozeLog; - mNotificationsDragEnabled = mResources.getBoolean( - R.bool.config_enableNotificationShadeDrag); + mNotificationsDragEnabled = featureFlags.isEnabled(Flags.NOTIFICATION_SHADE_DRAG); mVibratorHelper = vibratorHelper; mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation); mStatusBarTouchableRegionManager = statusBarTouchableRegionManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index ddf1eab33e73..0f0a2f061959 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -148,7 +148,6 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; -import com.android.systemui.dump.DumpManager; import com.android.systemui.emergency.EmergencyGesture; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; @@ -195,13 +194,12 @@ import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; -import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.PowerButtonReveal; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; +import com.android.systemui.statusbar.core.StatusBarInitializer; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.NotificationActivityStarter; @@ -221,9 +219,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule; -import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; -import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger; -import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.BatteryController; @@ -237,7 +232,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.window.StatusBarWindowController; -import com.android.systemui.tuner.TunerService; import com.android.systemui.util.DumpUtilsKt; import com.android.systemui.util.WallpaperController; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -458,7 +452,6 @@ public class StatusBar extends CoreStartable implements @Nullable protected LockscreenWallpaper mLockscreenWallpaper; private final AutoHideController mAutoHideController; - private final CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger; private final Point mCurrentDisplaySize = new Point(); @@ -509,10 +502,7 @@ public class StatusBar extends CoreStartable implements private final DemoModeController mDemoModeController; private final NotificationsController mNotificationsController; private final OngoingCallController mOngoingCallController; - private final SystemStatusAnimationScheduler mAnimationScheduler; private final StatusBarSignalPolicy mStatusBarSignalPolicy; - private final StatusBarLocationPublisher mStatusBarLocationPublisher; - private final StatusBarIconController mStatusBarIconController; private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; // expanded notifications @@ -522,7 +512,6 @@ public class StatusBar extends CoreStartable implements // settings private QSPanelController mQSPanelController; - private final OperatorNameViewController.Factory mOperatorNameViewControllerFactory; KeyguardIndicationController mKeyguardIndicationController; private View mReportRejectedTouch; @@ -547,7 +536,6 @@ public class StatusBar extends CoreStartable implements private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private final MessageRouter mMessageRouter; private final WallpaperManager mWallpaperManager; - private final TunerService mTunerService; private StatusBarComponent mStatusBarComponent; @@ -752,7 +740,6 @@ public class StatusBar extends CoreStartable implements DozeScrimController dozeScrimController, VolumeComponent volumeComponent, CommandQueue commandQueue, - CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger, StatusBarComponent.Factory statusBarComponentFactory, PluginManager pluginManager, Optional<LegacySplitScreen> splitScreenOptional, @@ -767,7 +754,6 @@ public class StatusBar extends CoreStartable implements KeyguardDismissUtil keyguardDismissUtil, ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, - OperatorNameViewController.Factory operatorNameViewControllerFactory, PhoneStatusBarPolicy phoneStatusBarPolicy, KeyguardIndicationController keyguardIndicationController, DemoModeController demoModeController, @@ -778,9 +764,6 @@ public class StatusBar extends CoreStartable implements ScreenOffAnimationController screenOffAnimationController, WallpaperController wallpaperController, OngoingCallController ongoingCallController, - SystemStatusAnimationScheduler animationScheduler, - StatusBarLocationPublisher locationPublisher, - StatusBarIconController statusBarIconController, StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, LockscreenShadeTransitionController lockscreenShadeTransitionController, FeatureFlags featureFlags, @@ -790,8 +773,6 @@ public class StatusBar extends CoreStartable implements @Main MessageRouter messageRouter, WallpaperManager wallpaperManager, Optional<StartingSurface> startingSurfaceOptional, - TunerService tunerService, - DumpManager dumpManager, ActivityLaunchAnimator activityLaunchAnimator, NotifPipelineFlags notifPipelineFlags) { super(context); @@ -806,7 +787,6 @@ public class StatusBar extends CoreStartable implements mKeyguardBypassController = keyguardBypassController; mKeyguardStateController = keyguardStateController; mHeadsUpManager = headsUpManagerPhone; - mOperatorNameViewControllerFactory = operatorNameViewControllerFactory; mKeyguardIndicationController = keyguardIndicationController; mStatusBarTouchableRegionManager = statusBarTouchableRegionManager; mDynamicPrivacyController = dynamicPrivacyController; @@ -856,7 +836,6 @@ public class StatusBar extends CoreStartable implements mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy; mVolumeComponent = volumeComponent; mCommandQueue = commandQueue; - mCollapsedStatusBarFragmentLogger = collapsedStatusBarFragmentLogger; mStatusBarComponentFactory = statusBarComponentFactory; mPluginManager = pluginManager; mSplitScreenOptional = splitScreenOptional; @@ -875,10 +854,7 @@ public class StatusBar extends CoreStartable implements mBrightnessSliderFactory = brightnessSliderFactory; mWallpaperController = wallpaperController; mOngoingCallController = ongoingCallController; - mAnimationScheduler = animationScheduler; mStatusBarSignalPolicy = statusBarSignalPolicy; - mStatusBarLocationPublisher = locationPublisher; - mStatusBarIconController = statusBarIconController; mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager; mFeatureFlags = featureFlags; mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; @@ -886,7 +862,6 @@ public class StatusBar extends CoreStartable implements mMainExecutor = delayableExecutor; mMessageRouter = messageRouter; mWallpaperManager = wallpaperManager; - mTunerService = tunerService; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; mStartingSurfaceOptional = startingSurfaceOptional; @@ -1145,34 +1120,29 @@ public class StatusBar extends CoreStartable implements // Allow plugins to reference DarkIconDispatcher and StatusBarStateController mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class); mPluginDependencyProvider.allowPluginDependency(StatusBarStateController.class); - mStatusBarWindowController.getFragmentHostManager() - .addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> { - StatusBarFragmentComponent statusBarFragmentComponent = - ((CollapsedStatusBarFragment) fragment).getStatusBarFragmentComponent(); - if (statusBarFragmentComponent == null) { - throw new IllegalStateException( - "CollapsedStatusBarFragment should have a valid component"); - } - mStatusBarView = statusBarFragmentComponent.getPhoneStatusBarView(); - mPhoneStatusBarViewController = - statusBarFragmentComponent.getPhoneStatusBarViewController(); - - // Ensure we re-propagate panel expansion values to the panel controller and - // any listeners it may have, such as PanelBar. This will also ensure we - // re-display the notification panel if necessary (for example, if - // a heads-up notification was being displayed and should continue being - // displayed). - mNotificationPanelViewController.updatePanelExpansionAndVisibility(); - setBouncerShowingForStatusBarComponents(mBouncerShowing); - mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView); - checkBarModes(); - }).getFragmentManager() - .beginTransaction() - .replace(R.id.status_bar_container, - mStatusBarComponent.createCollapsedStatusBarFragment(), - CollapsedStatusBarFragment.TAG) - .commit(); + // Set up CollapsedStatusBarFragment and PhoneStatusBarView + StatusBarInitializer initializer = mStatusBarComponent.getStatusBarInitializer(); + initializer.setStatusBarViewUpdatedListener( + new StatusBarInitializer.OnStatusBarViewUpdatedListener() { + @Override + public void onStatusBarViewUpdated( + @NonNull PhoneStatusBarView statusBarView, + @NonNull PhoneStatusBarViewController statusBarViewController) { + mStatusBarView = statusBarView; + mPhoneStatusBarViewController = statusBarViewController; + mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView); + // Ensure we re-propagate panel expansion values to the panel controller and + // any listeners it may have, such as PanelBar. This will also ensure we + // re-display the notification panel if necessary (for example, if + // a heads-up notification was being displayed and should continue being + // displayed). + mNotificationPanelViewController.updatePanelExpansionAndVisibility(); + setBouncerShowingForStatusBarComponents(mBouncerShowing); + checkBarModes(); + } + }); + initializer.initializeStatusBar(mStatusBarComponent); mHeadsUpManager.setup(mVisualStabilityManager); mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView); @@ -1547,8 +1517,6 @@ public class StatusBar extends CoreStartable implements mHeadsUpManager.addListener(mStatusBarComponent.getStatusBarHeadsUpChangeListener()); - mHeadsUpManager.addListener(mStatusBarComponent.getStatusBarHeadsUpChangeListener()); - // Listen for demo mode changes mDemoModeController.addCallback(mDemoModeCallback); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java index 05b4776c2366..abb7449e3e42 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java @@ -532,7 +532,7 @@ public class StatusBarCommandQueueCallbacks implements CommandQueue.Callbacks { if (mStatusBar.getStatusBarView() != null && !showing && mStatusBarStateController.getState() == StatusBarState.SHADE) { - mNotificationPanelViewController.collapsePanel( + mNotificationPanelViewController.collapsePanel( false /* animate */, false /* delayed */, 1.0f /* speedUpFactor */); } } 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 32aae6c05df6..2ba37c2ec29f 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 @@ class StatusBarLaunchAnimatorController( 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 43264b600a0e..8df7b452e202 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 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh * 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 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh * 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 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh 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/phone/dagger/StatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java index 8297c194fe12..61dba927927a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java @@ -23,6 +23,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.android.keyguard.LockIconViewController; import com.android.systemui.biometrics.AuthRippleController; import com.android.systemui.statusbar.NotificationShelfController; +import com.android.systemui.statusbar.core.StatusBarInitializer; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.NotificationShadeWindowView; @@ -131,4 +132,10 @@ public interface StatusBarComponent { */ @Named(STATUS_BAR_FRAGMENT) CollapsedStatusBarFragment createCollapsedStatusBarFragment(); + + /** + * Creates a StatusBarInitializer + */ + @StatusBarScope + StatusBarInitializer getStatusBarInitializer(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index 7b44526df486..f93a8dcad223 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -38,7 +38,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.demomode.DemoModeController; -import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; @@ -60,11 +59,9 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; -import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.NotificationEntryManager; @@ -89,19 +86,15 @@ import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.LockscreenWallpaper; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy; -import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; -import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter; import com.android.systemui.statusbar.phone.StatusBarSignalPolicy; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; -import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.BatteryController; @@ -112,7 +105,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.window.StatusBarWindowController; -import com.android.systemui.tuner.TunerService; import com.android.systemui.util.WallpaperController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.MessageRouter; @@ -201,7 +193,6 @@ public interface StatusBarPhoneModule { DozeScrimController dozeScrimController, VolumeComponent volumeComponent, CommandQueue commandQueue, - CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger, StatusBarComponent.Factory statusBarComponentFactory, PluginManager pluginManager, Optional<LegacySplitScreen> splitScreenOptional, @@ -216,7 +207,6 @@ public interface StatusBarPhoneModule { KeyguardDismissUtil keyguardDismissUtil, ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, - OperatorNameViewController.Factory operatorNameViewControllerFactory, PhoneStatusBarPolicy phoneStatusBarPolicy, KeyguardIndicationController keyguardIndicationController, DemoModeController demoModeController, @@ -227,9 +217,6 @@ public interface StatusBarPhoneModule { ScreenOffAnimationController screenOffAnimationController, WallpaperController wallpaperController, OngoingCallController ongoingCallController, - SystemStatusAnimationScheduler animationScheduler, - StatusBarLocationPublisher locationPublisher, - StatusBarIconController statusBarIconController, StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager, LockscreenShadeTransitionController transitionController, FeatureFlags featureFlags, @@ -239,8 +226,6 @@ public interface StatusBarPhoneModule { @Main MessageRouter messageRouter, WallpaperManager wallpaperManager, Optional<StartingSurface> startingSurfaceOptional, - TunerService tunerService, - DumpManager dumpManager, ActivityLaunchAnimator activityLaunchAnimator, NotifPipelineFlags notifPipelineFlags) { return new StatusBar( @@ -303,7 +288,6 @@ public interface StatusBarPhoneModule { dozeScrimController, volumeComponent, commandQueue, - collapsedStatusBarFragmentLogger, statusBarComponentFactory, pluginManager, splitScreenOptional, @@ -317,7 +301,6 @@ public interface StatusBarPhoneModule { keyguardDismissUtil, extensionController, userInfoControllerImpl, - operatorNameViewControllerFactory, phoneStatusBarPolicy, keyguardIndicationController, demoModeController, @@ -328,9 +311,6 @@ public interface StatusBarPhoneModule { screenOffAnimationController, wallpaperController, ongoingCallController, - animationScheduler, - locationPublisher, - statusBarIconController, statusBarHideIconsForBouncerManager, transitionController, featureFlags, @@ -340,8 +320,6 @@ public interface StatusBarPhoneModule { messageRouter, wallpaperManager, startingSurfaceOptional, - tunerService, - dumpManager, activityLaunchAnimator, notifPipelineFlags ); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index f0b4072fbd0f..051fbaf62219 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -28,6 +28,7 @@ import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedul import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.app.Fragment; import android.os.Bundle; import android.os.Parcelable; @@ -74,6 +75,7 @@ import java.util.List; * and keyguard state. Also manages lifecycle to make sure the views it contains are being * updated by the StatusBarIconController and DarkIconManager while it is attached. */ +@SuppressLint("ValidFragment") public class CollapsedStatusBarFragment extends Fragment implements CommandQueue.Callbacks, StatusBarStateController.StateListener, SystemStatusAnimationCallback { @@ -127,6 +129,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue }; private OperatorNameViewController mOperatorNameViewController; + @SuppressLint("ValidFragment") public CollapsedStatusBarFragment( StatusBarFragmentComponent.Factory statusBarFragmentComponentFactory, OngoingCallController ongoingCallController, 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 17dd26a9c89b..dc8dc99740ff 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 @@ public class UserSwitcherController implements Dumpable { ? 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 @@ public class UserSwitcherController implements Dumpable { 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 @@ public class UserSwitcherController implements Dumpable { 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 @@ public class UserSwitcherController implements Dumpable { 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 8ea7d62c9c74..fb6861d5f2d4 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 @@ import dagger.Lazy; @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 class TvStatusBar extends CoreStartable implements CommandQueue.Callbacks 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/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java index 700b58a046d0..bd845209a6d6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java @@ -64,7 +64,6 @@ public class StatusBarWindowController { private final WindowManager mWindowManager; private final IWindowManager mIWindowManager; private final StatusBarContentInsetsProvider mContentInsetsProvider; - private final Resources mResources; private int mBarHeight = -1; private final State mCurrentState = new State(); @@ -91,7 +90,6 @@ public class StatusBarWindowController { mLaunchAnimationContainer = mStatusBarWindowView.findViewById( R.id.status_bar_launch_animation_container); mLpChanged = new WindowManager.LayoutParams(); - mResources = resources; if (mBarHeight < 0) { mBarHeight = SystemBarUtils.getStatusBarHeight(mContext); diff --git a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalCondition.java b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java index 734ab6323ca0..0bbf56caaaf1 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalCondition.java +++ b/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.communal.conditions; +package com.android.systemui.util.condition; import android.util.Log; @@ -27,9 +27,10 @@ import java.util.ArrayList; import java.util.Iterator; /** - * Base class for a condition that needs to be fulfilled in order for Communal Mode to display. + * Base class for a condition that needs to be fulfilled in order for {@link Monitor} to inform + * its callbacks. */ -public abstract class CommunalCondition implements CallbackController<CommunalCondition.Callback> { +public abstract class Condition implements CallbackController<Condition.Callback> { private final String mTag = getClass().getSimpleName(); private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>(); @@ -125,6 +126,6 @@ public abstract class CommunalCondition implements CallbackController<CommunalCo * @param condition The condition in question. * @param isConditionMet True if the condition has been fulfilled. False otherwise. */ - void onConditionChanged(CommunalCondition condition, boolean isConditionMet); + void onConditionChanged(Condition condition, boolean isConditionMet); } } diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java new file mode 100644 index 000000000000..a7e9cdbf1a18 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java @@ -0,0 +1,153 @@ +/* + * 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.systemui.util.condition; + +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.statusbar.policy.CallbackController; + +import org.jetbrains.annotations.NotNull; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Set; + +import javax.inject.Inject; + +/** + * {@link Monitor} takes in a set of conditions, monitors whether all of them have + * been fulfilled, and informs any registered listeners. + */ +public class Monitor implements CallbackController<Monitor.Callback> { + private final String mTag = getClass().getSimpleName(); + + private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>(); + + // Set of all conditions that need to be monitored. + private final Set<Condition> mConditions; + + // Map of values of each condition. + private final HashMap<Condition, Boolean> mConditionsMap = new HashMap<>(); + + // Whether all conditions have been met. + private boolean mAllConditionsMet = false; + + // Whether the monitor has started listening for all the conditions. + private boolean mHaveConditionsStarted = false; + + // Callback for when each condition has been updated. + private final Condition.Callback mConditionCallback = (condition, isConditionMet) -> { + mConditionsMap.put(condition, isConditionMet); + + final boolean newAllConditionsMet = !mConditionsMap.containsValue(false); + + if (newAllConditionsMet == mAllConditionsMet) { + return; + } + + if (shouldLog()) Log.d(mTag, "all conditions met: " + newAllConditionsMet); + mAllConditionsMet = newAllConditionsMet; + + // Updates all callbacks. + final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator(); + while (iterator.hasNext()) { + final Callback callback = iterator.next().get(); + if (callback == null) { + iterator.remove(); + } else { + callback.onConditionsChanged(mAllConditionsMet); + } + } + }; + + @Inject + public Monitor(Set<Condition> conditions) { + mConditions = conditions; + + // If there is no condition, give green pass. + if (mConditions.isEmpty()) { + mAllConditionsMet = true; + return; + } + + // Initializes the conditions map and registers a callback for each condition. + mConditions.forEach((condition -> mConditionsMap.put(condition, false))); + } + + @Override + public void addCallback(@NotNull Callback callback) { + if (shouldLog()) Log.d(mTag, "adding callback"); + mCallbacks.add(new WeakReference<>(callback)); + + // Updates the callback immediately. + callback.onConditionsChanged(mAllConditionsMet); + + if (!mHaveConditionsStarted) { + if (shouldLog()) Log.d(mTag, "starting all conditions"); + mConditions.forEach(condition -> condition.addCallback(mConditionCallback)); + mHaveConditionsStarted = true; + } + } + + @Override + public void removeCallback(@NotNull Callback callback) { + if (shouldLog()) Log.d(mTag, "removing callback"); + final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator(); + while (iterator.hasNext()) { + final Callback cb = iterator.next().get(); + if (cb == null || cb == callback) { + iterator.remove(); + } + } + + if (mCallbacks.isEmpty() && mHaveConditionsStarted) { + if (shouldLog()) Log.d(mTag, "stopping all conditions"); + mConditions.forEach(condition -> condition.removeCallback(mConditionCallback)); + + mAllConditionsMet = false; + mHaveConditionsStarted = false; + } + } + + /** + * Force updates each condition to the value provided. + */ + @VisibleForTesting + public void overrideAllConditionsMet(boolean value) { + mConditions.forEach(condition -> condition.updateCondition(value)); + } + + private boolean shouldLog() { + return Log.isLoggable(mTag, Log.DEBUG); + } + + /** + * Callback that receives updates of whether all conditions have been fulfilled. + */ + public interface Callback { + /** + * Triggered when the fulfillment of all conditions have been met. + * + * @param allConditionsMet True if all conditions have been fulfilled. False if none or + * only partial conditions have been fulfilled. + */ + void onConditionsChanged(boolean allConditionsMet); + } +} 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 d819fa2adc38..1fe3d4417730 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 @@ import org.mockito.junit.MockitoJUnit @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 f9ad740f86df..b951345a145b 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 @@ import org.mockito.junit.MockitoJUnit @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 @@ class DialogLaunchAnimatorTest : SysuiTestCase() { @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 000000000000..dadf94e2a9dd --- /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/communal/CommunalSettingConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java index cf147f06979f..2d52c42fa75f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java @@ -31,8 +31,8 @@ import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.communal.conditions.CommunalCondition; import com.android.systemui.communal.conditions.CommunalSettingCondition; +import com.android.systemui.util.condition.Condition; import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.utils.os.FakeHandler; @@ -57,7 +57,7 @@ public class CommunalSettingConditionTest extends SysuiTestCase { public void addCallback_communalSettingEnabled_immediatelyReportsTrue() { updateCommunalSetting(true); - final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class); + final Condition.Callback callback = mock(Condition.Callback.class); mCondition.addCallback(callback); verify(callback).onConditionChanged(mCondition, true); } @@ -66,7 +66,7 @@ public class CommunalSettingConditionTest extends SysuiTestCase { public void addCallback_communalSettingDisabled_noReport() { updateCommunalSetting(false); - final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class); + final Condition.Callback callback = mock(Condition.Callback.class); mCondition.addCallback(callback); verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean()); } @@ -75,7 +75,7 @@ public class CommunalSettingConditionTest extends SysuiTestCase { public void updateCallback_communalSettingEnabled_reportsTrue() { updateCommunalSetting(false); - final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class); + final Condition.Callback callback = mock(Condition.Callback.class); mCondition.addCallback(callback); clearInvocations(callback); @@ -87,7 +87,7 @@ public class CommunalSettingConditionTest extends SysuiTestCase { public void updateCallback_communalSettingDisabled_reportsFalse() { updateCommunalSetting(true); - final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class); + final Condition.Callback callback = mock(Condition.Callback.class); mCondition.addCallback(callback); clearInvocations(callback); @@ -99,7 +99,7 @@ public class CommunalSettingConditionTest extends SysuiTestCase { public void updateCallback_communalSettingDidNotChange_neverReportDup() { updateCommunalSetting(true); - final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class); + final Condition.Callback callback = mock(Condition.Callback.class); mCondition.addCallback(callback); clearInvocations(callback); 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 24e47c5159a0..bd4bfff4c18a 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 @@ public class TileLayoutTest extends SysuiTestCase { } 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 95e7a98c0a60..6dca2a73b57b 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 @@ public class InternetDialogControllerTest extends SysuiTestCase { 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 @@ public class InternetDialogControllerTest extends SysuiTestCase { } @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 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(null /* accessPoints */); - verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any()); + verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any(), anyBoolean()); } @Test @@ -409,8 +419,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { 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 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(mAccessPoints); mWifiEntries.clear(); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + false /* hasMoreEntry */); } @Test @@ -437,8 +448,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.clear(); mWifiEntries.add(mWifiEntry1); - verify(mInternetDialogCallback) - .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, + null /* connectedEntry */, false /* hasMoreEntry */); } @Test @@ -453,7 +464,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { mWifiEntries.clear(); mWifiEntries.add(mWifiEntry1); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + false /* hasMoreEntry */); } @Test @@ -470,7 +482,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { 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 @@ public class InternetDialogControllerTest extends SysuiTestCase { 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 @@ public class InternetDialogControllerTest extends SysuiTestCase { mInternetDialogController.onAccessPointsChanged(mAccessPoints); mWifiEntries.remove(mWifiEntry3); - verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry); + verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry, + true /* hasMoreEntry */); } @Test @@ -518,7 +533,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { 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 @@ public class InternetDialogControllerTest extends SysuiTestCase { 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 @@ public class InternetDialogControllerTest extends SysuiTestCase { 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 @@ public class InternetDialogControllerTest extends SysuiTestCase { 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 @@ public class InternetDialogControllerTest extends SysuiTestCase { 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 @@ public class InternetDialogControllerTest extends SysuiTestCase { 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 0cf063f5db39..651bcdef9978 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 @@ public class InternetDialogTest extends SysuiTestCase { } @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 @@ public class InternetDialogTest extends SysuiTestCase { 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 @@ public class InternetDialogTest extends SysuiTestCase { @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 793851160dc2..89435ae164b5 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 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 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { 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 afbe668a4de1..8c5f04f92073 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 @@ import org.mockito.MockitoAnnotations; @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 @@ public class NotificationListenerTest extends SysuiTestCase { @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 @@ public class NotificationListenerTest extends SysuiTestCase { 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 @@ public class NotificationListenerTest extends SysuiTestCase { @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/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java index a39971d27303..9f152e1e2fe5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java @@ -269,6 +269,21 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { } @Test + public void testDisableWiFiWithVcnWithUnderlyingWifi() { + String testSsid = "Test VCN SSID"; + setWifiEnabled(true); + verifyLastWifiIcon(false, WifiIcons.WIFI_NO_NETWORK); + + mNetworkController.setNoNetworksAvailable(false); + setWifiStateForVcn(true, testSsid); + setWifiLevelForVcn(1); + verifyLastMobileDataIndicatorsForVcn(true, 1, TelephonyIcons.ICON_CWF, false); + + setWifiEnabled(false); + verifyLastMobileDataIndicatorsForVcn(false, 1, 0, false); + } + + @Test public void testCallStrengh() { if (true) return; String testSsid = "Test SSID"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index 22e7746614ef..9bcdcc96767a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -99,6 +99,7 @@ import com.android.systemui.controls.dagger.ControlsComponent; import com.android.systemui.doze.DozeLog; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentService; import com.android.systemui.idle.IdleHostViewController; @@ -382,7 +383,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { mConfiguration.orientation = ORIENTATION_PORTRAIT; when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics); mDisplayMetrics.density = 100; - when(mResources.getBoolean(R.bool.config_enableNotificationShadeDrag)).thenReturn(true); + when(mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHADE_DRAG)).thenReturn(true); when(mResources.getDimensionPixelSize(R.dimen.notifications_top_padding_split_shade)) .thenReturn(NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE); when(mResources.getDimensionPixelSize(R.dimen.qs_panel_width)).thenReturn(400); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 70cffb24e378..0289b9aff062 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -18,9 +18,11 @@ package com.android.systemui.statusbar.phone; import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; + import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static junit.framework.TestCase.fail; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -112,7 +114,6 @@ import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.connectivity.NetworkController; -import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.NotificationEntryManager; @@ -134,7 +135,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; -import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager; import com.android.systemui.statusbar.policy.BatteryController; @@ -146,7 +146,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.window.StatusBarWindowController; -import com.android.systemui.tuner.TunerService; import com.android.systemui.util.WallpaperController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.concurrency.MessageRouterImpl; @@ -244,7 +243,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private ViewMediatorCallback mKeyguardVieMediatorCallback; @Mock private VolumeComponent mVolumeComponent; @Mock private CommandQueue mCommandQueue; - @Mock private CollapsedStatusBarFragmentLogger mCollapsedStatusBarFragmentLogger; @Mock private StatusBarComponent.Factory mStatusBarComponentFactory; @Mock private StatusBarComponent mStatusBarComponent; @Mock private PluginManager mPluginManager; @@ -264,9 +262,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private BrightnessSliderController.Factory mBrightnessSliderFactory; @Mock private WallpaperController mWallpaperController; @Mock private OngoingCallController mOngoingCallController; - @Mock private SystemStatusAnimationScheduler mAnimationScheduler; - @Mock private StatusBarLocationPublisher mLocationPublisher; - @Mock private StatusBarIconController mIconController; @Mock private LockscreenShadeTransitionController mLockscreenTransitionController; @Mock private FeatureFlags mFeatureFlags; @Mock private NotificationVisibilityProvider mVisibilityProvider; @@ -274,7 +269,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private IWallpaperManager mIWallpaperManager; @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; @Mock private ScreenOffAnimationController mScreenOffAnimationController; - @Mock private TunerService mTunerService; @Mock private StartingSurface mStartingSurface; @Mock private OperatorNameViewController mOperatorNameViewController; @Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory; @@ -429,7 +423,6 @@ public class StatusBarTest extends SysuiTestCase { mDozeScrimController, mVolumeComponent, mCommandQueue, - mCollapsedStatusBarFragmentLogger, mStatusBarComponentFactory, mPluginManager, Optional.of(mLegacySplitScreen), @@ -443,7 +436,6 @@ public class StatusBarTest extends SysuiTestCase { mKeyguardDismissUtil, mExtensionController, mUserInfoControllerImpl, - mOperatorNameViewControllerFactory, mPhoneStatusBarPolicy, mKeyguardIndicationController, mDemoModeController, @@ -454,9 +446,6 @@ public class StatusBarTest extends SysuiTestCase { mScreenOffAnimationController, mWallpaperController, mOngoingCallController, - mAnimationScheduler, - mLocationPublisher, - mIconController, new StatusBarHideIconsForBouncerManager(mCommandQueue, mMainExecutor, mDumpManager), mLockscreenTransitionController, mFeatureFlags, @@ -466,8 +455,6 @@ public class StatusBarTest extends SysuiTestCase { new MessageRouterImpl(mMainExecutor), mWallpaperManager, Optional.of(mStartingSurface), - mTunerService, - mDumpManager, mActivityLaunchAnimator, mNotifPipelineFlags); when(mKeyguardViewMediator.registerStatusBar( 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 a1d9a7b50d81..be1720d07d11 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 @@ package com.android.systemui.unfold.updates 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.Before 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 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { @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 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { hingeAngleProvider, screenStatusProvider, deviceStateManager, - context.mainExecutor + context.mainExecutor, + handler ) foldStateProvider.addCallback(object : FoldStateProvider.FoldUpdatesListener { @@ -91,6 +109,22 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { 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 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { 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 @@ class DeviceFoldStateProviderTest : SysuiTestCase() { private fun fireScreenOnEvent() { screenOnListenerCaptor.value.onScreenTurnedOn() } + + private fun sendHingeAngleEvent(angle: Int) { + hingeAngleCaptor.value.accept(angle.toFloat()) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalConditionsMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java index 59ddba1b4597..878bdeac43c9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalConditionsMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.communal; +package com.android.systemui.util.condition; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.anyBoolean; @@ -30,8 +30,6 @@ import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.communal.conditions.CommunalCondition; -import com.android.systemui.communal.conditions.CommunalConditionsMonitor; import org.junit.Before; import org.junit.Test; @@ -43,97 +41,107 @@ import java.util.HashSet; @SmallTest @RunWith(AndroidTestingRunner.class) -public class CommunalConditionsMonitorTest extends SysuiTestCase { - private FakeCommunalCondition mCondition1; - private FakeCommunalCondition mCondition2; - private FakeCommunalCondition mCondition3; - private HashSet<CommunalCondition> mConditions; +public class ConditionMonitorTest extends SysuiTestCase { + private FakeCondition mCondition1; + private FakeCondition mCondition2; + private FakeCondition mCondition3; + private HashSet<Condition> mConditions; - private CommunalConditionsMonitor mCommunalConditionsMonitor; + private Monitor mConditionMonitor; @Before public void setup() { MockitoAnnotations.initMocks(this); - mCondition1 = spy(new FakeCommunalCondition()); - mCondition2 = spy(new FakeCommunalCondition()); - mCondition3 = spy(new FakeCommunalCondition()); + mCondition1 = spy(new FakeCondition()); + mCondition2 = spy(new FakeCondition()); + mCondition3 = spy(new FakeCondition()); mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3)); - mCommunalConditionsMonitor = new CommunalConditionsMonitor(mConditions); + mConditionMonitor = new Monitor(mConditions); } @Test public void addCallback_addFirstCallback_addCallbackToAllConditions() { - final CommunalConditionsMonitor.Callback callback1 = - mock(CommunalConditionsMonitor.Callback.class); - mCommunalConditionsMonitor.addCallback(callback1); + final Monitor.Callback callback1 = + mock(Monitor.Callback.class); + mConditionMonitor.addCallback(callback1); mConditions.forEach(condition -> verify(condition).addCallback(any())); - final CommunalConditionsMonitor.Callback callback2 = - mock(CommunalConditionsMonitor.Callback.class); - mCommunalConditionsMonitor.addCallback(callback2); + final Monitor.Callback callback2 = + mock(Monitor.Callback.class); + mConditionMonitor.addCallback(callback2); mConditions.forEach(condition -> verify(condition, times(1)).addCallback(any())); } @Test public void addCallback_addFirstCallback_reportWithDefaultValue() { - final CommunalConditionsMonitor.Callback callback = - mock(CommunalConditionsMonitor.Callback.class); - mCommunalConditionsMonitor.addCallback(callback); + final Monitor.Callback callback = + mock(Monitor.Callback.class); + mConditionMonitor.addCallback(callback); verify(callback).onConditionsChanged(false); } @Test public void addCallback_addSecondCallback_reportWithExistingValue() { - final CommunalConditionsMonitor.Callback callback1 = - mock(CommunalConditionsMonitor.Callback.class); - mCommunalConditionsMonitor.addCallback(callback1); + final Monitor.Callback callback1 = + mock(Monitor.Callback.class); + mConditionMonitor.addCallback(callback1); - mCommunalConditionsMonitor.overrideAllConditionsMet(true); + mConditionMonitor.overrideAllConditionsMet(true); - final CommunalConditionsMonitor.Callback callback2 = - mock(CommunalConditionsMonitor.Callback.class); - mCommunalConditionsMonitor.addCallback(callback2); + final Monitor.Callback callback2 = + mock(Monitor.Callback.class); + mConditionMonitor.addCallback(callback2); verify(callback2).onConditionsChanged(true); } @Test + public void addCallback_noConditions_reportAllConditionsMet() { + final Monitor monitor = new Monitor(new HashSet<>()); + final Monitor.Callback callback = mock(Monitor.Callback.class); + + monitor.addCallback(callback); + + verify(callback).onConditionsChanged(true); + } + + @Test public void removeCallback_shouldNoLongerReceiveUpdate() { - final CommunalConditionsMonitor.Callback callback = - mock(CommunalConditionsMonitor.Callback.class); - mCommunalConditionsMonitor.addCallback(callback); + final Monitor.Callback callback = + mock(Monitor.Callback.class); + mConditionMonitor.addCallback(callback); clearInvocations(callback); - mCommunalConditionsMonitor.removeCallback(callback); + mConditionMonitor.removeCallback(callback); - mCommunalConditionsMonitor.overrideAllConditionsMet(true); + mConditionMonitor.overrideAllConditionsMet(true); verify(callback, never()).onConditionsChanged(true); - mCommunalConditionsMonitor.overrideAllConditionsMet(false); + mConditionMonitor.overrideAllConditionsMet(false); verify(callback, never()).onConditionsChanged(false); } @Test public void removeCallback_removeLastCallback_removeCallbackFromAllConditions() { - final CommunalConditionsMonitor.Callback callback1 = - mock(CommunalConditionsMonitor.Callback.class); - final CommunalConditionsMonitor.Callback callback2 = - mock(CommunalConditionsMonitor.Callback.class); - mCommunalConditionsMonitor.addCallback(callback1); - mCommunalConditionsMonitor.addCallback(callback2); - - mCommunalConditionsMonitor.removeCallback(callback1); + final Monitor.Callback callback1 = + mock(Monitor.Callback.class); + final Monitor.Callback callback2 = + mock(Monitor.Callback.class); + mConditionMonitor.addCallback(callback1); + mConditionMonitor.addCallback(callback2); + + mConditionMonitor.removeCallback(callback1); mConditions.forEach(condition -> verify(condition, never()).removeCallback(any())); - mCommunalConditionsMonitor.removeCallback(callback2); + mConditionMonitor.removeCallback(callback2); mConditions.forEach(condition -> verify(condition).removeCallback(any())); } @Test public void updateCallbacks_allConditionsMet_reportTrue() { - final CommunalConditionsMonitor.Callback callback = - mock(CommunalConditionsMonitor.Callback.class); - mCommunalConditionsMonitor.addCallback(callback); + final Monitor.Callback callback = + mock(Monitor.Callback.class); + mConditionMonitor.addCallback(callback); clearInvocations(callback); mCondition1.fakeUpdateCondition(true); @@ -145,9 +153,9 @@ public class CommunalConditionsMonitorTest extends SysuiTestCase { @Test public void updateCallbacks_oneConditionStoppedMeeting_reportFalse() { - final CommunalConditionsMonitor.Callback callback = - mock(CommunalConditionsMonitor.Callback.class); - mCommunalConditionsMonitor.addCallback(callback); + final Monitor.Callback callback = + mock(Monitor.Callback.class); + mConditionMonitor.addCallback(callback); mCondition1.fakeUpdateCondition(true); mCondition2.fakeUpdateCondition(true); @@ -160,9 +168,9 @@ public class CommunalConditionsMonitorTest extends SysuiTestCase { @Test public void updateCallbacks_shouldOnlyUpdateWhenValueChanges() { - final CommunalConditionsMonitor.Callback callback = - mock(CommunalConditionsMonitor.Callback.class); - mCommunalConditionsMonitor.addCallback(callback); + final Monitor.Callback callback = + mock(Monitor.Callback.class); + mConditionMonitor.addCallback(callback); verify(callback).onConditionsChanged(false); clearInvocations(callback); diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java index 9d7ef0f68bd6..7fc6b51bf2a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalConditionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.communal; +package com.android.systemui.util.condition; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; @@ -29,7 +29,6 @@ import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.communal.conditions.CommunalCondition; import org.junit.Before; import org.junit.Test; @@ -37,26 +36,26 @@ import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidTestingRunner.class) -public class CommunalConditionTest extends SysuiTestCase { - private FakeCommunalCondition mCondition; +public class ConditionTest extends SysuiTestCase { + private FakeCondition mCondition; @Before public void setup() { - mCondition = spy(new FakeCommunalCondition()); + mCondition = spy(new FakeCondition()); } @Test public void addCallback_addFirstCallback_triggerStart() { - final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class); + final Condition.Callback callback = mock(Condition.Callback.class); mCondition.addCallback(callback); verify(mCondition).start(); } @Test public void addCallback_addMultipleCallbacks_triggerStartOnlyOnce() { - final CommunalCondition.Callback callback1 = mock(CommunalCondition.Callback.class); - final CommunalCondition.Callback callback2 = mock(CommunalCondition.Callback.class); - final CommunalCondition.Callback callback3 = mock(CommunalCondition.Callback.class); + final Condition.Callback callback1 = mock(Condition.Callback.class); + final Condition.Callback callback2 = mock(Condition.Callback.class); + final Condition.Callback callback3 = mock(Condition.Callback.class); mCondition.addCallback(callback1); mCondition.addCallback(callback2); @@ -67,19 +66,19 @@ public class CommunalConditionTest extends SysuiTestCase { @Test public void addCallback_alreadyStarted_triggerUpdate() { - final CommunalCondition.Callback callback1 = mock(CommunalCondition.Callback.class); + final Condition.Callback callback1 = mock(Condition.Callback.class); mCondition.addCallback(callback1); mCondition.fakeUpdateCondition(true); - final CommunalCondition.Callback callback2 = mock(CommunalCondition.Callback.class); + final Condition.Callback callback2 = mock(Condition.Callback.class); mCondition.addCallback(callback2); verify(callback2).onConditionChanged(mCondition, true); } @Test public void removeCallback_removeLastCallback_triggerStop() { - final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class); + final Condition.Callback callback = mock(Condition.Callback.class); mCondition.addCallback(callback); verify(mCondition, never()).stop(); @@ -91,7 +90,7 @@ public class CommunalConditionTest extends SysuiTestCase { public void updateCondition_falseToTrue_reportTrue() { mCondition.fakeUpdateCondition(false); - final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class); + final Condition.Callback callback = mock(Condition.Callback.class); mCondition.addCallback(callback); mCondition.fakeUpdateCondition(true); @@ -102,7 +101,7 @@ public class CommunalConditionTest extends SysuiTestCase { public void updateCondition_trueToFalse_reportFalse() { mCondition.fakeUpdateCondition(true); - final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class); + final Condition.Callback callback = mock(Condition.Callback.class); mCondition.addCallback(callback); mCondition.fakeUpdateCondition(false); @@ -113,7 +112,7 @@ public class CommunalConditionTest extends SysuiTestCase { public void updateCondition_trueToTrue_reportNothing() { mCondition.fakeUpdateCondition(true); - final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class); + final Condition.Callback callback = mock(Condition.Callback.class); mCondition.addCallback(callback); mCondition.fakeUpdateCondition(true); @@ -124,7 +123,7 @@ public class CommunalConditionTest extends SysuiTestCase { public void updateCondition_falseToFalse_reportNothing() { mCondition.fakeUpdateCondition(false); - final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class); + final Condition.Callback callback = mock(Condition.Callback.class); mCondition.addCallback(callback); mCondition.fakeUpdateCondition(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/FakeCommunalCondition.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/FakeCondition.java index 882effd8f1f2..9d5ccbec87ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/FakeCommunalCondition.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/FakeCondition.java @@ -14,15 +14,13 @@ * limitations under the License. */ -package com.android.systemui.communal; - -import com.android.systemui.communal.conditions.CommunalCondition; +package com.android.systemui.util.condition; /** - * Fake implementation of {@link CommunalCondition}, and provides a way for tests to update + * Fake implementation of {@link Condition}, and provides a way for tests to update * condition fulfillment. */ -public class FakeCommunalCondition extends CommunalCondition { +public class FakeCondition extends Condition { @Override public void start() {} diff --git a/services/art-profile b/services/art-profile index af58bca129f8..2d9e95eebdc5 100644 --- a/services/art-profile +++ b/services/art-profile @@ -1464,7 +1464,7 @@ HSPLcom/android/server/DeviceIdleController;->reportTempWhitelistChangedLocked(I HPLcom/android/server/DeviceIdleController;->resetIdleManagementLocked()V+]Lcom/android/server/AnyMotionDetector;Lcom/android/server/AnyMotionDetector;]Lcom/android/server/DeviceIdleController;Lcom/android/server/DeviceIdleController; HPLcom/android/server/DeviceIdleController;->resetLightIdleManagementLocked()V+]Lcom/android/server/DeviceIdleController;Lcom/android/server/DeviceIdleController; HPLcom/android/server/DeviceIdleController;->scheduleAlarmLocked(JZ)V+]Landroid/app/AlarmManager;Landroid/app/AlarmManager; -HPLcom/android/server/DeviceIdleController;->scheduleLightAlarmLocked(JJ)V+]Landroid/app/AlarmManager;Landroid/app/AlarmManager; +HPLcom/android/server/DeviceIdleController;->scheduleLightAlarmLocked(JJZ)V+]Landroid/app/AlarmManager;Landroid/app/AlarmManager; HPLcom/android/server/DeviceIdleController;->scheduleMotionRegistrationAlarmLocked()V+]Lcom/android/server/DeviceIdleController$Injector;Lcom/android/server/DeviceIdleController$Injector;]Landroid/app/AlarmManager;Landroid/app/AlarmManager; HSPLcom/android/server/DeviceIdleController;->scheduleMotionTimeoutAlarmLocked()V+]Landroid/app/AlarmManager;Landroid/app/AlarmManager;]Lcom/android/server/DeviceIdleController$Injector;Lcom/android/server/DeviceIdleController$Injector; HPLcom/android/server/DeviceIdleController;->scheduleReportActiveLocked(Ljava/lang/String;I)V+]Lcom/android/server/DeviceIdleController$MyHandler;Lcom/android/server/DeviceIdleController$MyHandler; diff --git a/services/backup/Android.bp b/services/backup/Android.bp index 7b0d6c0a997c..ead8aff30147 100644 --- a/services/backup/Android.bp +++ b/services/backup/Android.bp @@ -19,9 +19,5 @@ java_library_static { 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 594140efe5f2..21a22f44f3dd 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.GuardedBy; 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 @@ public class TransportManager { 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 @@ public class TransportManager { 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 @@ public class TransportManager { } /** 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 a3f6eb6f9842..85ab48c5f7fb 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.RestoreDescription; 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 @@ public class BackupTransportClient { 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 da77eba083c3..f9a3c36a3220 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.CompletableFuture; 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 @@ import java.util.concurrent.ExecutionException; * <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 @@ public class TransportConnection { private int mState = State.IDLE; @GuardedBy("mStateLock") - private volatile IBackupTransport mTransport; + private volatile BackupTransportClient mTransport; TransportConnection( @UserIdInt int userId, @@ -174,10 +174,12 @@ public class TransportConnection { * 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 @@ public class TransportConnection { * 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 @@ public class TransportConnection { * * <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 @@ public class TransportConnection { * * @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 @@ public class TransportConnection { } } - CompletableFuture<IBackupTransport> transportFuture = new CompletableFuture<>(); + CompletableFuture<BackupTransportClient> transportFuture = new CompletableFuture<>(); TransportConnectionListener requestListener = (requestedTransport, transportClient) -> transportFuture.complete(requestedTransport); @@ -359,13 +361,14 @@ public class TransportConnection { * * @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 @@ public class TransportConnection { * * @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 @@ public class TransportConnection { } 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 @@ public class TransportConnection { 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 @@ public class TransportConnection { } @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 03d35e46952c..1776c4104e26 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 @@ package com.android.server.backup.transport; 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 @@ import com.android.internal.backup.IBackupTransport; */ 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 155a4b57072e..466f647fd034 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 @@ public interface OperationStorage { 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 2a6c90f70636..98ea03e35296 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 android.util.SparseArray; 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.ClearRetryParams; 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 @@ public class UserBackupManagerService { // 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 @@ public class UserBackupManagerService { 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 @@ public class UserBackupManagerService { 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 1c860917c4ef..9ce4eabb0fa1 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.EventLog; 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.UserBackupManagerService; 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 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba 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 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba 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 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba 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 3b3bf8c694af..5c2485905814 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.Pair; 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.RestoreGetSetsParams; 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 @@ public class BackupHandler extends Handler { 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 @@ public class BackupHandler extends Handler { 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 b58c973d2aef..6908c60034b6 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.internal.annotations.GuardedBy; 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.Set; 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 @@ import javax.annotation.concurrent.ThreadSafe; * 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 @@ public class LifecycleOperationStorage implements OperationStorage { @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 class LifecycleOperationStorage implements OperationStorage { 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 @@ public class LifecycleOperationStorage implements OperationStorage { } } + /** 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 @@ public class LifecycleOperationStorage implements OperationStorage { 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 @@ public class LifecycleOperationStorage implements OperationStorage { 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 80bd60451dfd..de0177c1b62c 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 static com.android.server.backup.BackupManagerService.TAG; 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 class PerformClearTask implements Runnable { 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 7636ef65211f..888f49d44654 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.EventLog; 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 @@ public class PerformInitializeTask implements Runnable { 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 bdb2e6fc127f..30da8c1d5e76 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 android.util.Log; 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.internal.Operation; 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 @@ import java.util.concurrent.atomic.AtomicInteger; * </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 @@ import java.util.concurrent.atomic.AtomicInteger; * </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 @@ import java.util.concurrent.atomic.AtomicInteger; * <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 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { * @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 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { boolean noDataPackageEncountered = false; try { - IBackupTransport transport = + BackupTransportClient transport = mTransportConnection.connectOrThrow("KVBT.informTransportOfEmptyBackups()"); for (String packageName : succeedingPackages) { @@ -467,8 +467,8 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { } /** 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 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { 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 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { 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 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { @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 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { } } - 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 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { 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 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { 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 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { 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 8c786d556518..ac831af7612b 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.EventLog; 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.PackageManagerBackupAgent.Metadata; 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 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { PackageInfo[] packages = mAcceptSet.toArray(new PackageInfo[0]); - IBackupTransport transport = + BackupTransportClient transport = mTransportConnection.connectOrThrow("PerformUnifiedRestoreTask.startRestore()"); mStatus = transport.startRestore(mToken, packages); @@ -495,7 +495,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { private void dispatchNextRestore() { UnifiedRestoreState nextState = UnifiedRestoreState.FINAL; try { - IBackupTransport transport = + BackupTransportClient transport = mTransportConnection.connectOrThrow( "PerformUnifiedRestoreTask.dispatchNextRestore()"); mRestoreDescription = transport.nextRestorePackage(); @@ -709,7 +709,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { boolean startedAgentRestore = false; try { - IBackupTransport transport = + BackupTransportClient transport = mTransportConnection.connectOrThrow( "PerformUnifiedRestoreTask.initiateOneRestore()"); @@ -940,7 +940,8 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { 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 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // 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 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { 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 652386f13bea..bd1ac2dcffda 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.os.UserHandle; 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 @@ public class BackupEligibilityRules { } 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 a0a00f7629b1..bcc345ff98e4 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.CollectionUtils.filter; 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 @@ class AssociationRequestsProcessor { } // 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 e0cd4725bce2..b32d5434558b 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.internal.util.function.pooled.PooledLambda.obtainRunna 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 @@ public class CompanionDeviceManagerService extends SystemService { 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 @@ public class CompanionDeviceManagerService extends SystemService { @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 class CompanionDeviceManagerService extends SystemService { public void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId) { enforceCallerCanInteractWithUserId(getContext(), userId); - enforceCallerCanManagerCompanionDevice(getContext(), + enforceCallerCanManageCompanionDevice(getContext(), "addOnAssociationsChangedListener"); //TODO: Implement. @@ -621,7 +623,7 @@ public class CompanionDeviceManagerService extends SystemService { 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 45097f0d0ea6..ea570895c18f 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_APP_STREAMING; 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.annotation.UserIdInt; 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 @@ final class PermissionsUtils { 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 class PermissionsUtils { } 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 @@ final class PermissionsUtils { 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 @@ final class PermissionsUtils { } } + 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/companion/java/com/android/server/companion/virtual/PermissionUtils.java b/services/companion/java/com/android/server/companion/virtual/PermissionUtils.java new file mode 100644 index 000000000000..c397ea2997a4 --- /dev/null +++ b/services/companion/java/com/android/server/companion/virtual/PermissionUtils.java @@ -0,0 +1,54 @@ +/* + * 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.companion.virtual; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.util.Slog; + +/** + * Utility methods for checking permissions required for VirtualDeviceManager operations. + */ +class PermissionUtils { + + private static final String LOG_TAG = "VDM.PermissionUtils"; + + /** + * Verifies whether the calling package name matches the calling app uid. + * + * @param context the context + * @param callingPackage the calling application package name + * @param callingUid the calling application uid + * @return {@code true} if the package name matches the calling app uid, {@code false} otherwise + */ + public static boolean validatePackageName(Context context, String callingPackage, + int callingUid) { + try { + int packageUid = context.getPackageManager().getPackageUid(callingPackage, 0); + if (packageUid != callingUid) { + Slog.e(LOG_TAG, "validatePackageName: App with package name " + callingPackage + + " is UID " + packageUid + " but caller is " + callingUid); + return false; + } + } catch (PackageManager.NameNotFoundException e) { + Slog.e(LOG_TAG, "validatePackageName: App with package name " + callingPackage + + " does not exist"); + return false; + } + return true; + } +} diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 18cf6f874ab1..58d08013eeff 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -19,13 +19,18 @@ package com.android.server.companion.virtual; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; +import android.companion.AssociationInfo; +import android.companion.CompanionDeviceManager; +import android.companion.CompanionDeviceManager.OnAssociationsChangedListener; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceManager; import android.content.Context; +import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; import android.util.ExceptionUtils; import android.util.Slog; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; @@ -33,7 +38,8 @@ import com.android.server.SystemService; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; /** @hide */ @@ -42,9 +48,30 @@ public class VirtualDeviceManagerService extends SystemService { private static final boolean DEBUG = false; private static final String LOG_TAG = "VirtualDeviceManagerService"; + private final Object mVirtualDeviceManagerLock = new Object(); private final VirtualDeviceManagerImpl mImpl; - @GuardedBy("mVirtualDevices") - private final ArrayList<VirtualDeviceImpl> mVirtualDevices = new ArrayList<>(); + + /** + * Mapping from CDM association IDs to virtual devices. Only one virtual device is allowed for + * each CDM associated device. + */ + @GuardedBy("mVirtualDeviceManagerLock") + private final SparseArray<VirtualDeviceImpl> mVirtualDevices = new SparseArray<>(); + + /** + * Mapping from user ID to CDM associations. The associations come from + * {@link CompanionDeviceManager#getAllAssociations()}, which contains associations across all + * packages. + */ + private final ConcurrentHashMap<Integer, List<AssociationInfo>> mAllAssociations = + new ConcurrentHashMap<>(); + + /** + * Mapping from user ID to its change listener. The listeners are added when the user is + * started and removed when the user stops. + */ + private final SparseArray<OnAssociationsChangedListener> mOnAssociationsChangedListeners = + new SparseArray<>(); public VirtualDeviceManagerService(Context context) { super(context); @@ -56,30 +83,116 @@ public class VirtualDeviceManagerService extends SystemService { publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl); } - private class VirtualDeviceImpl extends IVirtualDevice.Stub { + @Override + public void onUserStarting(@NonNull TargetUser user) { + super.onUserStarting(user); + synchronized (mVirtualDeviceManagerLock) { + final CompanionDeviceManager cdm = getContext() + .createContextAsUser(user.getUserHandle(), 0) + .getSystemService(CompanionDeviceManager.class); + final int userId = user.getUserIdentifier(); + mAllAssociations.put(userId, cdm.getAllAssociations()); + OnAssociationsChangedListener listener = + associations -> mAllAssociations.put(userId, associations); + mOnAssociationsChangedListeners.put(userId, listener); + cdm.addOnAssociationsChangedListener(Runnable::run, listener); + } + } + + @Override + public void onUserStopping(@NonNull TargetUser user) { + super.onUserStopping(user); + synchronized (mVirtualDeviceManagerLock) { + int userId = user.getUserIdentifier(); + mAllAssociations.remove(userId); + final CompanionDeviceManager cdm = getContext().createContextAsUser( + user.getUserHandle(), 0) + .getSystemService(CompanionDeviceManager.class); + OnAssociationsChangedListener listener = mOnAssociationsChangedListeners.get(userId); + if (listener != null) { + cdm.removeOnAssociationsChangedListener(listener); + mOnAssociationsChangedListeners.remove(userId); + } + } + } + + private class VirtualDeviceImpl extends IVirtualDevice.Stub implements IBinder.DeathRecipient { + + private final AssociationInfo mAssociationInfo; - private VirtualDeviceImpl() {} + private VirtualDeviceImpl(IBinder token, AssociationInfo associationInfo) { + mAssociationInfo = associationInfo; + try { + token.linkToDeath(this, 0); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mVirtualDevices.put(associationInfo.getId(), this); + } + + @Override + public int getAssociationId() { + return mAssociationInfo.getId(); + } @Override public void close() { - synchronized (mVirtualDevices) { - mVirtualDevices.remove(this); + synchronized (mVirtualDeviceManagerLock) { + mVirtualDevices.remove(mAssociationInfo.getId()); } } + + @Override + public void binderDied() { + close(); + } } class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub { @Override - public IVirtualDevice createVirtualDevice() { + public IVirtualDevice createVirtualDevice( + IBinder token, String packageName, int associationId) { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.CREATE_VIRTUAL_DEVICE, "createVirtualDevice"); - VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(); - synchronized (mVirtualDevices) { - mVirtualDevices.add(virtualDevice); + if (!PermissionUtils.validatePackageName(getContext(), packageName, getCallingUid())) { + throw new SecurityException( + "Package name " + packageName + " does not belong to calling uid " + + getCallingUid()); + } + AssociationInfo associationInfo = getAssociationInfo(packageName, associationId); + if (associationInfo == null) { + throw new IllegalArgumentException("No association with ID " + associationId); + } + synchronized (mVirtualDeviceManagerLock) { + if (mVirtualDevices.contains(associationId)) { + throw new IllegalStateException( + "Virtual device for association ID " + associationId + + " already exists"); + } + return new VirtualDeviceImpl(token, associationInfo); + } + } + + @Nullable + private AssociationInfo getAssociationInfo(String packageName, int associationId) { + final int callingUserId = getCallingUserHandle().getIdentifier(); + final List<AssociationInfo> associations = + mAllAssociations.get(callingUserId); + if (associations != null) { + final int associationSize = associations.size(); + for (int i = 0; i < associationSize; i++) { + AssociationInfo associationInfo = associations.get(i); + if (associationInfo.belongsToPackage(callingUserId, packageName) + && associationId == associationInfo.getId()) { + return associationInfo; + } + } + } else { + Slog.w(LOG_TAG, "No associations for user " + callingUserId); } - return virtualDevice; + return null; } @Override @@ -101,9 +214,10 @@ public class VirtualDeviceManagerService extends SystemService { return; } fout.println("Created virtual devices: "); - synchronized (mVirtualDevices) { - for (VirtualDeviceImpl virtualDevice : mVirtualDevices) { - fout.println(virtualDevice.toString()); + synchronized (mVirtualDeviceManagerLock) { + for (int i = 0; i < mVirtualDevices.size(); i++) { + VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i); + fout.printf("%d: %s\n", mVirtualDevices.keyAt(i), virtualDevice); } } } diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index f3fad84b576b..121a0bc2143b 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.IBinder; 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 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP * * @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 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP /** * 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 b6413773046f..71b463a4db8c 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -25,13 +25,14 @@ per-file *Battery* = file:/BATTERY_STATS_OWNERS 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 b7b48703f35e..9180ef80ae71 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.annotations.VisibleForTesting; 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 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } @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 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync { } 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 80a8d6376825..1a89ae74ef91 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 @@ public final class ProcessList { // 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 b9cc992b438e..8de515d4d3e5 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 @@ final class DiscreteRegistry { 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 ff451a30dd69..e4ac7be6be59 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 @@ import java.util.concurrent.atomic.AtomicBoolean; 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 @@ import java.util.concurrent.atomic.AtomicBoolean; } } + /*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 @@ import java.util.concurrent.atomic.AtomicBoolean; /*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 f32d3b52d61c..a27e4b77959c 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 class AudioDeviceInventory { 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 @@ public class AudioDeviceInventory { } 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 @@ public class AudioDeviceInventory { } //------------------------------------------------------------ - // + // preferred device(s) /*package*/ int setPreferredDevicesForStrategySync(int strategy, @NonNull List<AudioDeviceAttributes> devices) { @@ -674,16 +675,34 @@ public class AudioDeviceInventory { 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 @@ public class AudioDeviceInventory { 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 @@ public class AudioDeviceInventory { } } + /*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 0f3b08210d0b..aa33644357be 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.IntDef; 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.IAudioServerStateDispatcher; 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub if (mHasSpatializerEffect) { mSpatializerHelper.reset(/* featureEnabled */ isSpatialAudioEnabled()); - monitorRoutingChanges(true); } onIndicateSystemReady(); @@ -3224,6 +3235,15 @@ public class AudioService extends IAudioService.Stub } } + 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect); if (mHasSpatializerEffect) { mSpatializerHelper.setFeatureEnabled(isSpatialAudioEnabled()); - monitorRoutingChanges(true); } mAudioEventWakeLock.release(); break; @@ -7758,7 +7811,7 @@ public class AudioService extends IAudioService.Stub break; case MSG_ROUTING_UPDATED: - mSpatializerHelper.onRoutingUpdated(); + onRoutingUpdatedFromAudioThread(); break; case MSG_PERSIST_SPATIAL_AUDIO_ENABLED: @@ -8169,7 +8222,10 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub } //========================================================================================== + 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 @@ public class AudioService extends IAudioService.Stub boolean requireValidProjection = false; boolean requireCaptureAudioOrMediaOutputPerm = false; boolean requireModifyRouting = false; + boolean requireCallAudioInterception = false; ArrayList<AudioMix> voiceCommunicationCaptureMixes = null; @@ -9618,7 +9844,10 @@ public class AudioService extends IAudioService.Stub // 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 @@ public class AudioService extends IAudioService.Stub 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 b94cea4d5d40..b333ed24eb1d 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.IPlaybackConfigDispatcher; 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 @@ public final class PlaybackActivityMonitor /*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 @@ public final class PlaybackActivityMonitor .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 @@ public final class PlaybackActivityMonitor 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 @@ public final class PlaybackActivityMonitor 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 @@ public final class PlaybackActivityMonitor sEventLogger.log(new NewPlayerEvent(apc)); synchronized(mPlayerLock) { mPlayers.put(newPiid, apc); + maybeMutePlayerAwaitingConnection(apc); } return newPiid; } @@ -323,6 +350,7 @@ public final class PlaybackActivityMonitor 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 @@ public final class PlaybackActivityMonitor 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 @@ public final class PlaybackActivityMonitor 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 @@ public final class PlaybackActivityMonitor } } + 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 f42870b4b734..758cf7a7d430 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 @@ public class BiometricService extends SystemService { 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 @@ public class BiometricService extends SystemService { 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 @@ public class BiometricService extends SystemService { + "), 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 a5a3542f49c7..05c3f68f355b 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.IntDef; 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 @@ class PreAuthInfo { 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 @@ class PreAuthInfo { 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 @@ class PreAuthInfo { 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 @@ class PreAuthInfo { 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 @@ class PreAuthInfo { return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested, eligibleSensors, ineligibleSensors, credentialAvailable, confirmationRequested, - promptInfo.isIgnoreEnrollmentState()); + promptInfo.isIgnoreEnrollmentState(), userId, context); } /** @@ -149,7 +163,7 @@ class PreAuthInfo { 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 @@ class PreAuthInfo { && !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 @@ class PreAuthInfo { 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 @@ class PreAuthInfo { this.credentialAvailable = credentialAvailable; this.confirmationRequested = confirmationRequested; this.ignoreEnrollmentState = ignoreEnrollmentState; + this.userId = userId; + this.context = context; } private Pair<BiometricSensor, Integer> calculateErrorByPriority() { @@ -280,15 +307,35 @@ class PreAuthInfo { 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 @@ class PreAuthInfo { } } 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 @@ class PreAuthInfo { 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 @@ class PreAuthInfo { 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 4f7c6b012c23..0e2582c23b86 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_LOCKOUT_TIMED; 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 @@ public class Utils { 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 @@ public class Utils { 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 97d791b7e1c9..4131ae127ab2 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.annotation.Nullable; 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 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements @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 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements 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 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements @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 2ef0911658b1..2158dfe7bde5 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 @@ package com.android.server.biometrics.sensors.face.aidl; 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 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det 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 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient); setRequestId(requestId); mIsStrongBiometric = isStrongBiometric; + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); } @Override @@ -73,6 +77,14 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det @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 40f2801541d3..7548d2871a15 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 @@ package com.android.server.biometrics.sensors.face.hidl; 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 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { private final int[] mKeyguardIgnoreListVendor; private int mLastAcquire; + private SensorPrivacyManager mSensorPrivacyManager; FaceAuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @@ -71,6 +73,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { isKeyguardBypassEnabled); setRequestId(requestId); mUsageStats = usageStats; + mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); final Resources resources = getContext().getResources(); mBiometricPromptIgnoreList = resources.getIntArray( @@ -97,6 +100,15 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> { @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 119644233607..8d9b13e21d77 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 @@ public final class CommunalManagerService extends SystemService { @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 @@ public final class CommunalManagerService extends SystemService { 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 @@ public final class CommunalManagerService extends SystemService { 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 d0aa40b9da4e..b66c4668f2a0 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 @@ public class NetworkPolicyLogger { } } - 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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index f65deebdaf52..137fc85c1e72 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -270,7 +270,6 @@ import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; @@ -624,12 +623,6 @@ public class NotificationManagerService extends SystemService { private NotificationRecordLogger mNotificationRecordLogger; private InstanceIdSequence mNotificationInstanceIdSequence; private Set<String> mMsgPkgsAllowedAsConvos = new HashSet(); - protected static final String ACTION_ENABLE_NAS = - "android.server.notification.action.ENABLE_NAS"; - protected static final String ACTION_DISABLE_NAS = - "android.server.notification.action.DISABLE_NAS"; - protected static final String ACTION_LEARNMORE_NAS = - "android.server.notification.action.LEARNMORE_NAS"; static class Archive { final SparseArray<Boolean> mEnabled; @@ -764,95 +757,25 @@ public class NotificationManagerService extends SystemService { setDefaultAssistantForUser(userId); } - protected void migrateDefaultNASShowNotificationIfNecessary() { + protected void migrateDefaultNAS() { final List<UserInfo> activeUsers = mUm.getUsers(); for (UserInfo userInfo : activeUsers) { int userId = userInfo.getUserHandle().getIdentifier(); if (isNASMigrationDone(userId) || mUm.isManagedProfile(userId)) { continue; } - if (mAssistants.hasUserSet(userId)) { - ComponentName defaultFromConfig = mAssistants.getDefaultFromConfig(); - List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId); - if (allowedComponents.size() == 0) { - setNASMigrationDone(userId); - mAssistants.clearDefaults(); - continue; - } else if (allowedComponents.contains(defaultFromConfig)) { - setNASMigrationDone(userId); - mAssistants.resetDefaultFromConfig(); - continue; - } - // TODO(b/192450820): re-enable when "user set" isn't over triggering - //User selected different NAS, need onboarding - /*enqueueNotificationInternal(getContext().getPackageName(), - getContext().getOpPackageName(), Binder.getCallingUid(), - Binder.getCallingPid(), TAG, - SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, - createNASUpgradeNotification(userId), userId);*/ - } - } - } - - protected Notification createNASUpgradeNotification(int userId) { - final Bundle extras = new Bundle(); - extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, - getContext().getResources().getString(R.string.global_action_settings)); - int title = R.string.nas_upgrade_notification_title; - int content = R.string.nas_upgrade_notification_content; - - Intent onboardingIntent = new Intent(Settings.ACTION_NOTIFICATION_ASSISTANT_SETTINGS); - onboardingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - - Intent enableIntent = new Intent(ACTION_ENABLE_NAS); - enableIntent.putExtra(Intent.EXTRA_USER_ID, userId); - PendingIntent enableNASPendingIntent = PendingIntent.getBroadcast(getContext(), - 0, enableIntent, PendingIntent.FLAG_IMMUTABLE); - - Intent disableIntent = new Intent(ACTION_DISABLE_NAS); - disableIntent.putExtra(Intent.EXTRA_USER_ID, userId); - PendingIntent disableNASPendingIntent = PendingIntent.getBroadcast(getContext(), - 0, disableIntent, PendingIntent.FLAG_IMMUTABLE); - - Intent learnMoreIntent = new Intent(ACTION_LEARNMORE_NAS); - learnMoreIntent.putExtra(Intent.EXTRA_USER_ID, userId); - PendingIntent learnNASPendingIntent = PendingIntent.getBroadcast(getContext(), - 0, learnMoreIntent, PendingIntent.FLAG_IMMUTABLE); - - Notification.Action enableNASAction = new Notification.Action.Builder( - 0, - getContext().getResources().getString( - R.string.nas_upgrade_notification_enable_action), - enableNASPendingIntent).build(); - - Notification.Action disableNASAction = new Notification.Action.Builder( - 0, - getContext().getResources().getString( - R.string.nas_upgrade_notification_disable_action), - disableNASPendingIntent).build(); - - Notification.Action learnMoreNASAction = new Notification.Action.Builder( - 0, - getContext().getResources().getString( - R.string.nas_upgrade_notification_learn_more_action), - learnNASPendingIntent).build(); - - - return new Notification.Builder(getContext(), SystemNotificationChannels.SYSTEM_CHANGES) - .setAutoCancel(false) - .setOngoing(true) - .setTicker(getContext().getResources().getString(title)) - .setSmallIcon(R.drawable.ic_settings_24dp) - .setContentTitle(getContext().getResources().getString(title)) - .setContentText(getContext().getResources().getString(content)) - .setContentIntent(PendingIntent.getActivity(getContext(), 0, onboardingIntent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE)) - .setLocalOnly(true) - .setStyle(new Notification.BigTextStyle()) - .addAction(enableNASAction) - .addAction(disableNASAction) - .addAction(learnMoreNASAction) - .build(); + List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId); + if (allowedComponents.size() == 0) { // user set to none + Slog.d(TAG, "NAS Migration: user set to none, disable new NAS setting"); + setNASMigrationDone(userId); + mAssistants.clearDefaults(); + } else { + Slog.d(TAG, "Reset NAS setting and migrate to new default"); + resetAssistantUserSet(userId); + // migrate to new default and set migration done + mAssistants.resetDefaultAssistantsIfNecessary(); + } + } } @VisibleForTesting @@ -1882,41 +1805,6 @@ public class NotificationManagerService extends SystemService { } }; - private final BroadcastReceiver mNASIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1); - if (ACTION_ENABLE_NAS.equals(action)) { - mAssistants.resetDefaultFromConfig(); - setNotificationAssistantAccessGrantedForUserInternal( - CollectionUtils.firstOrNull(mAssistants.getDefaultComponents()), - userId, true, true); - setNASMigrationDone(userId); - cancelNotificationInternal(getContext().getPackageName(), - getContext().getOpPackageName(), Binder.getCallingUid(), - Binder.getCallingPid(), TAG, - SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, userId); - } else if (ACTION_DISABLE_NAS.equals(action)) { - //Set default NAS to be null if user selected none during migration - mAssistants.clearDefaults(); - setNotificationAssistantAccessGrantedForUserInternal( - null, userId, true, true); - setNASMigrationDone(userId); - cancelNotificationInternal(getContext().getPackageName(), - getContext().getOpPackageName(), Binder.getCallingUid(), - Binder.getCallingPid(), TAG, - SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, userId); - } else if (ACTION_LEARNMORE_NAS.equals(action)) { - Intent i = new Intent(getContext(), NASLearnMoreActivity.class); - i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - getContext().sendBroadcastAsUser( - new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.of(userId)); - getContext().startActivity(i); - } - } - }; - private final class SettingsObserver extends ContentObserver { private final Uri NOTIFICATION_BADGING_URI = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING); @@ -2447,12 +2335,6 @@ public class NotificationManagerService extends SystemService { IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED); getContext().registerReceiver(mLocaleChangeReceiver, localeChangedFilter); - - IntentFilter nasFilter = new IntentFilter(); - nasFilter.addAction(ACTION_ENABLE_NAS); - nasFilter.addAction(ACTION_DISABLE_NAS); - nasFilter.addAction(ACTION_LEARNMORE_NAS); - getContext().registerReceiver(mNASIntentReceiver, nasFilter); } /** @@ -2464,7 +2346,6 @@ public class NotificationManagerService extends SystemService { getContext().unregisterReceiver(mNotificationTimeoutReceiver); getContext().unregisterReceiver(mRestoreReceiver); getContext().unregisterReceiver(mLocaleChangeReceiver); - getContext().unregisterReceiver(mNASIntentReceiver); if (mDeviceConfigChangedListener != null) { DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener); @@ -2740,7 +2621,7 @@ public class NotificationManagerService extends SystemService { mConditionProviders.onBootPhaseAppsCanStart(); mHistoryManager.onBootPhaseAppsCanStart(); registerDeviceConfigChange(); - migrateDefaultNASShowNotificationIfNecessary(); + migrateDefaultNAS(); } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis()); } @@ -5337,10 +5218,6 @@ public class NotificationManagerService extends SystemService { public void setNASMigrationDoneAndResetDefault(int userId, boolean loadFromConfig) { checkCallerIsSystem(); setNASMigrationDone(userId); - cancelNotificationInternal(getContext().getPackageName(), - getContext().getOpPackageName(), Binder.getCallingUid(), - Binder.getCallingPid(), TAG, - SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, userId); if (loadFromConfig) { mAssistants.resetDefaultFromConfig(); } else { diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 49e223cb6a3d..189ac586e533 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -225,7 +225,8 @@ public class PreferencesHelper implements RankingConfig { final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1); boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE; - boolean migrateToPermission = (xmlVersion < XML_VERSION); + boolean migrateToPermission = + (xmlVersion < XML_VERSION) && mPermissionHelper.isMigrationEnabled(); ArrayList<PermissionHelper.PackagePermission> pkgPerms = new ArrayList<>(); synchronized (mPackagePreferences) { while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { @@ -398,8 +399,10 @@ public class PreferencesHelper implements RankingConfig { } } } - for (PackagePermission p : pkgPerms) { - mPermissionHelper.setNotificationPermission(p); + if (migrateToPermission) { + for (PackagePermission p : pkgPerms) { + mPermissionHelper.setNotificationPermission(p); + } } } diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index 6ee198133281..1e2eb5dea263 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 @@ final class AppDataHelper { * </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 @@ final class AppDataHelper { } 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 @@ final class AppDataHelper { * 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 @@ final class AppDataHelper { } @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 @@ final class AppDataHelper { * @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 @@ final class AppDataHelper { * 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/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 9b2179026124..59250c5d452d 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -4629,9 +4629,9 @@ public class ComputerEngine implements Computer { } } else { list = new ArrayList<>(mPackages.size()); - for (PackageStateInternal packageState : packageStates.values()) { - final AndroidPackage pkg = packageState.getPkg(); - if (pkg == null) { + for (AndroidPackage p : mPackages.values()) { + final PackageStateInternal packageState = packageStates.get(p.getPackageName()); + if (packageState == null) { continue; } if (filterSharedLibPackage(packageState, Binder.getCallingUid(), userId, flags)) { @@ -4640,10 +4640,10 @@ public class ComputerEngine implements Computer { if (shouldFilterApplication(packageState, callingUid, userId)) { continue; } - ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo(pkg, flags, + ApplicationInfo ai = PackageInfoUtils.generateApplicationInfo(p, flags, packageState.getUserStateOrDefault(userId), userId, packageState); if (ai != null) { - ai.packageName = resolveExternalPackageName(pkg); + ai.packageName = resolveExternalPackageName(p); list.add(ai); } } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 80c2a7e97719..fce37a9f2345 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 @@ final class InstallPackageHelper { } } - // 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 26a5bbb094ac..356d6c93ae84 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 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements synchronized (mSessions) { readSessionsLocked(); + expireSessionsLocked(); reconcileStagesLocked(StorageManager.UUID_PRIVATE_INTERNAL); @@ -462,34 +463,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements 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 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } @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 55a0c96eea9e..28204eadd394 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { destroy(); // Dispatch message to remove session from PackageInstallerService. dispatchSessionFinished(error, msg, null); + maybeFinishChildSessions(error, msg); } } @@ -3646,20 +3648,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } /** - * 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { 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 333d3a4a98d8..fa23000865f2 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 @@ public class PackageManagerService extends IPackageManager.Stub @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 @@ public class PackageManagerService extends IPackageManager.Stub @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 @@ public class PackageManagerService extends IPackageManager.Stub * 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub } @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 @@ public class PackageManagerService extends IPackageManager.Stub } @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 328a55f72976..0f3b4bcfac56 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 @@ public class UserRestrictionsUtils { 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 @@ public class UserRestrictionsUtils { 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 @@ public class UserRestrictionsUtils { 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/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index 45f1b163506b..b1cc51768754 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -172,6 +172,7 @@ import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseIntArray; import android.util.StatsEvent; import android.util.proto.ProtoOutputStream; import android.view.Display; @@ -183,7 +184,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatterySipper; import com.android.internal.os.BatteryStatsHelper; import com.android.internal.os.BinderCallsStats.ExportedCallStat; -import com.android.internal.os.DmabufInfoReader; +import com.android.internal.os.KernelAllocationStats; import com.android.internal.os.KernelCpuBpfTracking; import com.android.internal.os.KernelCpuThreadReader; import com.android.internal.os.KernelCpuThreadReaderDiff; @@ -425,7 +426,6 @@ public class StatsPullAtomService extends SystemService { private final Object mSystemUptimeLock = new Object(); private final Object mProcessMemoryStateLock = new Object(); private final Object mProcessMemoryHighWaterMarkLock = new Object(); - private final Object mProcessMemorySnapshotLock = new Object(); private final Object mSystemIonHeapSizeLock = new Object(); private final Object mIonHeapSizeLock = new Object(); private final Object mProcessSystemIonHeapSizeLock = new Object(); @@ -563,9 +563,7 @@ public class StatsPullAtomService extends SystemService { return pullProcessMemoryHighWaterMarkLocked(atomTag, data); } case FrameworkStatsLog.PROCESS_MEMORY_SNAPSHOT: - synchronized (mProcessMemorySnapshotLock) { - return pullProcessMemorySnapshotLocked(atomTag, data); - } + return pullProcessMemorySnapshot(atomTag, data); case FrameworkStatsLog.SYSTEM_ION_HEAP_SIZE: synchronized (mSystemIonHeapSizeLock) { return pullSystemIonHeapSizeLocked(atomTag, data); @@ -2218,10 +2216,16 @@ public class StatsPullAtomService extends SystemService { ); } - int pullProcessMemorySnapshotLocked(int atomTag, List<StatsEvent> pulledData) { + int pullProcessMemorySnapshot(int atomTag, List<StatsEvent> pulledData) { List<ProcessMemoryState> managedProcessList = LocalServices.getService(ActivityManagerInternal.class) .getMemoryStateForProcesses(); + KernelAllocationStats.ProcessGpuMem[] gpuAllocations = + KernelAllocationStats.getGpuAllocations(); + SparseIntArray gpuMemPerPid = new SparseIntArray(gpuAllocations.length); + for (KernelAllocationStats.ProcessGpuMem processGpuMem : gpuAllocations) { + gpuMemPerPid.put(processGpuMem.pid, processGpuMem.gpuMemoryKb); + } for (ProcessMemoryState managedProcess : managedProcessList) { final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(managedProcess.pid); if (snapshot == null) { @@ -2230,7 +2234,8 @@ public class StatsPullAtomService extends SystemService { pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, managedProcess.uid, managedProcess.processName, managedProcess.pid, managedProcess.oomScore, snapshot.rssInKilobytes, snapshot.anonRssInKilobytes, snapshot.swapInKilobytes, - snapshot.anonRssInKilobytes + snapshot.swapInKilobytes)); + snapshot.anonRssInKilobytes + snapshot.swapInKilobytes, + gpuMemPerPid.get(managedProcess.pid))); } // Complement the data with native system processes. Given these measurements can be taken // in response to LMKs happening, we want to first collect the managed app stats (to @@ -2248,7 +2253,8 @@ public class StatsPullAtomService extends SystemService { processCmdlines.valueAt(i), pid, -1001 /*Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1.*/, snapshot.rssInKilobytes, snapshot.anonRssInKilobytes, snapshot.swapInKilobytes, - snapshot.anonRssInKilobytes + snapshot.swapInKilobytes)); + snapshot.anonRssInKilobytes + snapshot.swapInKilobytes, + gpuMemPerPid.get(pid))); } return StatsManager.PULL_SUCCESS; } @@ -2328,7 +2334,8 @@ public class StatsPullAtomService extends SystemService { if (process.uid == Process.SYSTEM_UID) { continue; } - DmabufInfoReader.ProcessDmabuf proc = DmabufInfoReader.getProcessStats(process.pid); + KernelAllocationStats.ProcessDmabuf proc = + KernelAllocationStats.getDmabufAllocations(process.pid); if (proc == null || (proc.retainedBuffersCount <= 0 && proc.mappedBuffersCount <= 0)) { continue; } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 232ea0977aba..4eae939f9e34 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.service.quicksettings.TileService; 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 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D 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 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D mActivityTaskManager = LocalServices.getService(ActivityTaskManagerInternal.class); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); + + mTileRequestTracker = new TileRequestTracker(mContext); } @Override @@ -1765,11 +1770,26 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D 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 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D 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 000000000000..d5ace3f7d177 --- /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 65907f1801bb..417177fa903e 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.lang.annotation.Target; 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 final class ServerFlags { 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 @@ public final class ServerFlags { 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 @@ public final class ServerFlags { 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 7f7d01c29710..5f1400097a3a 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.annotation.Nullable; 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 com.android.server.timezonedetector.ConfigurationChangeListener; 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 @@ final class ServiceConfigAccessor { 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 b452d90b9ec7..ae52912d4821 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.location.LocationManager; 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 com.android.server.timedetector.ServerFlags; 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 @@ public final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { /** * 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 043646041158..6628802ea9db 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.media.tv.tunerresourcemanager.TunerResourceManager; 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.HashSet; 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 class TunerResourceManagerService extends SystemService implements IBinde 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde // 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 @@ public class TunerResourceManagerService extends SystemService implements IBinde @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 @@ public class TunerResourceManagerService extends SystemService implements IBinde 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 584530c0f3bd..8b80b4a0b21e 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 @@ public class VcnGatewayConnection extends StateMachine { mUnderlyingNetworkController = mDeps.newUnderlyingNetworkController( mVcnContext, + mConnectionConfig, subscriptionGroup, mLastSnapshot, mUnderlyingNetworkControllerCallback); @@ -2376,11 +2377,12 @@ public class VcnGatewayConnection extends StateMachine { /** 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 bea8ae932a9d..7b26fe0370c9 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,61 +67,188 @@ class NetworkPriorityClassifier { */ @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<>(); + /** 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) { + // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED + + if (networkRecord.isBlocked) { + logWtf("Network blocked for System Server: " + networkRecord.network); + return PRIORITY_ANY; + } + + if (snapshot == null) { + logWtf("Got null snapshot"); + return PRIORITY_ANY; + } - 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"); + int priorityIndex = 0; + for (VcnUnderlyingNetworkPriority nwPriority : underlyingNetworkPriorities) { + if (checkMatchesPriorityRule( + vcnContext, + nwPriority, + networkRecord, + subscriptionGroup, + snapshot, + currentlySelected, + carrierConfig)) { + return priorityIndex; + } + priorityIndex++; + } + return 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( + @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; + } - // mRouteSelectionNetworkRequest requires a network be both VALIDATED and NOT_SUSPENDED + if (vcnContext.isInTestMode() && caps.hasTransport(TRANSPORT_TEST)) { + return true; + } - if (networkRecord.isBlocked) { - logWtf("Network blocked for System Server: " + networkRecord.network); - return PRIORITY_ANY; + 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; } - if (caps.hasTransport(TRANSPORT_CELLULAR) - && isOpportunistic(snapshot, caps.getSubscriptionIds())) { + 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 @@ class NetworkPriorityClassifier { // 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; - } - } - - 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; + return false; } } - // 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 @@ class NetworkPriorityClassifier { 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 071c7a683cbf..cd124ee6a9fa 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.Network; 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 @@ public class UnderlyingNetworkController { @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 class UnderlyingNetworkController { 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 @@ public class UnderlyingNetworkController { TreeSet<UnderlyingNetworkRecord> sorted = new TreeSet<>( UnderlyingNetworkRecord.getComparator( + mVcnContext, + mConnectionConfig.getVcnUnderlyingNetworkPriorities(), mSubscriptionGroup, mLastSnapshot, mCurrentRecord, @@ -495,12 +498,31 @@ public class UnderlyingNetworkController { 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 65c69dedcb28..27ba854ab197 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.annotation.Nullable; 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; 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 @@ public class UnderlyingNetworkRecord { } 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 @@ public class UnderlyingNetworkRecord { 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/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 658984a14dca..2ec42b41fc9f 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -2215,6 +2215,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub throw new IllegalArgumentException("padding must be positive: " + padding); } + int maxSize = getMaximumSizeDimension(displayId); + + final int paddingWidth = padding.left + padding.right; + final int paddingHeight = padding.top + padding.bottom; + if (paddingWidth > maxSize) { + throw new IllegalArgumentException("padding width " + paddingWidth + + " exceeds max width " + maxSize); + } + if (paddingHeight > maxSize) { + throw new IllegalArgumentException("padding height " + paddingHeight + + " exceeds max height " + maxSize); + } + final DisplayData wpdData = getDisplayDataOrCreate(displayId); if (!padding.equals(wpdData.mPadding)) { wpdData.mPadding.set(padding); diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 03d65905d6a9..d5abe4f8ed02 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 @@ public class AppTransition implements Dump { } @TransitionType int getKeyguardTransition() { + 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; + } // 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 (unoccludeIndex != -1 && unoccludeIndex < occludeIndex) { return TRANSIT_NONE; } - - for (int i = 0; i < mNextAppTransitionRequests.size(); ++i) { - final @TransitionType int transit = mNextAppTransitionRequests.get(i); - if (isKeyguardTransit(transit)) { - return transit; - } - } - 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 71ab5b617e8e..4768b27a4f9e 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_ALLOW_LOCK_WHILE_SCRE 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 @@ public class DisplayPolicy { } /** - * 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 @@ public class DisplayPolicy { } 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 f0b55cb7886b..038699be15f2 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 @@ class Task extends TaskFragment { } /** 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 @@ class Task extends TaskFragment { } 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 @@ class Task extends TaskFragment { } } } + // 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 59a5cdfe0460..fdaa2fc7dc31 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.FileDescriptor; 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 @@ class TaskFragment extends WindowContainer<WindowContainer> { 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 @@ class TaskFragment extends WindowContainer<WindowContainer> { if (isAddingActivity && task != null) { child.asActivityRecord().inHistory = true; - task.onDescendantActivityAdded(taskHadChild, activityType, child.asActivityRecord()); + task.onDescendantActivityAdded(taskHadActivity, activityType, child.asActivityRecord()); } } @@ -2186,14 +2187,13 @@ class TaskFragment extends WindowContainer<WindowContainer> { 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 29c27f9f3af6..c7fdefc412cc 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 static com.android.server.wm.WindowOrganizerController.configurationsAreE 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 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr 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 e986fd2e46e8..58860de3dcdd 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub } /** + * 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 45048530603a..06f5aed026ce 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 jclass class_gnssPowerStats; 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 IGnssDebug_V2_0 = android::hardware::gnss::V2_0::IGnssDebug; 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::GnssPowerStats; 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<IGnssPsdsAidl> gnssPsdsAidlIface = nullptr; 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::GnssMeasurementInterface> gnssMeasurementIface = 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 @@ static inline jboolean boolToJbool(bool value) { 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 @@ Return<bool> GnssVisibilityControlCallback::isInEmergencySession() { } /* - * 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 @@ static void android_location_gnss_hal_GnssNative_class_init_once(JNIEnv* env, jc "(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 @@ static void android_location_gnss_hal_GnssNative_class_init_once(JNIEnv* env, jc 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 @@ static void android_location_gnss_hal_GnssNative_init_once(JNIEnv* env, jobject } } - 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 @@ static jboolean android_location_gnss_hal_GnssNative_init(JNIEnv* /* env */, jcl } } - // 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 @@ static void android_location_gnss_hal_GnssNative_inject_psds_data(JNIEnv* env, j 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 @@ static void android_location_GnssNetworkConnectivityHandler_agps_data_conn_open( 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_open( 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_close 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_GnssNetworkConnectivityHandler_agps_data_conn_faile 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 000000000000..00403d631306 --- /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 000000000000..2828b82b92c1 --- /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 000000000000..466cdfa78de6 --- /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 000000000000..e9bb4717fe60 --- /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 ac50bfa5c442..e49e81c046d2 100644 --- a/services/core/jni/gnss/Android.bp +++ b/services/core/jni/gnss/Android.bp @@ -23,6 +23,8 @@ cc_library_shared { ], 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 1bd69c46527e..2640a7774c26 100644 --- a/services/core/jni/gnss/Utils.h +++ b/services/core/jni/gnss/Utils.h @@ -196,6 +196,30 @@ private: 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 98a7b5e40567..bfceeda08e3d 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } - 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."); - } + Preconditions.checkCallAuthorization( + hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); - synchronized (getLockObject()) { - boolean transitionCheckNeeded = true; + final CallerIdentity caller = getCallerIdentity(); + 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."); + } - // 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."); + 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."); + } + 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/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java b/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java index 49d5e50e0345..d3353cd6adc7 100644 --- a/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java +++ b/services/people/java/com/android/server/people/data/ConversationStatusExpirationBroadcastReceiver.java @@ -31,6 +31,7 @@ import android.os.CancellationSignal; import com.android.server.LocalServices; import com.android.server.people.PeopleServiceInternal; +import com.android.server.pm.PackageManagerService; /** * If a {@link ConversationStatus} is added to the system with an expiration time, remove that @@ -50,6 +51,7 @@ public class ConversationStatusExpirationBroadcastReceiver extends BroadcastRece final PendingIntent pi = PendingIntent.getBroadcast(context, REQUEST_CODE, new Intent(ACTION) + .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME) .setData(new Uri.Builder().scheme(SCHEME) .appendPath(getKey(userId, pkg, conversationId, status)) .build()) 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 bf4eeae4b4b2..856165100f99 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.os.DeadObjectException; 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 @@ public class PerformInitializeTaskTest { @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 @@ public class PerformInitializeTaskTest { @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 @@ public class PerformInitializeTaskTest { @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 @@ public class PerformInitializeTaskTest { @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 class PerformInitializeTaskTest { 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 @@ public class PerformInitializeTaskTest { @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 @@ public class PerformInitializeTaskTest { @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 @@ public class PerformInitializeTaskTest { @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 @@ public class PerformInitializeTaskTest { @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 @@ public class PerformInitializeTaskTest { @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 @@ public class PerformInitializeTaskTest { @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 @@ public class PerformInitializeTaskTest { 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 @@ public class PerformInitializeTaskTest { @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 @@ public class PerformInitializeTaskTest { } 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 @@ public class PerformInitializeTaskTest { 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 ce44f067aeaa..8131ac412d52 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.ResolveInfo; 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 @@ public class TransportTestUtils { 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 @@ public class TransportTestUtils { } } - 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 class TransportTestUtils { 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 de4aec61aef2..6a82f1656414 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 @@ public class TransportConnectionTest { @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 @@ public class TransportConnectionTest { "1", "caller", new Handler(mainLooper)); + mTransportClient = new BackupTransportClient(mTransportBinder); when(mContext.bindServiceAsUser( eq(mBindIntent), @@ -156,7 +159,8 @@ public class TransportConnectionTest { mShadowMainLooper.runToEndOfTasks(); verify(mTransportConnectionListener) - .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection)); + .onTransportConnectionResult(any(BackupTransportClient.class), + eq(mTransportConnection)); } @Test @@ -169,9 +173,11 @@ public class TransportConnectionTest { 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 @@ public class TransportConnectionTest { mShadowMainLooper.runToEndOfTasks(); verify(mTransportConnectionListener2) - .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportConnection)); + .onTransportConnectionResult(any(BackupTransportClient.class), + eq(mTransportConnection)); } @Test @@ -312,10 +319,10 @@ public class TransportConnectionTest { 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 @@ public class TransportConnectionTest { 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 @@ public class TransportConnectionTest { 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 @@ public class TransportConnectionTest { 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/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java index e472b062388e..d71030802c2b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java @@ -1047,7 +1047,8 @@ public class DeviceIdleControllerTest { verifyLightStateConditions(LIGHT_STATE_IDLE); inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked( longThat(l -> l == mConstants.LIGHT_IDLE_TIMEOUT), - longThat(l -> l == mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX)); + longThat(l -> l == mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX), + eq(false)); // Should just alternate between IDLE and IDLE_MAINTENANCE now. @@ -1055,19 +1056,22 @@ public class DeviceIdleControllerTest { verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked( longThat(l -> l >= mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET), - longThat(l -> l == mConstants.FLEX_TIME_SHORT)); + longThat(l -> l == mConstants.FLEX_TIME_SHORT), + eq(true)); mDeviceIdleController.stepLightIdleStateLocked("testing"); verifyLightStateConditions(LIGHT_STATE_IDLE); inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked( longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT), - longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX)); + longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX), + eq(false)); mDeviceIdleController.stepLightIdleStateLocked("testing"); verifyLightStateConditions(LIGHT_STATE_IDLE_MAINTENANCE); inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked( longThat(l -> l >= mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET), - longThat(l -> l == mConstants.FLEX_TIME_SHORT)); + longThat(l -> l == mConstants.FLEX_TIME_SHORT), + eq(true)); // Test that motion doesn't reset the idle timeout. mDeviceIdleController.handleMotionDetectedLocked(50, "test"); @@ -1076,7 +1080,8 @@ public class DeviceIdleControllerTest { verifyLightStateConditions(LIGHT_STATE_IDLE); inOrder.verify(mDeviceIdleController).scheduleLightAlarmLocked( longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT), - longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX)); + longThat(l -> l > mConstants.LIGHT_IDLE_TIMEOUT_INITIAL_FLEX), + eq(false)); } ///////////////// EXIT conditions /////////////////// 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 aa7d6aa7de71..c36e1a841a7d 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.Context; 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 @@ public class UserBackupManagerServiceTest { @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 c079c2da0326..948acccba947 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.BackupRestoreTask; 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 @@ public class LifecycleOperationStorageTest { 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 @@ public class LifecycleOperationStorageTest { Set<Integer> tokens = mOpStorage.operationTokensForOpType(OpType.BACKUP_WAIT); + assertThat(mOpStorage.numOperations()).isEqualTo(1); assertThat(tokens).isEqualTo(only(TOKEN_1)); } @@ -85,7 +87,8 @@ public class LifecycleOperationStorageTest { 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 @@ public class LifecycleOperationStorageTest { Set<Integer> tokens = mOpStorage.operationTokensForPackage(PKG_FOO); + assertThat(mOpStorage.numOperations()).isEqualTo(1); assertThat(tokens).isEqualTo(only(TOKEN_1)); } @@ -111,8 +115,9 @@ public class LifecycleOperationStorageTest { 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 @@ public class LifecycleOperationStorageTest { 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 @@ public class LifecycleOperationStorageTest { 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 @@ public class LifecycleOperationStorageTest { 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 @@ public class LifecycleOperationStorageTest { } 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 b3f7587df612..b255a35c512e 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 @@ public class AuthSessionTest { 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 @@ public class AuthSessionTest { 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 000000000000..f172279c50bb --- /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 000000000000..c293b5e7c593 --- /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 000000000000..dac6df916844 --- /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 4dcd633b4560..add4cdaa28ce 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 @@ public class SystemConfigTest { 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 @@ public class SystemConfigTest { "<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 @@ public class SystemConfigTest { "<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 @@ public class SystemConfigTest { "<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 @@ public class SystemConfigTest { "<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 @@ public class SystemConfigTest { "<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 @@ public class SystemConfigTest { "<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 @@ public class SystemConfigTest { "<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 @@ public class SystemConfigTest { "<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 @@ public class SystemConfigTest { * @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 @@ public class SystemConfigTest { 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/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 837850fc1011..62a0dd4e7c17 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -63,10 +63,6 @@ import static android.service.notification.NotificationListenerService.FLAG_FILT import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; -import static com.android.server.notification.NotificationManagerService.ACTION_DISABLE_NAS; -import static com.android.server.notification.NotificationManagerService.ACTION_ENABLE_NAS; -import static com.android.server.notification.NotificationManagerService.ACTION_LEARNMORE_NAS; - import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; @@ -340,7 +336,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock MultiRateLimiter mToastRateLimiter; BroadcastReceiver mPackageIntentReceiver; - BroadcastReceiver mNASIntentReceiver; NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake(); private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake( 1 << 30); @@ -500,14 +495,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { && filter.hasAction(Intent.ACTION_PACKAGES_UNSUSPENDED) && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) { mPackageIntentReceiver = broadcastReceivers.get(i); - } else if (filter.hasAction(ACTION_ENABLE_NAS) - && filter.hasAction(ACTION_DISABLE_NAS) - && filter.hasAction(ACTION_LEARNMORE_NAS)) { - mNASIntentReceiver = broadcastReceivers.get(i); } } assertNotNull("package intent receiver should exist", mPackageIntentReceiver); - assertNotNull("nas intent receiver should exist", mNASIntentReceiver); // Pretend the shortcut exists List<ShortcutInfo> shortcutInfos = new ArrayList<>(); @@ -602,16 +592,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mPackageIntentReceiver.onReceive(getContext(), intent); } - private void simulateNASUpgradeBroadcast(String action, int uid) { - final Bundle extras = new Bundle(); - extras.putInt(Intent.EXTRA_USER_ID, uid); - - final Intent intent = new Intent(action); - intent.putExtras(extras); - - mNASIntentReceiver.onReceive(getContext(), intent); - } - private ArrayMap<Boolean, ArrayList<ComponentName>> generateResetComponentValues() { ArrayMap<Boolean, ArrayList<ComponentName>> changed = new ArrayMap<>(); changed.put(true, new ArrayList<>()); @@ -6007,7 +5987,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testNASSettingUpgrade_userSetNull_noOnBoarding() throws RemoteException { + public void testNASSettingUpgrade_userSetNull() throws RemoteException { ComponentName newDefaultComponent = ComponentName.unflattenFromString("package/Component1"); TestableNotificationManagerService service = spy(mService); int userId = 11; @@ -6020,14 +6000,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(new ArrayList<>()); when(mAssistants.hasUserSet(userId)).thenReturn(true); - service.migrateDefaultNASShowNotificationIfNecessary(); + service.migrateDefaultNAS(); assertTrue(service.isNASMigrationDone(userId)); - verify(service, times(0)).createNASUpgradeNotification(eq(userId)); verify(mAssistants, times(1)).clearDefaults(); } @Test - public void testNASSettingUpgrade_userSetSameDefault_noOnBoarding() throws RemoteException { + public void testNASSettingUpgrade_userSet() throws RemoteException { ComponentName defaultComponent = ComponentName.unflattenFromString("package/Component1"); TestableNotificationManagerService service = spy(mService); int userId = 11; @@ -6040,55 +6019,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(new ArrayList(Arrays.asList(defaultComponent))); when(mAssistants.hasUserSet(userId)).thenReturn(true); - service.migrateDefaultNASShowNotificationIfNecessary(); - assertTrue(service.isNASMigrationDone(userId)); - verify(service, times(0)).createNASUpgradeNotification(eq(userId)); - verify(mAssistants, times(1)).resetDefaultFromConfig(); - } - - @Test - public void testNASSettingUpgrade_userSetDifferentDefault_showOnboarding() - throws RemoteException { - ComponentName oldDefaultComponent = ComponentName.unflattenFromString("package/Component1"); - ComponentName newDefaultComponent = ComponentName.unflattenFromString("package/Component2"); - TestableNotificationManagerService service = spy(mService); - int userId = 11; - setUsers(new int[]{userId}); - setNASMigrationDone(false, userId); - when(mAssistants.getDefaultComponents()) - .thenReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent))); - when(mAssistants.getDefaultFromConfig()) - .thenReturn(newDefaultComponent); - when(mAssistants.getAllowedComponents(anyInt())) - .thenReturn(Arrays.asList(oldDefaultComponent)); - when(mAssistants.hasUserSet(userId)).thenReturn(true); - - service.migrateDefaultNASShowNotificationIfNecessary(); - assertFalse(service.isNASMigrationDone(userId)); - //TODO(b/192450820) - //verify(service, times(1)).createNASUpgradeNotification(eq(userId)); - verify(mAssistants, times(0)).resetDefaultFromConfig(); - - //Test user clear data before enable/disable from onboarding notification - ArrayMap<Boolean, ArrayList<ComponentName>> changedListeners = - generateResetComponentValues(); - when(mListeners.resetComponents(anyString(), anyInt())).thenReturn(changedListeners); - ArrayMap<Boolean, ArrayList<ComponentName>> changes = new ArrayMap<>(); - changes.put(true, new ArrayList(Arrays.asList(newDefaultComponent))); - changes.put(false, new ArrayList()); - when(mAssistants.resetComponents(anyString(), anyInt())).thenReturn(changes); - - //Clear data - service.getBinderService().clearData("package", userId, false); - //Test migrate flow again - service.migrateDefaultNASShowNotificationIfNecessary(); - - //The notification should be still there - assertFalse(service.isNASMigrationDone(userId)); - //TODO(b/192450820) - //verify(service, times(2)).createNASUpgradeNotification(eq(userId)); - verify(mAssistants, times(0)).resetDefaultFromConfig(); - assertEquals(oldDefaultComponent, service.getApprovedAssistant(userId)); + service.migrateDefaultNAS(); + verify(mAssistants, times(1)).setUserSet(userId, false); + //resetDefaultAssistantsIfNecessary should invoke from readPolicyXml() and migration + verify(mAssistants, times(2)).resetDefaultAssistantsIfNecessary(); } @Test @@ -6108,24 +6042,22 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent))); when(mAssistants.getDefaultFromConfig()) .thenReturn(newDefaultComponent); - //User1: need onboarding + //User1: set different NAS when(mAssistants.getAllowedComponents(userId1)) .thenReturn(Arrays.asList(oldDefaultComponent)); - //User2: no onboarding + //User2: set to none when(mAssistants.getAllowedComponents(userId2)) - .thenReturn(Arrays.asList(newDefaultComponent)); + .thenReturn(new ArrayList<>()); when(mAssistants.hasUserSet(userId1)).thenReturn(true); when(mAssistants.hasUserSet(userId2)).thenReturn(true); - service.migrateDefaultNASShowNotificationIfNecessary(); - assertFalse(service.isNASMigrationDone(userId1)); + service.migrateDefaultNAS(); + // user1's setting get reset + verify(mAssistants, times(1)).setUserSet(userId1, false); + verify(mAssistants, times(0)).setUserSet(eq(userId2), anyBoolean()); assertTrue(service.isNASMigrationDone(userId2)); - //TODO(b/192450820) - //verify(service, times(1)).createNASUpgradeNotification(any(Integer.class)); - // only user2's default get updated - verify(mAssistants, times(1)).resetDefaultFromConfig(); } @Test @@ -6145,7 +6077,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { .thenReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent))); when(mAssistants.getDefaultFromConfig()) .thenReturn(newDefaultComponent); - //Both profiles: need onboarding + //Both profiles: set different NAS when(mAssistants.getAllowedComponents(userId1)) .thenReturn(Arrays.asList(oldDefaultComponent)); when(mAssistants.getAllowedComponents(userId2)) @@ -6154,13 +6086,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mAssistants.hasUserSet(userId1)).thenReturn(true); when(mAssistants.hasUserSet(userId2)).thenReturn(true); - service.migrateDefaultNASShowNotificationIfNecessary(); + service.migrateDefaultNAS(); assertFalse(service.isNASMigrationDone(userId1)); assertFalse(service.isNASMigrationDone(userId2)); - - // TODO(b/192450820): only user1 get notification - //verify(service, times(1)).createNASUpgradeNotification(eq(userId1)); - //verify(service, times(0)).createNASUpgradeNotification(eq(userId2)); } @@ -6188,79 +6116,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { //Clear data service.getBinderService().clearData("package", userId, false); //Test migrate flow again - service.migrateDefaultNASShowNotificationIfNecessary(); - - //TODO(b/192450820): The notification should not appear again - //verify(service, times(0)).createNASUpgradeNotification(eq(userId)); - verify(mAssistants, times(0)).resetDefaultFromConfig(); - } - - @Test - public void testNASUpgradeNotificationDisableBroadcast_multiProfile() { - int userId1 = 11; - int userId2 = 12; - setUsers(new int[]{userId1, userId2}); - when(mUm.isManagedProfile(userId2)).thenReturn(true); - when(mUm.getProfileIds(userId1, false)).thenReturn(new int[]{userId1, userId2}); - - TestableNotificationManagerService service = spy(mService); - setNASMigrationDone(false, userId1); - setNASMigrationDone(false, userId2); + service.migrateDefaultNAS(); - simulateNASUpgradeBroadcast(ACTION_DISABLE_NAS, userId1); - - assertTrue(service.isNASMigrationDone(userId1)); - assertTrue(service.isNASMigrationDone(userId2)); - // User disabled the NAS from notification, the default stored in xml should be null - // rather than the new default - verify(mAssistants, times(1)).clearDefaults(); - verify(mAssistants, times(0)).resetDefaultFromConfig(); + //Migration should not happen again + verify(mAssistants, times(0)).setUserSet(userId, false); + verify(mAssistants, times(0)).clearDefaults(); + //resetDefaultAssistantsIfNecessary should only invoke once from readPolicyXml() + verify(mAssistants, times(1)).resetDefaultAssistantsIfNecessary(); - //TODO(b/192450820):No more notification after disabled - //service.migrateDefaultNASShowNotificationIfNecessary(); - //verify(service, times(0)).createNASUpgradeNotification(anyInt()); } - @Test - public void testNASUpgradeNotificationEnableBroadcast_multiUser() { - int userId1 = 11; - int userId2 = 12; - setUsers(new int[]{userId1, userId2}); - when(mUm.getProfileIds(userId1, false)).thenReturn(new int[]{userId1}); - - TestableNotificationManagerService service = spy(mService); - setNASMigrationDone(false, userId1); - setNASMigrationDone(false, userId2); - - simulateNASUpgradeBroadcast(ACTION_ENABLE_NAS, userId1); - - assertTrue(service.isNASMigrationDone(userId1)); - assertFalse(service.isNASMigrationDone(userId2)); - verify(mAssistants, times(1)).resetDefaultFromConfig(); - - //TODO(b/192450820) - //service.migrateDefaultNASShowNotificationIfNecessary(); - //verify(service, times(0)).createNASUpgradeNotification(eq(userId1)); - } - - @Test - public void testNASUpgradeNotificationLearnMoreBroadcast() { - int userId = 11; - setUsers(new int[]{userId}); - TestableNotificationManagerService service = spy(mService); - setNASMigrationDone(false, userId); - doNothing().when(mContext).startActivity(any()); - - simulateNASUpgradeBroadcast(ACTION_LEARNMORE_NAS, userId); - - verify(mContext, times(1)).startActivity(any(Intent.class)); - assertFalse(service.isNASMigrationDone(userId)); - //TODO(b/192450820) - //verify(service, times(0)).createNASUpgradeNotification(eq(userId)); - verify(mAssistants, times(0)).resetDefaultFromConfig(); - } - - private void setNASMigrationDone(boolean done, int userId) { Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.NAS_SETTINGS_UPDATED, done ? 1 : 0, userId); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java index a9fd3c9b609f..0c8fe35484f7 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationPermissionMigrationTest.java @@ -27,10 +27,6 @@ import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.UserHandle.USER_SYSTEM; -import static com.android.server.notification.NotificationManagerService.ACTION_DISABLE_NAS; -import static com.android.server.notification.NotificationManagerService.ACTION_ENABLE_NAS; -import static com.android.server.notification.NotificationManagerService.ACTION_LEARNMORE_NAS; - import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; @@ -252,7 +248,6 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { @Mock MultiRateLimiter mToastRateLimiter; BroadcastReceiver mPackageIntentReceiver; - BroadcastReceiver mNASIntentReceiver; NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake(); private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake( 1 << 30); @@ -407,14 +402,9 @@ public class NotificationPermissionMigrationTest extends UiServiceTestCase { && filter.hasAction(Intent.ACTION_PACKAGES_UNSUSPENDED) && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) { mPackageIntentReceiver = broadcastReceivers.get(i); - } else if (filter.hasAction(ACTION_ENABLE_NAS) - && filter.hasAction(ACTION_DISABLE_NAS) - && filter.hasAction(ACTION_LEARNMORE_NAS)) { - mNASIntentReceiver = broadcastReceivers.get(i); } } assertNotNull("package intent receiver should exist", mPackageIntentReceiver); - assertNotNull("nas intent receiver should exist", mNASIntentReceiver); // Pretend the shortcut exists List<ShortcutInfo> shortcutInfos = new ArrayList<>(); 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 c0959d311ed5..2ea7fdaf6348 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_CHANGE; 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 @@ public class AppTransitionTests extends WindowTestsBase { 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 @@ public class AppTransitionTests extends WindowTestsBase { } @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 1fab89e51416..f72832438204 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -3811,14 +3811,21 @@ public class SubscriptionManager { } /** - * 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 @@ public class SubscriptionManager { * @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 @@ public class SubscriptionManager { * 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 @@ public class SubscriptionManager { * @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 @@ public class SubscriptionManager { * {@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 @@ public class SubscriptionManager { * @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 5f21e878254b..0267b686d54e 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -117,6 +117,7 @@ import com.android.internal.telephony.ISms; 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 @@ public class TelephonyManager { 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 class TelephonyManager { 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 @@ public class TelephonyManager { 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 class TelephonyManager { 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 00b57e665481..d5c9ec44b960 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.ims.internal.IImsServiceFeatureCallback; 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 @@ interface ITelephony { 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 000000000000..a84e752a98b3 --- /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/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt index 64cb790d324b..aec80ac9d7b6 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt @@ -143,17 +143,26 @@ fun FlickerTestParameter.statusBarLayerRotatesScales() { * @param newLayer Layer that should be visible at the end * @param ignoreSnapshot If the snapshot layer should be ignored during the transition * (useful mostly for app launch) + * @param ignoreSplashscreen If the splashscreen layer should be ignored during the transition. + * If true then we will allow for a splashscreen to be shown before the layer is shown, + * otherwise we won't and the layer must appear immediately. */ fun FlickerTestParameter.replacesLayer( originalLayer: FlickerComponentName, newLayer: FlickerComponentName, - ignoreSnapshot: Boolean = false + ignoreSnapshot: Boolean = false, + ignoreSplashscreen: Boolean = true ) { assertLayers { val assertion = this.isVisible(originalLayer) - if (ignoreSnapshot) { + if (ignoreSnapshot || ignoreSplashscreen) { assertion.then() - .isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + } + if (ignoreSnapshot) { + assertion.isVisible(FlickerComponentName.SNAPSHOT, isOptional = true) + } + if (ignoreSplashscreen) { + assertion.isSplashScreenVisibleFor(newLayer, isOptional = true) } assertion.then().isVisible(newLayer) } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt index b104b970766f..62e3fa619679 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppTransition.kt @@ -166,7 +166,7 @@ abstract class OpenAppTransition(protected val testSpec: FlickerTestParameter) { * is replaced by [testApp], which remains visible until the end */ open fun appLayerReplacesLauncher() { - testSpec.replacesLayer(LAUNCHER_COMPONENT, testApp.component) + testSpec.replacesLayer(LAUNCHER_COMPONENT, testApp.component, ignoreSnapshot = true) } /** diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index 8a0af2dff8c8..5628321b5975 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 @@ public class VcnGatewayConnectionTestBase { 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 000000000000..2e1aab60152d --- /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 c954cb84df7f..fad9669911bb 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.Network; 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 @@ public class UnderlyingNetworkControllerTest { mUnderlyingNetworkController = new UnderlyingNetworkController( - mVcnContext, SUB_GROUP, mSubscriptionSnapshot, mNetworkControllerCb); + mVcnContext, + VcnGatewayConnectionConfigTest.buildTestConfig(), + SUB_GROUP, + mSubscriptionSnapshot, + mNetworkControllerCb); } private void resetVcnContext() { @@ -153,7 +158,8 @@ public class UnderlyingNetworkControllerTest { 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 @@ public class UnderlyingNetworkControllerTest { true /* isInTestMode */); new UnderlyingNetworkController( - vcnContext, SUB_GROUP, mSubscriptionSnapshot, mNetworkControllerCb); + vcnContext, + VcnGatewayConnectionConfigTest.buildTestConfig(), + SUB_GROUP, + mSubscriptionSnapshot, + mNetworkControllerCb); verify(cm) .registerNetworkCallback( @@ -345,6 +355,17 @@ public class UnderlyingNetworkControllerTest { 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 @@ public class UnderlyingNetworkControllerTest { 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 class UnderlyingNetworkControllerTest { 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 @@ public class UnderlyingNetworkControllerTest { 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 class UnderlyingNetworkControllerTest { 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 @@ public class UnderlyingNetworkControllerTest { 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 @@ public class UnderlyingNetworkControllerTest { 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 class UnderlyingNetworkControllerTest { 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 @@ public class UnderlyingNetworkControllerTest { 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()); |