Merge "Apply scale to boundsInParent"
diff --git a/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java b/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
index 762b16c..b7460cd 100644
--- a/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
+++ b/apct-tests/perftests/core/src/android/database/SQLiteDatabasePerfTest.java
@@ -24,10 +24,12 @@
import android.database.sqlite.SQLiteDatabase;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
+import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import java.io.File;
+import java.util.ArrayList;
import java.util.Random;
import org.junit.After;
import org.junit.Before;
@@ -107,6 +109,51 @@
}
@Test
+ public void testSelectCacheMissRate() {
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+
+ insertT1TestDataSet();
+
+ ArrayList<String> queryPool = new ArrayList<>();
+ queryPool.add("SELECT _ID, COL_A, COL_B, COL_C FROM T1 WHERE _ID=?");
+ queryPool.add("SELECT _ID FROM T1 WHERE _ID=?");
+ queryPool.add("SELECT COL_A FROM T1 WHERE _ID=?");
+ queryPool.add("SELECT COL_B FROM T1 WHERE _ID=?");
+ queryPool.add("SELECT COL_C FROM T1 WHERE _ID=?");
+ queryPool.add("SELECT _ID, COL_A FROM T1 WHERE _ID=?");
+ queryPool.add("SELECT _ID, COL_B FROM T1 WHERE _ID=?");
+ queryPool.add("SELECT _ID, COL_C FROM T1 WHERE _ID=?");
+ queryPool.add("SELECT COL_A, COL_B FROM T1 WHERE _ID=?");
+ queryPool.add("SELECT COL_A, COL_C FROM T1 WHERE _ID=?");
+ queryPool.add("SELECT COL_B, COL_C FROM T1 WHERE _ID=?");
+ while (state.keepRunning()) {
+ Random rnd = new Random(0);
+
+ int queries = 1000;
+ for (int iQuery = 0; iQuery < queries; ++iQuery) {
+ int queryIndex = rnd.nextInt(queryPool.size());
+ int index = rnd.nextInt(DEFAULT_DATASET_SIZE);
+
+ try (Cursor cursor = mDatabase.rawQuery(
+ queryPool.get(queryIndex), new String[] {String.valueOf(index)})) {
+ assertTrue(cursor.moveToNext());
+ }
+ }
+ }
+
+ Log.d("testSelectMemory",
+ "cacheMissRate: " + mDatabase.getStatementCacheMissRate()
+ + "Total Statements: " + mDatabase.getTotalPreparedStatements()
+ + ". Misses: " + mDatabase.getTotalStatementCacheMisses());
+
+ // Make sure caching is working and our miss rate should definitely be less than 100%
+ // however, we would expect this number to be actually closer to 0.
+ assertTrue(mDatabase.getStatementCacheMissRate() < 1);
+ mDatabase.close();
+ mContext.deleteDatabase(DB_NAME);
+ }
+
+ @Test
public void testSelectMultipleRows() {
insertT1TestDataSet();
diff --git a/core/api/current.txt b/core/api/current.txt
index 10957ff..bb32647 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9010,8 +9010,6 @@
method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName);
method public void requestNotificationAccess(android.content.ComponentName);
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
- method @Deprecated public void startSystemDataTransfer(int) throws android.companion.DeviceNotAssociatedException;
- method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException;
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
field public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
field @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
@@ -9027,19 +9025,14 @@
public abstract class CompanionDeviceService extends android.app.Service {
ctor public CompanionDeviceService();
- method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void dispatchMessageToSystem(int, int, @NonNull byte[]) throws android.companion.DeviceNotAssociatedException;
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
method @Deprecated @MainThread public void onDeviceAppeared(@NonNull String);
method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
- method public void onMessageDispatchedFromSystem(int, int, @NonNull byte[]);
field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
}
- public class CompanionException extends java.lang.RuntimeException {
- }
-
public interface DeviceFilter<D extends android.os.Parcelable> extends android.os.Parcelable {
}
@@ -18215,8 +18208,6 @@
}
public final class Face {
- ctor public Face(@NonNull android.graphics.Rect, int, int, @NonNull android.graphics.Point, @NonNull android.graphics.Point, @NonNull android.graphics.Point);
- ctor public Face(@NonNull android.graphics.Rect, int);
method public android.graphics.Rect getBounds();
method public int getId();
method public android.graphics.Point getLeftEyePosition();
@@ -18228,6 +18219,18 @@
field public static final int SCORE_MIN = 1; // 0x1
}
+ public static final class Face.Builder {
+ ctor public Face.Builder();
+ ctor public Face.Builder(@NonNull android.hardware.camera2.params.Face);
+ method @NonNull public android.hardware.camera2.params.Face build();
+ method @NonNull public android.hardware.camera2.params.Face.Builder setBounds(@NonNull android.graphics.Rect);
+ method @NonNull public android.hardware.camera2.params.Face.Builder setId(int);
+ method @NonNull public android.hardware.camera2.params.Face.Builder setLeftEyePosition(@NonNull android.graphics.Point);
+ method @NonNull public android.hardware.camera2.params.Face.Builder setMouthPosition(@NonNull android.graphics.Point);
+ method @NonNull public android.hardware.camera2.params.Face.Builder setRightEyePosition(@NonNull android.graphics.Point);
+ method @NonNull public android.hardware.camera2.params.Face.Builder setScore(int);
+ }
+
public final class InputConfiguration {
ctor public InputConfiguration(int, int, int);
ctor public InputConfiguration(@NonNull java.util.Collection<android.hardware.camera2.params.MultiResolutionStreamInfo>, int);
@@ -39637,6 +39640,7 @@
package android.speech {
public final class AlternativeSpan implements android.os.Parcelable {
+ ctor public AlternativeSpan(int, int, @NonNull java.util.List<java.lang.String>);
method public int describeContents();
method @NonNull public java.util.List<java.lang.String> getAlternatives();
method public int getEndPosition();
@@ -39645,25 +39649,14 @@
field @NonNull public static final android.os.Parcelable.Creator<android.speech.AlternativeSpan> CREATOR;
}
- public static final class AlternativeSpan.Builder {
- ctor public AlternativeSpan.Builder(int, int);
- method @NonNull public android.speech.AlternativeSpan build();
- method @NonNull public android.speech.AlternativeSpan.Builder setAlternatives(@NonNull java.util.List<java.lang.String>);
- }
-
public final class AlternativeSpans implements android.os.Parcelable {
+ ctor public AlternativeSpans(@NonNull java.util.List<android.speech.AlternativeSpan>);
method public int describeContents();
method @NonNull public java.util.List<android.speech.AlternativeSpan> getSpans();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.speech.AlternativeSpans> CREATOR;
}
- public static final class AlternativeSpans.Builder {
- ctor public AlternativeSpans.Builder();
- method @NonNull public android.speech.AlternativeSpans build();
- method @NonNull public android.speech.AlternativeSpans.Builder setSpans(@NonNull java.util.List<android.speech.AlternativeSpan>);
- }
-
public interface RecognitionListener {
method public void onBeginningOfSpeech();
method public void onBufferReceived(byte[]);
@@ -46000,7 +45993,9 @@
ctor public DrawableMarginSpan(@NonNull android.graphics.drawable.Drawable, int);
method public void chooseHeight(@NonNull CharSequence, int, int, int, int, @NonNull android.graphics.Paint.FontMetricsInt);
method public void drawLeadingMargin(@NonNull android.graphics.Canvas, @NonNull android.graphics.Paint, int, int, int, int, int, @NonNull CharSequence, int, int, boolean, @NonNull android.text.Layout);
+ method @NonNull public android.graphics.drawable.Drawable getDrawable();
method public int getLeadingMargin(boolean);
+ method @Px public int getPadding();
}
public abstract class DynamicDrawableSpan extends android.text.style.ReplacementSpan {
@@ -46043,7 +46038,9 @@
ctor public IconMarginSpan(@NonNull android.graphics.Bitmap, @IntRange(from=0) int);
method public void chooseHeight(CharSequence, int, int, int, int, android.graphics.Paint.FontMetricsInt);
method public void drawLeadingMargin(android.graphics.Canvas, android.graphics.Paint, int, int, int, int, int, CharSequence, int, int, boolean, android.text.Layout);
+ method @NonNull public android.graphics.Bitmap getBitmap();
method public int getLeadingMargin(boolean);
+ method @Px public int getPadding();
}
public class ImageSpan extends android.text.style.DynamicDrawableSpan {
@@ -46291,6 +46288,7 @@
method public String getFamily();
method @Nullable public String getFontFeatureSettings();
method @Nullable public String getFontVariationSettings();
+ method public float getLetterSpacing();
method public android.content.res.ColorStateList getLinkTextColor();
method public int getShadowColor();
method public float getShadowDx();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index b1d458c..631f0fb 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -167,6 +167,7 @@
method public void setEligibleForLegacyPermissionPrompt(boolean);
method public static void setExitTransitionTimeout(long);
method public void setLaunchActivityType(int);
+ method public void setLaunchTaskDisplayAreaFeatureId(int);
method public void setLaunchWindowingMode(int);
method public void setLaunchedFromBubble(boolean);
method public void setTaskAlwaysOnTop(boolean);
@@ -974,8 +975,8 @@
}
public static class SQLiteDebug.DbStats {
- ctor public SQLiteDebug.DbStats(String, long, long, int, int, int, int);
- field public String cache;
+ ctor public SQLiteDebug.DbStats(@NonNull String, long, long, int, int, int, int, boolean);
+ field public final boolean arePoolStats;
field public String dbName;
field public long dbSize;
field public int lookaside;
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index d6441a2..00ab559 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.INVALID_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -223,6 +224,14 @@
"android.activity.launchTaskDisplayAreaToken";
/**
+ * The task display area feature id the activity should be launched into.
+ * @see #setLaunchTaskDisplayAreaFeatureId(int)
+ * @hide
+ */
+ private static final String KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID =
+ "android.activity.launchTaskDisplayAreaFeatureId";
+
+ /**
* The root task token the activity should be launched into.
* @see #setLaunchRootTask(WindowContainerToken)
* @hide
@@ -432,6 +441,7 @@
private int mLaunchDisplayId = INVALID_DISPLAY;
private int mCallerDisplayId = INVALID_DISPLAY;
private WindowContainerToken mLaunchTaskDisplayArea;
+ private int mLaunchTaskDisplayAreaFeatureId = FEATURE_UNDEFINED;
private WindowContainerToken mLaunchRootTask;
private IBinder mLaunchTaskFragmentToken;
@WindowConfiguration.WindowingMode
@@ -1225,6 +1235,8 @@
mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY);
mCallerDisplayId = opts.getInt(KEY_CALLER_DISPLAY_ID, INVALID_DISPLAY);
mLaunchTaskDisplayArea = opts.getParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN);
+ mLaunchTaskDisplayAreaFeatureId = opts.getInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID,
+ FEATURE_UNDEFINED);
mLaunchRootTask = opts.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN);
mLaunchTaskFragmentToken = opts.getBinder(KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED);
@@ -1585,6 +1597,23 @@
}
/** @hide */
+ public int getLaunchTaskDisplayAreaFeatureId() {
+ return mLaunchTaskDisplayAreaFeatureId;
+ }
+
+ /**
+ * Sets the TaskDisplayArea feature Id the activity should launch into.
+ * Note: It is possible to have TaskDisplayAreas with the same featureId on multiple displays.
+ * If launch display id is not specified, the TaskDisplayArea on the default display will be
+ * used.
+ * @hide
+ */
+ @TestApi
+ public void setLaunchTaskDisplayAreaFeatureId(int launchTaskDisplayAreaFeatureId) {
+ mLaunchTaskDisplayAreaFeatureId = launchTaskDisplayAreaFeatureId;
+ }
+
+ /** @hide */
public WindowContainerToken getLaunchRootTask() {
return mLaunchRootTask;
}
@@ -2075,6 +2104,9 @@
if (mLaunchTaskDisplayArea != null) {
b.putParcelable(KEY_LAUNCH_TASK_DISPLAY_AREA_TOKEN, mLaunchTaskDisplayArea);
}
+ if (mLaunchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
+ b.putInt(KEY_LAUNCH_TASK_DISPLAY_AREA_FEATURE_ID, mLaunchTaskDisplayAreaFeatureId);
+ }
if (mLaunchRootTask != null) {
b.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, mLaunchRootTask);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 37e7b1b..8a9ec09 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1026,7 +1026,10 @@
}
private class ApplicationThread extends IApplicationThread.Stub {
- private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s";
+ private static final String DB_CONNECTION_INFO_HEADER = " %8s %8s %14s %5s %5s %5s %s";
+ private static final String DB_CONNECTION_INFO_FORMAT = " %8s %8s %14s %5d %5d %5d %s";
+ private static final String DB_POOL_INFO_HEADER = " %13s %13s %13s %s";
+ private static final String DB_POOL_INFO_FORMAT = " %13d %13d %13d %s";
public final void scheduleReceiver(Intent intent, ActivityInfo info,
CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
@@ -1454,8 +1457,9 @@
pw.print(','); pw.print(dbStats.pageSize);
pw.print(','); pw.print(dbStats.dbSize);
pw.print(','); pw.print(dbStats.lookaside);
- pw.print(','); pw.print(dbStats.cache);
- pw.print(','); pw.print(dbStats.cache);
+ pw.print(','); pw.print(dbStats.cacheHits);
+ pw.print(','); pw.print(dbStats.cacheMisses);
+ pw.print(','); pw.print(dbStats.cacheSize);
}
pw.println();
@@ -1490,15 +1494,34 @@
int N = stats.dbStats.size();
if (N > 0) {
pw.println(" DATABASES");
- printRow(pw, DB_INFO_FORMAT, "pgsz", "dbsz", "Lookaside(b)", "cache",
- "Dbname");
+ printRow(pw, DB_CONNECTION_INFO_HEADER, "pgsz", "dbsz", "Lookaside(b)",
+ "cache hits", "cache misses", "cache size", "Dbname");
+ pw.println("PER CONNECTION STATS");
for (int i = 0; i < N; i++) {
DbStats dbStats = stats.dbStats.get(i);
- printRow(pw, DB_INFO_FORMAT,
+ if (dbStats.arePoolStats) {
+ // these will be printed after
+ continue;
+ }
+ printRow(pw, DB_CONNECTION_INFO_FORMAT,
(dbStats.pageSize > 0) ? String.valueOf(dbStats.pageSize) : " ",
(dbStats.dbSize > 0) ? String.valueOf(dbStats.dbSize) : " ",
(dbStats.lookaside > 0) ? String.valueOf(dbStats.lookaside) : " ",
- dbStats.cache, dbStats.dbName);
+ dbStats.cacheHits, dbStats.cacheMisses, dbStats.cacheSize,
+ dbStats.dbName);
+ }
+ // Print stats accumulated through all the connections that have existed in the
+ // pool since it was opened.
+ pw.println("POOL STATS");
+ printRow(pw, DB_POOL_INFO_HEADER, "cache hits", "cache misses", "cache size",
+ "Dbname");
+ for (int i = 0; i < N; i++) {
+ DbStats dbStats = stats.dbStats.get(i);
+ if (!dbStats.arePoolStats) {
+ continue;
+ }
+ printRow(pw, DB_POOL_INFO_FORMAT, dbStats.cacheHits, dbStats.cacheMisses,
+ dbStats.cacheSize, dbStats.dbName);
}
}
@@ -1623,7 +1646,12 @@
proto.write(MemInfoDumpProto.AppData.SqlStats.Database.DB_SIZE, dbStats.dbSize);
proto.write(MemInfoDumpProto.AppData.SqlStats.Database.LOOKASIDE_B,
dbStats.lookaside);
- proto.write(MemInfoDumpProto.AppData.SqlStats.Database.CACHE, dbStats.cache);
+ proto.write(
+ MemInfoDumpProto.AppData.SqlStats.Database.CACHE_HITS, dbStats.cacheHits);
+ proto.write(MemInfoDumpProto.AppData.SqlStats.Database.CACHE_MISSES,
+ dbStats.cacheMisses);
+ proto.write(
+ MemInfoDumpProto.AppData.SqlStats.Database.CACHE_SIZE, dbStats.cacheSize);
proto.end(dToken);
}
proto.end(sToken);
@@ -5857,16 +5885,16 @@
final boolean movedToDifferentDisplay = isDifferentDisplay(activity.getDisplayId(),
displayId);
- final Configuration currentResConfig = activity.getResources().getConfiguration();
- final int diff = currentResConfig.diffPublicOnly(newConfig);
- final boolean hasPublicResConfigChange = diff != 0;
+ final Configuration currentConfig = activity.mCurrentConfig;
+ final int diff = currentConfig.diffPublicOnly(newConfig);
+ final boolean hasPublicConfigChange = diff != 0;
final ActivityClientRecord r = getActivityClient(activityToken);
// TODO(b/173090263): Use diff instead after the improvement of AssetManager and
// ResourcesImpl constructions.
- final boolean shouldUpdateResources = hasPublicResConfigChange
- || shouldUpdateResources(activityToken, currentResConfig, newConfig,
- amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange);
- final boolean shouldReportChange = shouldReportChange(activity.mCurrentConfig, newConfig,
+ final boolean shouldUpdateResources = hasPublicConfigChange
+ || shouldUpdateResources(activityToken, currentConfig, newConfig, amOverrideConfig,
+ movedToDifferentDisplay, hasPublicConfigChange);
+ final boolean shouldReportChange = shouldReportChange(diff, currentConfig, newConfig,
r != null ? r.mSizeConfigurations : null,
activity.mActivityInfo.getRealConfigChanged());
// Nothing significant, don't proceed with updating and reporting.
@@ -5890,6 +5918,9 @@
amOverrideConfig, contextThemeWrapperOverrideConfig);
mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig, displayId);
+ activity.mConfigChangeFlags = 0;
+ activity.mCurrentConfig = new Configuration(newConfig);
+
// Apply the ContextThemeWrapper override if necessary.
// NOTE: Make sure the configurations are not modified, as they are treated as immutable
// in many places.
@@ -5900,10 +5931,8 @@
activity.dispatchMovedToDisplay(displayId, configToReport);
}
- activity.mConfigChangeFlags = 0;
if (shouldReportChange) {
activity.mCalled = false;
- activity.mCurrentConfig = new Configuration(newConfig);
activity.onConfigurationChanged(configToReport);
if (!activity.mCalled) {
throw new SuperNotCalledException("Activity " + activity.getLocalClassName() +
@@ -5918,6 +5947,8 @@
* Returns {@code true} if {@link Activity#onConfigurationChanged(Configuration)} should be
* dispatched.
*
+ * @param publicDiff Usually computed by {@link Configuration#diffPublicOnly(Configuration)}.
+ * This parameter is to prevent we compute it again.
* @param currentConfig The current configuration cached in {@link Activity#mCurrentConfig}.
* It is {@code null} before the first config update from the server side.
* @param newConfig The updated {@link Configuration}
@@ -5926,10 +5957,9 @@
* @return {@code true} if the config change should be reported to the Activity
*/
@VisibleForTesting
- public static boolean shouldReportChange(@Nullable Configuration currentConfig,
+ public static boolean shouldReportChange(int publicDiff, @Nullable Configuration currentConfig,
@NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets sizeBuckets,
int handledConfigChanges) {
- final int publicDiff = currentConfig.diffPublicOnly(newConfig);
// Don't report the change if there's no public diff between current and new config.
if (publicDiff == 0) {
return false;
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index fe75dd3..dc6825c 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -263,9 +263,12 @@
* @param taskId the id of the task to retrieve the sAutoapshots for
* @param isLowResolution if set, if the snapshot needs to be loaded from disk, this will load
* a reduced resolution of it, which is much faster
+ * @param takeSnapshotIfNeeded if set, call {@link #takeTaskSnapshot} to trigger the snapshot
+ if no cache exists.
* @return a graphic buffer representing a screenshot of a task
*/
- android.window.TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution);
+ android.window.TaskSnapshot getTaskSnapshot(
+ int taskId, boolean isLowResolution, boolean takeSnapshotIfNeeded);
/**
* @param taskId the id of the task to take a snapshot of
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index b90408d..904db5f 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -518,7 +518,7 @@
/**
* Indicates that an event occurred as the device attempted to connect to
- * a WiFi network. The log entry contains the following information about the
+ * a managed WiFi network. The log entry contains the following information about the
* event, encapsulated in an {@link Object} array and accessible via
* {@link SecurityEvent#getData()}:
* <li> [0] Last 2 octets of the network BSSID ({@code String}, in the form "xx:xx:xx:xx:AA:BB")
@@ -530,7 +530,7 @@
public static final int TAG_WIFI_CONNECTION = SecurityLogTags.SECURITY_WIFI_CONNECTION;
/**
- * Indicates that the device disconnects from a connected WiFi network.
+ * Indicates that the device disconnects from a managed WiFi network.
* The log entry contains the following information about the
* event, encapsulated in an {@link Object} array and accessible via
* {@link SecurityEvent#getData()}:
diff --git a/core/java/android/app/smartspace/OWNERS b/core/java/android/app/smartspace/OWNERS
index 19ef9d7..4d9a633 100644
--- a/core/java/android/app/smartspace/OWNERS
+++ b/core/java/android/app/smartspace/OWNERS
@@ -1,2 +1 @@
-srazdan@google.com
-alexmang@google.com
\ No newline at end of file
+include /core/java/android/service/smartspace/OWNERS
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 1b51faf3..8ffc6a2 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -800,18 +800,15 @@
*
* @hide
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
public void dispatchMessage(int messageId, int associationId, @NonNull byte[] message)
throws DeviceNotAssociatedException {
- try {
- mService.dispatchMessage(messageId, associationId, message);
- } catch (RemoteException e) {
- ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
- throw e.rethrowFromSystemServer();
- }
+ Log.w(LOG_TAG, "dispatchMessage replaced by attachSystemDataTransport");
}
/** {@hide} */
+ @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
public final void attachSystemDataTransport(int associationId, @NonNull InputStream in,
@NonNull OutputStream out) throws DeviceNotAssociatedException {
synchronized (mTransports) {
@@ -830,6 +827,7 @@
}
/** {@hide} */
+ @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
public final void detachSystemDataTransport(int associationId)
throws DeviceNotAssociatedException {
synchronized (mTransports) {
@@ -927,7 +925,7 @@
*
* <p>The permission transfer doesn't happen immediately after the call or user consented.
* The app needs to trigger the system data transfer manually by calling
- * {@link #startSystemDataTransfer(int)}, when it confirms the communication channel between
+ * {@code #startSystemDataTransfer(int)}, when it confirms the communication channel between
* the two devices is established.</p>
*
* @param associationId The unique {@link AssociationInfo#getId ID} assigned to the association
@@ -965,8 +963,8 @@
* @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association
* of the companion device recorded by CompanionDeviceManager
* @throws DeviceNotAssociatedException Exception if the companion device is not associated
- *
* @deprecated Use {@link #startSystemDataTransfer(int, Executor, OutcomeReceiver)} instead.
+ * @hide
*/
@Deprecated
@UserHandleAware
@@ -993,6 +991,7 @@
* @param executor The executor which will be used to invoke the result callback.
* @param result The callback to notify the app of the result of the system data transfer.
* @throws DeviceNotAssociatedException Exception if the companion device is not associated
+ * @hide
*/
@UserHandleAware
public void startSystemDataTransfer(
@@ -1125,12 +1124,14 @@
public void start() throws IOException {
final ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair();
- mLocalIn = new ParcelFileDescriptor.AutoCloseInputStream(pair[0]);
- mLocalOut = new ParcelFileDescriptor.AutoCloseOutputStream(pair[0]);
+ final ParcelFileDescriptor localFd = pair[0];
+ final ParcelFileDescriptor remoteFd = pair[1];
+ mLocalIn = new ParcelFileDescriptor.AutoCloseInputStream(localFd);
+ mLocalOut = new ParcelFileDescriptor.AutoCloseOutputStream(localFd);
try {
mService.attachSystemDataTransport(mContext.getPackageName(),
- mContext.getUserId(), mAssociationId, pair[1]);
+ mContext.getUserId(), mAssociationId, remoteFd);
} catch (RemoteException e) {
throw new IOException("Failed to configure transport", e);
}
@@ -1160,17 +1161,17 @@
public void stop() {
mStopped = true;
- IoUtils.closeQuietly(mRemoteIn);
- IoUtils.closeQuietly(mRemoteOut);
- IoUtils.closeQuietly(mLocalIn);
- IoUtils.closeQuietly(mLocalOut);
-
try {
mService.detachSystemDataTransport(mContext.getPackageName(),
mContext.getUserId(), mAssociationId);
} catch (RemoteException e) {
Log.w(LOG_TAG, "Failed to detach transport", e);
}
+
+ IoUtils.closeQuietly(mRemoteIn);
+ IoUtils.closeQuietly(mRemoteOut);
+ IoUtils.closeQuietly(mLocalIn);
+ IoUtils.closeQuietly(mLocalOut);
}
/**
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 83d2713..a79983a 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -156,9 +156,12 @@
* @param messageId system assigned id of the message to be sent
* @param associationId association id of the associated device
* @param message message to be sent
+ * @hide
*/
+ @Deprecated
public void onMessageDispatchedFromSystem(int messageId, int associationId,
@NonNull byte[] message) {
+ Log.w(LOG_TAG, "Replaced by attachSystemDataTransport");
// do nothing. Companion apps can override this function for system to send messages.
}
@@ -185,22 +188,14 @@
* @param messageId id of the message
* @param associationId id of the associated device
* @param message message received from the associated device
+ * @hide
*/
+ @Deprecated
@RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
public final void dispatchMessageToSystem(int messageId, int associationId,
@NonNull byte[] message)
throws DeviceNotAssociatedException {
- if (getBaseContext() == null) {
- Log.e(LOG_TAG, "Dispatch failed. Start your service before calling this method.");
- return;
- }
- CompanionDeviceManager companionDeviceManager =
- getSystemService(CompanionDeviceManager.class);
- if (companionDeviceManager != null) {
- companionDeviceManager.dispatchMessage(messageId, associationId, message);
- } else {
- Log.e(LOG_TAG, "CompanionDeviceManager is null. Can't dispatch messages.");
- }
+ Log.w(LOG_TAG, "Replaced by attachSystemDataTransport");
}
/**
@@ -223,10 +218,13 @@
* device
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
public final void attachSystemDataTransport(int associationId, @NonNull InputStream in,
@NonNull OutputStream out) throws DeviceNotAssociatedException {
getSystemService(CompanionDeviceManager.class)
- .attachSystemDataTransport(associationId, in, out);
+ .attachSystemDataTransport(associationId,
+ Objects.requireNonNull(in),
+ Objects.requireNonNull(out));
}
/**
@@ -236,6 +234,7 @@
* @param associationId id of the associated device
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES)
public final void detachSystemDataTransport(int associationId)
throws DeviceNotAssociatedException {
getSystemService(CompanionDeviceManager.class)
@@ -299,13 +298,5 @@
public void onDeviceDisappeared(AssociationInfo associationInfo) {
mMainHandler.postAtFrontOfQueue(() -> mService.onDeviceDisappeared(associationInfo));
}
-
- @Override
- public void onMessageDispatchedFromSystem(int messageId, int associationId,
- @NonNull byte[] message) {
- mMainHandler.postAtFrontOfQueue(
- () -> mService.onMessageDispatchedFromSystem(messageId, associationId,
- message));
- }
}
}
diff --git a/core/java/android/companion/CompanionException.java b/core/java/android/companion/CompanionException.java
index fb78e8d..9a92401 100644
--- a/core/java/android/companion/CompanionException.java
+++ b/core/java/android/companion/CompanionException.java
@@ -19,7 +19,10 @@
import android.annotation.NonNull;
/**
- * {@code CompanionException} can be thrown during the companion system data transfer process.
+ * {@code CompanionException} can be thrown during the companion system data
+ * transfer process.
+ *
+ * @hide
*/
public class CompanionException extends RuntimeException {
/** @hide */
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 42f9083..6e6e187 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -63,8 +63,6 @@
void createAssociation(in String packageName, in String macAddress, int userId,
in byte[] certificate);
- void dispatchMessage(int messageId, int associationId, in byte[] message);
-
void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
diff --git a/core/java/android/companion/ICompanionDeviceService.aidl b/core/java/android/companion/ICompanionDeviceService.aidl
index 3c90b86..fa68508 100644
--- a/core/java/android/companion/ICompanionDeviceService.aidl
+++ b/core/java/android/companion/ICompanionDeviceService.aidl
@@ -22,5 +22,4 @@
oneway interface ICompanionDeviceService {
void onDeviceAppeared(in AssociationInfo associationInfo);
void onDeviceDisappeared(in AssociationInfo associationInfo);
- void onMessageDispatchedFromSystem(in int messageId, in int associationId, in byte[] message);
}
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 6d6ec06..4b3eb3a 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -1053,6 +1053,7 @@
}
private PreparedStatement acquirePreparedStatement(String sql) {
+ ++mPool.mTotalPrepareStatements;
PreparedStatement statement = mPreparedStatementCache.get(sql);
boolean skipCache = false;
if (statement != null) {
@@ -1064,7 +1065,7 @@
// statement but do not cache it.
skipCache = true;
}
-
+ ++mPool.mTotalPrepareStatementCacheMiss;
final long statementPtr = nativePrepareStatement(mConnectionPtr, sql);
try {
final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
@@ -1320,7 +1321,8 @@
if (!path.isEmpty()) {
label.append(": ").append(path);
}
- dbStatsList.add(new DbStats(label.toString(), pageCount, pageSize, 0, 0, 0, 0));
+ dbStatsList.add(
+ new DbStats(label.toString(), pageCount, pageSize, 0, 0, 0, 0, false));
}
} catch (SQLiteException ex) {
// Ignore.
@@ -1349,9 +1351,8 @@
label = mConfiguration.path + " (" + mConnectionId + ")";
}
return new DbStats(label, pageCount, pageSize, lookaside,
- mPreparedStatementCache.hitCount(),
- mPreparedStatementCache.missCount(),
- mPreparedStatementCache.size());
+ mPreparedStatementCache.hitCount(), mPreparedStatementCache.missCount(),
+ mPreparedStatementCache.size(), false);
}
@Override
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index db0cac3..069c264 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -32,6 +32,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import dalvik.annotation.optimization.NeverCompile;
import dalvik.system.CloseGuard;
import java.io.Closeable;
@@ -103,6 +104,10 @@
new ArrayList<SQLiteConnection>();
private SQLiteConnection mAvailablePrimaryConnection;
+ // Prepare statement cache statistics
+ public int mTotalPrepareStatementCacheMiss = 0;
+ public int mTotalPrepareStatements = 0;
+
@GuardedBy("mLock")
private IdleConnectionHandler mIdleConnectionHandler;
@@ -507,6 +512,12 @@
for (SQLiteConnection connection : mAcquiredConnections.keySet()) {
connection.collectDbStatsUnsafe(dbStatsList);
}
+
+ // Global pool stats
+ DbStats poolStats = new DbStats(mConfiguration.path, 0, 0, 0,
+ mTotalPrepareStatements - mTotalPrepareStatementCacheMiss,
+ mTotalPrepareStatementCacheMiss, mTotalPrepareStatements, true);
+ dbStatsList.add(poolStats);
}
}
@@ -1203,6 +1214,16 @@
}
}
+ /** @hide */
+ @NeverCompile
+ public double getStatementCacheMissRate() {
+ if (mTotalPrepareStatements == 0) {
+ // no statements executed thus no miss rate.
+ return 0;
+ }
+ return (double) mTotalPrepareStatementCacheMiss / (double) mTotalPrepareStatements;
+ }
+
public long getTotalStatementsTime() {
return mTotalStatementsTime.get();
}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 0d0615a..c08294f 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -45,6 +45,8 @@
import android.util.Pair;
import android.util.Printer;
import com.android.internal.util.Preconditions;
+
+import dalvik.annotation.optimization.NeverCompile;
import dalvik.system.CloseGuard;
import java.io.File;
import java.io.FileFilter;
@@ -2233,6 +2235,16 @@
}
}
+ /** @hide */
+ @NeverCompile
+ public double getStatementCacheMissRate() {
+ synchronized (mLock) {
+ throwIfNotOpenLocked();
+
+ return mConnectionPoolLocked.getStatementCacheMissRate();
+ }
+ }
+
/**
* Sets whether foreign key constraints are enabled for the database.
* <p>
@@ -2489,6 +2501,22 @@
return connectionPools;
}
+ /** @hide */
+ @NeverCompile
+ public int getTotalPreparedStatements() {
+ throwIfNotOpenLocked();
+
+ return mConnectionPoolLocked.mTotalPrepareStatements;
+ }
+
+ /** @hide */
+ @NeverCompile
+ public int getTotalStatementCacheMisses() {
+ throwIfNotOpenLocked();
+
+ return mConnectionPoolLocked.mTotalPrepareStatementCacheMiss;
+ }
+
/**
* Dump detailed information about all open databases in the current process.
* Used by bug report.
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index b84a8d2..93d74b1 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -16,6 +16,7 @@
package android.database.sqlite;
+import android.annotation.NonNull;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -23,7 +24,6 @@
import android.os.SystemProperties;
import android.util.Log;
import android.util.Printer;
-
import java.util.ArrayList;
/**
@@ -173,16 +173,26 @@
@UnsupportedAppUsage
public int lookaside;
- /** statement cache stats: hits/misses/cachesize */
- public String cache;
+ /** @hide */
+ final public int cacheHits;
+ /** @hide */
+ final public int cacheMisses;
+ /** @hide */
+ final public int cacheSize;
- public DbStats(String dbName, long pageCount, long pageSize, int lookaside,
- int hits, int misses, int cachesize) {
+ /** true if connection specific stats or whole connection pool if false */
+ public final boolean arePoolStats;
+
+ public DbStats(@NonNull String dbName, long pageCount, long pageSize, int lookaside,
+ int hits, int misses, int cachesize, boolean arePoolStats) {
this.dbName = dbName;
this.pageSize = pageSize / 1024;
dbSize = (pageCount * pageSize) / 1024;
this.lookaside = lookaside;
- this.cache = hits + "/" + misses + "/" + cachesize;
+ this.cacheHits = hits;
+ this.cacheMisses = misses;
+ this.cacheSize = cachesize;
+ this.arePoolStats = arePoolStats;
}
}
diff --git a/core/java/android/hardware/camera2/params/Face.java b/core/java/android/hardware/camera2/params/Face.java
index fa1202b..1d9a5a3a 100644
--- a/core/java/android/hardware/camera2/params/Face.java
+++ b/core/java/android/hardware/camera2/params/Face.java
@@ -69,10 +69,6 @@
* mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition,
* rightEyePosition, and mouthPosition may be independently null or not-null.</p>
*
- * <p>This constructor is public to allow for easier application testing by
- * creating custom object instances. It's not necessary to construct these
- * objects during normal use of the camera API.</p>
- *
* @param bounds Bounds of the face.
* @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}.
* @param id A unique ID per face visible to the tracker.
@@ -87,6 +83,8 @@
* or if id is {@value #ID_UNSUPPORTED} and
* leftEyePosition/rightEyePosition/mouthPosition aren't all null,
* or else if id is negative.
+ *
+ * @hide
*/
public Face(@NonNull Rect bounds, int score, int id,
@NonNull Point leftEyePosition, @NonNull Point rightEyePosition,
@@ -106,10 +104,6 @@
* the face id of each face is expected to be {@value #ID_UNSUPPORTED}, the leftEyePosition,
* rightEyePosition, and mouthPositions are expected to be {@code null} for each face.</p>
*
- * <p>This constructor is public to allow for easier application testing by
- * creating custom object instances. It's not necessary to construct these
- * objects during normal use of the camera API.</p>
- *
* @param bounds Bounds of the face.
* @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}.
*
@@ -117,6 +111,8 @@
* if bounds is {@code null},
* or if the confidence is not in the range of
* {@value #SCORE_MIN}-{@value #SCORE_MAX}.
+ *
+ * @hide
*/
public Face(@NonNull Rect bounds, int score) {
init(bounds, score, ID_UNSUPPORTED,
@@ -130,16 +126,14 @@
@Nullable Point leftEyePosition, @Nullable Point rightEyePosition,
@Nullable Point mouthPosition) {
checkNotNull("bounds", bounds);
- if (score < SCORE_MIN || score > SCORE_MAX) {
- throw new IllegalArgumentException("Confidence out of range");
- } else if (id < 0 && id != ID_UNSUPPORTED) {
- throw new IllegalArgumentException("Id out of range");
- }
+ checkScore(score);
+ checkId(id);
if (id == ID_UNSUPPORTED) {
checkNull("leftEyePosition", leftEyePosition);
checkNull("rightEyePosition", rightEyePosition);
checkNull("mouthPosition", mouthPosition);
}
+ checkFace(leftEyePosition, rightEyePosition, mouthPosition);
mBounds = bounds;
mScore = score;
@@ -156,7 +150,7 @@
* {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}, with (0,0)
* representing the top-left corner of the active array rectangle.</p>
*
- * <p>There is no constraints on the the Rectangle value other than it
+ * <p>There is no constraints on the Rectangle value other than it
* is not-{@code null}.</p>
*/
public Rect getBounds() {
@@ -190,7 +184,7 @@
* If the face leaves the field-of-view and comes back, it will get a new
* id.</p>
*
- * <p>This is an optional field, may not be supported on all devices.
+ * <p>This is an optional field and may not be supported on all devices.
* If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and
* mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition,
* rightEyePosition, and mouthPosition may be independently null or not-null. When devices
@@ -212,7 +206,7 @@
*
* <p>The coordinates are in
* the same space as the ones for {@link #getBounds}. This is an
- * optional field, may not be supported on all devices. If not
+ * optional field and may not be supported on all devices. If not
* supported, the value will always be set to null.
* This value will always be null only if {@link #getId()} returns
* {@value #ID_UNSUPPORTED}.</p>
@@ -228,7 +222,7 @@
*
* <p>The coordinates are
* in the same space as the ones for {@link #getBounds}.This is an
- * optional field, may not be supported on all devices. If not
+ * optional field and may not be supported on all devices. If not
* supported, the value will always be set to null.
* This value will always be null only if {@link #getId()} returns
* {@value #ID_UNSUPPORTED}.</p>
@@ -244,7 +238,7 @@
*
* <p>The coordinates are in
* the same space as the ones for {@link #getBounds}. This is an optional
- * field, may not be supported on all devices. If not
+ * field and may not be supported on all devices. If not
* supported, the value will always be set to null.
* This value will always be null only if {@link #getId()} returns
* {@value #ID_UNSUPPORTED}.</p>
@@ -277,4 +271,253 @@
throw new IllegalArgumentException(name + " was required to be null, but it wasn't");
}
}
+
+ private static void checkScore(int score) {
+ if (score < SCORE_MIN || score > SCORE_MAX) {
+ throw new IllegalArgumentException("Confidence out of range");
+ }
+ }
+
+ private static void checkId(int id) {
+ if (id < 0 && id != ID_UNSUPPORTED) {
+ throw new IllegalArgumentException("Id out of range");
+ }
+ }
+
+ private static void checkFace(@Nullable Point leftEyePosition,
+ @Nullable Point rightEyePosition, @Nullable Point mouthPosition) {
+ if (leftEyePosition != null || rightEyePosition != null || mouthPosition != null) {
+ if (leftEyePosition == null || rightEyePosition == null || mouthPosition == null) {
+ throw new IllegalArgumentException("If any of leftEyePosition, rightEyePosition, "
+ + "or mouthPosition are non-null, all three must be non-null.");
+ }
+ }
+ }
+
+ /**
+ * Builds a Face object.
+ *
+ * <p>This builder is public to allow for easier application testing by
+ * creating custom object instances. It's not necessary to construct these
+ * objects during normal use of the camera API.</p>
+ */
+ public static final class Builder {
+ private long mBuilderFieldsSet = 0L;
+
+ private static final long FIELD_BOUNDS = 1 << 1;
+ private static final long FIELD_SCORE = 1 << 2;
+ private static final long FIELD_ID = 1 << 3;
+ private static final long FIELD_LEFT_EYE = 1 << 4;
+ private static final long FIELD_RIGHT_EYE = 1 << 5;
+ private static final long FIELD_MOUTH = 1 << 6;
+ private static final long FIELD_BUILT = 1 << 0;
+
+ private static final String FIELD_NAME_BOUNDS = "bounds";
+ private static final String FIELD_NAME_SCORE = "score";
+ private static final String FIELD_NAME_LEFT_EYE = "left eye";
+ private static final String FIELD_NAME_RIGHT_EYE = "right eye";
+ private static final String FIELD_NAME_MOUTH = "mouth";
+
+ private Rect mBounds = null;
+ private int mScore = 0;
+ private int mId = ID_UNSUPPORTED;
+ private Point mLeftEye = null;
+ private Point mRightEye = null;
+ private Point mMouth = null;
+
+ public Builder() {
+ // Empty
+ }
+
+ public Builder(@NonNull Face current) {
+ mBounds = current.mBounds;
+ mScore = current.mScore;
+ mId = current.mId;
+ mLeftEye = current.mLeftEye;
+ mRightEye = current.mRightEye;
+ mMouth = current.mMouth;
+ }
+
+ /**
+ * Bounds of the face.
+ *
+ * <p>A rectangle relative to the sensor's
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}, with (0,0)
+ * representing the top-left corner of the active array rectangle.</p>
+ *
+ * <p>There is no constraints on the Rectangle value other than it
+ * is not-{@code null}.</p>
+ *
+ * @param bounds Bounds of the face.
+ * @return This builder.
+ */
+ public @NonNull Builder setBounds(@NonNull Rect bounds) {
+ checkNotUsed();
+ mBuilderFieldsSet |= FIELD_BOUNDS;
+ mBounds = bounds;
+ return this;
+ }
+
+ /**
+ * The confidence level for the detection of the face.
+ *
+ * <p>The range is {@value #SCORE_MIN} to {@value #SCORE_MAX}.
+ * {@value #SCORE_MAX} is the highest confidence.</p>
+ *
+ * <p>Depending on the device, even very low-confidence faces may be
+ * listed, so applications should filter out faces with low confidence,
+ * depending on the use case. For a typical point-and-shoot camera
+ * application that wishes to display rectangles around detected faces,
+ * filtering out faces with confidence less than half of {@value #SCORE_MAX}
+ * is recommended.</p>
+ *
+ * @see #SCORE_MAX
+ * @see #SCORE_MIN
+ *
+ * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}.
+ * @return This builder.
+ */
+ public @NonNull Builder setScore(int score) {
+ checkNotUsed();
+ checkScore(score);
+ mBuilderFieldsSet |= FIELD_SCORE;
+ mScore = score;
+ return this;
+ }
+
+ /**
+ * An unique id per face while the face is visible to the tracker.
+ *
+ * <p>
+ * If the face leaves the field-of-view and comes back, it will get a new
+ * id.</p>
+ *
+ * <p>This is an optional field and may not be supported on all devices.
+ * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and
+ * mouthPositions should be {@code null}. Otherwise, each of leftEyePosition,
+ * rightEyePosition, and mouthPosition may be independently null or not-null. When devices
+ * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as
+ * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link CaptureResult},
+ * the face id of each face is expected to be {@value #ID_UNSUPPORTED}.</p>
+ *
+ * <p>This value should either be {@value #ID_UNSUPPORTED} or
+ * otherwise greater than {@code 0}.</p>
+ *
+ * @see #ID_UNSUPPORTED
+ *
+ * @param id A unique ID per face visible to the tracker.
+ * @return This builder.
+ */
+ public @NonNull Builder setId(int id) {
+ checkNotUsed();
+ checkId(id);
+ mBuilderFieldsSet |= FIELD_ID;
+ mId = id;
+ return this;
+ }
+
+ /**
+ * The coordinates of the center of the left eye.
+ *
+ * <p>The coordinates should be
+ * in the same space as the ones for {@link #setBounds}. This is an
+ * optional field and may not be supported on all devices. If not
+ * supported, the value should always be unset or set to null.
+ * This value should always be null if {@link #setId} is called with
+ * {@value #ID_UNSUPPORTED}.</p>
+ *
+ * @param leftEyePosition The position of the left eye.
+ * @return This builder.
+ */
+ public @NonNull Builder setLeftEyePosition(@NonNull Point leftEyePosition) {
+ checkNotUsed();
+ mBuilderFieldsSet |= FIELD_LEFT_EYE;
+ mLeftEye = leftEyePosition;
+ return this;
+ }
+
+ /**
+ * The coordinates of the center of the right eye.
+ *
+ * <p>The coordinates should be
+ * in the same space as the ones for {@link #setBounds}.This is an
+ * optional field and may not be supported on all devices. If not
+ * supported, the value should always be set to null.
+ * This value should always be null if {@link #setId} is called with
+ * {@value #ID_UNSUPPORTED}.</p>
+ *
+ * @param rightEyePosition The position of the right eye.
+ * @return This builder.
+ */
+ public @NonNull Builder setRightEyePosition(@NonNull Point rightEyePosition) {
+ checkNotUsed();
+ mBuilderFieldsSet |= FIELD_RIGHT_EYE;
+ mRightEye = rightEyePosition;
+ return this;
+ }
+
+ /**
+ * The coordinates of the center of the mouth.
+ *
+ * <p>The coordinates should be in
+ * the same space as the ones for {@link #setBounds}. This is an optional
+ * field and may not be supported on all devices. If not
+ * supported, the value should always be set to null.
+ * This value should always be null if {@link #setId} is called with
+ * {@value #ID_UNSUPPORTED}.</p>
+ * </p>
+ *
+ * @param mouthPosition The position of the mouth.
+ * @return This builder.
+ */
+ public @NonNull Builder setMouthPosition(@NonNull Point mouthPosition) {
+ checkNotUsed();
+ mBuilderFieldsSet |= FIELD_MOUTH;
+ mMouth = mouthPosition;
+ return this;
+ }
+
+ /**
+ * Returns an instance of <code>Face</code> created from the fields set
+ * on this builder.
+ *
+ * @return A Face.
+ */
+ public @NonNull Face build() {
+ checkNotUsed();
+ checkFieldSet(FIELD_BOUNDS, FIELD_NAME_BOUNDS);
+ checkFieldSet(FIELD_SCORE, FIELD_NAME_SCORE);
+ if (mId == ID_UNSUPPORTED) {
+ checkIdUnsupportedThenNull(mLeftEye, FIELD_NAME_LEFT_EYE);
+ checkIdUnsupportedThenNull(mRightEye, FIELD_NAME_RIGHT_EYE);
+ checkIdUnsupportedThenNull(mMouth, FIELD_NAME_MOUTH);
+ }
+ checkFace(mLeftEye, mRightEye, mMouth);
+
+ mBuilderFieldsSet |= FIELD_BUILT;
+
+ return new Face(mBounds, mScore, mId, mLeftEye, mRightEye, mMouth);
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & FIELD_BUILT) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+
+ private void checkFieldSet(long field, String fieldName) {
+ if ((mBuilderFieldsSet & field) == 0) {
+ throw new IllegalStateException(
+ "Field \"" + fieldName + "\" must be set before building.");
+ }
+ }
+
+ private void checkIdUnsupportedThenNull(Object obj, String fieldName) {
+ if (obj != null) {
+ throw new IllegalArgumentException("Field \"" + fieldName
+ + "\" must be unset or null if id is ID_UNSUPPORTED.");
+ }
+ }
+ }
}
diff --git a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
index d06bc1d..3e41d63 100644
--- a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
+++ b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
@@ -122,6 +122,9 @@
}
private IHdmiControlCallback getCallbackWrapper(final OneTouchPlayCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("OneTouchPlayCallback cannot be null.");
+ }
return new IHdmiControlCallback.Stub() {
@Override
public void onComplete(int result) {
@@ -131,6 +134,9 @@
}
private IHdmiControlCallback getCallbackWrapper(final DisplayStatusCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("DisplayStatusCallback cannot be null.");
+ }
return new IHdmiControlCallback.Stub() {
@Override
public void onComplete(int status) {
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index 3a042a5..e8e4fc9 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -26,7 +26,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -173,38 +172,63 @@
}
}
- void apply(@NonNull Chunk chunk) {
+ void apply(Chunk chunk) {
+ List<ProgramSelector.Identifier> removedList = new ArrayList<>();
+ List<ProgramSelector.Identifier> changedList = new ArrayList<>();
+ List<ProgramList.ListCallback> listCallbacksCopied;
+ List<OnCompleteListener> onCompleteListenersCopied = new ArrayList<>();
synchronized (mLock) {
if (mIsClosed) return;
mIsComplete = false;
+ listCallbacksCopied = new ArrayList<>(mListCallbacks);
if (chunk.isPurge()) {
- new HashSet<>(mPrograms.keySet()).stream().forEach(id -> removeLocked(id));
+ for (ProgramSelector.Identifier id : mPrograms.keySet()) {
+ removeLocked(id, removedList);
+ }
}
- chunk.getRemoved().stream().forEach(id -> removeLocked(id));
- chunk.getModified().stream().forEach(info -> putLocked(info));
+ chunk.getRemoved().stream().forEach(id -> removeLocked(id, removedList));
+ chunk.getModified().stream().forEach(info -> putLocked(info, changedList));
if (chunk.isComplete()) {
mIsComplete = true;
- mOnCompleteListeners.forEach(cb -> cb.onComplete());
+ onCompleteListenersCopied = new ArrayList<>(mOnCompleteListeners);
+ }
+ }
+
+ for (int i = 0; i < removedList.size(); i++) {
+ for (int cbIndex = 0; cbIndex < listCallbacksCopied.size(); cbIndex++) {
+ listCallbacksCopied.get(cbIndex).onItemRemoved(removedList.get(i));
+ }
+ }
+ for (int i = 0; i < changedList.size(); i++) {
+ for (int cbIndex = 0; cbIndex < listCallbacksCopied.size(); cbIndex++) {
+ listCallbacksCopied.get(cbIndex).onItemChanged(changedList.get(i));
+ }
+ }
+ if (chunk.isComplete()) {
+ for (int cbIndex = 0; cbIndex < onCompleteListenersCopied.size(); cbIndex++) {
+ onCompleteListenersCopied.get(cbIndex).onComplete();
}
}
}
- private void putLocked(@NonNull RadioManager.ProgramInfo value) {
+ private void putLocked(RadioManager.ProgramInfo value,
+ List<ProgramSelector.Identifier> changedIdentifierList) {
ProgramSelector.Identifier key = value.getSelector().getPrimaryId();
mPrograms.put(Objects.requireNonNull(key), value);
ProgramSelector.Identifier sel = value.getSelector().getPrimaryId();
- mListCallbacks.forEach(cb -> cb.onItemChanged(sel));
+ changedIdentifierList.add(sel);
}
- private void removeLocked(@NonNull ProgramSelector.Identifier key) {
+ private void removeLocked(ProgramSelector.Identifier key,
+ List<ProgramSelector.Identifier> removedIdentifierList) {
RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key));
if (removed == null) return;
ProgramSelector.Identifier sel = removed.getSelector().getPrimaryId();
- mListCallbacks.forEach(cb -> cb.onItemRemoved(sel));
+ removedIdentifierList.add(sel);
}
/**
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index b47e92d..e960df1 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -31,18 +31,15 @@
import android.util.Log;
import android.view.InputChannel;
import android.view.MotionEvent;
-import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodSession;
import android.view.inputmethod.InputMethodSubtype;
-import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.inputmethod.CancellationGroup;
import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
import com.android.internal.inputmethod.IInputMethod;
-import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.inputmethod.IInputMethodSession;
import com.android.internal.inputmethod.IInputMethodSessionCallback;
import com.android.internal.inputmethod.IRemoteInputConnection;
@@ -98,9 +95,9 @@
*
* <p>This field must be set and cleared only from the binder thread(s), where the system
* guarantees that {@link #bindInput(InputBinding)},
- * {@link #startInput(IBinder, IRemoteInputConnection, int, EditorInfo, boolean, boolean)}, and
- * {@link #unbindInput()} are called with the same order as the original calls
- * in {@link com.android.server.inputmethod.InputMethodManagerService}.
+ * {@link #startInput(IInputMethod.StartInputParams)}, and {@link #unbindInput()} are called
+ * with the same order as the original calls in
+ * {@link com.android.server.inputmethod.InputMethodManagerService}.
* See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p>
*/
@Nullable
@@ -174,17 +171,9 @@
args.recycle();
return;
}
- case DO_INITIALIZE_INTERNAL: {
- SomeArgs args = (SomeArgs) msg.obj;
- try {
- inputMethod.initializeInternal((IBinder) args.arg1,
- (IInputMethodPrivilegedOperations) args.arg2, msg.arg1,
- (boolean) args.arg3, msg.arg2);
- } finally {
- args.recycle();
- }
+ case DO_INITIALIZE_INTERNAL:
+ inputMethod.initializeInternal((IInputMethod.InitParams) msg.obj);
return;
- }
case DO_SET_INPUT_CONTEXT: {
inputMethod.bindInput((InputBinding)msg.obj);
return;
@@ -194,21 +183,10 @@
return;
case DO_START_INPUT: {
final SomeArgs args = (SomeArgs) msg.obj;
- final IBinder startInputToken = (IBinder) args.arg1;
- final IRemoteInputConnection remoteIc = (IRemoteInputConnection) args.arg2;
- final EditorInfo info = (EditorInfo) args.arg3;
- final ImeOnBackInvokedDispatcher imeDispatcher =
- (ImeOnBackInvokedDispatcher) args.arg4;
- final CancellationGroup cancellationGroup = (CancellationGroup) args.arg5;
- final boolean restarting = args.argi1 == 1;
- @InputMethodNavButtonFlags
- final int navButtonFlags = args.argi2;
- final InputConnection ic = remoteIc != null
- ? new RemoteInputConnection(mTarget, remoteIc, cancellationGroup)
- : null;
- info.makeCompatible(mTargetSdkVersion);
- inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken,
- navButtonFlags, imeDispatcher);
+ final InputConnection inputConnection = (InputConnection) args.arg1;
+ final IInputMethod.StartInputParams params =
+ (IInputMethod.StartInputParams) args.arg2;
+ inputMethod.dispatchStartInput(inputConnection, params);
args.recycle();
return;
}
@@ -306,11 +284,8 @@
@BinderThread
@Override
- public void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
- int configChanges, boolean stylusHwSupported,
- @InputMethodNavButtonFlags int navButtonFlags) {
- mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_INITIALIZE_INTERNAL,
- configChanges, navButtonFlags, token, privOps, stylusHwSupported));
+ public void initializeInternal(@NonNull IInputMethod.InitParams params) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_INITIALIZE_INTERNAL, params));
}
@BinderThread
@@ -350,23 +325,19 @@
@BinderThread
@Override
- public void startInput(IBinder startInputToken, IRemoteInputConnection inputConnection,
- EditorInfo editorInfo, boolean restarting,
- @InputMethodNavButtonFlags int navButtonFlags,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ public void startInput(@NonNull IInputMethod.StartInputParams params) {
if (mCancellationGroup == null) {
Log.e(TAG, "startInput must be called after bindInput.");
mCancellationGroup = new CancellationGroup();
}
- final SomeArgs args = SomeArgs.obtain();
- args.arg1 = startInputToken;
- args.arg2 = inputConnection;
- args.arg3 = editorInfo;
- args.argi1 = restarting ? 1 : 0;
- args.argi2 = navButtonFlags;
- args.arg4 = imeDispatcher;
- args.arg5 = mCancellationGroup;
- mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_START_INPUT, args));
+
+ params.editorInfo.makeCompatible(mTargetSdkVersion);
+
+ final InputConnection ic = params.remoteInputConnection == null ? null
+ : new RemoteInputConnection(mTarget, params.remoteInputConnection,
+ mCancellationGroup);
+
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_START_INPUT, ic, params));
}
@BinderThread
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index e442e6d..c02f870 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -144,7 +144,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
import com.android.internal.inputmethod.IInputContentUriToken;
-import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.inputmethod.IInputMethod;
import com.android.internal.inputmethod.IRemoteInputConnection;
import com.android.internal.inputmethod.ImeTracing;
import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
@@ -698,23 +698,21 @@
*/
@MainThread
@Override
- public final void initializeInternal(@NonNull IBinder token,
- IInputMethodPrivilegedOperations privilegedOperations, int configChanges,
- boolean stylusHwSupported, @InputMethodNavButtonFlags int navButtonFlags) {
+ public final void initializeInternal(@NonNull IInputMethod.InitParams params) {
if (mDestroyed) {
Log.i(TAG, "The InputMethodService has already onDestroyed()."
+ "Ignore the initialization.");
return;
}
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal");
- mConfigTracker.onInitialize(configChanges);
- mPrivOps.set(privilegedOperations);
- InputMethodPrivilegedOperationsRegistry.put(token, mPrivOps);
- if (stylusHwSupported) {
+ mConfigTracker.onInitialize(params.configChanges);
+ mPrivOps.set(params.privilegedOperations);
+ InputMethodPrivilegedOperationsRegistry.put(params.token, mPrivOps);
+ if (params.stylusHandWritingSupported) {
mInkWindow = new InkWindow(mWindow.getContext());
}
- mNavigationBarController.onNavButtonFlagsChanged(navButtonFlags);
- attachToken(token);
+ mNavigationBarController.onNavButtonFlagsChanged(params.navigationBarFlags);
+ attachToken(params.token);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@@ -821,25 +819,23 @@
*/
@MainThread
@Override
- public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
- @NonNull EditorInfo editorInfo, boolean restarting,
- @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
- mPrivOps.reportStartInputAsync(startInputToken);
- mNavigationBarController.onNavButtonFlagsChanged(navButtonFlags);
- if (restarting) {
- restartInput(inputConnection, editorInfo);
+ public final void dispatchStartInput(@Nullable InputConnection inputConnection,
+ @NonNull IInputMethod.StartInputParams params) {
+ mPrivOps.reportStartInputAsync(params.startInputToken);
+ mNavigationBarController.onNavButtonFlagsChanged(params.navigationBarFlags);
+ if (params.restarting) {
+ restartInput(inputConnection, params.editorInfo);
} else {
- startInput(inputConnection, editorInfo);
+ startInput(inputConnection, params.editorInfo);
}
// Update the IME dispatcher last, so that the previously registered back callback
// (if any) can be unregistered using the old dispatcher if {@link #doFinishInput()}
// is called from {@link #startInput(InputConnection, EditorInfo)} or
// {@link #restartInput(InputConnection, EditorInfo)}.
- mImeDispatcher = imeDispatcher;
+ mImeDispatcher = params.imeDispatcher;
if (mWindow != null) {
mWindow.getOnBackInvokedDispatcher().setImeOnBackInvokedDispatcher(
- imeDispatcher);
+ params.imeDispatcher);
}
}
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index e02c45d..3a56662 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -58,6 +58,18 @@
},
{
"file_patterns": [
+ "Parcel\\.java",
+ "[^/]*Bundle[^/]*\\.java"
+ ],
+ "name": "FrameworksMockingCoreTests",
+ "options": [
+ { "include-filter": "android.os.BundleRecyclingTest"},
+ { "exclude-annotation": "androidx.test.filters.FlakyTest" },
+ { "exclude-annotation": "org.junit.Ignore" }
+ ]
+ },
+ {
+ "file_patterns": [
"BatteryUsageStats[^/]*\\.java",
"PowerComponents\\.java",
"[^/]*BatteryConsumer[^/]*\\.java"
diff --git a/core/java/android/service/appprediction/OWNERS b/core/java/android/service/appprediction/OWNERS
index fe012da..6efb975 100644
--- a/core/java/android/service/appprediction/OWNERS
+++ b/core/java/android/service/appprediction/OWNERS
@@ -1,2 +1,4 @@
adamcohen@google.com
+hyunyoungs@google.com
+pinyaoting@google.com
sunnygoyal@google.com
diff --git a/core/java/android/service/smartspace/OWNERS b/core/java/android/service/smartspace/OWNERS
index 19ef9d7..d3acd3d3 100644
--- a/core/java/android/service/smartspace/OWNERS
+++ b/core/java/android/service/smartspace/OWNERS
@@ -1,2 +1,4 @@
+hyunyoungs@google.com
+awickham@google.com
srazdan@google.com
-alexmang@google.com
\ No newline at end of file
+sunnygoyal@google.com
diff --git a/core/java/android/speech/AlternativeSpan.java b/core/java/android/speech/AlternativeSpan.java
index 9f6ad99..f21624d 100644
--- a/core/java/android/speech/AlternativeSpan.java
+++ b/core/java/android/speech/AlternativeSpan.java
@@ -38,14 +38,10 @@
* other ways manipulating the SpeechRecognizer results before powering dictation features.
*/
@DataClass(
- genBuilder = true,
- genConstructor = false,
genEqualsHashCode = true,
genParcelable = true,
genToString = true
)
-@DataClass.Suppress(
- {"Builder.setStartPosition", "Builder.setEndPosition", "Builder.addAlternative"})
public final class AlternativeSpan implements Parcelable {
/**
* The start position of the span of the originally recognized string.
@@ -64,17 +60,14 @@
/**
* All the alternatives for the [mStart, mEnd) span.
*
- * <p> Must not be empty. If the recognizer does not produce an alternative, this list will
- * contain a single empty string.
+ * <p> Must not be empty. The object will only be created
+ * if there are some alternatives for the given span.
*
* <p> The alternatives may be strings of different lengths than the span they can replace.
*/
@NonNull
@DataClass.PluralOf("alternative")
private final List<String> mAlternatives;
- private static List<String> defaultAlternatives() {
- return new ArrayList<>();
- }
private void onConstructed() {
Preconditions.checkArgumentNonnegative(mStartPosition,
@@ -100,8 +93,27 @@
//@formatter:off
+ /**
+ * Creates a new AlternativeSpan.
+ *
+ * @param startPosition
+ * The start position of the span of the originally recognized string.
+ *
+ * <p> Must be set to a non-negative value before building.
+ * @param endPosition
+ * The exclusive end position of the span of the originally recognized string.
+ *
+ * <p> Must be set to a value greater than the start of the span before building.
+ * @param alternatives
+ * All the alternatives for the [mStart, mEnd) span.
+ *
+ * <p> Must not be empty. The object will only be created
+ * if there are some alternatives for the given span.
+ *
+ * <p> The alternatives may be strings of different lengths than the span they can replace.
+ */
@DataClass.Generated.Member
- /* package-private */ AlternativeSpan(
+ public AlternativeSpan(
int startPosition,
int endPosition,
@NonNull List<String> alternatives) {
@@ -137,8 +149,8 @@
/**
* All the alternatives for the [mStart, mEnd) span.
*
- * <p> Must not be empty. If the recognizer does not produce an alternative, this list will
- * contain a single empty string.
+ * <p> Must not be empty. The object will only be created
+ * if there are some alternatives for the given span.
*
* <p> The alternatives may be strings of different lengths than the span they can replace.
*/
@@ -241,82 +253,11 @@
}
};
- /**
- * A builder for {@link AlternativeSpan}
- */
- @SuppressWarnings("WeakerAccess")
- @DataClass.Generated.Member
- public static final class Builder {
-
- private int mStartPosition;
- private int mEndPosition;
- private @NonNull List<String> mAlternatives;
-
- private long mBuilderFieldsSet = 0L;
-
- /**
- * Creates a new Builder.
- *
- * @param startPosition
- * The start position of the span of the originally recognized string.
- *
- * <p> Must be set to a non-negative value before building.
- * @param endPosition
- * The exclusive end position of the span of the originally recognized string.
- *
- * <p> Must be set to a value greater than the start of the span before building.
- */
- public Builder(
- int startPosition,
- int endPosition) {
- mStartPosition = startPosition;
- mEndPosition = endPosition;
- }
-
- /**
- * All the alternatives for the [mStart, mEnd) span.
- *
- * <p> Must not be empty. If the recognizer does not produce an alternative, this list will
- * contain a single empty string.
- *
- * <p> The alternatives may be strings of different lengths than the span they can replace.
- */
- @DataClass.Generated.Member
- public @NonNull Builder setAlternatives(@NonNull List<String> value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x4;
- mAlternatives = value;
- return this;
- }
-
- /** Builds the instance. This builder should not be touched after calling this! */
- public @NonNull AlternativeSpan build() {
- checkNotUsed();
- mBuilderFieldsSet |= 0x8; // Mark builder used
-
- if ((mBuilderFieldsSet & 0x4) == 0) {
- mAlternatives = defaultAlternatives();
- }
- AlternativeSpan o = new AlternativeSpan(
- mStartPosition,
- mEndPosition,
- mAlternatives);
- return o;
- }
-
- private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x8) != 0) {
- throw new IllegalStateException(
- "This Builder should not be reused. Use a new Builder instance instead");
- }
- }
- }
-
@DataClass.Generated(
- time = 1655225556488L,
+ time = 1656603431902L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/speech/AlternativeSpan.java",
- inputSignatures = "private final int mStartPosition\nprivate final int mEndPosition\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"alternative\") java.util.List<java.lang.String> mAlternatives\nprivate static java.util.List<java.lang.String> defaultAlternatives()\nprivate void onConstructed()\nclass AlternativeSpan extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genEqualsHashCode=true, genParcelable=true, genToString=true)")
+ inputSignatures = "private final int mStartPosition\nprivate final int mEndPosition\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"alternative\") java.util.List<java.lang.String> mAlternatives\nprivate void onConstructed()\nclass AlternativeSpan extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genParcelable=true, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/speech/AlternativeSpans.java b/core/java/android/speech/AlternativeSpans.java
index 3393790..0de8c3e 100644
--- a/core/java/android/speech/AlternativeSpans.java
+++ b/core/java/android/speech/AlternativeSpans.java
@@ -32,21 +32,15 @@
* specific span (substring) of the originally recognized string.
*/
@DataClass(
- genBuilder = true,
- genConstructor = false,
genEqualsHashCode = true,
genParcelable = true,
genToString = true
)
-@DataClass.Suppress({"Builder.addSpan"})
public final class AlternativeSpans implements Parcelable {
/** List of {@link AlternativeSpan} for a specific speech recognition result. */
@NonNull
@DataClass.PluralOf("span")
private final List<AlternativeSpan> mSpans;
- private static List<AlternativeSpan> defaultSpans() {
- return new ArrayList<>();
- }
@@ -63,8 +57,14 @@
//@formatter:off
+ /**
+ * Creates a new AlternativeSpans.
+ *
+ * @param spans
+ * List of {@link AlternativeSpan} for a specific speech recognition result.
+ */
@DataClass.Generated.Member
- /* package-private */ AlternativeSpans(
+ public AlternativeSpans(
@NonNull List<AlternativeSpan> spans) {
this.mSpans = spans;
com.android.internal.util.AnnotationValidations.validate(
@@ -163,57 +163,11 @@
}
};
- /**
- * A builder for {@link AlternativeSpans}
- */
- @SuppressWarnings("WeakerAccess")
- @DataClass.Generated.Member
- public static final class Builder {
-
- private @NonNull List<AlternativeSpan> mSpans;
-
- private long mBuilderFieldsSet = 0L;
-
- public Builder() {
- }
-
- /**
- * List of {@link AlternativeSpan} for a specific speech recognition result.
- */
- @DataClass.Generated.Member
- public @NonNull Builder setSpans(@NonNull List<AlternativeSpan> value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x1;
- mSpans = value;
- return this;
- }
-
- /** Builds the instance. This builder should not be touched after calling this! */
- public @NonNull AlternativeSpans build() {
- checkNotUsed();
- mBuilderFieldsSet |= 0x2; // Mark builder used
-
- if ((mBuilderFieldsSet & 0x1) == 0) {
- mSpans = defaultSpans();
- }
- AlternativeSpans o = new AlternativeSpans(
- mSpans);
- return o;
- }
-
- private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x2) != 0) {
- throw new IllegalStateException(
- "This Builder should not be reused. Use a new Builder instance instead");
- }
- }
- }
-
@DataClass.Generated(
- time = 1655151024975L,
+ time = 1656603476918L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/speech/AlternativeSpans.java",
- inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"span\") java.util.List<android.speech.AlternativeSpan> mSpans\nprivate static java.util.List<android.speech.AlternativeSpan> defaultSpans()\nclass AlternativeSpans extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genEqualsHashCode=true, genParcelable=true, genToString=true)")
+ inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"span\") java.util.List<android.speech.AlternativeSpan> mSpans\nclass AlternativeSpans extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genParcelable=true, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/text/style/AbsoluteSizeSpan.java b/core/java/android/text/style/AbsoluteSizeSpan.java
index 3b4eea7..6d4f05a 100644
--- a/core/java/android/text/style/AbsoluteSizeSpan.java
+++ b/core/java/android/text/style/AbsoluteSizeSpan.java
@@ -130,4 +130,10 @@
ds.setTextSize(mSize);
}
}
+
+
+ @Override
+ public String toString() {
+ return "AbsoluteSizeSpan{size=" + getSize() + ", isDip=" + getDip() + '}';
+ }
}
diff --git a/core/java/android/text/style/AlignmentSpan.java b/core/java/android/text/style/AlignmentSpan.java
index 18c3e16..31db78a 100644
--- a/core/java/android/text/style/AlignmentSpan.java
+++ b/core/java/android/text/style/AlignmentSpan.java
@@ -101,5 +101,10 @@
public Layout.Alignment getAlignment() {
return mAlignment;
}
+
+ @Override
+ public String toString() {
+ return "AlignmentSpan.Standard{alignment=" + getAlignment() + '}';
+ }
}
}
diff --git a/core/java/android/text/style/BackgroundColorSpan.java b/core/java/android/text/style/BackgroundColorSpan.java
index 44e35615..13647d9 100644
--- a/core/java/android/text/style/BackgroundColorSpan.java
+++ b/core/java/android/text/style/BackgroundColorSpan.java
@@ -100,4 +100,9 @@
public void updateDrawState(@NonNull TextPaint textPaint) {
textPaint.bgColor = mColor;
}
+
+ @Override
+ public String toString() {
+ return "BackgroundColorSpan{color=#" + String.format("%08X", getBackgroundColor()) + '}';
+ }
}
diff --git a/core/java/android/text/style/BulletSpan.java b/core/java/android/text/style/BulletSpan.java
index ad61788..b3e7bda 100644
--- a/core/java/android/text/style/BulletSpan.java
+++ b/core/java/android/text/style/BulletSpan.java
@@ -235,4 +235,13 @@
paint.setStyle(style);
}
}
+
+ @Override
+ public String toString() {
+ return "BulletSpan{"
+ + "gapWidth=" + getGapWidth()
+ + ", bulletRadius=" + getBulletRadius()
+ + ", color=" + String.format("%08X", getColor())
+ + '}';
+ }
}
diff --git a/core/java/android/text/style/ClickableSpan.java b/core/java/android/text/style/ClickableSpan.java
index 60aed2a..238da55 100644
--- a/core/java/android/text/style/ClickableSpan.java
+++ b/core/java/android/text/style/ClickableSpan.java
@@ -64,4 +64,9 @@
public int getId() {
return mId;
}
+
+ @Override
+ public String toString() {
+ return "ClickableSpan{}";
+ }
}
diff --git a/core/java/android/text/style/DrawableMarginSpan.java b/core/java/android/text/style/DrawableMarginSpan.java
index cd199b3..1f3446e 100644
--- a/core/java/android/text/style/DrawableMarginSpan.java
+++ b/core/java/android/text/style/DrawableMarginSpan.java
@@ -112,4 +112,25 @@
}
}
}
+
+ @Override
+ public String toString() {
+ return "DrawableMarginSpan{drawable=" + mDrawable + ", padding=" + mPad + '}';
+ }
+
+ /**
+ * Returns the drawable used.
+ * @return a drawable
+ */
+ @NonNull public Drawable getDrawable() {
+ return mDrawable;
+ }
+
+ /**
+ * Returns a distance between the drawable and text in pixel.
+ * @return a distance pixel from the text
+ */
+ @Px public int getPadding() {
+ return mPad;
+ }
}
diff --git a/core/java/android/text/style/DynamicDrawableSpan.java b/core/java/android/text/style/DynamicDrawableSpan.java
index d6d99f8..38897ed 100644
--- a/core/java/android/text/style/DynamicDrawableSpan.java
+++ b/core/java/android/text/style/DynamicDrawableSpan.java
@@ -189,5 +189,13 @@
return d;
}
+
+ @Override
+ public String toString() {
+ return "DynamicDrawableSpan{"
+ + "verticalAlignment=" + getVerticalAlignment()
+ + ", drawable=" + getDrawable()
+ + '}';
+ }
}
diff --git a/core/java/android/text/style/ForegroundColorSpan.java b/core/java/android/text/style/ForegroundColorSpan.java
index f7706745..5c97426 100644
--- a/core/java/android/text/style/ForegroundColorSpan.java
+++ b/core/java/android/text/style/ForegroundColorSpan.java
@@ -101,4 +101,9 @@
public void updateDrawState(@NonNull TextPaint textPaint) {
textPaint.setColor(mColor);
}
+
+ @Override
+ public String toString() {
+ return "ForegroundColorSpan{color=#" + String.format("%08X", getForegroundColor()) + '}';
+ }
}
diff --git a/core/java/android/text/style/IconMarginSpan.java b/core/java/android/text/style/IconMarginSpan.java
index ad78bd5..a6c5139 100644
--- a/core/java/android/text/style/IconMarginSpan.java
+++ b/core/java/android/text/style/IconMarginSpan.java
@@ -110,4 +110,24 @@
}
}
+ @Override
+ public String toString() {
+ return "IconMarginSpan{bitmap=" + getBitmap() + ", padding=" + getPadding() + '}';
+ }
+
+ /**
+ * Returns the bitmap to be used at the beginning of the text
+ * @return a bitmap
+ */
+ @NonNull public Bitmap getBitmap() {
+ return mBitmap;
+ }
+
+ /**
+ * Returns the padding width between the bitmap and the text.
+ * @return a padding width in pixels
+ */
+ @Px public int getPadding() {
+ return mPad;
+ }
}
diff --git a/core/java/android/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java
index ec55aeb..fc85590 100644
--- a/core/java/android/text/style/ImageSpan.java
+++ b/core/java/android/text/style/ImageSpan.java
@@ -266,4 +266,13 @@
public String getSource() {
return mSource;
}
+
+ @Override
+ public String toString() {
+ return "ImageSpan{"
+ + "drawable=" + getDrawable()
+ + ", source='" + getSource() + '\''
+ + ", verticalAlignment=" + getVerticalAlignment()
+ + '}';
+ }
}
diff --git a/core/java/android/text/style/LocaleSpan.java b/core/java/android/text/style/LocaleSpan.java
index a3a4bf8..489ceea 100644
--- a/core/java/android/text/style/LocaleSpan.java
+++ b/core/java/android/text/style/LocaleSpan.java
@@ -126,4 +126,9 @@
private static void apply(@NonNull Paint paint, @NonNull LocaleList locales) {
paint.setTextLocales(locales);
}
+
+ @Override
+ public String toString() {
+ return "LocaleSpan{locales=" + getLocales() + '}';
+ }
}
diff --git a/core/java/android/text/style/MaskFilterSpan.java b/core/java/android/text/style/MaskFilterSpan.java
index d76ef94..587d1b4 100644
--- a/core/java/android/text/style/MaskFilterSpan.java
+++ b/core/java/android/text/style/MaskFilterSpan.java
@@ -56,4 +56,9 @@
public void updateDrawState(TextPaint ds) {
ds.setMaskFilter(mFilter);
}
+
+ @Override
+ public String toString() {
+ return "MaskFilterSpan{filter=" + getMaskFilter() + '}';
+ }
}
diff --git a/core/java/android/text/style/QuoteSpan.java b/core/java/android/text/style/QuoteSpan.java
index a1c12c2..393ede6 100644
--- a/core/java/android/text/style/QuoteSpan.java
+++ b/core/java/android/text/style/QuoteSpan.java
@@ -203,4 +203,13 @@
p.setStyle(style);
p.setColor(color);
}
+
+ @Override
+ public String toString() {
+ return "QuoteSpan{"
+ + "color=" + String.format("#%08X", getColor())
+ + ", stripeWidth=" + getStripeWidth()
+ + ", gapWidth=" + getGapWidth()
+ + '}';
+ }
}
diff --git a/core/java/android/text/style/RelativeSizeSpan.java b/core/java/android/text/style/RelativeSizeSpan.java
index 3094f27..5c91b20 100644
--- a/core/java/android/text/style/RelativeSizeSpan.java
+++ b/core/java/android/text/style/RelativeSizeSpan.java
@@ -97,4 +97,9 @@
public void updateMeasureState(@NonNull TextPaint ds) {
ds.setTextSize(ds.getTextSize() * mProportion);
}
+
+ @Override
+ public String toString() {
+ return "RelativeSizeSpan{proportion=" + getSizeChange() + '}';
+ }
}
diff --git a/core/java/android/text/style/ScaleXSpan.java b/core/java/android/text/style/ScaleXSpan.java
index 6ef4cec..d022b07 100644
--- a/core/java/android/text/style/ScaleXSpan.java
+++ b/core/java/android/text/style/ScaleXSpan.java
@@ -102,4 +102,9 @@
public void updateMeasureState(TextPaint ds) {
ds.setTextScaleX(ds.getTextScaleX() * mProportion);
}
+
+ @Override
+ public String toString() {
+ return "ScaleXSpan{scaleX=" + getScaleX() + '}';
+ }
}
diff --git a/core/java/android/text/style/StrikethroughSpan.java b/core/java/android/text/style/StrikethroughSpan.java
index a630505..65ee347 100644
--- a/core/java/android/text/style/StrikethroughSpan.java
+++ b/core/java/android/text/style/StrikethroughSpan.java
@@ -77,4 +77,9 @@
public void updateDrawState(@NonNull TextPaint ds) {
ds.setStrikeThruText(true);
}
+
+ @Override
+ public String toString() {
+ return "StrikethroughSpan{}";
+ }
}
diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java
index 176a068..378682b 100644
--- a/core/java/android/text/style/StyleSpan.java
+++ b/core/java/android/text/style/StyleSpan.java
@@ -185,4 +185,12 @@
paint.setTypeface(tf);
}
+
+ @Override
+ public String toString() {
+ return "StyleSpan{"
+ + "style=" + getStyle()
+ + ", fontWeightAdjustment=" + getFontWeightAdjustment()
+ + '}';
+ }
}
diff --git a/core/java/android/text/style/SubscriptSpan.java b/core/java/android/text/style/SubscriptSpan.java
index 3d15aad..729a9ad 100644
--- a/core/java/android/text/style/SubscriptSpan.java
+++ b/core/java/android/text/style/SubscriptSpan.java
@@ -86,4 +86,9 @@
public void updateMeasureState(@NonNull TextPaint textPaint) {
textPaint.baselineShift -= (int) (textPaint.ascent() / 2);
}
+
+ @Override
+ public String toString() {
+ return "SubscriptSpan{}";
+ }
}
diff --git a/core/java/android/text/style/SuperscriptSpan.java b/core/java/android/text/style/SuperscriptSpan.java
index 3dc9d3f..5610223 100644
--- a/core/java/android/text/style/SuperscriptSpan.java
+++ b/core/java/android/text/style/SuperscriptSpan.java
@@ -83,4 +83,9 @@
public void updateMeasureState(@NonNull TextPaint textPaint) {
textPaint.baselineShift += (int) (textPaint.ascent() / 2);
}
+
+ @Override
+ public String toString() {
+ return "SuperscriptSpan{}";
+ }
}
diff --git a/core/java/android/text/style/TabStopSpan.java b/core/java/android/text/style/TabStopSpan.java
index 2cceb2c..8128475 100644
--- a/core/java/android/text/style/TabStopSpan.java
+++ b/core/java/android/text/style/TabStopSpan.java
@@ -65,5 +65,10 @@
public int getTabStop() {
return mTabOffset;
}
+
+ @Override
+ public String toString() {
+ return "TabStopSpan.Standard{tabOffset=" + getTabStop() + '}';
+ }
}
}
diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java
index adb379a..85b7ae9 100644
--- a/core/java/android/text/style/TextAppearanceSpan.java
+++ b/core/java/android/text/style/TextAppearanceSpan.java
@@ -442,6 +442,14 @@
return mElegantTextHeight;
}
+ /**
+ * Returns the value of letter spacing to be added in em unit.
+ * @return a letter spacing amount
+ */
+ public float getLetterSpacing() {
+ return mLetterSpacing;
+ }
+
@Override
public void updateDrawState(TextPaint ds) {
updateMeasureState(ds);
@@ -534,4 +542,26 @@
ds.setFontVariationSettings(mFontVariationSettings);
}
}
+
+ @Override
+ public String toString() {
+ return "TextAppearanceSpan{"
+ + "familyName='" + getFamily() + '\''
+ + ", style=" + getTextStyle()
+ + ", textSize=" + getTextSize()
+ + ", textColor=" + getTextColor()
+ + ", textColorLink=" + getLinkTextColor()
+ + ", typeface=" + getTypeface()
+ + ", textFontWeight=" + getTextFontWeight()
+ + ", textLocales=" + getTextLocales()
+ + ", shadowRadius=" + getShadowRadius()
+ + ", shadowDx=" + getShadowDx()
+ + ", shadowDy=" + getShadowDy()
+ + ", shadowColor=" + String.format("#%08X", getShadowColor())
+ + ", elegantTextHeight=" + isElegantTextHeight()
+ + ", letterSpacing=" + getLetterSpacing()
+ + ", fontFeatureSettings='" + getFontFeatureSettings() + '\''
+ + ", fontVariationSettings='" + getFontVariationSettings() + '\''
+ + '}';
+ }
}
diff --git a/core/java/android/text/style/TypefaceSpan.java b/core/java/android/text/style/TypefaceSpan.java
index 908de29..bdfc772 100644
--- a/core/java/android/text/style/TypefaceSpan.java
+++ b/core/java/android/text/style/TypefaceSpan.java
@@ -180,4 +180,12 @@
}
paint.setTypeface(styledTypeface);
}
+
+ @Override
+ public String toString() {
+ return "TypefaceSpan{"
+ + "family='" + getFamily() + '\''
+ + ", typeface=" + getTypeface()
+ + '}';
+ }
}
diff --git a/core/java/android/text/style/URLSpan.java b/core/java/android/text/style/URLSpan.java
index eab1ef4..e811390 100644
--- a/core/java/android/text/style/URLSpan.java
+++ b/core/java/android/text/style/URLSpan.java
@@ -109,4 +109,9 @@
Log.w("URLSpan", "Actvity was not found for intent, " + intent.toString());
}
}
+
+ @Override
+ public String toString() {
+ return "URLSpan{" + "URL='" + getURL() + '\'' + '}';
+ }
}
diff --git a/core/java/android/text/style/UnderlineSpan.java b/core/java/android/text/style/UnderlineSpan.java
index 800838e..075e70b 100644
--- a/core/java/android/text/style/UnderlineSpan.java
+++ b/core/java/android/text/style/UnderlineSpan.java
@@ -77,4 +77,9 @@
public void updateDrawState(@NonNull TextPaint ds) {
ds.setUnderlineText(true);
}
+
+ @Override
+ public String toString() {
+ return "UnderlineSpan{}";
+ }
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 1a37d11..ff1817a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -2351,47 +2351,6 @@
}
/**
- * @hide
- */
- @UnsupportedAppUsage
- public static void setDisplayProjection(IBinder displayToken,
- int orientation, Rect layerStackRect, Rect displayRect) {
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setDisplayProjection(displayToken, orientation,
- layerStackRect, displayRect);
- }
- }
-
- /**
- * @hide
- */
- @UnsupportedAppUsage
- public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setDisplayLayerStack(displayToken, layerStack);
- }
- }
-
- /**
- * @hide
- */
- @UnsupportedAppUsage
- public static void setDisplaySurface(IBinder displayToken, Surface surface) {
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setDisplaySurface(displayToken, surface);
- }
- }
-
- /**
- * @hide
- */
- public static void setDisplaySize(IBinder displayToken, int width, int height) {
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setDisplaySize(displayToken, width, height);
- }
- }
-
- /**
* Overrides HDR modes for a display device.
*
* If the caller does not have ACCESS_SURFACE_FLINGER permission, this will throw a Security
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 657c0b7..8907420 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -604,6 +604,8 @@
*/
private boolean mCheckIfCanDraw = false;
+ private boolean mDrewOnceForSync = false;
+
int mSyncSeqId = 0;
int mLastSyncSeqId = 0;
@@ -2820,10 +2822,6 @@
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
- if (mApplyInsetsRequested) {
- dispatchApplyInsets(host);
- }
-
if (mFirst) {
// make sure touch mode code executes by setting cached value
// to opposite of the added touch mode.
@@ -2887,6 +2885,18 @@
}
}
+ if (mApplyInsetsRequested) {
+ dispatchApplyInsets(host);
+ if (mLayoutRequested) {
+ // Short-circuit catching a new layout request here, so
+ // we don't need to go through two layout passes when things
+ // change due to fitting system windows, which can happen a lot.
+ windowSizeMayChange |= measureHierarchy(host, lp,
+ mView.getContext().getResources(),
+ desiredWindowWidth, desiredWindowHeight);
+ }
+ }
+
if (layoutRequested) {
// Clear this now, so that if anything requests a layout in the
// rest of this function we will catch it and re-run a full
@@ -2995,6 +3005,9 @@
reportNextDraw();
mSyncBuffer = true;
isSyncRequest = true;
+ if (!cancelDraw) {
+ mDrewOnceForSync = false;
+ }
}
final boolean surfaceControlChanged =
@@ -3516,9 +3529,11 @@
mCheckIfCanDraw = isSyncRequest || cancelDraw;
- boolean cancelAndRedraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || cancelDraw;
+ boolean cancelAndRedraw =
+ mAttachInfo.mTreeObserver.dispatchOnPreDraw() || (cancelDraw && mDrewOnceForSync);
if (!cancelAndRedraw) {
createSyncIfNeeded();
+ mDrewOnceForSync = true;
}
if (!isViewVisible) {
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 418132a..4fc9a07 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -63,9 +63,6 @@
void unregisterUiTestAutomationService(IAccessibilityServiceClient client);
- void temporaryEnableAccessibilityStateUntilKeyguardRemoved(in ComponentName service,
- boolean touchExplorationEnabled);
-
// Used by UiAutomation
IBinder getWindowToken(int windowId, int userId);
diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java
index ae145de..313dc43 100644
--- a/core/java/android/view/autofill/AutofillId.java
+++ b/core/java/android/view/autofill/AutofillId.java
@@ -74,6 +74,10 @@
/** @hide */
@NonNull
+ public static final AutofillId NO_AUTOFILL_ID = new AutofillId(0);
+
+ /** @hide */
+ @NonNull
@TestApi
public static AutofillId withoutSession(@NonNull AutofillId id) {
final int flags = id.mFlags & ~FLAG_HAS_SESSION;
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 6b7a6e9..9492375 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -1016,6 +1016,7 @@
newEditorInfo.mInitialSurroundingText = mInitialSurroundingText;
newEditorInfo.hintLocales = hintLocales;
newEditorInfo.contentMimeTypes = ArrayUtils.cloneOrNull(contentMimeTypes);
+ newEditorInfo.targetInputMethodUser = targetInputMethodUser;
return newEditorInfo;
}
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
new file mode 100644
index 0000000..4b472ed
--- /dev/null
+++ b/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2022 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.view.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.window.ImeOnBackInvokedDispatcher;
+
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+import com.android.internal.view.IInputMethodManager;
+
+import java.util.List;
+
+/**
+ * A wrapper class to invoke IPCs defined in {@link IInputMethodManager}.
+ */
+final class IInputMethodManagerInvoker {
+ @NonNull
+ private final IInputMethodManager mTarget;
+
+ private IInputMethodManagerInvoker(@NonNull IInputMethodManager target) {
+ mTarget = target;
+ }
+
+ @AnyThread
+ @NonNull
+ static IInputMethodManagerInvoker create(@NonNull IInputMethodManager imm) {
+ return new IInputMethodManagerInvoker(imm);
+ }
+
+ @AnyThread
+ void addClient(IInputMethodClient client, IRemoteInputConnection fallbackInputConnection,
+ int untrustedDisplayId) {
+ try {
+ mTarget.addClient(client, fallbackInputConnection, untrustedDisplayId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ List<InputMethodInfo> getInputMethodList(@UserIdInt int userId) {
+ try {
+ return mTarget.getInputMethodList(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ List<InputMethodInfo> getAwareLockedInputMethodList(@UserIdInt int userId,
+ @DirectBootAwareness int directBootAwareness) {
+ try {
+ return mTarget.getAwareLockedInputMethodList(userId, directBootAwareness);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId) {
+ try {
+ return mTarget.getEnabledInputMethodList(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
+ boolean allowsImplicitlySelectedSubtypes) {
+ try {
+ return mTarget.getEnabledInputMethodSubtypeList(imiId,
+ allowsImplicitlySelectedSubtypes);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ InputMethodSubtype getLastInputMethodSubtype() {
+ try {
+ return mTarget.getLastInputMethodSubtype();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ boolean showSoftInput(IInputMethodClient client, IBinder windowToken,
+ int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ try {
+ return mTarget.showSoftInput(client, windowToken, flags, resultReceiver, reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
+ int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+ try {
+ return mTarget.hideSoftInput(client, windowToken, flags, resultReceiver, reason);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ InputBindResult startInputOrWindowGainedFocus(@StartInputReason int startInputReason,
+ IInputMethodClient client, IBinder windowToken,
+ @StartInputFlags int startInputFlags,
+ @android.view.WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+ int windowFlags, EditorInfo editorInfo, IRemoteInputConnection remoteInputConnection,
+ IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+ int unverifiedTargetSdkVersion, ImeOnBackInvokedDispatcher imeDispatcher) {
+ try {
+ return mTarget.startInputOrWindowGainedFocus(startInputReason, client, windowToken,
+ startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection,
+ remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, imeDispatcher);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ void showInputMethodPickerFromClient(IInputMethodClient client, int auxiliarySubtypeMode) {
+ try {
+ mTarget.showInputMethodPickerFromClient(client, auxiliarySubtypeMode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ void showInputMethodPickerFromSystem(IInputMethodClient client, int auxiliarySubtypeMode,
+ int displayId) {
+ try {
+ mTarget.showInputMethodPickerFromSystem(client, auxiliarySubtypeMode, displayId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ void showInputMethodAndSubtypeEnablerFromClient(IInputMethodClient client, String imeId) {
+ try {
+ mTarget.showInputMethodAndSubtypeEnablerFromClient(client, imeId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ boolean isInputMethodPickerShownForTest() {
+ try {
+ return mTarget.isInputMethodPickerShownForTest();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ InputMethodSubtype getCurrentInputMethodSubtype() {
+ try {
+ return mTarget.getCurrentInputMethodSubtype();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ void setAdditionalInputMethodSubtypes(String imeId, InputMethodSubtype[] subtypes) {
+ try {
+ mTarget.setAdditionalInputMethodSubtypes(imeId, subtypes);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ int getInputMethodWindowVisibleHeight(IInputMethodClient client) {
+ try {
+ return mTarget.getInputMethodWindowVisibleHeight(client);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ void reportVirtualDisplayGeometryAsync(IInputMethodClient client, int childDisplayId,
+ float[] matrixValues) {
+ try {
+ mTarget.reportVirtualDisplayGeometryAsync(client, childDisplayId, matrixValues);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
+ try {
+ mTarget.reportPerceptibleAsync(windowToken, perceptible);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ void removeImeSurfaceFromWindowAsync(IBinder windowToken) {
+ try {
+ mTarget.removeImeSurfaceFromWindowAsync(windowToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ void startStylusHandwriting(IInputMethodClient client) {
+ try {
+ mTarget.startStylusHandwriting(client);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @AnyThread
+ boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
+ try {
+ return mTarget.isStylusHandwritingAvailableAsUser(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java
index 54ff11c..95add29 100644
--- a/core/java/android/view/inputmethod/InputMethod.java
+++ b/core/java/android/view/inputmethod/InputMethod.java
@@ -29,10 +29,9 @@
import android.view.InputChannel;
import android.view.MotionEvent;
import android.view.View;
-import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback;
-import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.inputmethod.IInputMethod;
import com.android.internal.inputmethod.InlineSuggestionsRequestInfo;
import com.android.internal.inputmethod.InputMethodNavButtonFlags;
@@ -100,21 +99,12 @@
* unique token for the session it has with the system service as well as
* IPC endpoint to do some other privileged operations.
*
- * @param token special token for the system to identify
- * {@link InputMethodService}
- * @param privilegedOperations IPC endpoint to do some privileged
- * operations that are allowed only to the
- * current IME.
- * @param configChanges {@link InputMethodInfo#getConfigChanges()} declared by IME.
- * @param stylusHwSupported {@link InputMethodInfo#supportsStylusHandwriting()} declared by IME.
- * @param navButtonFlags The initial state of {@link InputMethodNavButtonFlags}.
+ * @param params Contains parameters to initialize the {@link InputMethodService}.
* @hide
*/
@MainThread
- default void initializeInternal(IBinder token,
- IInputMethodPrivilegedOperations privilegedOperations, int configChanges,
- boolean stylusHwSupported, @InputMethodNavButtonFlags int navButtonFlags) {
- attachToken(token);
+ default void initializeInternal(@NonNull IInputMethod.InitParams params) {
+ attachToken(params.token);
}
/**
@@ -210,47 +200,30 @@
public void restartInput(InputConnection inputConnection, EditorInfo editorInfo);
/**
- * This method is called when {@code {@link #startInput(InputConnection, EditorInfo)} or
- * {@code {@link #restartInput(InputConnection, EditorInfo)} needs to be dispatched.
+ * This method is called when {@link #startInput(InputConnection, EditorInfo)} or
+ * {@link #restartInput(InputConnection, EditorInfo)} needs to be dispatched.
*
- * <p>Note: This method is hidden because the {@code startInputToken} that this method is
- * dealing with is one of internal details, which should not be exposed to the IME developers.
- * If you override this method, you are responsible for not breaking existing IMEs that expect
+ * <p>Note: This method is hidden because {@link IInputMethod.StartInputParams} is an internal
+ * details, which should not be exposed to the IME developers. If you override this method, you
+ * are responsible for not breaking existing IMEs that expect
* {@link #startInput(InputConnection, EditorInfo)} to be still called back.</p>
*
* @param inputConnection optional specific input connection for communicating with the text
* box; if {@code null}, you should use the generic bound input
* connection
- * @param editorInfo information about the text box (typically, an EditText) that requests input
- * @param restarting {@code false} if this corresponds to
- * {@link #startInput(InputConnection, EditorInfo)}. Otherwise this
- * corresponds to {@link #restartInput(InputConnection, EditorInfo)}.
- * @param startInputToken a token that identifies a logical session that starts with this method
- * call. Some internal IPCs such as {@link
- * InputMethodManager#setImeWindowStatus(IBinder, IBinder, int, int)}
- * require this token to work, and you have to keep the token alive until
- * the next {@link #startInput(InputConnection, EditorInfo, IBinder)} as
- * long as your implementation of {@link InputMethod} relies on such
- * IPCs
- * @param navButtonFlags {@link InputMethodNavButtonFlags} in the initial state of this session.
- * @param imeDispatcher The {@link ImeOnBackInvokedDispatcher} to be set on the
- * IME's {@link android.window.WindowOnBackInvokedDispatcher}, so that IME
- * {@link android.window.OnBackInvokedCallback}s can be forwarded to
- * the client requesting to start input.
+ * @param params Raw object of {@link IInputMethod.StartInputParams}.
* @see #startInput(InputConnection, EditorInfo)
* @see #restartInput(InputConnection, EditorInfo)
* @see EditorInfo
* @hide
*/
@MainThread
- default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
- @NonNull EditorInfo editorInfo, boolean restarting,
- @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags,
- @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
- if (restarting) {
- restartInput(inputConnection, editorInfo);
+ default void dispatchStartInput(@Nullable InputConnection inputConnection,
+ @NonNull IInputMethod.StartInputParams params) {
+ if (params.restarting) {
+ restartInput(inputConnection, params.editorInfo);
} else {
- startInput(inputConnection, editorInfo);
+ startInput(inputConnection, params.editorInfo);
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 587fcef..59d3314 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -65,7 +65,6 @@
import android.os.Looper;
import android.os.Message;
import android.os.Process;
-import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
@@ -90,6 +89,7 @@
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.window.ImeOnBackInvokedDispatcher;
import android.window.WindowOnBackInvokedDispatcher;
@@ -394,10 +394,17 @@
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // This is a bug id.
+ /**
+ * @deprecated Use {@link #mServiceInvoker} instead.
+ */
+ @Deprecated
@UnsupportedAppUsage
final IInputMethodManager mService;
final Looper mMainLooper;
+ @NonNull
+ private final IInputMethodManagerInvoker mServiceInvoker;
+
// For scheduling work on the main thread. This also serves as our
// global lock.
// Remark on @UnsupportedAppUsage: there were context leaks on old versions
@@ -490,29 +497,26 @@
@GuardedBy("mH")
private Matrix mVirtualDisplayToScreenMatrix = null;
- /**
- * As reported by {@link InputBindResult}. This value is determined by
- * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecking}.
- */
- @GuardedBy("mH")
- private boolean mIsInputMethodSuppressingSpellChecker = false;
-
// -----------------------------------------------------------
/**
* Sequence number of this binding, as returned by the server.
*/
int mBindSequence = -1;
+
/**
* ID of the method we are bound to.
+ *
+ * @deprecated New code should use {@code mCurBindState.mImeId}.
*/
+ @Deprecated
@UnsupportedAppUsage
String mCurId;
/**
* Kept for {@link UnsupportedAppUsage}. Not officially maintained.
*
- * @deprecated New code should use {@link #mCurrentInputMethodSession}.
+ * @deprecated New code should use {@code mCurBindState.mImeSession}.
*/
@Deprecated
@GuardedBy("mH")
@@ -521,11 +525,12 @@
IInputMethodSession mCurMethod;
/**
- * Encapsulates IPCs to the currently connected InputMethodService.
+ * Encapsulates per-binding state from {@link InputBindResult}.
*/
- @Nullable
@GuardedBy("mH")
- private IInputMethodSessionInvoker mCurrentInputMethodSession = null;
+ @Nullable
+ private BindState mCurBindState;
+
/**
* Encapsulates IPCs to the currently connected AccessibilityServices.
*/
@@ -638,11 +643,7 @@
* @hide
*/
public void reportPerceptible(IBinder windowToken, boolean perceptible) {
- try {
- mService.reportPerceptibleAsync(windowToken, perceptible);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ mServiceInvoker.reportPerceptibleAsync(windowToken, perceptible);
}
private final class DelegateImpl implements
@@ -692,8 +693,8 @@
public void finishInputAndReportToIme() {
synchronized (mH) {
finishInputLocked();
- if (mCurrentInputMethodSession != null) {
- mCurrentInputMethodSession.finishInput();
+ if (isImeSessionAvailableLocked()) {
+ mCurBindState.mImeSession.finishInput();
}
forAccessibilitySessionsLocked(
IAccessibilityInputMethodSessionInvoker::finishInput);
@@ -739,30 +740,26 @@
synchronized (mH) {
// For some reason we didn't do a startInput + windowFocusGain, so
// we'll just do a window focus gain and call it a day.
- try {
- View servedView = controller.getServedView();
- boolean nextFocusHasConnection = servedView != null && servedView == focusedView
- && hasActiveConnection(focusedView);
- if (DEBUG) {
- Log.v(TAG, "Reporting focus gain, without startInput"
- + ", nextFocusIsServedView=" + nextFocusHasConnection);
- }
-
- final int startInputReason = nextFocusHasConnection
- ? WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION
- : WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
- // ignore the result
- mService.startInputOrWindowGainedFocus(
- startInputReason, mClient,
- focusedView.getWindowToken(), startInputFlags, softInputMode,
- windowFlags,
- null,
- null, null,
- mCurRootView.mContext.getApplicationInfo().targetSdkVersion,
- mImeDispatcher);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ View servedView = controller.getServedView();
+ boolean nextFocusHasConnection = servedView != null && servedView == focusedView
+ && hasActiveConnection(focusedView);
+ if (DEBUG) {
+ Log.v(TAG, "Reporting focus gain, without startInput"
+ + ", nextFocusIsServedView=" + nextFocusHasConnection);
}
+
+ final int startInputReason = nextFocusHasConnection
+ ? WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION
+ : WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION;
+ // ignore the result
+ mServiceInvoker.startInputOrWindowGainedFocus(
+ startInputReason, mClient,
+ focusedView.getWindowToken(), startInputFlags, softInputMode,
+ windowFlags,
+ null,
+ null, null,
+ mCurRootView.mContext.getApplicationInfo().targetSdkVersion,
+ mImeDispatcher);
}
}
@@ -822,8 +819,7 @@
@Override
public boolean hasActiveConnection(View view) {
synchronized (mH) {
- if (!hasServedByInputMethodLocked(view)
- || mCurrentInputMethodSession == null) {
+ if (!hasServedByInputMethodLocked(view) || !isImeSessionAvailableLocked()) {
return false;
}
@@ -931,13 +927,10 @@
setInputChannelLocked(res.channel);
mCurMethod = res.method; // for @UnsupportedAppUsage
- mCurrentInputMethodSession =
- IInputMethodSessionInvoker.createOrNull(res.method);
- mCurId = res.id;
+ mCurBindState = new BindState(res);
+ mCurId = res.id; // for @UnsupportedAppUsage
mBindSequence = res.sequence;
mVirtualDisplayToScreenMatrix = res.getVirtualDisplayToScreenMatrix();
- mIsInputMethodSuppressingSpellChecker =
- res.isInputMethodSuppressingSpellChecker;
}
startInputInner(StartInputReason.BOUND_TO_IMMS, null, 0, 0, 0);
return;
@@ -1127,7 +1120,7 @@
}
mVirtualDisplayToScreenMatrix.setValues(matrixValues);
- if (mCursorAnchorInfo == null || mCurrentInputMethodSession == null
+ if (mCursorAnchorInfo == null || !isImeSessionAvailableLocked()
|| mServedInputConnection == null) {
return;
}
@@ -1136,7 +1129,7 @@
}
// Since the host VirtualDisplay is moved, we need to issue
// IMS#updateCursorAnchorInfo() again.
- mCurrentInputMethodSession.updateCursorAnchorInfo(
+ mCurBindState.mImeSession.updateCursorAnchorInfo(
CursorAnchorInfo.createForAdditionalParentMatrix(
mCursorAnchorInfo, mVirtualDisplayToScreenMatrix));
}
@@ -1279,9 +1272,7 @@
// 1) doing so has no effect for A and 2) doing so is sufficient for B.
final long identity = Binder.clearCallingIdentity();
try {
- service.addClient(imm.mClient, imm.mFallbackInputConnection, displayId);
- } catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ imm.mServiceInvoker.addClient(imm.mClient, imm.mFallbackInputConnection, displayId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -1319,8 +1310,9 @@
return new InputMethodManager(stubInterface, displayId, looper);
}
- private InputMethodManager(IInputMethodManager service, int displayId, Looper looper) {
- mService = service;
+ private InputMethodManager(@NonNull IInputMethodManager service, int displayId, Looper looper) {
+ mService = service; // For @UnsupportedAppUsage
+ mServiceInvoker = IInputMethodManagerInvoker.create(service);
mMainLooper = looper;
mH = new H(looper);
mDisplayId = displayId;
@@ -1410,28 +1402,44 @@
* @return {@link List} of {@link InputMethodInfo}.
*/
public List<InputMethodInfo> getInputMethodList() {
- try {
- // We intentionally do not use UserHandle.getCallingUserId() here because for system
- // services InputMethodManagerInternal.getInputMethodListAsUser() should be used
- // instead.
- return mService.getInputMethodList(UserHandle.myUserId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ // We intentionally do not use UserHandle.getCallingUserId() here because for system
+ // services InputMethodManagerInternal.getInputMethodListAsUser() should be used
+ // instead.
+ return mServiceInvoker.getInputMethodList(UserHandle.myUserId());
}
/**
- * Returns {@code true} if currently selected IME supports Stylus handwriting.
+ * Returns {@code true} if currently selected IME supports Stylus handwriting & is enabled.
* If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be
* called and Stylus touch should continue as normal touch input.
* @see #startStylusHandwriting(View)
*/
public boolean isStylusHandwritingAvailable() {
- try {
- return mService.isStylusHandwritingAvailable();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ return isStylusHandwritingAvailableAsUser(UserHandle.myUserId());
+ }
+
+ /**
+ * Returns {@code true} if currently selected IME supports Stylus handwriting & is enabled for
+ * the given userId.
+ * If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be
+ * called and Stylus touch should continue as normal touch input.
+ * @see #startStylusHandwriting(View)
+ * @param userId user ID to query.
+ * @hide
+ */
+ public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
+ final Context fallbackContext = ActivityThread.currentApplication();
+ if (fallbackContext == null) {
+ return false;
}
+ if (Settings.Global.getInt(fallbackContext.getContentResolver(),
+ Settings.Global.STYLUS_HANDWRITING_ENABLED, 0) == 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Stylus handwriting is not enabled in settings.");
+ }
+ return false;
+ }
+ return mServiceInvoker.isStylusHandwritingAvailableAsUser(userId);
}
/**
@@ -1445,11 +1453,7 @@
@RequiresPermission(INTERACT_ACROSS_USERS_FULL)
@NonNull
public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
- try {
- return mService.getInputMethodList(userId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mServiceInvoker.getInputMethodList(userId);
}
/**
@@ -1465,11 +1469,7 @@
@NonNull
public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId,
@DirectBootAwareness int directBootAwareness) {
- try {
- return mService.getAwareLockedInputMethodList(userId, directBootAwareness);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mServiceInvoker.getAwareLockedInputMethodList(userId, directBootAwareness);
}
/**
@@ -1480,14 +1480,10 @@
* @return {@link List} of {@link InputMethodInfo}.
*/
public List<InputMethodInfo> getEnabledInputMethodList() {
- try {
- // We intentionally do not use UserHandle.getCallingUserId() here because for system
- // services InputMethodManagerInternal.getEnabledInputMethodListAsUser() should be used
- // instead.
- return mService.getEnabledInputMethodList(UserHandle.myUserId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ // We intentionally do not use UserHandle.getCallingUserId() here because for system
+ // services InputMethodManagerInternal.getEnabledInputMethodListAsUser() should be used
+ // instead.
+ return mServiceInvoker.getEnabledInputMethodList(UserHandle.myUserId());
}
/**
@@ -1499,11 +1495,7 @@
*/
@RequiresPermission(INTERACT_ACROSS_USERS_FULL)
public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
- try {
- return mService.getEnabledInputMethodList(userId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mServiceInvoker.getEnabledInputMethodList(userId);
}
/**
@@ -1518,13 +1510,9 @@
*/
public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
boolean allowsImplicitlySelectedSubtypes) {
- try {
- return mService.getEnabledInputMethodSubtypeList(
- imi == null ? null : imi.getId(),
- allowsImplicitlySelectedSubtypes);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mServiceInvoker.getEnabledInputMethodSubtypeList(
+ imi == null ? null : imi.getId(),
+ allowsImplicitlySelectedSubtypes);
}
/**
@@ -1628,7 +1616,8 @@
*/
public boolean isInputMethodSuppressingSpellChecker() {
synchronized (mH) {
- return mIsInputMethodSuppressingSpellChecker;
+ return mCurBindState != null
+ && mCurBindState.mIsInputMethodSuppressingSpellChecker;
}
}
@@ -1642,10 +1631,9 @@
setInputChannelLocked(null);
// We only reset sequence number for input method, but not accessibility.
mBindSequence = -1;
- mCurId = null;
+ mCurId = null; // for @UnsupportedAppUsage
mCurMethod = null; // for @UnsupportedAppUsage
- mCurrentInputMethodSession = null;
- mIsInputMethodSuppressingSpellChecker = false;
+ mCurBindState = null;
}
/**
@@ -1738,8 +1726,8 @@
}
mCompletions = completions;
- if (mCurrentInputMethodSession != null) {
- mCurrentInputMethodSession.displayCompletions(mCompletions);
+ if (isImeSessionAvailableLocked()) {
+ mCurBindState.mImeSession.displayCompletions(mCompletions);
}
}
}
@@ -1758,8 +1746,8 @@
return;
}
- if (mCurrentInputMethodSession != null) {
- mCurrentInputMethodSession.updateExtractedText(token, text);
+ if (isImeSessionAvailableLocked()) {
+ mCurBindState.mImeSession.updateExtractedText(token, text);
}
}
}
@@ -1887,18 +1875,14 @@
// Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
// TODO(b/229426865): call WindowInsetsController#show instead.
mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
- try {
- Log.d(TAG, "showSoftInput() view=" + view + " flags=" + flags + " reason="
- + InputMethodDebug.softInputDisplayReasonToString(reason));
- return mService.showSoftInput(
- mClient,
- view.getWindowToken(),
- flags,
- resultReceiver,
- reason);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ Log.d(TAG, "showSoftInput() view=" + view + " flags=" + flags + " reason="
+ + InputMethodDebug.softInputDisplayReasonToString(reason));
+ return mServiceInvoker.showSoftInput(
+ mClient,
+ view.getWindowToken(),
+ flags,
+ resultReceiver,
+ reason);
}
}
@@ -1914,26 +1898,22 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
synchronized (mH) {
- try {
- Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
- + " removed soon. If you are using androidx.appcompat.widget.SearchView,"
- + " please update to version 26.0 or newer version.");
- if (mCurRootView == null || mCurRootView.getView() == null) {
- Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
- return;
- }
- // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
- // TODO(b/229426865): call WindowInsetsController#show instead.
- mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
- mService.showSoftInput(
- mClient,
- mCurRootView.getView().getWindowToken(),
- flags,
- resultReceiver,
- SoftInputShowHideReason.SHOW_SOFT_INPUT);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
+ + " removed soon. If you are using androidx.appcompat.widget.SearchView,"
+ + " please update to version 26.0 or newer version.");
+ if (mCurRootView == null || mCurRootView.getView() == null) {
+ Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
+ return;
}
+ // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
+ // TODO(b/229426865): call WindowInsetsController#show instead.
+ mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
+ mServiceInvoker.showSoftInput(
+ mClient,
+ mCurRootView.getView().getWindowToken(),
+ flags,
+ resultReceiver,
+ SoftInputShowHideReason.SHOW_SOFT_INPUT);
}
}
@@ -2008,11 +1988,8 @@
return false;
}
- try {
- return mService.hideSoftInput(mClient, windowToken, flags, resultReceiver, reason);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mServiceInvoker.hideSoftInput(mClient, windowToken, flags, resultReceiver,
+ reason);
}
}
@@ -2069,13 +2046,9 @@
mDisplayId);
}
- try {
- mService.startStylusHandwriting(mClient);
- // TODO(b/210039666): do we need any extra work for supporting non-native
- // UI toolkits?
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ mServiceInvoker.startStylusHandwriting(mClient);
+ // TODO(b/210039666): do we need any extra work for supporting non-native
+ // UI toolkits?
}
}
@@ -2194,7 +2167,7 @@
// OK to ignore because the calling InputConnection is already abandoned.
return true;
}
- if (mCurrentInputMethodSession == null) {
+ if (!isImeSessionAvailableLocked()) {
// IME is not yet bound to the client. Need to fall back to the restartInput().
return false;
}
@@ -2205,7 +2178,7 @@
mCursorCandEnd = textSnapshot.getCompositionEnd();
editorInfo.initialCapsMode = textSnapshot.getCursorCapsMode();
editorInfo.setInitialSurroundingTextInternal(textSnapshot.getSurroundingText());
- mCurrentInputMethodSession.invalidateInput(editorInfo, mServedInputConnection,
+ mCurBindState.mImeSession.invalidateInput(editorInfo, mServedInputConnection,
sessionId);
final IRemoteAccessibilityInputConnection accessibilityInputConnection =
mServedInputConnection.asIRemoteAccessibilityInputConnection();
@@ -2321,6 +2294,14 @@
InputConnection ic = view.onCreateInputConnection(tba);
if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic);
+ // Clear autofill and field ids if a connection could not be established.
+ // This ensures that even disconnected EditorInfos have well-defined attributes,
+ // making them consistently and straightforwardly comparable.
+ if (ic == null) {
+ tba.autofillId = AutofillId.NO_AUTOFILL_ID;
+ tba.fieldId = 0;
+ }
+
final Handler icHandler;
InputBindResult res = null;
synchronized (mH) {
@@ -2358,7 +2339,7 @@
mServedInputConnection = null;
mServedInputConnectionHandler = null;
}
- RemoteInputConnectionImpl servedInputConnection;
+ final RemoteInputConnectionImpl servedInputConnection;
if (ic != null) {
mCursorSelStart = tba.initialSelStart;
mCursorSelEnd = tba.initialSelEnd;
@@ -2390,17 +2371,13 @@
+ ic + " tba=" + tba + " startInputFlags="
+ InputMethodDebug.startInputFlagsToString(startInputFlags));
}
- try {
- res = mService.startInputOrWindowGainedFocus(
- startInputReason, mClient, windowGainingFocus, startInputFlags,
- softInputMode, windowFlags, tba, servedInputConnection,
- servedInputConnection == null ? null
- : servedInputConnection.asIRemoteAccessibilityInputConnection(),
- view.getContext().getApplicationInfo().targetSdkVersion,
- mImeDispatcher);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ res = mServiceInvoker.startInputOrWindowGainedFocus(
+ startInputReason, mClient, windowGainingFocus, startInputFlags,
+ softInputMode, windowFlags, tba, servedInputConnection,
+ servedInputConnection == null ? null
+ : servedInputConnection.asIRemoteAccessibilityInputConnection(),
+ view.getContext().getApplicationInfo().targetSdkVersion,
+ mImeDispatcher);
if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
if (res == null) {
Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
@@ -2412,12 +2389,11 @@
return false;
}
mVirtualDisplayToScreenMatrix = res.getVirtualDisplayToScreenMatrix();
- mIsInputMethodSuppressingSpellChecker = res.isInputMethodSuppressingSpellChecker;
if (res.id != null) {
setInputChannelLocked(res.channel);
mBindSequence = res.sequence;
mCurMethod = res.method; // for @UnsupportedAppUsage
- mCurrentInputMethodSession = IInputMethodSessionInvoker.createOrNull(res.method);
+ mCurBindState = new BindState(res);
mAccessibilityInputMethodSession.clear();
if (res.accessibilitySessions != null) {
for (int i = 0; i < res.accessibilitySessions.size(); i++) {
@@ -2430,7 +2406,7 @@
}
}
}
- mCurId = res.id;
+ mCurId = res.id; // for @UnsupportedAppUsage
} else if (res.channel != null && res.channel != mCurChannel) {
res.channel.dispose();
}
@@ -2440,8 +2416,8 @@
break;
}
if (mCompletions != null) {
- if (mCurrentInputMethodSession != null) {
- mCurrentInputMethodSession.displayCompletions(mCompletions);
+ if (isImeSessionAvailableLocked()) {
+ mCurBindState.mImeSession.displayCompletions(mCompletions);
}
}
}
@@ -2528,16 +2504,12 @@
Log.w(TAG, "No current root view, ignoring closeCurrentInput()");
return;
}
- try {
- mService.hideSoftInput(
- mClient,
- mCurRootView.getView().getWindowToken(),
- HIDE_NOT_ALWAYS,
- null,
- SoftInputShowHideReason.HIDE_SOFT_INPUT);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ mServiceInvoker.hideSoftInput(
+ mClient,
+ mCurRootView.getView().getWindowToken(),
+ HIDE_NOT_ALWAYS,
+ null,
+ SoftInputShowHideReason.HIDE_SOFT_INPUT);
}
}
@@ -2606,15 +2578,11 @@
ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
null /* icProto */);
synchronized (mH) {
- if (mCurrentInputMethodSession != null && mCurRootView != null
+ if (isImeSessionAvailableLocked() && mCurRootView != null
&& mCurRootView.getWindowToken() == windowToken) {
- try {
- mService.hideSoftInput(mClient, windowToken, 0 /* flags */,
- null /* resultReceiver */,
- SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ mServiceInvoker.hideSoftInput(mClient, windowToken, 0 /* flags */,
+ null /* resultReceiver */,
+ SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
}
}
}
@@ -2626,11 +2594,7 @@
*/
public void removeImeSurface(IBinder windowToken) {
synchronized (mH) {
- try {
- mService.removeImeSurfaceFromWindowAsync(windowToken);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ mServiceInvoker.removeImeSurfaceFromWindowAsync(windowToken);
}
}
@@ -2657,7 +2621,7 @@
checkFocus();
synchronized (mH) {
if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
- || mCurrentInputMethodSession == null) {
+ || !isImeSessionAvailableLocked()) {
return;
}
@@ -2671,9 +2635,9 @@
if (DEBUG) Log.d(TAG, "updateSelection");
if (DEBUG) {
- Log.v(TAG, "SELECTION CHANGE: " + mCurrentInputMethodSession);
+ Log.v(TAG, "SELECTION CHANGE: " + mCurBindState.mImeSession);
}
- mCurrentInputMethodSession.updateSelection(mCursorSelStart, mCursorSelEnd, selStart,
+ mCurBindState.mImeSession.updateSelection(mCursorSelStart, mCursorSelEnd, selStart,
selEnd, candidatesStart, candidatesEnd);
forAccessibilitySessionsLocked(wrapper -> wrapper.updateSelection(mCursorSelStart,
mCursorSelEnd, selStart, selEnd, candidatesStart, candidatesEnd));
@@ -2714,11 +2678,11 @@
checkFocus();
synchronized (mH) {
if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
- || mCurrentInputMethodSession == null) {
+ || !isImeSessionAvailableLocked()) {
return;
}
if (DEBUG) Log.v(TAG, "onViewClicked: " + focusChanged);
- mCurrentInputMethodSession.viewClicked(focusChanged);
+ mCurBindState.mImeSession.viewClicked(focusChanged);
}
}
@@ -2783,15 +2747,15 @@
checkFocus();
synchronized (mH) {
if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
- || mCurrentInputMethodSession == null) {
+ || !isImeSessionAvailableLocked()) {
return;
}
mTmpCursorRect.set(left, top, right, bottom);
if (!mCursorRect.equals(mTmpCursorRect)) {
- if (DEBUG) Log.d(TAG, "updateCursor: " + mCurrentInputMethodSession);
+ if (DEBUG) Log.d(TAG, "updateCursor: " + mCurBindState.mImeSession);
- mCurrentInputMethodSession.updateCursor(mTmpCursorRect);
+ mCurBindState.mImeSession.updateCursor(mTmpCursorRect);
mCursorRect.set(mTmpCursorRect);
}
}
@@ -2815,7 +2779,7 @@
checkFocus();
synchronized (mH) {
if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
- || mCurrentInputMethodSession == null) {
+ || !isImeSessionAvailableLocked()) {
return;
}
// If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has
@@ -2833,11 +2797,11 @@
}
if (DEBUG) Log.v(TAG, "updateCursorAnchorInfo: " + cursorAnchorInfo);
if (mVirtualDisplayToScreenMatrix != null) {
- mCurrentInputMethodSession.updateCursorAnchorInfo(
+ mCurBindState.mImeSession.updateCursorAnchorInfo(
CursorAnchorInfo.createForAdditionalParentMatrix(
cursorAnchorInfo, mVirtualDisplayToScreenMatrix));
} else {
- mCurrentInputMethodSession.updateCursorAnchorInfo(cursorAnchorInfo);
+ mCurBindState.mImeSession.updateCursorAnchorInfo(cursorAnchorInfo);
}
mCursorAnchorInfo = cursorAnchorInfo;
}
@@ -2865,11 +2829,11 @@
checkFocus();
synchronized (mH) {
if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
- || mCurrentInputMethodSession == null) {
+ || !isImeSessionAvailableLocked()) {
return;
}
if (DEBUG) Log.v(TAG, "APP PRIVATE COMMAND " + action + ": " + data);
- mCurrentInputMethodSession.appPrivateCommand(action, data);
+ mCurBindState.mImeSession.appPrivateCommand(action, data);
}
}
@@ -3028,7 +2992,7 @@
public int dispatchInputEvent(InputEvent event, Object token,
FinishedInputEventCallback callback, Handler handler) {
synchronized (mH) {
- if (mCurrentInputMethodSession != null) {
+ if (isImeSessionAvailableLocked()) {
if (event instanceof KeyEvent) {
KeyEvent keyEvent = (KeyEvent)event;
if (keyEvent.getAction() == KeyEvent.ACTION_DOWN
@@ -3040,11 +3004,11 @@
}
if (DEBUG) {
- Log.v(TAG, "DISPATCH INPUT EVENT: " + mCurrentInputMethodSession);
+ Log.v(TAG, "DISPATCH INPUT EVENT: " + mCurBindState.mImeSession);
}
PendingEvent p = obtainPendingEventLocked(
- event, token, mCurId, callback, handler);
+ event, token, mCurBindState.mImeId, callback, handler);
if (mMainLooper.isCurrentThread()) {
// Already running on the IMM thread so we can send the event immediately.
return sendInputEventOnMainLooperLocked(p);
@@ -3131,7 +3095,8 @@
return DISPATCH_IN_PROGRESS;
}
- Log.w(TAG, "Unable to send input event to IME: " + mCurId + " dropping: " + event);
+ Log.w(TAG, "Unable to send input event to IME: " + getImeIdLocked()
+ + " dropping: " + event);
}
return DISPATCH_NOT_HANDLED;
}
@@ -3230,19 +3195,11 @@
final int mode = showAuxiliarySubtypes
? SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES
: SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES;
- try {
- mService.showInputMethodPickerFromSystem(mClient, mode, displayId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ mServiceInvoker.showInputMethodPickerFromSystem(mClient, mode, displayId);
}
private void showInputMethodPickerLocked() {
- try {
- mService.showInputMethodPickerFromClient(mClient, SHOW_IM_PICKER_MODE_AUTO);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ mServiceInvoker.showInputMethodPickerFromClient(mClient, SHOW_IM_PICKER_MODE_AUTO);
}
/**
@@ -3258,11 +3215,7 @@
*/
@TestApi
public boolean isInputMethodPickerShown() {
- try {
- return mService.isInputMethodPickerShownForTest();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mServiceInvoker.isInputMethodPickerShownForTest();
}
/**
@@ -3272,11 +3225,7 @@
* subtypes of all input methods will be shown.
*/
public void showInputMethodAndSubtypeEnabler(String imiId) {
- try {
- mService.showInputMethodAndSubtypeEnablerFromClient(mClient, imiId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ mServiceInvoker.showInputMethodAndSubtypeEnablerFromClient(mClient, imiId);
}
/**
@@ -3285,11 +3234,7 @@
* have any input method subtype.
*/
public InputMethodSubtype getCurrentInputMethodSubtype() {
- try {
- return mService.getCurrentInputMethodSubtype();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mServiceInvoker.getCurrentInputMethodSubtype();
}
/**
@@ -3333,12 +3278,8 @@
// Null or invalid IME ID format.
return false;
}
- final List<InputMethodSubtype> enabledSubtypes;
- try {
- enabledSubtypes = mService.getEnabledInputMethodSubtypeList(imeId, true);
- } catch (RemoteException e) {
- return false;
- }
+ final List<InputMethodSubtype> enabledSubtypes =
+ mServiceInvoker.getEnabledInputMethodSubtypeList(imeId, true);
final int numSubtypes = enabledSubtypes.size();
for (int i = 0; i < numSubtypes; ++i) {
final InputMethodSubtype enabledSubtype = enabledSubtypes.get(i);
@@ -3403,11 +3344,7 @@
@UnsupportedAppUsage(trackingBug = 204906124, maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
publicAlternatives = "Use {@link android.view.WindowInsets} instead")
public int getInputMethodWindowVisibleHeight() {
- try {
- return mService.getInputMethodWindowVisibleHeight(mClient);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mServiceInvoker.getInputMethodWindowVisibleHeight(mClient);
}
/**
@@ -3420,18 +3357,14 @@
* @hide
*/
public void reportVirtualDisplayGeometry(int childDisplayId, @Nullable Matrix matrix) {
- try {
- final float[] matrixValues;
- if (matrix == null) {
- matrixValues = null;
- } else {
- matrixValues = new float[9];
- matrix.getValues(matrixValues);
- }
- mService.reportVirtualDisplayGeometryAsync(mClient, childDisplayId, matrixValues);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ final float[] matrixValues;
+ if (matrix == null) {
+ matrixValues = null;
+ } else {
+ matrixValues = new float[9];
+ matrix.getValues(matrixValues);
}
+ mServiceInvoker.reportVirtualDisplayGeometryAsync(mClient, childDisplayId, matrixValues);
}
/**
@@ -3535,19 +3468,11 @@
*/
@Deprecated
public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
- try {
- mService.setAdditionalInputMethodSubtypes(imiId, subtypes);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ mServiceInvoker.setAdditionalInputMethodSubtypes(imiId, subtypes);
}
public InputMethodSubtype getLastInputMethodSubtype() {
- try {
- return mService.getLastInputMethodSubtype();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return mServiceInvoker.getLastInputMethodSubtype();
}
/**
@@ -3567,17 +3492,14 @@
final Printer p = new PrintWriterPrinter(fout);
p.println("Input method client state for " + this + ":");
-
- p.println(" mService=" + mService);
- p.println(" mMainLooper=" + mMainLooper);
p.println(" mFallbackInputConnection=" + mFallbackInputConnection);
p.println(" mActive=" + mActive
+ " mRestartOnNextWindowFocus=" + mRestartOnNextWindowFocus
+ " mBindSequence=" + mBindSequence
- + " mCurId=" + mCurId);
+ + " mCurImeId=" + getImeIdLocked());
p.println(" mFullscreenMode=" + mFullscreenMode);
- if (mCurrentInputMethodSession != null) {
- p.println(" mCurMethod=" + mCurrentInputMethodSession);
+ if (isImeSessionAvailableLocked()) {
+ p.println(" mCurMethod=" + mCurBindState.mImeSession);
} else {
p.println(" mCurMethod= null");
}
@@ -3653,6 +3575,43 @@
}
}
+ private static final class BindState {
+ /**
+ * Encapsulates IPCs to the currently connected InputMethodService.
+ */
+ @Nullable
+ final IInputMethodSessionInvoker mImeSession;
+
+ /**
+ * As reported by {@link InputBindResult}. This value is determined by
+ * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecking}.
+ */
+ final boolean mIsInputMethodSuppressingSpellChecker;
+
+ /**
+ * As reported by {@link InputBindResult}. This value indicates the bound input method ID.
+ */
+ @Nullable
+ final String mImeId;
+
+ BindState(@NonNull InputBindResult inputBindResult) {
+ mImeSession = IInputMethodSessionInvoker.createOrNull(inputBindResult.method);
+ mIsInputMethodSuppressingSpellChecker =
+ inputBindResult.isInputMethodSuppressingSpellChecker;
+ mImeId = inputBindResult.id;
+ }
+ }
+
+ @GuardedBy("mH")
+ private boolean isImeSessionAvailableLocked() {
+ return mCurBindState != null && mCurBindState.mImeSession != null;
+ }
+
+ @GuardedBy("mH")
+ private String getImeIdLocked() {
+ return mCurBindState != null ? mCurBindState.mImeId : null;
+ }
+
private static String dumpViewInfo(@Nullable final View view) {
if (view == null) {
return "null";
@@ -3702,14 +3661,14 @@
*/
@GuardedBy("mH")
public void dumpDebug(ProtoOutputStream proto, @Nullable byte[] icProto) {
- if (mCurrentInputMethodSession == null) {
+ if (!isImeSessionAvailableLocked()) {
return;
}
proto.write(DISPLAY_ID, mDisplayId);
final long token = proto.start(INPUT_METHOD_MANAGER);
synchronized (mH) {
- proto.write(CUR_ID, mCurId);
+ proto.write(CUR_ID, mCurBindState.mImeId);
proto.write(FULLSCREEN_MODE, mFullscreenMode);
proto.write(ACTIVE, mActive);
proto.write(SERVED_CONNECTING, mServedConnecting);
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index cbaac5f..44997b4 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -548,6 +548,12 @@
*/
public static final String TASK_MANAGER_SHOW_FOOTER_DOT = "task_manager_show_footer_dot";
+ /**
+ * (boolean) Whether the task manager should show a stop button if the app is allowlisted
+ * by the user.
+ */
+ public static final String TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS =
+ "show_stop_button_for_user_allowlisted_apps";
/**
* (boolean) Whether the clipboard overlay is enabled.
diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
index b819615..8bab5c3 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
@@ -35,8 +35,16 @@
* Top-level interface to an input method component (implemented in a Service).
*/
oneway interface IInputMethod {
- void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
- int configChanges, boolean stylusHwSupported, int navigationBarFlags);
+
+ parcelable InitParams {
+ IBinder token;
+ IInputMethodPrivilegedOperations privilegedOperations;
+ int configChanges;
+ boolean stylusHandWritingSupported;
+ int navigationBarFlags;
+ }
+
+ void initializeInternal(in InitParams params);
void onCreateInlineSuggestionsRequest(in InlineSuggestionsRequestInfo requestInfo,
in IInlineSuggestionsRequestCallback cb);
@@ -45,9 +53,16 @@
void unbindInput();
- void startInput(in IBinder startInputToken, in IRemoteInputConnection inputConnection,
- in EditorInfo editorInfo, boolean restarting, int navigationBarFlags,
- in ImeOnBackInvokedDispatcher imeDispatcher);
+ parcelable StartInputParams {
+ IBinder startInputToken;
+ IRemoteInputConnection remoteInputConnection;
+ EditorInfo editorInfo;
+ boolean restarting;
+ int navigationBarFlags;
+ ImeOnBackInvokedDispatcher imeDispatcher;
+ }
+
+ void startInput(in StartInputParams params);
void onNavButtonFlagsChanged(int navButtonFlags);
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 508e445..4bed47c 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -36,10 +36,19 @@
int untrustedDisplayId);
// TODO: Use ParceledListSlice instead
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
List<InputMethodInfo> getInputMethodList(int userId);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
List<InputMethodInfo> getAwareLockedInputMethodList(int userId, int directBootAwareness);
+
// TODO: Use ParceledListSlice instead
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
List<InputMethodInfo> getEnabledInputMethodList(int userId);
+
List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId,
boolean allowsImplicitlySelectedSubtypes);
InputMethodSubtype getLastInputMethodSubtype();
@@ -48,9 +57,12 @@
in ResultReceiver resultReceiver, int reason);
boolean hideSoftInput(in IInputMethodClient client, IBinder windowToken, int flags,
in ResultReceiver resultReceiver, int reason);
+
// If windowToken is null, this just does startInput(). Otherwise this reports that a window
// has gained focus, and if 'editorInfo' is non-null then also does startInput.
// @NonNull
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
InputBindResult startInputOrWindowGainedFocus(
/* @StartInputReason */ int startInputReason,
in IInputMethodClient client, in IBinder windowToken,
@@ -62,8 +74,12 @@
void showInputMethodPickerFromClient(in IInputMethodClient client,
int auxiliarySubtypeMode);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.WRITE_SECURE_SETTINGS)")
void showInputMethodPickerFromSystem(in IInputMethodClient client,
int auxiliarySubtypeMode, int displayId);
+
void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId);
boolean isInputMethodPickerShownForTest();
InputMethodSubtype getCurrentInputMethodSubtype();
@@ -76,8 +92,11 @@
int childDisplayId, in float[] matrixValues);
oneway void reportPerceptibleAsync(in IBinder windowToken, boolean perceptible);
- /** Remove the IME surface. Requires INTERNAL_SYSTEM_WINDOW permission. */
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
void removeImeSurface();
+
/** Remove the IME surface. Requires passing the currently focused window. */
oneway void removeImeSurfaceFromWindowAsync(in IBinder windowToken);
void startProtoDump(in byte[] protoDump, int source, String where);
@@ -90,6 +109,9 @@
/** Start Stylus handwriting session **/
void startStylusHandwriting(in IInputMethodClient client);
+
/** Returns {@code true} if currently selected IME supports Stylus handwriting. */
- boolean isStylusHandwritingAvailable();
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
+ boolean isStylusHandwritingAvailableAsUser(int userId);
}
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 6ec71ba..5099dd2 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -266,6 +266,9 @@
optional int32 lookaside_b = 4;
// Statement cache stats: hits/misses/cachesize
optional string cache = 5;
+ optional int32 cache_hits = 6;
+ optional int32 cache_misses = 7;
+ optional int32 cache_size = 8;
}
repeated Database databases = 4;
}
diff --git a/core/res/res/values-mcc440-mnc20/config.xml b/core/res/res/values-mcc440-mnc20/config.xml
index 62001d9..7a48342 100644
--- a/core/res/res/values-mcc440-mnc20/config.xml
+++ b/core/res/res/values-mcc440-mnc20/config.xml
@@ -23,6 +23,6 @@
<!-- Configure mobile network MTU. Carrier specific value is set here.
-->
- <integer name="config_mobile_mtu">1422</integer>
+ <integer name="config_mobile_mtu">1358</integer>
</resources>
diff --git a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
index be04b6c..6bd498c 100644
--- a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
+++ b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
@@ -16,12 +16,15 @@
package android.companion;
+import android.content.Context;
import android.os.SystemClock;
import android.test.InstrumentationTestCase;
import android.util.Log;
import com.android.internal.util.HexDump;
+import libcore.util.EmptyArray;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
@@ -31,69 +34,81 @@
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
+import java.util.List;
import java.util.Random;
public class SystemDataTransportTest extends InstrumentationTestCase {
private static final String TAG = "SystemDataTransportTest";
- private static final int COMMAND_INVALID = 0xF00DCAFE;
- private static final int COMMAND_PING_V0 = 0x50490000;
- private static final int COMMAND_PONG_V0 = 0x504F0000;
+ private static final int MESSAGE_INVALID = 0xF00DCAFE;
+ private static final int MESSAGE_REQUEST_INVALID = 0x63636363; // ????
+ private static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
+
+ private static final int MESSAGE_RESPONSE_INVALID = 0x33333333; // !!!!
+ private static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
+ private static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI
+
+ private Context mContext;
private CompanionDeviceManager mCdm;
+ private int mAssociationId;
@Override
protected void setUp() throws Exception {
super.setUp();
- mCdm = getInstrumentation().getTargetContext()
- .getSystemService(CompanionDeviceManager.class);
+ mContext = getInstrumentation().getTargetContext();
+ mCdm = mContext.getSystemService(CompanionDeviceManager.class);
+ mAssociationId = createAssociation();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ mCdm.disassociate(mAssociationId);
}
public void testPingHandRolled() {
// NOTE: These packets are explicitly hand-rolled to verify wire format;
// the remainder of the tests are fine using generated packets
- // PING v0 with payload "HELLO WORLD!"
+ // MESSAGE_REQUEST_PING with payload "HELLO WORLD!"
final byte[] input = new byte[] {
- 0x50, 0x49, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x0C,
+ 0x63, (byte) 0x80, 0x73, 0x78, // message: MESSAGE_REQUEST_PING
+ 0x00, 0x00, 0x00, 0x2A, // sequence: 42
+ 0x00, 0x00, 0x00, 0x0C, // length: 12
0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x20, 0x57, 0x4F, 0x52, 0x4C, 0x44, 0x21,
};
- // PONG v0 with payload "HELLO WORLD!"
+ // MESSAGE_RESPONSE_SUCCESS with payload "HELLO WORLD!"
final byte[] expected = new byte[] {
- 0x50, 0x4F, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x0C,
+ 0x33, (byte) 0x83, (byte) 0x85, 0x67, // message: MESSAGE_RESPONSE_SUCCESS
+ 0x00, 0x00, 0x00, 0x2A, // sequence: 42
+ 0x00, 0x00, 0x00, 0x0C, // length: 12
0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x20, 0x57, 0x4F, 0x52, 0x4C, 0x44, 0x21,
};
-
- final ByteArrayInputStream in = new ByteArrayInputStream(input);
- final ByteArrayOutputStream out = new ByteArrayOutputStream();
- mCdm.attachSystemDataTransport(42, in, out);
-
- final byte[] actual = waitForByteArray(out, expected.length);
- assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+ assertTransportBehavior(input, expected);
}
public void testPingTrickle() {
- final byte[] input = generatePacket(COMMAND_PING_V0, TAG);
- final byte[] expected = generatePacket(COMMAND_PONG_V0, TAG);
+ final byte[] input = generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, TAG);
+ final byte[] expected = generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, TAG);
final ByteArrayInputStream in = new ByteArrayInputStream(input);
final ByteArrayOutputStream out = new ByteArrayOutputStream();
- mCdm.attachSystemDataTransport(42, new TrickleInputStream(in), out);
+ mCdm.attachSystemDataTransport(mAssociationId, new TrickleInputStream(in), out);
final byte[] actual = waitForByteArray(out, expected.length);
assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
}
public void testPingDelay() {
- final byte[] input = generatePacket(COMMAND_PING_V0, TAG);
- final byte[] expected = generatePacket(COMMAND_PONG_V0, TAG);
+ final byte[] input = generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, TAG);
+ final byte[] expected = generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, TAG);
final ByteArrayInputStream in = new ByteArrayInputStream(input);
final ByteArrayOutputStream out = new ByteArrayOutputStream();
- mCdm.attachSystemDataTransport(42, new DelayingInputStream(in, 1000),
+ mCdm.attachSystemDataTransport(mAssociationId, new DelayingInputStream(in, 1000),
new DelayingOutputStream(out, 1000));
final byte[] actual = waitForByteArray(out, expected.length);
@@ -104,49 +119,54 @@
final byte[] blob = new byte[500_000];
new Random().nextBytes(blob);
- final byte[] input = generatePacket(COMMAND_PING_V0, blob);
- final byte[] expected = generatePacket(COMMAND_PONG_V0, blob);
-
- final ByteArrayInputStream in = new ByteArrayInputStream(input);
- final ByteArrayOutputStream out = new ByteArrayOutputStream();
- mCdm.attachSystemDataTransport(42, in, out);
-
- final byte[] actual = waitForByteArray(out, expected.length);
- assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+ final byte[] input = generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, blob);
+ final byte[] expected = generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, blob);
+ assertTransportBehavior(input, expected);
}
public void testMutiplePingPing() {
- final byte[] input = concat(generatePacket(COMMAND_PING_V0, "red"),
- generatePacket(COMMAND_PING_V0, "green"));
- final byte[] expected = concat(generatePacket(COMMAND_PONG_V0, "red"),
- generatePacket(COMMAND_PONG_V0, "green"));
-
- final ByteArrayInputStream in = new ByteArrayInputStream(input);
- final ByteArrayOutputStream out = new ByteArrayOutputStream();
- mCdm.attachSystemDataTransport(42, in, out);
-
- final byte[] actual = waitForByteArray(out, expected.length);
- assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+ final byte[] input = concat(
+ generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, "red"),
+ generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green"));
+ final byte[] expected = concat(
+ generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, "red"),
+ generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 2, "green"));
+ assertTransportBehavior(input, expected);
}
public void testMultipleInvalidPing() {
- final byte[] input = concat(generatePacket(COMMAND_INVALID, "red"),
- generatePacket(COMMAND_PING_V0, "green"));
- final byte[] expected = generatePacket(COMMAND_PONG_V0, "green");
+ final byte[] input = concat(
+ generatePacket(MESSAGE_INVALID, /* sequence */ 1, "red"),
+ generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green"));
+ final byte[] expected =
+ generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 2, "green");
+ assertTransportBehavior(input, expected);
+ }
- final ByteArrayInputStream in = new ByteArrayInputStream(input);
- final ByteArrayOutputStream out = new ByteArrayOutputStream();
- mCdm.attachSystemDataTransport(42, in, out);
+ public void testMultipleInvalidRequestPing() {
+ final byte[] input = concat(
+ generatePacket(MESSAGE_REQUEST_INVALID, /* sequence */ 1, "red"),
+ generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green"));
+ final byte[] expected = concat(
+ generatePacket(MESSAGE_RESPONSE_FAILURE, /* sequence */ 1),
+ generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 2, "green"));
+ assertTransportBehavior(input, expected);
+ }
- final byte[] actual = waitForByteArray(out, expected.length);
- assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
+ public void testMultipleInvalidResponsePing() {
+ final byte[] input = concat(
+ generatePacket(MESSAGE_RESPONSE_INVALID, /* sequence */ 1, "red"),
+ generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 2, "green"));
+ final byte[] expected =
+ generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 2, "green");
+ assertTransportBehavior(input, expected);
}
public void testDoubleAttach() {
// Connect an empty connection that is stalled out
final InputStream in = new EmptyInputStream();
final OutputStream out = new ByteArrayOutputStream();
- mCdm.attachSystemDataTransport(42, in, out);
+ mCdm.attachSystemDataTransport(mAssociationId, in, out);
SystemClock.sleep(1000);
// Attach a second transport that has some packets; it should disconnect
@@ -154,17 +174,57 @@
testPingHandRolled();
}
- public static byte[] concat(byte[] a, byte[] b) {
- return ByteBuffer.allocate(a.length + b.length).put(a).put(b).array();
+ public static byte[] concat(byte[]... blobs) {
+ int length = 0;
+ for (byte[] blob : blobs) {
+ length += blob.length;
+ }
+ final ByteBuffer buf = ByteBuffer.allocate(length);
+ for (byte[] blob : blobs) {
+ buf.put(blob);
+ }
+ return buf.array();
}
- public static byte[] generatePacket(int command, String data) {
- return generatePacket(command, data.getBytes(StandardCharsets.UTF_8));
+ public static byte[] generatePacket(int message, int sequence) {
+ return generatePacket(message, sequence, EmptyArray.BYTE);
}
- public static byte[] generatePacket(int command, byte[] data) {
- return ByteBuffer.allocate(data.length + 8)
- .putInt(command).putInt(data.length).put(data).array();
+ public static byte[] generatePacket(int message, int sequence, String data) {
+ return generatePacket(message, sequence, data.getBytes(StandardCharsets.UTF_8));
+ }
+
+ public static byte[] generatePacket(int message, int sequence, byte[] data) {
+ return ByteBuffer.allocate(data.length + 12)
+ .putInt(message)
+ .putInt(sequence)
+ .putInt(data.length)
+ .put(data)
+ .array();
+ }
+
+ private int createAssociation() {
+ getInstrumentation().getUiAutomation().executeShellCommand("cmd companiondevice associate "
+ + mContext.getUserId() + " " + mContext.getPackageName() + " AA:BB:CC:DD:EE:FF");
+ List<AssociationInfo> infos;
+ for (int i = 0; i < 100; i++) {
+ infos = mCdm.getMyAssociations();
+ if (!infos.isEmpty()) {
+ return infos.get(0).getId();
+ } else {
+ SystemClock.sleep(100);
+ }
+ }
+ throw new AssertionError();
+ }
+
+ private void assertTransportBehavior(byte[] input, byte[] expected) {
+ final ByteArrayInputStream in = new ByteArrayInputStream(input);
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ mCdm.attachSystemDataTransport(mAssociationId, in, out);
+
+ final byte[] actual = waitForByteArray(out, expected.length);
+ assertEquals(HexDump.toHexString(expected), HexDump.toHexString(actual));
}
private static byte[] waitForByteArray(ByteArrayOutputStream out, int size) {
diff --git a/core/tests/coretests/src/android/graphics/TypefaceTest.java b/core/tests/coretests/src/android/graphics/TypefaceTest.java
index 6defe91..a528c19 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceTest.java
@@ -41,6 +41,7 @@
import org.junit.runner.RunWith;
import java.nio.ByteOrder;
+import java.util.HashMap;
import java.util.Map;
import java.util.Random;
@@ -218,6 +219,10 @@
@SmallTest
@Test
public void testSetSystemFontMap() throws Exception {
+
+ // Typeface.setSystemFontMap mutate the returned map. So copying for the backup.
+ HashMap<String, Typeface> backup = new HashMap<>(Typeface.getSystemFontMap());
+
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
Resources res = context.getResources();
Map<String, Typeface> fontMap = Map.of(
@@ -226,27 +231,38 @@
"monospace", Typeface.create(res.getFont(R.font.samplefont3), Typeface.NORMAL),
"sample", Typeface.create(res.getFont(R.font.samplefont4), Typeface.NORMAL),
"sample-italic", Typeface.create(res.getFont(R.font.samplefont4), Typeface.ITALIC));
- Typeface.setSystemFontMap(fontMap);
- // Test public static final fields
- assertEquals(fontMap.get("sans-serif"), Typeface.DEFAULT);
- assertEquals(Typeface.BOLD, Typeface.DEFAULT_BOLD.getStyle());
- assertEquals(fontMap.get("sans-serif"), Typeface.SANS_SERIF);
- assertEquals(fontMap.get("serif"), Typeface.SERIF);
- assertEquals(fontMap.get("monospace"), Typeface.MONOSPACE);
+ try {
+ Typeface.setSystemFontMap(fontMap);
- // Test defaults
- assertEquals(fontMap.get("sans-serif"), Typeface.defaultFromStyle(Typeface.NORMAL));
- for (int style : STYLES) {
- String msg = "style = " + style;
- assertNotNull(msg, Typeface.defaultFromStyle(style));
- assertEquals(msg, style, Typeface.defaultFromStyle(style).getStyle());
+ // Test public static final fields
+ assertEquals(fontMap.get("sans-serif"), Typeface.DEFAULT);
+ assertEquals(Typeface.BOLD, Typeface.DEFAULT_BOLD.getStyle());
+ assertEquals(fontMap.get("sans-serif"), Typeface.SANS_SERIF);
+ assertEquals(fontMap.get("serif"), Typeface.SERIF);
+ assertEquals(fontMap.get("monospace"), Typeface.MONOSPACE);
+
+ // Test defaults
+ assertEquals(fontMap.get("sans-serif"), Typeface.defaultFromStyle(Typeface.NORMAL));
+ for (int style : STYLES) {
+ String msg = "style = " + style;
+ assertNotNull(msg, Typeface.defaultFromStyle(style));
+ assertEquals(msg, style, Typeface.defaultFromStyle(style).getStyle());
+ }
+
+ // Test create()
+ assertEquals(fontMap.get("sample"), Typeface.create("sample", Typeface.NORMAL));
+ assertEquals(
+ fontMap.get("sample-italic"),
+ Typeface.create("sample-italic", Typeface.ITALIC));
+ } finally {
+ // This tests breaks many default font configuration and break the assumption of the
+ // subsequent test cases. To recover the original configuration, call the
+ // setSystemFontMap function with the original data even if it is a test target.
+ // Ideally, this test should be isolated and app should be restart after this test
+ // been executed.
+ Typeface.setSystemFontMap(backup);
}
-
- // Test create()
- assertEquals(fontMap.get("sample"), Typeface.create("sample", Typeface.NORMAL));
- assertEquals(
- fontMap.get("sample-italic"), Typeface.create("sample-italic", Typeface.ITALIC));
}
private static float measureText(Typeface typeface, String text) {
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 8d3ee2a..18da1a4 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -16,6 +16,7 @@
package android.view.stylus;
+import static android.provider.Settings.Global.STYLUS_HANDWRITING_ENABLED;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
@@ -32,6 +33,7 @@
import android.content.Context;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
import android.view.HandwritingInitiator;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -67,6 +69,8 @@
private static final int HW_BOUNDS_OFFSETS_TOP_PX = 20;
private static final int HW_BOUNDS_OFFSETS_RIGHT_PX = 30;
private static final int HW_BOUNDS_OFFSETS_BOTTOM_PX = 40;
+ private static final int SETTING_VALUE_ON = 1;
+ private static final int SETTING_VALUE_OFF = 0;
private int mHandwritingSlop = 4;
private static final Rect sHwArea = new Rect(100, 200, 500, 500);
@@ -74,12 +78,21 @@
private HandwritingInitiator mHandwritingInitiator;
private View mTestView;
private Context mContext;
+ private int mHwInitialState;
+ private boolean mShouldRestoreInitialHwState;
@Before
public void setup() throws Exception {
final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
mContext = instrumentation.getTargetContext();
+ mHwInitialState = Settings.Global.getInt(mContext.getContentResolver(),
+ STYLUS_HANDWRITING_ENABLED, SETTING_VALUE_OFF);
+ if (mHwInitialState != SETTING_VALUE_ON) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ STYLUS_HANDWRITING_ENABLED, SETTING_VALUE_ON);
+ mShouldRestoreInitialHwState = true;
+ }
String imeId = HandwritingImeService.getImeId();
instrumentation.getUiAutomation().executeShellCommand("ime enable " + imeId);
instrumentation.getUiAutomation().executeShellCommand("ime set " + imeId);
@@ -105,6 +118,11 @@
@After
public void tearDown() throws Exception {
+ if (mShouldRestoreInitialHwState) {
+ mShouldRestoreInitialHwState = false;
+ Settings.Global.putInt(mContext.getContentResolver(),
+ STYLUS_HANDWRITING_ENABLED, mHwInitialState);
+ }
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.executeShellCommand("ime reset");
}
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 47f70dd..8d3751e 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -207,8 +207,8 @@
final Configuration currentConfig = new Configuration();
assertFalse("Must not report change if no public diff",
- shouldReportChange(currentConfig, newConfig, null /* sizeBuckets */,
- 0 /* handledConfigChanges */));
+ shouldReportChange(0 /* publicDiff */, currentConfig, newConfig,
+ null /* sizeBuckets */, 0 /* handledConfigChanges */));
final int[] verticalThresholds = {100, 400};
final SizeConfigurationBuckets buckets = new SizeConfigurationBuckets(
@@ -221,25 +221,25 @@
newConfig.screenHeightDp = 300;
assertFalse("Must not report changes if the diff is small and not handled",
- shouldReportChange(currentConfig, newConfig, buckets,
- CONFIG_FONT_SCALE /* handledConfigChanges */));
+ shouldReportChange(CONFIG_SCREEN_SIZE /* publicDiff */, currentConfig,
+ newConfig, buckets, CONFIG_FONT_SCALE /* handledConfigChanges */));
assertTrue("Must report changes if the small diff is handled",
- shouldReportChange(currentConfig, newConfig, buckets,
- CONFIG_SCREEN_SIZE /* handledConfigChanges */));
+ shouldReportChange(CONFIG_SCREEN_SIZE /* publicDiff */, currentConfig, newConfig,
+ buckets, CONFIG_SCREEN_SIZE /* handledConfigChanges */));
currentConfig.fontScale = 0.8f;
newConfig.fontScale = 1.2f;
assertTrue("Must report handled changes regardless of small unhandled change",
- shouldReportChange(currentConfig, newConfig, buckets,
- CONFIG_FONT_SCALE /* handledConfigChanges */));
+ shouldReportChange(CONFIG_SCREEN_SIZE | CONFIG_FONT_SCALE /* publicDiff */,
+ currentConfig, newConfig, buckets, CONFIG_FONT_SCALE /* handledConfigChanges */));
newConfig.screenHeightDp = 500;
assertFalse("Must not report changes if there's unhandled big changes",
- shouldReportChange(currentConfig, newConfig, buckets,
- CONFIG_FONT_SCALE /* handledConfigChanges */));
+ shouldReportChange(CONFIG_SCREEN_SIZE | CONFIG_FONT_SCALE /* publicDiff */,
+ currentConfig, newConfig, buckets, CONFIG_FONT_SCALE /* handledConfigChanges */));
}
private void recreateAndVerifyNoRelaunch(ActivityThread activityThread, TestActivity activity) {
diff --git a/core/tests/mockingcoretests/src/android/os/BundleRecyclingTest.java b/core/tests/mockingcoretests/src/android/os/BundleRecyclingTest.java
new file mode 100644
index 0000000..7c76498
--- /dev/null
+++ b/core/tests/mockingcoretests/src/android/os/BundleRecyclingTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2022 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.os;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Test for verifying {@link android.os.Bundle} recycles the underlying parcel where needed.
+ *
+ * <p>Build/Install/Run:
+ * atest FrameworksMockingCoreTests:android.os.BundleRecyclingTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class BundleRecyclingTest {
+ private Parcel mParcelSpy;
+ private Bundle mBundle;
+
+ @Before
+ public void setUp() throws Exception {
+ setUpBundle(/* hasLazy */ true);
+ }
+
+ @Test
+ public void bundleClear_whenUnparcelledWithoutLazy_recyclesParcelOnce() {
+ setUpBundle(/* hasLazy */ false);
+ // Will unparcel and immediately recycle parcel
+ assertNotNull(mBundle.getString("key"));
+ verify(mParcelSpy, times(1)).recycle();
+ assertFalse(mBundle.isDefinitelyEmpty());
+
+ // Should not recycle again
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ assertTrue(mBundle.isDefinitelyEmpty());
+ }
+
+ @Test
+ public void bundleClear_whenParcelled_recyclesParcel() {
+ assertTrue(mBundle.isParcelled());
+ verify(mParcelSpy, times(0)).recycle();
+
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ // Should not recycle again
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ }
+
+ @Test
+ public void bundleClear_whenUnparcelledWithLazyValueUnwrapped_recyclesParcel() {
+ // Will unparcel with a lazy value, and immediately unwrap the lazy value,
+ // with no lazy values left at the end of getParcelable
+ assertNotNull(mBundle.getParcelable("key", CustomParcelable.class));
+ verify(mParcelSpy, times(0)).recycle();
+
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ // Should not recycle again
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ }
+
+ @Test
+ public void bundleClear_whenUnparcelledWithLazy_recyclesParcel() {
+ // Will unparcel but keep the CustomParcelable lazy
+ assertFalse(mBundle.isEmpty());
+ verify(mParcelSpy, times(0)).recycle();
+
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ // Should not recycle again
+ mBundle.clear();
+ verify(mParcelSpy, times(1)).recycle();
+ }
+
+ @Test
+ public void bundleClear_whenClearedWithSharedParcel_doesNotRecycleParcel() {
+ Bundle copy = new Bundle();
+ copy.putAll(mBundle);
+
+ mBundle.clear();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ copy.clear();
+ assertTrue(copy.isDefinitelyEmpty());
+
+ verify(mParcelSpy, never()).recycle();
+ }
+
+ @Test
+ public void bundleClear_whenClearedWithCopiedParcel_doesNotRecycleParcel() {
+ // Will unparcel but keep the CustomParcelable lazy
+ assertFalse(mBundle.isEmpty());
+
+ Bundle copy = mBundle.deepCopy();
+ copy.putAll(mBundle);
+
+ mBundle.clear();
+ assertTrue(mBundle.isDefinitelyEmpty());
+
+ copy.clear();
+ assertTrue(copy.isDefinitelyEmpty());
+
+ verify(mParcelSpy, never()).recycle();
+ }
+
+ private void setUpBundle(boolean hasLazy) {
+ AtomicReference<Parcel> parcel = new AtomicReference<>();
+ StaticMockitoSession session = mockitoSession()
+ .strictness(Strictness.STRICT_STUBS)
+ .spyStatic(Parcel.class)
+ .startMocking();
+ doAnswer((Answer<Parcel>) invocationOnSpy -> {
+ Parcel spy = (Parcel) invocationOnSpy.callRealMethod();
+ spyOn(spy);
+ parcel.set(spy);
+ return spy;
+ }).when(() -> Parcel.obtain());
+
+ Bundle bundle = new Bundle();
+ bundle.setClassLoader(getClass().getClassLoader());
+ Parcel p = createBundle(hasLazy);
+ bundle.readFromParcel(p);
+ p.recycle();
+
+ session.finishMocking();
+
+ mParcelSpy = parcel.get();
+ mBundle = bundle;
+ }
+
+ /**
+ * Create a test bundle, parcel it and return the parcel.
+ */
+ private Parcel createBundle(boolean hasLazy) {
+ final Bundle source = new Bundle();
+ if (hasLazy) {
+ source.putParcelable("key", new CustomParcelable(13, "Tiramisu"));
+ } else {
+ source.putString("key", "tiramisu");
+ }
+ return getParcelledBundle(source);
+ }
+
+ /**
+ * Take a bundle, write it to a parcel and return the parcel.
+ */
+ private Parcel getParcelledBundle(Bundle bundle) {
+ final Parcel p = Parcel.obtain();
+ // Don't use p.writeParcelabe(), which would write the creator, which we don't need.
+ bundle.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ return p;
+ }
+
+ private static class CustomParcelable implements Parcelable {
+ public final int integer;
+ public final String string;
+
+ CustomParcelable(int integer, String string) {
+ this.integer = integer;
+ this.string = string;
+ }
+
+ protected CustomParcelable(Parcel in) {
+ integer = in.readInt();
+ string = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(integer);
+ out.writeString(string);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof CustomParcelable)) {
+ return false;
+ }
+ CustomParcelable that = (CustomParcelable) other;
+ return integer == that.integer && string.equals(that.string);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(integer, string);
+ }
+
+ public static final Creator<CustomParcelable> CREATOR = new Creator<CustomParcelable>() {
+ @Override
+ public CustomParcelable createFromParcel(Parcel in) {
+ return new CustomParcelable(in);
+ }
+ @Override
+ public CustomParcelable[] newArray(int size) {
+ return new CustomParcelable[size];
+ }
+ };
+ }
+}
diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java
index 6abe34b..9c36fc3 100644
--- a/graphics/java/android/graphics/RuntimeShader.java
+++ b/graphics/java/android/graphics/RuntimeShader.java
@@ -214,7 +214,7 @@
* uniform shader myShader;
* vec4 main(vec2 canvas_coordinates) {
* // swap the red and blue color channels when sampling from myShader
- * return myShader.sample(canvas_coordinates).bgra;
+ * return myShader.eval(canvas_coordinates).bgra;
* }</pre>
*
* <p>After creating a {@link RuntimeShader} with that program the shader uniform can
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 41791af..2f79cae 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -43,6 +43,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.SystemProperties;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -70,6 +71,8 @@
public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
ActivityEmbeddingComponent {
static final String TAG = "SplitController";
+ static final boolean ENABLE_SHELL_TRANSITIONS =
+ SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
@VisibleForTesting
@GuardedBy("mLock")
@@ -332,6 +335,11 @@
* bounds is large enough for at least one split rule.
*/
private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // TODO(b/207070762): cleanup with legacy app transition
+ // Animation will be handled by WM Shell with Shell transition enabled.
+ return;
+ }
if (!taskContainer.isTaskBoundsInitialized()
|| !taskContainer.isWindowingModeInitialized()) {
// We don't know about the Task bounds/windowingMode yet.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
index dc60bcf..29434f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
@@ -116,7 +116,7 @@
if (taskId <= 0) return null;
try {
return ActivityTaskManager.getService().getTaskSnapshot(
- taskId, isLowResolution);
+ taskId, isLowResolution, false /* takeSnapshotIfNeeded */);
} catch (RemoteException e) {
Log.e(TAG, "Failed to get task snapshot, taskId=" + taskId, e);
return null;
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
index d4298b8..49eca63 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
@@ -25,11 +25,13 @@
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
import com.android.wm.shell.flicker.testapp.Components
+import org.junit.Assert
class SplitScreenHelper(
instrumentation: Instrumentation,
@@ -187,5 +189,23 @@
SystemClock.sleep(GESTURE_STEP_MS)
}
}
+
+ fun createShortcutOnHotseatIfNotExist(
+ taplInstrumentation: LauncherInstrumentation,
+ appName: String
+ ) {
+ taplInstrumentation.workspace
+ .deleteAppIcon(taplInstrumentation.workspace.getHotseatAppIcon(0))
+ val allApps = taplInstrumentation.workspace.switchToAllApps()
+ allApps.freeze()
+ try {
+ val appIconSrc = allApps.getAppIcon(appName)
+ Assert.assertNotNull("Unable to find app icon", appIconSrc)
+ val appIconDest = appIconSrc.dragToHotseat(0)
+ Assert.assertNotNull("Unable to drag app icon on hotseat", appIconDest)
+ } finally {
+ allApps.unfreeze()
+ }
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index c18c8ce..19c573b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -77,11 +77,6 @@
}
}
- /** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
/**
* Checks [pipApp] window remains visible throughout the animation
*/
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index 0333577..c2ed5a1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -28,7 +28,6 @@
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
@@ -116,14 +115,6 @@
override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
/**
- * Checks that the [FlickerComponentName.STATUS_BAR] has the correct position at
- * the start and end of the transition
- */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
- /**
* Checks that all parts of the screen are covered at the start and end of the transition
*
* TODO b/197726599 Prevents all states from being checked
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index 32022ad..4e7a9ff 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -81,11 +81,6 @@
}
/** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- /** {@inheritDoc} */
@FlakyTest(bugId = 197726610)
@Test
override fun pipLayerExpands() = super.pipLayerExpands()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
index f28c1e9..77dcbe0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt
@@ -18,7 +18,6 @@
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -64,11 +63,6 @@
}
}
- /** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
/**
* Checks that the focus changes between the pip menu window and the launcher when clicking the
* dismiss button on pip menu to close the pip window.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index 210b196..b9e72a4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -25,7 +25,6 @@
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -81,10 +80,6 @@
@Test
override fun pipLayerBecomesInvisible() = super.pipLayerBecomesInvisible()
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
/**
* Checks that the focus doesn't change between windows during the transition
*/
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index dc1fdde..32900b1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -168,10 +168,6 @@
}
}
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
companion object {
/**
* Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
index 4935fb0..768c090 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownShelfHeightChangeTest.kt
@@ -17,7 +17,6 @@
package com.android.wm.shell.flicker.pip
import android.view.Surface
-import android.platform.test.annotations.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -26,7 +25,6 @@
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.traces.region.RegionSubject
import org.junit.FixMethodOrder
-import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -80,11 +78,6 @@
current.isHigherOrEqual(previous.region)
}
- /** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
companion object {
/**
* Creates the test configurations.
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index 42af1bb..2c158b8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -29,7 +29,6 @@
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import org.junit.FixMethodOrder
import org.junit.Test
@@ -96,13 +95,6 @@
override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
/**
- * Checks the position of the status bar at the start and end of the transition
- */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
- /**
* Checks that [fixedApp] layer is within [screenBoundsStart] at the start of the transition
*/
@Presubmit
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 83d3fe2..1f078b4 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
@@ -116,10 +116,6 @@
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
@Presubmit
@Test
fun pipWindowInsideDisplay() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
new file mode 100644
index 0000000..60b0f8e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test enter split screen by dragging app icon from taskbar.
+ * This test is only for large screen devices.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromTaskbar`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class EnterSplitScreenByDragFromTaskbar(
+ testSpec: FlickerTestParameter
+) : SplitScreenBase(testSpec) {
+
+ @Before
+ fun before() {
+ Assume.assumeTrue(taplInstrumentation.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ taplInstrumentation.goHome()
+ SplitScreenHelper.createShortcutOnHotseatIfNotExist(
+ taplInstrumentation, secondaryApp.appName
+ )
+ primaryApp.launchViaIntent(wmHelper)
+ }
+ }
+ transitions {
+ taplInstrumentation.launchedAppState.taskbar
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(
+ secondaryApp.`package`,
+ primaryApp.`package`
+ )
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp.component)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ testSpec.endRotation, primaryApp.component, false /* splitLeftTop */
+ )
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
+ testSpec.endRotation, secondaryApp.component, true /* splitLeftTop */
+ )
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() =
+ testSpec.appWindowBecomesVisible(secondaryApp.component)
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+ )
+ }
+ }
+}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index c2be2ef..d7f3338 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -2320,6 +2320,10 @@
*/
public final void start() {
native_start();
+ synchronized(mBufferLock) {
+ cacheBuffers(true /* input */);
+ cacheBuffers(false /* input */);
+ }
}
private native final void native_start();
@@ -3951,9 +3955,6 @@
+ "objects and attach to QueueRequest objects.");
}
if (mCachedInputBuffers == null) {
- cacheBuffers(true /* input */);
- }
- if (mCachedInputBuffers == null) {
throw new IllegalStateException();
}
// FIXME: check codec status
@@ -3992,9 +3993,6 @@
+ "Please use getOutputFrame to get output frames.");
}
if (mCachedOutputBuffers == null) {
- cacheBuffers(false /* input */);
- }
- if (mCachedOutputBuffers == null) {
throw new IllegalStateException();
}
// FIXME: check codec status
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 9a7ce75..861d84d7 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -125,9 +125,9 @@
<string name="bluetooth_profile_a2dp_high_quality" msgid="4739440941324792775">"صدای HD: <xliff:g id="CODEC_NAME">%1$s</xliff:g>"</string>
<string name="bluetooth_profile_a2dp_high_quality_unknown_codec" msgid="2477639096903834374">"صدای HD"</string>
<string name="bluetooth_profile_hearing_aid" msgid="58154575573984914">"سمعک"</string>
- <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"صدای LE"</string>
+ <string name="bluetooth_profile_le_audio" msgid="3237854988278539061">"صدای کممصرف"</string>
<string name="bluetooth_hearing_aid_profile_summary_connected" msgid="8191273236809964030">"به سمعک متصل شد"</string>
- <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"متصل به صدای LE"</string>
+ <string name="bluetooth_le_audio_profile_summary_connected" msgid="6916226974453480650">"به «صدای کممصرف» وصل است"</string>
<string name="bluetooth_a2dp_profile_summary_connected" msgid="7422607970115444153">"به رسانه صوتی متصل شد"</string>
<string name="bluetooth_headset_profile_summary_connected" msgid="2420981566026949688">"به تلفن صوتی متصل شد"</string>
<string name="bluetooth_opp_profile_summary_connected" msgid="2393521801478157362">"به سرور انتقال فایل متصل شد"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index a3af304..8d9a619 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -184,7 +184,7 @@
<string name="launch_defaults_some" msgid="3631650616557252926">"Algumas predefinições definidas"</string>
<string name="launch_defaults_none" msgid="8049374306261262709">"Nenhuma predefinição definida"</string>
<string name="tts_settings" msgid="8130616705989351312">"Definições de texto para voz"</string>
- <string name="tts_settings_title" msgid="7602210956640483039">"Saída de síntese de voz"</string>
+ <string name="tts_settings_title" msgid="7602210956640483039">"Saída de conversão de texto em voz"</string>
<string name="tts_default_rate_title" msgid="3964187817364304022">"Taxa de voz"</string>
<string name="tts_default_rate_summary" msgid="3781937042151716987">"Velocidade a que o texto é falado"</string>
<string name="tts_default_pitch_title" msgid="6988592215554485479">"Tonalidade"</string>
@@ -194,12 +194,12 @@
<string name="tts_lang_not_selected" msgid="7927823081096056147">"Idioma não selecionado"</string>
<string name="tts_default_lang_summary" msgid="9042620014800063470">"Define a voz do idioma específico para o texto lido"</string>
<string name="tts_play_example_title" msgid="1599468547216481684">"Ouvir um exemplo"</string>
- <string name="tts_play_example_summary" msgid="634044730710636383">"Reproduzir uma breve demonstração de síntese de voz"</string>
+ <string name="tts_play_example_summary" msgid="634044730710636383">"Reproduzir uma breve demonstração de conversão de texto em voz"</string>
<string name="tts_install_data_title" msgid="1829942496472751703">"Instalar dados de voz"</string>
- <string name="tts_install_data_summary" msgid="3608874324992243851">"Instalar os dados de voz necessários para a síntese de voz"</string>
+ <string name="tts_install_data_summary" msgid="3608874324992243851">"Instalar os dados de voz necessários para a conversão de texto em voz"</string>
<string name="tts_engine_security_warning" msgid="3372432853837988146">"Este motor de síntese de discurso pode permitir a recolha de todo o texto que será falado, incluindo dados pessoais, como palavras-passe e números de cartão de crédito. O serviço é fornecido com o motor <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g>. Permitir a utilização deste motor de síntese de discurso?"</string>
- <string name="tts_engine_network_required" msgid="8722087649733906851">"Este idioma requer uma ligação de rede ativa para uma saída de síntese de voz."</string>
- <string name="tts_default_sample_string" msgid="6388016028292967973">"Exemplo de síntese de voz."</string>
+ <string name="tts_engine_network_required" msgid="8722087649733906851">"Este idioma requer uma ligação de rede ativa para uma saída de conversão de texto em voz."</string>
+ <string name="tts_default_sample_string" msgid="6388016028292967973">"Exemplo de conversão de texto em voz."</string>
<string name="tts_status_title" msgid="8190784181389278640">"Estado do idioma predefinido"</string>
<string name="tts_status_ok" msgid="8583076006537547379">"<xliff:g id="LOCALE">%1$s</xliff:g> é totalmente suportado"</string>
<string name="tts_status_requires_network" msgid="8327617638884678896">"<xliff:g id="LOCALE">%1$s</xliff:g> necessita de ligação de rede"</string>
diff --git a/packages/SettingsLib/res/values-te/arrays.xml b/packages/SettingsLib/res/values-te/arrays.xml
index 52554e2..a5b9f79 100644
--- a/packages/SettingsLib/res/values-te/arrays.xml
+++ b/packages/SettingsLib/res/values-te/arrays.xml
@@ -151,9 +151,9 @@
</string-array>
<string-array name="bluetooth_audio_active_device_summaries">
<item msgid="8019740759207729126"></item>
- <item msgid="204248102837117183">", సక్రియంగా ఉంది"</item>
- <item msgid="253388653486517049">", (మీడియా) సక్రియంగా ఉంది"</item>
- <item msgid="5001852592115448348">", (ఫోన్) సక్రియంగా ఉంది"</item>
+ <item msgid="204248102837117183">", యాక్టివ్గా ఉంది"</item>
+ <item msgid="253388653486517049">", (మీడియా) యాక్టివ్గా ఉంది"</item>
+ <item msgid="5001852592115448348">", (ఫోన్) యాక్టివ్గా ఉంది"</item>
</string-array>
<string-array name="select_logd_size_titles">
<item msgid="1191094707770726722">"ఆఫ్"</item>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index da0054b..f89775f 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -291,14 +291,14 @@
<string name="bluetooth_select_map_version_string" msgid="526308145174175327">"బ్లూటూత్ MAP వెర్షన్"</string>
<string name="bluetooth_select_map_version_dialog_title" msgid="7085934373987428460">"బ్లూటూత్ MAP వెర్షన్ను ఎంచుకోండి"</string>
<string name="bluetooth_select_a2dp_codec_type" msgid="952001408455456494">"బ్లూటూత్ ఆడియో కోడెక్"</string>
- <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"బ్లూటూత్ ఆడియో కోడెక్ని సక్రియం చేయండి\nఎంపిక"</string>
+ <string name="bluetooth_select_a2dp_codec_type_dialog_title" msgid="7510542404227225545">"బ్లూటూత్ ఆడియో కోడెక్ని యాక్టివేట్ చేయండి\nఎంపిక"</string>
<string name="bluetooth_select_a2dp_codec_sample_rate" msgid="1638623076480928191">"బ్లూటూత్ ఆడియో శాంపిల్ రేట్"</string>
- <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"బ్లూటూత్ ఆడియో కోడెక్ని సక్రియం చేయండి\nఎంపిక: నమూనా రేట్"</string>
+ <string name="bluetooth_select_a2dp_codec_sample_rate_dialog_title" msgid="5876305103137067798">"బ్లూటూత్ ఆడియో కోడెక్ని యాక్టివేట్ చేయండి\nఎంపిక: నమూనా రేట్"</string>
<string name="bluetooth_select_a2dp_codec_type_help_info" msgid="8647200416514412338">"గ్రే-అవుట్ అంటే ఫోన్ లేదా హెడ్సెట్ మద్దతు లేదు అని అర్ధం"</string>
<string name="bluetooth_select_a2dp_codec_bits_per_sample" msgid="6253965294594390806">"ఒక్కో శాంపిల్కు బ్లూటూత్ ఆడియో బిట్లు"</string>
- <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4898693684282596143">"బ్లూటూత్ ఆడియో కోడెక్ని సక్రియం చేయండి\nఎంపిక: ఒక్కో నమూనాలో బిట్లు"</string>
+ <string name="bluetooth_select_a2dp_codec_bits_per_sample_dialog_title" msgid="4898693684282596143">"బ్లూటూత్ ఆడియో కోడెక్ని యాక్టివేట్ చేయండి\nఎంపిక: ఒక్కో నమూనాలో బిట్లు"</string>
<string name="bluetooth_select_a2dp_codec_channel_mode" msgid="364277285688014427">"బ్లూటూత్ ఆడియో ఛానెల్ మోడ్"</string>
- <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="2076949781460359589">"బ్లూటూత్ ఆడియో కోడెక్ని సక్రియం చేయండి\nఎంపిక: ఛానెల్ మోడ్"</string>
+ <string name="bluetooth_select_a2dp_codec_channel_mode_dialog_title" msgid="2076949781460359589">"బ్లూటూత్ ఆడియో కోడెక్ని యాక్టివేట్ చేయండి\nఎంపిక: ఛానెల్ మోడ్"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality" msgid="3233402355917446304">"బ్లూటూత్ ఆడియో LDAC కోడెక్: ప్లేబ్యాక్ క్వాలిటీ"</string>
<string name="bluetooth_select_a2dp_codec_ldac_playback_quality_dialog_title" msgid="7274396574659784285">"బ్లూటూత్ ఆడియో LDAC యాక్టివ్ చేయండి\nకోడెక్ ఎంపిక: ప్లేబ్యాక్ క్వాలిటీ"</string>
<string name="bluetooth_select_a2dp_codec_streaming_label" msgid="2040810756832027227">"ప్రసారం చేస్తోంది: <xliff:g id="STREAMING_PARAMETER">%1$s</xliff:g>"</string>
@@ -423,11 +423,11 @@
<string-array name="color_mode_descriptions">
<item msgid="6828141153199944847">"మెరుగైన రంగులు"</item>
<item msgid="4548987861791236754">"కంటికి కనిపించే విధంగా సహజమైన రంగులు"</item>
- <item msgid="1282170165150762976">"డిజిటల్ కంటెంట్ కోసం అనుకూలీకరించిన రంగులు"</item>
+ <item msgid="1282170165150762976">"డిజిటల్ కంటెంట్ కోసం అనుకూలంగా మార్చిన రంగులు"</item>
</string-array>
<string name="inactive_apps_title" msgid="5372523625297212320">"స్టాండ్బై యాప్లు"</string>
<string name="inactive_app_inactive_summary" msgid="3161222402614236260">"నిష్క్రియంగా ఉంది. టోగుల్ చేయడానికి నొక్కండి."</string>
- <string name="inactive_app_active_summary" msgid="8047630990208722344">"సక్రియంగా ఉంది. టోగుల్ చేయడానికి నొక్కండి."</string>
+ <string name="inactive_app_active_summary" msgid="8047630990208722344">"యాక్టివ్గా ఉంది. టోగుల్ చేయడానికి నొక్కండి."</string>
<string name="standby_bucket_summary" msgid="5128193447550429600">"యాప్ స్టాండ్బై స్థితి:<xliff:g id="BUCKET"> %s</xliff:g>"</string>
<string name="transcode_settings_title" msgid="2581975870429850549">"మీడియా ట్రాన్స్కోడింగ్ సెట్టింగ్లు"</string>
<string name="transcode_user_control" msgid="6176368544817731314">"ట్రాన్స్కోడింగ్ ఆటోమేటిక్ సెట్టింగ్లను ఓవర్రైడ్ చేయండి"</string>
@@ -570,7 +570,7 @@
<string name="user_add_user_item_title" msgid="2394272381086965029">"యూజర్"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"పరిమితం చేయబడిన ప్రొఫైల్"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"కొత్త యూజర్ను జోడించాలా?"</string>
- <string name="user_add_user_message_long" msgid="1527434966294733380">"అదనపు యూజర్లను క్రియేట్ చేయడం ద్వారా మీరు ఈ పరికరాన్ని ఇతరులతో షేర్ చేయవచ్చు. ప్రతి యూజర్కు వారికంటూ ప్రత్యేక స్థలం ఉంటుంది, వారు ఆ స్థలాన్ని యాప్లు, వాల్పేపర్ మొదలైనవాటితో అనుకూలీకరించవచ్చు. యూజర్లు ప్రతి ఒక్కరిపై ప్రభావం చూపే Wi‑Fi వంటి పరికర సెట్టింగ్లను కూడా సర్దుబాటు చేయవచ్చు.\n\nమీరు కొత్త యూజర్ను జోడించినప్పుడు, ఆ వ్యక్తి వారికంటూ స్వంత స్థలం సెట్ చేసుకోవాలి.\n\nఏ యూజర్ అయినా మిగిలిన యూజర్లందరి కోసం యాప్లను అప్డేట్ చేయవచ్చు. యాక్సెసిబిలిటీ సెట్టింగ్లు, సర్వీస్లు కొత్త యూజర్కి బదిలీ కాకపోవచ్చు."</string>
+ <string name="user_add_user_message_long" msgid="1527434966294733380">"అదనపు యూజర్లను క్రియేట్ చేయడం ద్వారా మీరు ఈ పరికరాన్ని ఇతరులతో షేర్ చేయవచ్చు. ప్రతి యూజర్కు వారికంటూ ప్రత్యేక స్థలం ఉంటుంది, వారు ఆ స్థలాన్ని యాప్లు, వాల్పేపర్ మొదలైనవాటితో అనుకూలంగా మార్చవచ్చు. యూజర్లు ప్రతి ఒక్కరిపై ప్రభావం చూపే Wi‑Fi వంటి పరికర సెట్టింగ్లను కూడా సర్దుబాటు చేయవచ్చు.\n\nమీరు కొత్త యూజర్ను జోడించినప్పుడు, ఆ వ్యక్తి వారికంటూ స్వంత స్థలం సెట్ చేసుకోవాలి.\n\nఏ యూజర్ అయినా మిగిలిన యూజర్లందరి కోసం యాప్లను అప్డేట్ చేయవచ్చు. యాక్సెసిబిలిటీ సెట్టింగ్లు, సర్వీస్లు కొత్త యూజర్కి బదిలీ కాకపోవచ్చు."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"మీరు కొత్త యూజర్ను జోడించినప్పుడు, ఆ వ్యక్తి తన స్పేస్ను సెటప్ చేసుకోవాలి.\n\nఏ యూజర్ అయినా మిగతా యూజర్ల కోసం యాప్లను అప్డేట్ చేయగలరు."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"యూజర్ను ఇప్పుడే సెటప్ చేయాలా?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"పరికరాన్ని తీసుకోవడానికి వ్యక్తి అందుబాటులో ఉన్నారని నిర్ధారించుకొని, ఆపై వారికి నిల్వ స్థలాన్ని సెటప్ చేయండి"</string>
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 3bd6d51..f9a9ef6 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -52,6 +52,17 @@
]
},
{
+ "name": "SystemUIGoogleScreenshotTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
// Permission indicators
"name": "CtsPermission4TestCases",
"options": [
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt b/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt
index 1ad5484..c58c162 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt
@@ -17,8 +17,12 @@
package com.android.systemui
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@@ -29,14 +33,37 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import kotlin.math.roundToInt
/**
* This is an example Compose feature, which shows a text and a count that is incremented when
- * clicked.
+ * clicked. We also show the max width available to this component, which is displayed either next
+ * to or below the text depending on that max width.
*/
@Composable
fun ExampleFeature(text: String, modifier: Modifier = Modifier) {
+ BoxWithConstraints(modifier) {
+ val maxWidth = maxWidth
+ if (maxWidth < 600.dp) {
+ Column {
+ CounterTile(text)
+ Spacer(Modifier.size(16.dp))
+ MaxWidthTile(maxWidth)
+ }
+ } else {
+ Row {
+ CounterTile(text)
+ Spacer(Modifier.size(16.dp))
+ MaxWidthTile(maxWidth)
+ }
+ }
+ }
+}
+
+@Composable
+private fun CounterTile(text: String, modifier: Modifier = Modifier) {
Surface(
modifier,
color = MaterialTheme.colorScheme.primaryContainer,
@@ -51,3 +78,17 @@
}
}
}
+
+@Composable
+private fun MaxWidthTile(maxWidth: Dp, modifier: Modifier = Modifier) {
+ Surface(
+ modifier,
+ color = MaterialTheme.colorScheme.tertiaryContainer,
+ shape = RoundedCornerShape(28.dp),
+ ) {
+ Text(
+ "The max available width to me is: ${maxWidth.value.roundToInt()}dp",
+ Modifier.padding(16.dp)
+ )
+ }
+}
diff --git a/packages/SystemUI/compose/gallery/Android.bp b/packages/SystemUI/compose/gallery/Android.bp
index 030ad85..bddc1e6 100644
--- a/packages/SystemUI/compose/gallery/Android.bp
+++ b/packages/SystemUI/compose/gallery/Android.bp
@@ -52,6 +52,7 @@
android_app {
name: "SystemUIComposeGallery",
defaults: ["platform_app_defaults"],
+ manifest: "app/AndroidManifest.xml",
static_libs: [
"SystemUIComposeGalleryLib",
diff --git a/packages/SystemUI/compose/gallery/AndroidManifest.xml b/packages/SystemUI/compose/gallery/AndroidManifest.xml
index 8113263..562e1cc 100644
--- a/packages/SystemUI/compose/gallery/AndroidManifest.xml
+++ b/packages/SystemUI/compose/gallery/AndroidManifest.xml
@@ -17,21 +17,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.systemui.compose.gallery">
- <application
- android:allowBackup="true"
- android:icon="@mipmap/ic_launcher"
- android:label="@string/app_name"
- android:roundIcon="@mipmap/ic_launcher_round"
- android:supportsRtl="true"
- android:theme="@style/Theme.SystemUIGallery">
- <activity
- android:name=".GalleryActivity"
- android:exported="true"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
+
</manifest>
diff --git a/packages/SystemUI/compose/gallery/app/AndroidManifest.xml b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml
new file mode 100644
index 0000000..8113263
--- /dev/null
+++ b/packages/SystemUI/compose/gallery/app/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.compose.gallery">
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.SystemUIGallery">
+ <activity
+ android:name=".GalleryActivity"
+ android:exported="true"
+ android:label="@string/app_name">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt
index 652c403..6e17214 100644
--- a/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt
+++ b/packages/SystemUI/compose/gallery/src/com/android/systemui/compose/gallery/ExampleFeatureScreen.kt
@@ -18,10 +18,11 @@
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
import com.android.systemui.ExampleFeature
/** The screen that shows ExampleFeature. */
@Composable
-fun ExampleFeatureScreen() {
- Column { ExampleFeature("This is an example feature!") }
+fun ExampleFeatureScreen(modifier: Modifier = Modifier) {
+ Column(modifier) { ExampleFeature("This is an example feature!") }
}
diff --git a/packages/SystemUI/compose/testing/Android.bp b/packages/SystemUI/compose/testing/Android.bp
new file mode 100644
index 0000000..293e51f
--- /dev/null
+++ b/packages/SystemUI/compose/testing/Android.bp
@@ -0,0 +1,43 @@
+// Copyright (C) 2022 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+ name: "SystemUIComposeTesting",
+ manifest: "AndroidManifest.xml",
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "SystemUIComposeCore",
+ "SystemUIScreenshotLib",
+
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.material3_material3",
+ "androidx.compose.ui_ui-test-junit4",
+ "androidx.compose.ui_ui-test-manifest",
+ ],
+
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SystemUI/compose/testing/AndroidManifest.xml b/packages/SystemUI/compose/testing/AndroidManifest.xml
new file mode 100644
index 0000000..b1f7c3b
--- /dev/null
+++ b/packages/SystemUI/compose/testing/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.systemui.testing.compose">
+ <application
+ android:appComponentFactory="androidx.core.app.AppComponentFactory"
+ tools:replace="android:appComponentFactory">
+ </application>
+</manifest>
diff --git a/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
new file mode 100644
index 0000000..7a0b1f1
--- /dev/null
+++ b/packages/SystemUI/compose/testing/src/com/android/systemui/testing/compose/ComposeScreenshotTestRule.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 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.testing.compose
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.ViewRootForTest
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onRoot
+import com.android.systemui.compose.theme.SystemUITheme
+import com.android.systemui.testing.screenshot.ScreenshotActivity
+import com.android.systemui.testing.screenshot.ScreenshotTestRule
+import com.android.systemui.testing.screenshot.ScreenshotTestSpec
+import org.junit.rules.RuleChain
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/** A rule for Compose screenshot diff tests. */
+class ComposeScreenshotTestRule(testSpec: ScreenshotTestSpec) : TestRule {
+ private val composeRule = createAndroidComposeRule<ScreenshotActivity>()
+ private val screenshotRule = ScreenshotTestRule(testSpec)
+
+ private val delegate = RuleChain.outerRule(screenshotRule).around(composeRule)
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return delegate.apply(base, description)
+ }
+
+ /**
+ * Compare [content] with the golden image identified by [goldenIdentifier] in the context of
+ * [testSpec].
+ */
+ fun screenshotTest(
+ goldenIdentifier: String,
+ content: @Composable () -> Unit,
+ ) {
+ // Make sure that the activity draws full screen and fits the whole display instead of the
+ // system bars.
+ val activity = composeRule.activity
+ activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) }
+
+ // Set the content using the AndroidComposeRule to make sure that the Activity is set up
+ // correctly.
+ composeRule.setContent {
+ SystemUITheme {
+ Surface(
+ color = MaterialTheme.colorScheme.background,
+ ) {
+ content()
+ }
+ }
+ }
+ composeRule.waitForIdle()
+
+ val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
+ screenshotRule.screenshotTest(goldenIdentifier, view)
+ }
+}
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 0e20fa3..9c49607 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -42,7 +42,6 @@
android:adjustViewBounds="true"
android:clipToOutline="true"
android:background="@drawable/qs_media_outline_album_bg"
- android:foreground="@drawable/qs_media_scrim"
/>
<!-- Guideline for output switcher -->
diff --git a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
index 5e8b892..e079fd3 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
@@ -14,20 +14,25 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<!-- TODO(b/203800646): layout_marginTop doesn't seem to work on some large screens. -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/media_ttt_receiver_chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="@drawable/media_ttt_chip_background_receiver"
>
+ <com.android.systemui.media.taptotransfer.receiver.ReceiverChipRippleView
+ android:id="@+id/ripple"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+
<com.android.internal.widget.CachingIconView
android:id="@+id/app_icon"
android:layout_width="@dimen/media_ttt_icon_size_receiver"
android:layout_height="@dimen/media_ttt_icon_size_receiver"
- android:layout_gravity="center"
+ android:layout_gravity="center|bottom"
+ android:alpha="0.0"
/>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/wireless_charging_layout.xml b/packages/SystemUI/res/layout/wireless_charging_layout.xml
index 1312b41..887e3e7 100644
--- a/packages/SystemUI/res/layout/wireless_charging_layout.xml
+++ b/packages/SystemUI/res/layout/wireless_charging_layout.xml
@@ -22,7 +22,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <com.android.systemui.statusbar.charging.ChargingRippleView
+ <com.android.systemui.ripple.RippleView
android:id="@+id/wireless_charging_ripple"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index bde1262..13250c8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1048,6 +1048,7 @@
<!-- Since the generic icon isn't circular, we need to scale it down so it still fits within
the circular chip. -->
<dimen name="media_ttt_generic_icon_size_receiver">70dp</dimen>
+ <dimen name="media_ttt_receiver_vert_translation">20dp</dimen>
<!-- Window magnification -->
<dimen name="magnification_border_drag_size">35dp</dimen>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index be3dfdc..c5beaa7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -137,12 +137,14 @@
}
/**
- * @return the task snapshot for the given {@param taskId}.
+ * @return a {@link ThumbnailData} with {@link TaskSnapshot} for the given {@param taskId}.
+ * The snapshot will be triggered if no cached {@link TaskSnapshot} exists.
*/
public @NonNull ThumbnailData getTaskThumbnail(int taskId, boolean isLowResolution) {
TaskSnapshot snapshot = null;
try {
- snapshot = getService().getTaskSnapshot(taskId, isLowResolution);
+ snapshot = getService().getTaskSnapshot(taskId, isLowResolution,
+ true /* takeSnapshotIfNeeded */);
} catch (RemoteException e) {
Log.w(TAG, "Failed to retrieve task snapshot", e);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
deleted file mode 100644
index e0b11d8..0000000
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2020 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.keyguard;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.graphics.Color;
-import android.icu.text.NumberFormat;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.settingslib.Utils;
-import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.clocks.AnimatableClockView;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.util.ViewController;
-
-import java.io.PrintWriter;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.TimeZone;
-
-/**
- * Controller for an AnimatableClockView on the keyguard. Instantiated by
- * {@link KeyguardClockSwitchController}.
- */
-public class AnimatableClockController extends ViewController<AnimatableClockView> {
- private static final String TAG = "AnimatableClockCtrl";
- private static final int FORMAT_NUMBER = 1234567890;
-
- private final StatusBarStateController mStatusBarStateController;
- private final BroadcastDispatcher mBroadcastDispatcher;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final BatteryController mBatteryController;
- private final int mDozingColor = Color.WHITE;
- private int mLockScreenColor;
-
- private boolean mIsDozing;
- private boolean mIsCharging;
- private float mDozeAmount;
- boolean mKeyguardShowing;
- private Locale mLocale;
-
- private final NumberFormat mBurmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"));
- private final String mBurmeseNumerals;
- private final float mBurmeseLineSpacing;
- private final float mDefaultLineSpacing;
-
- public AnimatableClockController(
- AnimatableClockView view,
- StatusBarStateController statusBarStateController,
- BroadcastDispatcher broadcastDispatcher,
- BatteryController batteryController,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- @Main Resources resources
- ) {
- super(view);
- mStatusBarStateController = statusBarStateController;
- mBroadcastDispatcher = broadcastDispatcher;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mBatteryController = batteryController;
-
- mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER);
- mBurmeseLineSpacing = resources.getFloat(
- R.dimen.keyguard_clock_line_spacing_scale_burmese);
- mDefaultLineSpacing = resources.getFloat(
- R.dimen.keyguard_clock_line_spacing_scale);
- }
-
- private void reset() {
- mView.animateDoze(mIsDozing, false);
- }
-
- private final BatteryController.BatteryStateChangeCallback mBatteryCallback =
- new BatteryController.BatteryStateChangeCallback() {
- @Override
- public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
- if (mKeyguardShowing && !mIsCharging && charging) {
- mView.animateCharge(mStatusBarStateController::isDozing);
- }
- mIsCharging = charging;
- }
- };
-
- private final BroadcastReceiver mLocaleBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- updateLocale();
- }
- };
-
- private final StatusBarStateController.StateListener mStatusBarStateListener =
- new StatusBarStateController.StateListener() {
- @Override
- public void onDozeAmountChanged(float linear, float eased) {
- boolean noAnimation = (mDozeAmount == 0f && linear == 1f)
- || (mDozeAmount == 1f && linear == 0f);
- boolean isDozing = linear > mDozeAmount;
- mDozeAmount = linear;
- if (mIsDozing != isDozing) {
- mIsDozing = isDozing;
- mView.animateDoze(mIsDozing, !noAnimation);
- }
- }
- };
-
- private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
- new KeyguardUpdateMonitorCallback() {
- @Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mKeyguardShowing = showing;
- if (!mKeyguardShowing) {
- // reset state (ie: after weight animations)
- reset();
- }
- }
-
- @Override
- public void onTimeFormatChanged(String timeFormat) {
- mView.refreshFormat();
- }
-
- @Override
- public void onTimeZoneChanged(TimeZone timeZone) {
- mView.onTimeZoneChanged(timeZone);
- }
-
- @Override
- public void onUserSwitchComplete(int userId) {
- mView.refreshFormat();
- }
- };
-
- @Override
- protected void onInit() {
- mIsDozing = mStatusBarStateController.isDozing();
- }
-
- @Override
- protected void onViewAttached() {
- updateLocale();
- mBroadcastDispatcher.registerReceiver(mLocaleBroadcastReceiver,
- new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
- mDozeAmount = mStatusBarStateController.getDozeAmount();
- mIsDozing = mStatusBarStateController.isDozing() || mDozeAmount != 0;
- mBatteryController.addCallback(mBatteryCallback);
- mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
-
- mStatusBarStateController.addCallback(mStatusBarStateListener);
-
- refreshTime();
- initColors();
- mView.animateDoze(mIsDozing, false);
- }
-
- @Override
- protected void onViewDetached() {
- mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver);
- mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
- mBatteryController.removeCallback(mBatteryCallback);
- mStatusBarStateController.removeCallback(mStatusBarStateListener);
- }
-
- /** Animate the clock appearance */
- public void animateAppear() {
- if (!mIsDozing) mView.animateAppearOnLockscreen();
- }
-
- /** Animate the clock appearance when a foldable device goes from fully-open/half-open state to
- * fully folded state and it goes to sleep (always on display screen) */
- public void animateFoldAppear() {
- mView.animateFoldAppear(true);
- }
-
- /**
- * Updates the time for the view.
- */
- public void refreshTime() {
- mView.refreshTime();
- }
-
- /**
- * Return locallly stored dozing state.
- */
- @VisibleForTesting
- public boolean isDozing() {
- return mIsDozing;
- }
-
- private void updateLocale() {
- Locale currLocale = Locale.getDefault();
- if (!Objects.equals(currLocale, mLocale)) {
- mLocale = currLocale;
- NumberFormat nf = NumberFormat.getInstance(mLocale);
- if (nf.format(FORMAT_NUMBER).equals(mBurmeseNumerals)) {
- mView.setLineSpacingScale(mBurmeseLineSpacing);
- } else {
- mView.setLineSpacingScale(mDefaultLineSpacing);
- }
- mView.refreshFormat();
- }
- }
-
- private void initColors() {
- mLockScreenColor = Utils.getColorAttrDefaultColor(getContext(),
- com.android.systemui.R.attr.wallpaperTextColorAccent);
- mView.setColors(mDozingColor, mLockScreenColor);
- mView.animateDoze(mIsDozing, false);
- }
-
- /**
- * Dump information for debugging
- */
- public void dump(@NonNull PrintWriter pw) {
- pw.println(this);
- mView.dump(pw);
- }
-}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index a783e1c..de5de32 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -3627,6 +3627,10 @@
mHandler.sendEmptyMessage(MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED);
}
+ /**
+ * @return true when the screen is on (including when a screensaver is showing),
+ * false when the screen is OFF or DOZE (including showing AOD UI)
+ */
public boolean isDeviceInteractive() {
return mDeviceInteractive;
}
@@ -3769,6 +3773,8 @@
pw.println(" mFingerprintLockedOut=" + mFingerprintLockedOut);
pw.println(" mFingerprintLockedOutPermanent=" + mFingerprintLockedOutPermanent);
pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId));
+ pw.println(" mKeyguardOccluded=" + mKeyguardOccluded);
+ pw.println(" mIsDreaming=" + mIsDreaming);
if (isUdfpsSupported()) {
pw.println(" udfpsEnrolled=" + isUdfpsEnrolled());
pw.println(" shouldListenForUdfps=" + shouldListenForFingerprint(true));
diff --git a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
index d2c229b..a3351e1 100644
--- a/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
+++ b/packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt
@@ -47,7 +47,8 @@
open class DisplayCutoutBaseView : View, RegionInterceptableView {
private var shouldDrawCutout: Boolean = DisplayCutout.getFillBuiltInDisplayCutout(
- context.resources, context.display?.uniqueId)
+ context.resources, context.display?.uniqueId
+ )
private var displayUniqueId: String? = null
private var displayMode: Display.Mode? = null
protected val location = IntArray(2)
@@ -74,8 +75,8 @@
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
- constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
- : super(context, attrs, defStyleAttr)
+ constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :
+ super(context, attrs, defStyleAttr)
override fun onAttachedToWindow() {
super.onAttachedToWindow()
@@ -85,7 +86,7 @@
onUpdate()
}
- fun onDisplayChanged(displayId: Int) {
+ fun onDisplayChanged(newDisplayUniqueId: String?) {
val oldMode: Display.Mode? = displayMode
val display: Display? = context.display
displayMode = display?.mode
@@ -93,7 +94,8 @@
if (displayUniqueId != display?.uniqueId) {
displayUniqueId = display?.uniqueId
shouldDrawCutout = DisplayCutout.getFillBuiltInDisplayCutout(
- context.resources, displayUniqueId)
+ context.resources, displayUniqueId
+ )
}
// Skip if display mode or cutout hasn't changed.
@@ -101,7 +103,7 @@
display?.cutout == displayInfo.displayCutout) {
return
}
- if (displayId == display?.displayId) {
+ if (newDisplayUniqueId == display?.uniqueId) {
updateCutout()
updateProtectionBoundingPath()
onUpdate()
@@ -147,8 +149,9 @@
cutoutBounds.translate(-location[0], -location[1])
// Intersect with window's frame
- cutoutBounds.op(rootView.left, rootView.top, rootView.right, rootView.bottom,
- Region.Op.INTERSECT)
+ cutoutBounds.op(
+ rootView.left, rootView.top, rootView.right, rootView.bottom, Region.Op.INTERSECT
+ )
return cutoutBounds
}
@@ -171,9 +174,12 @@
protected open fun drawCutoutProtection(canvas: Canvas) {
if (cameraProtectionProgress > HIDDEN_CAMERA_PROTECTION_SCALE &&
- !protectionRect.isEmpty) {
- canvas.scale(cameraProtectionProgress, cameraProtectionProgress,
- protectionRect.centerX(), protectionRect.centerY())
+ !protectionRect.isEmpty
+ ) {
+ canvas.scale(
+ cameraProtectionProgress, cameraProtectionProgress, protectionRect.centerX(),
+ protectionRect.centerY()
+ )
canvas.drawPath(protectionPath, paint)
}
}
@@ -205,14 +211,17 @@
requestLayout()
}
cameraProtectionAnimator?.cancel()
- cameraProtectionAnimator = ValueAnimator.ofFloat(cameraProtectionProgress,
- if (showProtection) 1.0f else HIDDEN_CAMERA_PROTECTION_SCALE).setDuration(750)
+ cameraProtectionAnimator = ValueAnimator.ofFloat(
+ cameraProtectionProgress,
+ if (showProtection) 1.0f else HIDDEN_CAMERA_PROTECTION_SCALE
+ ).setDuration(750)
cameraProtectionAnimator?.interpolator = Interpolators.DECELERATE_QUINT
- cameraProtectionAnimator?.addUpdateListener(ValueAnimator.AnimatorUpdateListener {
- animation: ValueAnimator ->
- cameraProtectionProgress = animation.animatedValue as Float
- invalidate()
- })
+ cameraProtectionAnimator?.addUpdateListener(
+ ValueAnimator.AnimatorUpdateListener { animation: ValueAnimator ->
+ cameraProtectionProgress = animation.animatedValue as Float
+ invalidate()
+ }
+ )
cameraProtectionAnimator?.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
cameraProtectionAnimator = null
@@ -245,8 +254,10 @@
// Apply rotation.
val lw: Int = displayInfo.logicalWidth
val lh: Int = displayInfo.logicalHeight
- val flipped = (displayInfo.rotation == Surface.ROTATION_90 ||
- displayInfo.rotation == Surface.ROTATION_270)
+ val flipped = (
+ displayInfo.rotation == Surface.ROTATION_90 ||
+ displayInfo.rotation == Surface.ROTATION_270
+ )
val dw = if (flipped) lh else lw
val dh = if (flipped) lw else lh
transformPhysicalToLogicalCoordinates(displayInfo.rotation, dw, dh, m)
@@ -275,7 +286,7 @@
// We purposely ignore refresh rate and id changes here, because we don't need to
// invalidate for those, and they can trigger the refresh rate to increase
return oldMode?.physicalHeight != newMode?.physicalHeight ||
- oldMode?.physicalWidth != newMode?.physicalWidth
+ oldMode?.physicalWidth != newMode?.physicalWidth
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index f99293a..aaaa3f7 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -32,7 +32,6 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
-import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
@@ -92,10 +91,8 @@
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
import java.util.Objects;
-import java.util.Set;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -448,6 +445,7 @@
}
}
+ boolean needToUpdateProviderViews = false;
final String newUniqueId = mDisplayInfo.uniqueId;
if (!Objects.equals(newUniqueId, mDisplayUniqueId)) {
mDisplayUniqueId = newUniqueId;
@@ -470,8 +468,7 @@
updateHwLayerRoundedCornerDrawable();
updateHwLayerRoundedCornerExistAndSize();
}
-
- updateOverlayProviderViews();
+ needToUpdateProviderViews = true;
}
final float newRatio = getPhysicalPixelDisplaySizeRatio();
@@ -480,7 +477,13 @@
if (mScreenDecorHwcLayer != null) {
updateHwLayerRoundedCornerExistAndSize();
}
- updateOverlayProviderViews();
+ needToUpdateProviderViews = true;
+ }
+
+ if (needToUpdateProviderViews) {
+ updateOverlayProviderViews(null);
+ } else {
+ updateOverlayProviderViews(new Integer[] { mFaceScanningViewId });
}
if (mCutoutViews != null) {
@@ -490,18 +493,12 @@
if (cutoutView == null) {
continue;
}
- cutoutView.onDisplayChanged(displayId);
+ cutoutView.onDisplayChanged(newUniqueId);
}
}
- DisplayCutoutView overlay = (DisplayCutoutView) getOverlayView(mFaceScanningViewId);
- if (overlay != null) {
- // handle display resolution changes
- overlay.onDisplayChanged(displayId);
- }
-
if (mScreenDecorHwcLayer != null) {
- mScreenDecorHwcLayer.onDisplayChanged(displayId);
+ mScreenDecorHwcLayer.onDisplayChanged(newUniqueId);
}
}
};
@@ -804,7 +801,7 @@
return;
}
removeOverlayView(provider.getViewId());
- overlay.addDecorProvider(provider, mRotation);
+ overlay.addDecorProvider(provider, mRotation, mTintColor);
});
}
// Use visibility of privacy dot views & face scanning view to determine the overlay's
@@ -954,24 +951,6 @@
return;
}
- // When the hwc supports screen decorations, the layer will use the A8 color mode which
- // won't be affected by the color inversion. If the composition goes the client composition
- // route, the color inversion will be handled by the RenderEngine.
- final Set<Integer> viewsMayNeedColorUpdate = new HashSet<>();
- if (mHwcScreenDecorationSupport == null) {
- ColorStateList tintList = ColorStateList.valueOf(mTintColor);
- mRoundedCornerResDelegate.setColorTintList(tintList);
- viewsMayNeedColorUpdate.add(R.id.rounded_corner_top_left);
- viewsMayNeedColorUpdate.add(R.id.rounded_corner_top_right);
- viewsMayNeedColorUpdate.add(R.id.rounded_corner_bottom_left);
- viewsMayNeedColorUpdate.add(R.id.rounded_corner_bottom_right);
- viewsMayNeedColorUpdate.add(R.id.display_cutout);
- }
- if (getOverlayView(mFaceScanningViewId) != null) {
- viewsMayNeedColorUpdate.add(mFaceScanningViewId);
- }
- final Integer[] views = new Integer[viewsMayNeedColorUpdate.size()];
- viewsMayNeedColorUpdate.toArray(views);
for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
if (mOverlays[i] == null) {
continue;
@@ -981,14 +960,19 @@
View child;
for (int j = 0; j < size; j++) {
child = overlayView.getChildAt(j);
- if (viewsMayNeedColorUpdate.contains(child.getId())
- && child instanceof DisplayCutoutView) {
+ if (child instanceof DisplayCutoutView && child.getId() == R.id.display_cutout) {
((DisplayCutoutView) child).setColor(mTintColor);
}
}
- mOverlays[i].onReloadResAndMeasure(views, mProviderRefreshToken,
- mRotation, mDisplayUniqueId);
}
+
+ updateOverlayProviderViews(new Integer[] {
+ mFaceScanningViewId,
+ R.id.rounded_corner_top_left,
+ R.id.rounded_corner_top_right,
+ R.id.rounded_corner_bottom_left,
+ R.id.rounded_corner_bottom_right
+ });
}
@VisibleForTesting
@@ -1119,7 +1103,7 @@
}
// update all provider views inside overlay
- updateOverlayProviderViews();
+ updateOverlayProviderViews(null);
}
FaceScanningOverlay faceScanningOverlay =
@@ -1191,7 +1175,7 @@
context.getResources(), context.getDisplay().getUniqueId());
}
- private void updateOverlayProviderViews() {
+ private void updateOverlayProviderViews(@Nullable Integer[] filterIds) {
if (mOverlays == null) {
return;
}
@@ -1200,7 +1184,8 @@
if (overlay == null) {
continue;
}
- overlay.onReloadResAndMeasure(null, mProviderRefreshToken, mRotation, mDisplayUniqueId);
+ overlay.onReloadResAndMeasure(filterIds, mProviderRefreshToken, mRotation, mTintColor,
+ mDisplayUniqueId);
}
}
@@ -1239,19 +1224,12 @@
} catch (NumberFormatException e) {
mRoundedCornerResDelegate.setTuningSizeFactor(null);
}
- Integer[] filterIds = {
+ updateOverlayProviderViews(new Integer[] {
R.id.rounded_corner_top_left,
R.id.rounded_corner_top_right,
R.id.rounded_corner_bottom_left,
R.id.rounded_corner_bottom_right
- };
- for (final OverlayWindow overlay: mOverlays) {
- if (overlay == null) {
- continue;
- }
- overlay.onReloadResAndMeasure(filterIds, mProviderRefreshToken, mRotation,
- mDisplayUniqueId);
- }
+ });
updateHwLayerRoundedCornerExistAndSize();
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 378ae14..fef7383 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -29,8 +29,7 @@
import android.view.animation.PathInterpolator
import com.android.internal.graphics.ColorUtils
import com.android.systemui.animation.Interpolators
-import com.android.systemui.statusbar.charging.DwellRippleShader
-import com.android.systemui.statusbar.charging.RippleShader
+import com.android.systemui.ripple.RippleShader
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
@@ -298,7 +297,7 @@
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
unlockedRippleInProgress = true
- rippleShader.shouldFadeOutRipple = true
+ rippleShader.rippleFill = false
drawRipple = true
visibility = VISIBLE
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt b/packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt
rename to packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt
index 236129f..979fe33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.charging
+package com.android.systemui.biometrics
import android.graphics.PointF
import android.graphics.RuntimeShader
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
new file mode 100644
index 0000000..4986fe8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.camera
+
+import android.app.ActivityManager
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityOptions
+import android.app.ActivityTaskManager
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.os.AsyncTask
+import android.os.RemoteException
+import android.os.UserHandle
+import android.util.Log
+import android.view.WindowManager
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.camera.CameraIntents.Companion.isSecureCameraIntent
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.PanelViewController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import javax.inject.Inject
+
+/**
+ * Helps with handling camera-related gestures (for example, double-tap the power button to launch
+ * the camera).
+ */
+class CameraGestureHelper @Inject constructor(
+ private val context: Context,
+ private val centralSurfaces: CentralSurfaces,
+ private val keyguardStateController: KeyguardStateController,
+ private val packageManager: PackageManager,
+ private val activityManager: ActivityManager,
+ private val activityStarter: ActivityStarter,
+ private val activityIntentHelper: ActivityIntentHelper,
+ private val cameraIntents: CameraIntentsWrapper,
+ private val contentResolver: ContentResolver,
+) {
+ /**
+ * Whether the camera application can be launched for the camera launch gesture.
+ */
+ fun canCameraGestureBeLaunched(statusBarState: Int): Boolean {
+ if (!centralSurfaces.isCameraAllowedByAdmin) {
+ return false
+ }
+
+ val resolveInfo: ResolveInfo = packageManager.resolveActivityAsUser(
+ getStartCameraIntent(),
+ PackageManager.MATCH_DEFAULT_ONLY,
+ KeyguardUpdateMonitor.getCurrentUser()
+ )
+ val resolvedPackage = resolveInfo.activityInfo?.packageName
+ return (resolvedPackage != null &&
+ (statusBarState != StatusBarState.SHADE ||
+ !isForegroundApp(resolvedPackage)))
+ }
+
+ /**
+ * Launches the camera.
+ *
+ * @param source The source of the camera launch, to be passed to the camera app via [Intent]
+ */
+ fun launchCamera(source: Int) {
+ val intent: Intent = getStartCameraIntent()
+ intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source)
+ val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
+ intent, KeyguardUpdateMonitor.getCurrentUser()
+ )
+ if (isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
+ AsyncTask.execute {
+ // Normally an activity will set its requested rotation animation on its window.
+ // However when launching an activity causes the orientation to change this is too
+ // late. In these cases, the default animation is used. This doesn't look good for
+ // the camera (as it rotates the camera contents out of sync with physical reality).
+ // Therefore, we ask the WindowManager to force the cross-fade animation if an
+ // orientation change happens to occur during the launch.
+ val activityOptions = ActivityOptions.makeBasic()
+ activityOptions.setDisallowEnterPictureInPictureWhileLaunching(true)
+ activityOptions.rotationAnimationHint =
+ WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
+ try {
+ ActivityTaskManager.getService().startActivityAsUser(
+ null,
+ context.basePackageName,
+ context.attributionTag,
+ intent,
+ intent.resolveTypeIfNeeded(contentResolver),
+ null,
+ null,
+ 0,
+ Intent.FLAG_ACTIVITY_NEW_TASK,
+ null,
+ activityOptions.toBundle(),
+ UserHandle.CURRENT.identifier,
+ )
+ } catch (e: RemoteException) {
+ Log.w(
+ PanelViewController.TAG,
+ "Unable to start camera activity",
+ e
+ )
+ }
+ }
+ } else {
+ // We need to delay starting the activity because ResolverActivity finishes itself if
+ // launched from behind the lock-screen.
+ activityStarter.startActivity(intent, false /* dismissShade */)
+ }
+ }
+
+ /**
+ * Returns an [Intent] that can be used to start the camera app such that it occludes the
+ * lock-screen, if needed.
+ */
+ private fun getStartCameraIntent(): Intent {
+ val isLockScreenDismissible = keyguardStateController.canDismissLockScreen()
+ val isSecure = keyguardStateController.isMethodSecure
+ return if (isSecure && !isLockScreenDismissible) {
+ cameraIntents.getSecureCameraIntent()
+ } else {
+ cameraIntents.getInsecureCameraIntent()
+ }
+ }
+
+ /**
+ * Returns `true` if the application with the given package name is running in the foreground;
+ * `false` otherwise
+ */
+ private fun isForegroundApp(packageName: String): Boolean {
+ val tasks: List<RunningTaskInfo> = activityManager.getRunningTasks(1)
+ return tasks.isNotEmpty() && packageName == tasks[0].topActivity.packageName
+ }
+
+ companion object {
+ private const val EXTRA_CAMERA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
new file mode 100644
index 0000000..cf02f8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.camera
+
+import android.content.Context
+import android.content.Intent
+import javax.inject.Inject
+
+/** Injectable wrapper around [CameraIntents]. */
+class CameraIntentsWrapper @Inject constructor(
+ private val context: Context,
+) {
+
+ /**
+ * Returns an [Intent] that can be used to start the camera, suitable for when the device is
+ * already unlocked
+ */
+ fun getSecureCameraIntent(): Intent {
+ return CameraIntents.getSecureCameraIntent(context)
+ }
+
+ /**
+ * Returns an [Intent] that can be used to start the camera, suitable for when the device is not
+ * already unlocked
+ */
+ fun getInsecureCameraIntent(): Intent {
+ return CameraIntents.getInsecureCameraIntent(context)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
rename to packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 558bcac..8292e52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.charging
+package com.android.systemui.charging
import android.content.Context
import android.content.res.Configuration
@@ -32,6 +32,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.ripple.RippleView
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
@@ -84,7 +85,7 @@
private var debounceLevel = 0
@VisibleForTesting
- var rippleView: ChargingRippleView = ChargingRippleView(context, attrs = null)
+ var rippleView: RippleView = RippleView(context, attrs = null)
init {
pluggedIn = batteryController.isPluggedIn
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 0d3e2ae..f6368ee 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -34,7 +34,7 @@
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.statusbar.charging.ChargingRippleView;
+import com.android.systemui.ripple.RippleView;
import java.text.NumberFormat;
@@ -46,7 +46,7 @@
private static final long RIPPLE_ANIMATION_DURATION = 1500;
private static final int SCRIM_COLOR = 0x4C000000;
private static final int SCRIM_FADE_DURATION = 300;
- private ChargingRippleView mRippleView;
+ private RippleView mRippleView;
public WirelessChargingLayout(Context context) {
super(context);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 366ef26..ba1e057 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -55,8 +55,6 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.QsFrameTranslateModule;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
@@ -220,11 +218,9 @@
ZenModeController zenModeController,
NotificationLockscreenUserManager notifUserManager,
NotificationGroupManagerLegacy groupManager,
- NotificationEntryManager entryManager,
CommonNotifCollection notifCollection,
NotifPipeline notifPipeline,
SysUiState sysUiState,
- NotifPipelineFlags notifPipelineFlags,
DumpManager dumpManager,
@Main Executor sysuiMainExecutor) {
return Optional.ofNullable(BubblesManager.create(context,
@@ -240,11 +236,9 @@
zenModeController,
notifUserManager,
groupManager,
- entryManager,
notifCollection,
notifPipeline,
sysUiState,
- notifPipelineFlags,
dumpManager,
sysuiMainExecutor));
}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
index 169b50e..de6d727 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
@@ -33,7 +33,7 @@
/** The number of total aligned bounds */
val numOfAlignedEdge: Int
- get() = alignedBounds.size
+ get() = alignedBounds.size
/** The aligned bounds for the view which is created through inflateView() */
abstract val alignedBounds: List<Int>
@@ -46,14 +46,16 @@
view: View,
reloadToken: Int,
@Surface.Rotation rotation: Int,
- displayUniqueId: String? = null
+ tintColor: Int,
+ displayUniqueId: String?
)
/** Inflate view into parent as current rotation */
abstract fun inflateView(
context: Context,
parent: ViewGroup,
- @Surface.Rotation rotation: Int
+ @Surface.Rotation rotation: Int,
+ tintColor: Int
): View
}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 81d3d6c..5925c57 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -115,19 +115,25 @@
override fun onReloadResAndMeasure(
view: View,
reloadToken: Int,
- rotation: Int,
+ @Surface.Rotation rotation: Int,
+ tintColor: Int,
displayUniqueId: String?
) {
(view.layoutParams as FrameLayout.LayoutParams).let {
updateLayoutParams(it, rotation)
view.layoutParams = it
+ (view as? FaceScanningOverlay)?.let { overlay ->
+ overlay.setColor(tintColor)
+ overlay.onDisplayChanged(displayUniqueId)
+ }
}
}
override fun inflateView(
context: Context,
parent: ViewGroup,
- @Surface.Rotation rotation: Int
+ @Surface.Rotation rotation: Int,
+ tintColor: Int
): View {
val view = FaceScanningOverlay(
context,
@@ -137,6 +143,7 @@
mainExecutor
)
view.id = viewId
+ view.setColor(tintColor)
FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT).let {
updateLayoutParams(it, rotation)
diff --git a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
index 3c0748e..dfb0b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
@@ -34,9 +34,10 @@
fun addDecorProvider(
decorProvider: DecorProvider,
- @Surface.Rotation rotation: Int
+ @Surface.Rotation rotation: Int,
+ tintColor: Int
) {
- val view = decorProvider.inflateView(context, rootView, rotation)
+ val view = decorProvider.inflateView(context, rootView, rotation, tintColor)
viewProviderMap[decorProvider.viewId] = Pair(view, decorProvider)
}
@@ -69,7 +70,7 @@
*/
fun hasSameProviders(newProviders: List<DecorProvider>): Boolean {
return (newProviders.size == viewProviderMap.size) &&
- newProviders.all { getView(it.viewId) != null }
+ newProviders.all { getView(it.viewId) != null }
}
/**
@@ -82,23 +83,28 @@
filterIds: Array<Int>? = null,
reloadToken: Int,
@Surface.Rotation rotation: Int,
+ tintColor: Int,
displayUniqueId: String? = null
) {
filterIds?.forEach { id ->
viewProviderMap[id]?.let {
it.second.onReloadResAndMeasure(
- view = it.first,
- reloadToken = reloadToken,
- displayUniqueId = displayUniqueId,
- rotation = rotation)
+ view = it.first,
+ reloadToken = reloadToken,
+ rotation = rotation,
+ tintColor = tintColor,
+ displayUniqueId = displayUniqueId
+ )
}
} ?: run {
viewProviderMap.values.forEach {
it.second.onReloadResAndMeasure(
- view = it.first,
- reloadToken = reloadToken,
- displayUniqueId = displayUniqueId,
- rotation = rotation)
+ view = it.first,
+ reloadToken = reloadToken,
+ rotation = rotation,
+ tintColor = tintColor,
+ displayUniqueId = displayUniqueId
+ )
}
}
}
@@ -111,4 +117,4 @@
pw.println(" child[$i]=$child")
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
index 9f624b3..e18c0e1 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
@@ -20,9 +20,9 @@
import android.content.res.Resources
import android.view.DisplayCutout
import android.view.LayoutInflater
+import android.view.Surface
import android.view.View
import android.view.ViewGroup
-import android.view.Surface
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
@@ -85,6 +85,7 @@
view: View,
reloadToken: Int,
rotation: Int,
+ tintColor: Int,
displayUniqueId: String?
) {
// Do nothing here because it is handled inside PrivacyDotViewController
@@ -93,7 +94,8 @@
override fun inflateView(
context: Context,
parent: ViewGroup,
- @Surface.Rotation rotation: Int
+ @Surface.Rotation rotation: Int,
+ tintColor: Int
): View {
LayoutInflater.from(context).inflate(layoutId, parent, true)
return parent.getChildAt(parent.childCount - 1 /* latest new added child */)
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt
index e316722..8156797 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt
@@ -17,6 +17,7 @@
package com.android.systemui.decor
import android.content.Context
+import android.content.res.ColorStateList
import android.view.DisplayCutout
import android.view.Gravity
import android.view.Surface
@@ -38,12 +39,13 @@
override fun inflateView(
context: Context,
parent: ViewGroup,
- @Surface.Rotation rotation: Int
+ @Surface.Rotation rotation: Int,
+ tintColor: Int
): View {
return ImageView(context).also { view ->
// View
view.id = viewId
- initView(view, rotation)
+ initView(view, rotation, tintColor)
// LayoutParams
val layoutSize = if (isTop) {
@@ -52,31 +54,36 @@
roundedCornerResDelegate.bottomRoundedSize
}
val params = FrameLayout.LayoutParams(
- layoutSize.width,
- layoutSize.height,
- alignedBound1.toLayoutGravity(rotation) or
- alignedBound2.toLayoutGravity(rotation))
+ layoutSize.width,
+ layoutSize.height,
+ alignedBound1.toLayoutGravity(rotation) or alignedBound2.toLayoutGravity(rotation)
+ )
// AddView
parent.addView(view, params)
}
}
- private fun initView(view: ImageView, @Surface.Rotation rotation: Int) {
+ private fun initView(
+ view: ImageView,
+ @Surface.Rotation rotation: Int,
+ tintColor: Int
+ ) {
view.setRoundedCornerImage(roundedCornerResDelegate, isTop)
view.adjustRotation(alignedBounds, rotation)
- view.imageTintList = roundedCornerResDelegate.colorTintList
+ view.imageTintList = ColorStateList.valueOf(tintColor)
}
override fun onReloadResAndMeasure(
view: View,
reloadToken: Int,
@Surface.Rotation rotation: Int,
+ tintColor: Int,
displayUniqueId: String?
) {
roundedCornerResDelegate.updateDisplayUniqueId(displayUniqueId, reloadToken)
- initView((view as ImageView), rotation)
+ initView((view as ImageView), rotation, tintColor)
val layoutSize = if (isTop) {
roundedCornerResDelegate.topRoundedSize
@@ -87,7 +94,7 @@
it.width = layoutSize.width
it.height = layoutSize.height
it.gravity = alignedBound1.toLayoutGravity(rotation) or
- alignedBound2.toLayoutGravity(rotation)
+ alignedBound2.toLayoutGravity(rotation)
view.setLayoutParams(it)
}
}
@@ -134,10 +141,10 @@
setImageDrawable(drawable)
} else {
setImageResource(
- if (isTop)
- R.drawable.rounded_corner_top
- else
- R.drawable.rounded_corner_bottom
+ if (isTop)
+ R.drawable.rounded_corner_top
+ else
+ R.drawable.rounded_corner_bottom
)
}
}
@@ -187,4 +194,4 @@
this.rotation = newRotation
this.scaleX = newScaleX
this.scaleY = newScaleY
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
index b5a0cfc..a252864 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
@@ -18,9 +18,7 @@
import android.annotation.ArrayRes
import android.annotation.DrawableRes
-import android.content.res.ColorStateList
import android.content.res.Resources
-import android.graphics.Color
import android.graphics.drawable.Drawable
import android.util.DisplayUtils
import android.util.Size
@@ -57,8 +55,6 @@
var bottomRoundedSize = Size(0, 0)
private set
- var colorTintList = ColorStateList.valueOf(Color.BLACK)
-
var tuningSizeFactor: Int? = null
set(value) {
if (field == value) {
@@ -107,19 +103,19 @@
val hasDefaultRadius = RoundedCorners.getRoundedCornerRadius(res, displayUniqueId) > 0
hasTop = hasDefaultRadius ||
- (RoundedCorners.getRoundedCornerTopRadius(res, displayUniqueId) > 0)
+ (RoundedCorners.getRoundedCornerTopRadius(res, displayUniqueId) > 0)
hasBottom = hasDefaultRadius ||
- (RoundedCorners.getRoundedCornerBottomRadius(res, displayUniqueId) > 0)
+ (RoundedCorners.getRoundedCornerBottomRadius(res, displayUniqueId) > 0)
topRoundedDrawable = getDrawable(
- displayConfigIndex = configIdx,
- arrayResId = R.array.config_roundedCornerTopDrawableArray,
- backupDrawableId = R.drawable.rounded_corner_top
+ displayConfigIndex = configIdx,
+ arrayResId = R.array.config_roundedCornerTopDrawableArray,
+ backupDrawableId = R.drawable.rounded_corner_top
)
bottomRoundedDrawable = getDrawable(
- displayConfigIndex = configIdx,
- arrayResId = R.array.config_roundedCornerBottomDrawableArray,
- backupDrawableId = R.drawable.rounded_corner_bottom
+ displayConfigIndex = configIdx,
+ arrayResId = R.array.config_roundedCornerBottomDrawableArray,
+ backupDrawableId = R.drawable.rounded_corner_bottom
)
}
@@ -147,13 +143,15 @@
if (physicalPixelDisplaySizeRatio != 1f) {
if (topRoundedSize.width != 0) {
topRoundedSize = Size(
- (physicalPixelDisplaySizeRatio * topRoundedSize.width + 0.5f).toInt(),
- (physicalPixelDisplaySizeRatio * topRoundedSize.height + 0.5f).toInt())
+ (physicalPixelDisplaySizeRatio * topRoundedSize.width + 0.5f).toInt(),
+ (physicalPixelDisplaySizeRatio * topRoundedSize.height + 0.5f).toInt()
+ )
}
if (bottomRoundedSize.width != 0) {
bottomRoundedSize = Size(
- (physicalPixelDisplaySizeRatio * bottomRoundedSize.width + 0.5f).toInt(),
- (physicalPixelDisplaySizeRatio * bottomRoundedSize.height + 0.5f).toInt())
+ (physicalPixelDisplaySizeRatio * bottomRoundedSize.width + 0.5f).toInt(),
+ (physicalPixelDisplaySizeRatio * bottomRoundedSize.height + 0.5f).toInt()
+ )
}
}
}
@@ -180,8 +178,9 @@
pw.println(" hasTop=$hasTop")
pw.println(" hasBottom=$hasBottom")
pw.println(" topRoundedSize(w,h)=(${topRoundedSize.width},${topRoundedSize.height})")
- pw.println(" bottomRoundedSize(w,h)=(${bottomRoundedSize.width}," +
- "${bottomRoundedSize.height})")
+ pw.println(
+ " bottomRoundedSize(w,h)=(${bottomRoundedSize.width},${bottomRoundedSize.height})"
+ )
pw.println(" physicalPixelDisplaySizeRatio=$physicalPixelDisplaySizeRatio")
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 064388f..2fa104a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -162,6 +162,11 @@
new ResourceBooleanFlag(800, R.bool.flag_monet);
/***************************************/
+ // 801 - region sampling
+ public static final BooleanFlag REGION_SAMPLING =
+ new BooleanFlag(801, false);
+
+ /***************************************/
// 900 - media
public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true);
public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 95b3b3f..3eb3c80 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -453,6 +453,8 @@
@Override // Binder interface
public void setOccluded(boolean isOccluded, boolean animate) {
+ Log.d(TAG, "setOccluded(" + isOccluded + ")");
+
Trace.beginSection("KeyguardService.mBinder#setOccluded");
checkPermission();
mKeyguardViewMediator.setOccluded(isOccluded, animate);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 4ee8985..c6da7d9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -837,13 +837,10 @@
private final ActivityLaunchAnimator.Controller mOccludeAnimationController =
new ActivityLaunchAnimator.Controller() {
@Override
- public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
- setOccluded(true /* occluded */, false /* animate */);
- }
+ public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {}
@Override
public void onLaunchAnimationCancelled() {
- setOccluded(true /* occluded */, false /* animate */);
Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: "
+ mOccluded);
}
@@ -916,7 +913,7 @@
mUnoccludeAnimator.cancel();
}
- setOccluded(isKeyguardOccluded, false /* animate */);
+ setOccluded(isKeyguardOccluded /* isOccluded */, false /* animate */);
Log.d(TAG, "Unocclude animation cancelled. Occluded state is now: "
+ mOccluded);
}
@@ -926,6 +923,7 @@
RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+ Log.d(TAG, "UnoccludeAnimator#onAnimationStart. Set occluded = false.");
setOccluded(false /* isOccluded */, true /* animate */);
if (apps == null || apps.length == 0 || apps[0] == null) {
@@ -1677,6 +1675,8 @@
* Notify us when the keyguard is occluded by another window
*/
public void setOccluded(boolean isOccluded, boolean animate) {
+ Log.d(TAG, "setOccluded(" + isOccluded + ")");
+
Trace.beginSection("KeyguardViewMediator#setOccluded");
if (DEBUG) Log.d(TAG, "setOccluded " + isOccluded);
mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_TRANSITION_FROM_AOD);
@@ -1707,6 +1707,7 @@
*/
private void handleSetOccluded(boolean isOccluded, boolean animate) {
Trace.beginSection("KeyguardViewMediator#handleSetOccluded");
+ Log.d(TAG, "handleSetOccluded(" + isOccluded + ")");
synchronized (KeyguardViewMediator.this) {
if (mHiding && isOccluded) {
// We're in the process of going away but WindowManager wants to show a
@@ -3208,13 +3209,18 @@
// internal state to reflect that immediately, vs. waiting for the launch animator to
// begin. Otherwise, calls to setShowingLocked, etc. will not know that we're about to
// be occluded and might re-show the keyguard.
+ Log.d(TAG, "OccludeAnimator#onAnimationStart. Set occluded = true.");
setOccluded(true /* isOccluded */, false /* animate */);
}
@Override
public void onAnimationCancelled(boolean isKeyguardOccluded) throws RemoteException {
super.onAnimationCancelled(isKeyguardOccluded);
- Log.d(TAG, "Occlude launch animation cancelled. Occluded state is now: " + mOccluded);
+
+ Log.d(TAG, "Occlude animation cancelled by WM. "
+ + "Setting occluded state to: " + isKeyguardOccluded);
+ setOccluded(isKeyguardOccluded /* occluded */, false /* animate */);
+
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 90cca15..d0da18a 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -27,6 +27,8 @@
import com.android.systemui.log.LogcatEchoTracker;
import com.android.systemui.log.LogcatEchoTrackerDebug;
import com.android.systemui.log.LogcatEchoTrackerProd;
+import com.android.systemui.statusbar.notification.NotifPipelineFlags;
+import com.android.systemui.util.Compile;
import dagger.Module;
import dagger.Provides;
@@ -48,8 +50,14 @@
@Provides
@SysUISingleton
@NotificationLog
- public static LogBuffer provideNotificationsLogBuffer(LogBufferFactory factory) {
- return factory.create("NotifLog", 1000 /* maxSize */, false /* systrace */);
+ public static LogBuffer provideNotificationsLogBuffer(
+ LogBufferFactory factory,
+ NotifPipelineFlags notifPipelineFlags) {
+ int maxSize = 1000;
+ if (Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled()) {
+ maxSize *= 10;
+ }
+ return factory.create("NotifLog", maxSize, false /* systrace */);
}
/** Provides a logging buffer for logs related to heads up presentation of notifications. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
index f1d5e94..d082655 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
@@ -17,20 +17,17 @@
package com.android.systemui.media
import android.animation.ArgbEvaluator
-import android.animation.ValueAnimator.AnimatorUpdateListener
import android.animation.ValueAnimator
+import android.animation.ValueAnimator.AnimatorUpdateListener
import android.content.Context
import android.content.res.ColorStateList
-import android.graphics.Color
-import android.graphics.drawable.GradientDrawable
-import android.graphics.drawable.RippleDrawable
import android.content.res.Configuration
import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.graphics.drawable.RippleDrawable
import com.android.internal.R
import com.android.internal.annotations.VisibleForTesting
import com.android.settingslib.Utils
import com.android.systemui.monet.ColorScheme
-import com.android.systemui.util.getColorWithAlpha
/**
* A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme]
@@ -106,7 +103,6 @@
constructor(context: Context, mediaViewHolder: MediaViewHolder) :
this(context, mediaViewHolder, ::AnimatingColorTransition)
- private var isGradientEnabled = true
val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
val surfaceColor = animatingColorTransitionFactory(
bgColor,
@@ -187,16 +183,6 @@
mediaViewHolder.seekBar.progressBackgroundTintList = ColorStateList.valueOf(textTertiary)
}
- val bgGradientStart = animatingColorTransitionFactory(
- bgColor,
- albumGradientPicker(::backgroundStartFromScheme, 0.25f)
- ) { _ -> updateAlbumGradient() }
-
- val bgGradientEnd = animatingColorTransitionFactory(
- bgColor,
- albumGradientPicker(::backgroundEndFromScheme, 0.9f)
- ) { _ -> updateAlbumGradient() }
-
val colorTransitions = arrayOf(
surfaceColor,
colorSeamless,
@@ -206,37 +192,13 @@
textPrimaryInverse,
textSecondary,
textTertiary,
- bgGradientStart,
- bgGradientEnd
)
- private fun updateAlbumGradient() {
- val gradient = mediaViewHolder.albumView.foreground?.mutate()
- if (gradient is GradientDrawable) {
- gradient.colors = intArrayOf(
- bgGradientStart?.currentColor ?: 0,
- bgGradientEnd?.currentColor ?: 0)
- }
- }
-
- private fun albumGradientPicker(
- inner: (ColorScheme) -> Int,
- targetAlpha: Float
- ): (ColorScheme) -> Int {
- return { scheme ->
- if (isGradientEnabled)
- getColorWithAlpha(inner(scheme), targetAlpha)
- else
- Color.TRANSPARENT
- }
- }
-
private fun loadDefaultColor(id: Int): Int {
return Utils.getColorAttr(context, id).defaultColor
}
- fun updateColorScheme(colorScheme: ColorScheme?, enableGradient: Boolean) {
- isGradientEnabled = enableGradient
+ fun updateColorScheme(colorScheme: ColorScheme?) {
colorTransitions.forEach { it.updateColorScheme(colorScheme) }
colorScheme?.let { mediaViewHolder.gutsViewHolder.colorScheme = colorScheme }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 68aba62..1ed65b3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -38,7 +38,9 @@
import android.graphics.drawable.Animatable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
+import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.TransitionDrawable;
import android.media.session.MediaController;
import android.media.session.MediaSession;
@@ -82,6 +84,7 @@
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.ColorUtilKt;
import com.android.systemui.util.animation.TransitionLayout;
import com.android.systemui.util.time.SystemClock;
@@ -683,7 +686,18 @@
WallpaperColors wallpaperColors = WallpaperColors
.fromBitmap(artworkIcon.getBitmap());
mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
- artwork = getScaledBackground(artworkIcon, width, height);
+ Drawable albumArt = getScaledBackground(artworkIcon, width, height);
+ GradientDrawable gradient = (GradientDrawable) mContext
+ .getDrawable(R.drawable.qs_media_scrim);
+ gradient.setColors(new int[] {
+ ColorUtilKt.getColorWithAlpha(
+ MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme),
+ 0.25f),
+ ColorUtilKt.getColorWithAlpha(
+ MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme),
+ 0.9f),
+ });
+ artwork = new LayerDrawable(new Drawable[] { albumArt, gradient });
isArtworkBound = true;
} else {
// If there's no artwork, use colors from the app icon
@@ -735,7 +749,7 @@
}
// Transition Colors to current color scheme
- mColorSchemeTransition.updateColorScheme(colorScheme, mIsArtworkBound);
+ mColorSchemeTransition.updateColorScheme(colorScheme);
// App icon - use notification icon
ImageView appIconView = mMediaViewHolder.getAppIcon();
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index fe1ac80..c9fce79 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -25,17 +25,14 @@
import android.os.PowerManager
import android.os.SystemClock
import android.util.Log
-import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
-import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
-import android.widget.LinearLayout
import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils
import com.android.systemui.R
@@ -57,7 +54,7 @@
abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
internal val context: Context,
internal val logger: MediaTttLogger,
- private val windowManager: WindowManager,
+ internal val windowManager: WindowManager,
private val viewUtil: ViewUtil,
@Main private val mainExecutor: DelayableExecutor,
private val accessibilityManager: AccessibilityManager,
@@ -65,12 +62,15 @@
private val powerManager: PowerManager,
@LayoutRes private val chipLayoutRes: Int
) {
- /** The window layout parameters we'll use when attaching the view to a window. */
+
+ /**
+ * Window layout params that will be used as a starting point for the [windowLayoutParams] of
+ * all subclasses.
+ */
@SuppressLint("WrongConstant") // We're allowed to use TYPE_VOLUME_OVERLAY
- private val windowLayoutParams = WindowManager.LayoutParams().apply {
+ internal val commonWindowLayoutParams = WindowManager.LayoutParams().apply {
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
- gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
title = WINDOW_TITLE
@@ -78,6 +78,14 @@
setTrustedOverlay()
}
+ /**
+ * The window layout parameters we'll use when attaching the view to a window.
+ *
+ * Subclasses must override this to provide their specific layout params, and they should use
+ * [commonWindowLayoutParams] as part of their layout params.
+ */
+ internal abstract val windowLayoutParams: WindowManager.LayoutParams
+
/** The chip view currently being displayed. Null if the chip is not being displayed. */
private var chipView: ViewGroup? = null
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index a5d763c..99a5b8b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -16,17 +16,23 @@
package com.android.systemui.media.taptotransfer.receiver
+import android.annotation.SuppressLint
import android.app.StatusBarManager
import android.content.Context
+import android.graphics.PointF
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.media.MediaRoute2Info
import android.os.Handler
import android.os.PowerManager
import android.util.Log
+import android.view.Gravity
+import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
+import androidx.core.graphics.ColorUtils
+import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
@@ -36,6 +42,7 @@
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.util.animation.AnimationUtil.Companion.frames
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
import javax.inject.Inject
@@ -69,6 +76,17 @@
powerManager,
R.layout.media_ttt_chip_receiver
) {
+ @SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ override val windowLayoutParams = commonWindowLayoutParams.apply {
+ gravity = Gravity.BOTTOM.or(Gravity.CENTER_HORIZONTAL)
+ // Params below are needed for the ripple to work correctly
+ width = WindowManager.LayoutParams.MATCH_PARENT
+ height = WindowManager.LayoutParams.MATCH_PARENT
+ layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ fitInsetsTypes = 0 // Ignore insets from all system bars
+ flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ }
+
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
override fun updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState displayState: Int,
@@ -131,6 +149,19 @@
)
}
+ override fun animateChipIn(chipView: ViewGroup) {
+ val appIconView = chipView.requireViewById<View>(R.id.app_icon)
+ appIconView.animate()
+ .translationYBy(-1 * getTranslationAmount().toFloat())
+ .setDuration(30.frames)
+ .start()
+ appIconView.animate()
+ .alpha(1f)
+ .setDuration(5.frames)
+ .start()
+ startRipple(chipView.requireViewById(R.id.ripple))
+ }
+
override fun getIconSize(isAppIcon: Boolean): Int? =
context.resources.getDimensionPixelSize(
if (isAppIcon) {
@@ -139,6 +170,45 @@
R.dimen.media_ttt_generic_icon_size_receiver
}
)
+
+ /** Returns the amount that the chip will be translated by in its intro animation. */
+ private fun getTranslationAmount(): Int {
+ return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
+ }
+
+ private fun startRipple(rippleView: ReceiverChipRippleView) {
+ if (rippleView.rippleInProgress) {
+ // Skip if ripple is still playing
+ return
+ }
+ rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
+ override fun onViewDetachedFromWindow(view: View?) {}
+
+ override fun onViewAttachedToWindow(view: View?) {
+ if (view == null) {
+ return
+ }
+ val attachedRippleView = view as ReceiverChipRippleView
+ layoutRipple(attachedRippleView)
+ attachedRippleView.startRipple()
+ attachedRippleView.removeOnAttachStateChangeListener(this)
+ }
+ })
+ }
+
+ private fun layoutRipple(rippleView: ReceiverChipRippleView) {
+ val windowBounds = windowManager.currentWindowMetrics.bounds
+ val height = windowBounds.height()
+ val width = windowBounds.width()
+
+ rippleView.radius = height / 5f
+ // Center the ripple on the bottom of the screen in the middle.
+ rippleView.origin = PointF(/* x= */ width / 2f, /* y= */ height.toFloat())
+
+ val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
+ val colorWithAlpha = ColorUtils.setAlphaComponent(color, 70)
+ rippleView.setColor(colorWithAlpha)
+ }
}
data class ChipReceiverInfo(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
new file mode 100644
index 0000000..fed546bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.media.taptotransfer.receiver
+
+import android.content.Context
+import android.util.AttributeSet
+import com.android.systemui.ripple.RippleView
+
+/**
+ * An expanding ripple effect for the media tap-to-transfer receiver chip.
+ */
+class ReceiverChipRippleView(
+ context: Context?, attrs: AttributeSet?
+) : RippleView(context, attrs) {
+ init {
+ setRippleFill(true)
+ duration = 3000L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 943604c..797a770 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -21,6 +21,7 @@
import android.media.MediaRoute2Info
import android.os.PowerManager
import android.util.Log
+import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
@@ -69,6 +70,10 @@
powerManager,
R.layout.media_ttt_chip
) {
+ override val windowLayoutParams = commonWindowLayoutParams.apply {
+ gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
+ }
+
private var currentlyDisplayedChipState: ChipStateSender? = null
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 0288c9f..8f6c373 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -40,11 +40,13 @@
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.GuardedBy
+import androidx.annotation.VisibleForTesting
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_FOOTER_DOT
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.Dumpable
import com.android.systemui.R
@@ -87,6 +89,7 @@
private val LOG_TAG = FgsManagerController::class.java.simpleName
private const val DEFAULT_TASK_MANAGER_ENABLED = true
private const val DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT = false
+ private const val DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS = true
}
var changesSinceDialog = false
@@ -96,6 +99,8 @@
private set
var showFooterDot = false
private set
+ var showStopBtnForUserAllowlistedApps = false
+ private set
private val lock = Any()
@@ -163,6 +168,9 @@
isAvailable = it.getBoolean(TASK_MANAGER_ENABLED, isAvailable)
showFooterDot =
it.getBoolean(TASK_MANAGER_SHOW_FOOTER_DOT, showFooterDot)
+ showStopBtnForUserAllowlistedApps = it.getBoolean(
+ TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+ showStopBtnForUserAllowlistedApps)
}
isAvailable = deviceConfigProxy.getBoolean(
@@ -173,6 +181,10 @@
NAMESPACE_SYSTEMUI,
TASK_MANAGER_SHOW_FOOTER_DOT, DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT
)
+ showStopBtnForUserAllowlistedApps = deviceConfigProxy.getBoolean(
+ NAMESPACE_SYSTEMUI,
+ TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+ DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS)
dumpManager.registerDumpable(this)
@@ -275,6 +287,20 @@
}
}
+ @VisibleForTesting
+ @JvmName("getNumVisibleButtons")
+ internal fun getNumVisibleButtons(): Int {
+ synchronized(lock) {
+ return getNumVisibleButtonsLocked()
+ }
+ }
+
+ private fun getNumVisibleButtonsLocked(): Int {
+ return runningServiceTokens.keys.count {
+ it.uiControl != UIControl.HIDE_BUTTON && currentProfileIds.contains(it.userId)
+ }
+ }
+
fun shouldUpdateFooterVisibility() = dialog == null
fun showDialog(viewLaunchedFrom: View?) {
@@ -505,6 +531,13 @@
PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI,
PowerExemptionManager.REASON_ROLE_DIALER,
PowerExemptionManager.REASON_SYSTEM_MODULE -> UIControl.HIDE_BUTTON
+
+ PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE ->
+ if (showStopBtnForUserAllowlistedApps) {
+ UIControl.NORMAL
+ } else {
+ UIControl.HIDE_BUTTON
+ }
else -> UIControl.NORMAL
}
uiControlInitialized = true
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index 75bf189..260a371 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -95,7 +95,9 @@
getButton(BUTTON_NEUTRAL)
)
- dismiss()
+ if (controller == null) {
+ dismiss()
+ }
activityStarter.postStartActivityDismissingKeyguard(
USER_SETTINGS_INTENT, 0, controller
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
rename to packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
index bdad36c..93a2efc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.statusbar.charging
+package com.android.systemui.ripple
import android.graphics.PointF
import android.graphics.RuntimeShader
@@ -148,7 +148,7 @@
val fadeOutNoise = subProgress(0.4f, 1f, value)
var fadeOutRipple = 0f
var fadeCircle = 0f
- if (shouldFadeOutRipple) {
+ if (!rippleFill) {
fadeCircle = subProgress(0f, 0.2f, value)
fadeOutRipple = subProgress(0.3f, 1f, value)
}
@@ -202,5 +202,9 @@
setFloatUniform("in_pixelDensity", value)
}
- var shouldFadeOutRipple: Boolean = true
+ /**
+ * True if the ripple should stayed filled in as it expands to give a filled-in circle effect.
+ * False for a ring effect.
+ */
+ var rippleFill: Boolean = false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
rename to packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
index 10e90fe..fc52464 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.charging
+package com.android.systemui.ripple
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
@@ -30,9 +30,10 @@
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
/**
- * Expanding ripple effect that shows when charging begins.
+ * A generic expanding ripple effect. To trigger the ripple expansion, set [radius] and [origin],
+ * then call [startRipple].
*/
-class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
private val rippleShader = RippleShader()
private val defaultColor: Int = 0xffffffff.toInt()
private val ripplePaint = Paint()
@@ -74,9 +75,9 @@
}
val animator = ValueAnimator.ofFloat(0f, 1f)
animator.duration = duration
- animator.addUpdateListener { animator ->
- val now = animator.currentPlayTime
- val progress = animator.animatedValue as Float
+ animator.addUpdateListener { updateListener ->
+ val now = updateListener.currentPlayTime
+ val progress = updateListener.animatedValue as Float
rippleShader.progress = progress
rippleShader.distortionStrength = 1 - progress
rippleShader.time = now.toFloat()
@@ -92,10 +93,20 @@
rippleInProgress = true
}
+ /** Set the color to be used for the ripple. */
fun setColor(color: Int) {
rippleShader.color = color
}
+ /**
+ * Set whether the ripple should remain filled as the ripple expands.
+ *
+ * See [RippleShader.rippleFill].
+ */
+ fun setRippleFill(rippleFill: Boolean) {
+ rippleShader.rippleFill = rippleFill
+ }
+
override fun onDraw(canvas: Canvas?) {
if (canvas == null || !canvas.isHardwareAccelerated) {
// Drawing with the ripple shader requires hardware acceleration, so skip
@@ -107,6 +118,6 @@
// animation implementation in the ripple shader.
val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
(1 - rippleShader.progress)) * radius * 2
- canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
+ canvas.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 1ce05ec..c900c5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -36,7 +36,6 @@
import android.media.session.PlaybackState;
import android.os.AsyncTask;
import android.os.Trace;
-import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
@@ -44,7 +43,6 @@
import android.view.View;
import android.widget.ImageView;
-import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.Interpolators;
@@ -56,9 +54,6 @@
import com.android.systemui.media.SmartspaceMediaData;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -112,11 +107,9 @@
}
private final NotificationVisibilityProvider mVisibilityProvider;
- private final NotificationEntryManager mEntryManager;
private final MediaDataManager mMediaDataManager;
private final NotifPipeline mNotifPipeline;
private final NotifCollection mNotifCollection;
- private final boolean mUsingNotifPipeline;
@Nullable
private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController;
@@ -180,12 +173,10 @@
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
NotificationVisibilityProvider visibilityProvider,
- NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
NotifPipeline notifPipeline,
NotifCollection notifCollection,
- NotifPipelineFlags notifPipelineFlags,
@Main DelayableExecutor mainExecutor,
MediaDataManager mediaDataManager,
DumpManager dumpManager) {
@@ -197,19 +188,12 @@
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mNotificationShadeWindowController = notificationShadeWindowController;
mVisibilityProvider = visibilityProvider;
- mEntryManager = notificationEntryManager;
mMainExecutor = mainExecutor;
mMediaDataManager = mediaDataManager;
mNotifPipeline = notifPipeline;
mNotifCollection = notifCollection;
- if (!notifPipelineFlags.isNewPipelineEnabled()) {
- setupNEM();
- mUsingNotifPipeline = false;
- } else {
- setupNotifPipeline();
- mUsingNotifPipeline = true;
- }
+ setupNotifPipeline();
dumpManager.registerDumpable(this);
}
@@ -273,79 +257,6 @@
});
}
- private void setupNEM() {
- mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
-
- @Override
- public void onPendingEntryAdded(NotificationEntry entry) {
- mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
- }
-
- @Override
- public void onPreEntryUpdated(NotificationEntry entry) {
- mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
- }
-
- @Override
- public void onEntryInflated(NotificationEntry entry) {
- findAndUpdateMediaNotifications();
- }
-
- @Override
- public void onEntryReinflated(NotificationEntry entry) {
- findAndUpdateMediaNotifications();
- }
-
- @Override
- public void onEntryRemoved(
- @NonNull NotificationEntry entry,
- @Nullable NotificationVisibility visibility,
- boolean removedByUser,
- int reason) {
- removeEntry(entry);
- }
- });
-
- // Pending entries are never inflated, and will never generate a call to onEntryRemoved().
- // This can happen when notifications are added and canceled before inflation. Add this
- // separate listener for cleanup, since media inflation occurs onPendingEntryAdded().
- mEntryManager.addCollectionListener(new NotifCollectionListener() {
- @Override
- public void onEntryCleanUp(@NonNull NotificationEntry entry) {
- removeEntry(entry);
- }
- });
-
- mMediaDataManager.addListener(new MediaDataManager.Listener() {
- @Override
- public void onMediaDataLoaded(@NonNull String key,
- @Nullable String oldKey, @NonNull MediaData data, boolean immediately,
- int receivedSmartspaceCardLatency, boolean isSsReactivated) {
- }
-
- @Override
- public void onSmartspaceMediaDataLoaded(@NonNull String key,
- @NonNull SmartspaceMediaData data, boolean shouldPrioritize) {
-
- }
-
- @Override
- public void onMediaDataRemoved(@NonNull String key) {
- NotificationEntry entry = mEntryManager.getPendingOrActiveNotif(key);
- if (entry != null) {
- // TODO(b/160713608): "removing" this notification won't happen and
- // won't send the 'deleteIntent' if the notification is ongoing.
- mEntryManager.performRemoveNotification(entry.getSbn(),
- getDismissedByUserStats(entry),
- NotificationListenerService.REASON_CANCEL);
- }
- }
-
- @Override
- public void onSmartspaceMediaDataRemoved(@NonNull String key, boolean immediately) {}
- });
- }
-
private DismissedByUserStats getDismissedByUserStats(NotificationEntry entry) {
return new DismissedByUserStats(
NotificationStats.DISMISSAL_SHADE, // Add DISMISSAL_MEDIA?
@@ -401,22 +312,10 @@
if (mMediaNotificationKey == null) {
return null;
}
- if (mUsingNotifPipeline) {
- return Optional.ofNullable(mNotifPipeline.getEntry(mMediaNotificationKey))
- .map(entry -> entry.getIcons().getShelfIcon())
- .map(StatusBarIconView::getSourceIcon)
- .orElse(null);
- } else {
- synchronized (mEntryManager) {
- NotificationEntry entry = mEntryManager
- .getActiveNotificationUnfiltered(mMediaNotificationKey);
- if (entry == null || entry.getIcons().getShelfIcon() == null) {
- return null;
- }
-
- return entry.getIcons().getShelfIcon().getSourceIcon();
- }
- }
+ return Optional.ofNullable(mNotifPipeline.getEntry(mMediaNotificationKey))
+ .map(entry -> entry.getIcons().getShelfIcon())
+ .map(StatusBarIconView::getSourceIcon)
+ .orElse(null);
}
public void addCallback(MediaListener callback) {
@@ -431,21 +330,9 @@
public void findAndUpdateMediaNotifications() {
boolean metaDataChanged;
- if (mUsingNotifPipeline) {
- // TODO(b/169655907): get the semi-filtered notifications for current user
- Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs();
- metaDataChanged = findPlayingMediaNotification(allNotifications);
- } else {
- synchronized (mEntryManager) {
- Collection<NotificationEntry> allNotifications = mEntryManager.getAllNotifs();
- metaDataChanged = findPlayingMediaNotification(allNotifications);
- }
-
- if (metaDataChanged) {
- mEntryManager.updateNotifications("NotificationMediaManager - metaDataChanged");
- }
-
- }
+ // TODO(b/169655907): get the semi-filtered notifications for current user
+ Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs();
+ metaDataChanged = findPlayingMediaNotification(allNotifications);
dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 5cdd01f..78b3b0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -286,10 +286,6 @@
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mRebuilder = rebuilder;
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- mRemoteInputListener = createLegacyRemoteInputLifetimeExtender(mainHandler,
- notificationEntryManager, smartReplyController);
- }
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mStatusBarStateController = statusBarStateController;
mRemoteInputUriController = remoteInputUriController;
@@ -313,34 +309,19 @@
int reason) {
// We're removing the notification, the smart controller can forget about it.
mSmartReplyController.stopSending(entry);
-
- if (removedByUser && entry != null) {
- onPerformRemoveNotification(entry, entry.getKey());
- }
}
});
}
/** Add a listener for various remote input events. Works with NEW pipeline only. */
public void setRemoteInputListener(@NonNull RemoteInputListener remoteInputListener) {
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- if (mRemoteInputListener != null) {
- throw new IllegalStateException("mRemoteInputListener is already set");
- }
- mRemoteInputListener = remoteInputListener;
- if (mRemoteInputController != null) {
- mRemoteInputListener.setRemoteInputController(mRemoteInputController);
- }
+ if (mRemoteInputListener != null) {
+ throw new IllegalStateException("mRemoteInputListener is already set");
}
- }
-
- @NonNull
- @VisibleForTesting
- protected LegacyRemoteInputLifetimeExtender createLegacyRemoteInputLifetimeExtender(
- Handler mainHandler,
- NotificationEntryManager notificationEntryManager,
- SmartReplyController smartReplyController) {
- return new LegacyRemoteInputLifetimeExtender();
+ mRemoteInputListener = remoteInputListener;
+ if (mRemoteInputController != null) {
+ mRemoteInputListener.setRemoteInputController(mRemoteInputController);
+ }
}
/** Initializes this component with the provided dependencies. */
@@ -381,12 +362,6 @@
}
}
});
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- mSmartReplyController.setCallback((entry, reply) -> {
- StatusBarNotification newSbn = mRebuilder.rebuildForSendingSmartReply(entry, reply);
- mEntryManager.updateNotification(newSbn, null /* ranking */);
- });
- }
}
public void addControllerCallback(RemoteInputController.Callback callback) {
@@ -588,19 +563,6 @@
return v.findViewWithTag(RemoteInputView.VIEW_TAG);
}
- public ArrayList<NotificationLifetimeExtender> getLifetimeExtenders() {
- // OLD pipeline code ONLY; can assume implementation
- return ((LegacyRemoteInputLifetimeExtender) mRemoteInputListener).mLifetimeExtenders;
- }
-
- @VisibleForTesting
- void onPerformRemoveNotification(NotificationEntry entry, final String key) {
- // OLD pipeline code ONLY; can assume implementation
- ((LegacyRemoteInputLifetimeExtender) mRemoteInputListener)
- .mKeysKeptForRemoteInputHistory.remove(key);
- cleanUpRemoteInputForUserRemoval(entry);
- }
-
/**
* Disable remote input on the entry and remove the remote input view.
* This should be called when a user dismisses a notification that won't be lifetime extended.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 03d66ee..587c23b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -139,12 +139,10 @@
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
NotificationVisibilityProvider visibilityProvider,
- NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
NotifPipeline notifPipeline,
NotifCollection notifCollection,
- NotifPipelineFlags notifPipelineFlags,
@Main DelayableExecutor mainExecutor,
MediaDataManager mediaDataManager,
DumpManager dumpManager) {
@@ -153,12 +151,10 @@
centralSurfacesOptionalLazy,
notificationShadeWindowController,
visibilityProvider,
- notificationEntryManager,
mediaArtworkProcessor,
keyguardBypassController,
notifPipeline,
notifCollection,
- notifPipelineFlags,
mainExecutor,
mediaDataManager,
dumpManager);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 6a01df6..1fdf18f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -310,7 +310,7 @@
}
locallyDismissNotifications(entriesToLocallyDismiss);
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("dismissNotifications");
}
/**
@@ -354,7 +354,7 @@
}
locallyDismissNotifications(entries);
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("dismissAllNotifications");
}
/**
@@ -401,7 +401,7 @@
postNotification(sbn, requireRanking(rankingMap, sbn.getKey()));
applyRanking(rankingMap);
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onNotificationPosted");
}
private void onNotificationGroupPosted(List<CoalescedEvent> batch) {
@@ -412,7 +412,7 @@
for (CoalescedEvent event : batch) {
postNotification(event.getSbn(), event.getRanking());
}
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onNotificationGroupPosted");
}
private void onNotificationRemoved(
@@ -433,14 +433,14 @@
entry.mCancellationReason = reason;
tryRemoveNotification(entry);
applyRanking(rankingMap);
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onNotificationRemoved");
}
private void onNotificationRankingUpdate(RankingMap rankingMap) {
Assert.isMainThread();
mEventQueue.add(new RankingUpdatedEvent(rankingMap));
applyRanking(rankingMap);
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onNotificationRankingUpdate");
}
private void onNotificationChannelModified(
@@ -450,7 +450,7 @@
int modificationType) {
Assert.isMainThread();
mEventQueue.add(new ChannelChangedEvent(pkgName, user, channel, modificationType));
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onNotificationChannelModified");
}
private void onNotificationsInitialized() {
@@ -578,12 +578,10 @@
// TODO: (b/145659174) update the sbn's overrideGroupKey in
// NotificationEntry.setRanking instead of here once we fully migrate to the
// NewNotifPipeline
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- final String newOverrideGroupKey = ranking.getOverrideGroupKey();
- if (!Objects.equals(entry.getSbn().getOverrideGroupKey(),
- newOverrideGroupKey)) {
- entry.getSbn().setOverrideGroupKey(newOverrideGroupKey);
- }
+ final String newOverrideGroupKey = ranking.getOverrideGroupKey();
+ if (!Objects.equals(entry.getSbn().getOverrideGroupKey(),
+ newOverrideGroupKey)) {
+ entry.getSbn().setOverrideGroupKey(newOverrideGroupKey);
}
} else {
if (currentEntriesWithoutRankings == null) {
@@ -610,7 +608,7 @@
mEventQueue.add(new RankingAppliedEvent());
}
- private void dispatchEventsAndRebuildList() {
+ private void dispatchEventsAndRebuildList(String reason) {
Trace.beginSection("NotifCollection.dispatchEventsAndRebuildList");
mAmDispatchingToOtherCode = true;
while (!mEventQueue.isEmpty()) {
@@ -619,7 +617,7 @@
mAmDispatchingToOtherCode = false;
if (mBuildListener != null) {
- mBuildListener.onBuildList(mReadOnlyNotificationSet);
+ mBuildListener.onBuildList(mReadOnlyNotificationSet, reason);
}
Trace.endSection();
}
@@ -654,7 +652,7 @@
if (!isLifetimeExtended(entry)) {
if (tryRemoveNotification(entry)) {
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onEndLifetimeExtension");
}
}
}
@@ -963,7 +961,7 @@
mEventQueue.add(new EntryUpdatedEvent(entry, false /* fromSystem */));
// Skip the applyRanking step and go straight to dispatching the events
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("updateNotificationInternally");
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
index e3ee813..a57440c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
@@ -76,7 +76,6 @@
*/
@SysUISingleton
class NotifPipeline @Inject constructor(
- notifPipelineFlags: NotifPipelineFlags,
private val mNotifCollection: NotifCollection,
private val mShadeListBuilder: ShadeListBuilder,
private val mRenderStageManager: RenderStageManager
@@ -107,8 +106,6 @@
return mNotifCollection.getEntry(key)
}
- val isNewPipelineEnabled: Boolean = notifPipelineFlags.isNewPipelineEnabled()
-
/**
* Registers a lifetime extender. Lifetime extenders can cause notifications that have been
* dismissed or retracted by system server to be temporarily retained in the collection.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 26d2ee3..8a18d31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -304,11 +304,11 @@
private final CollectionReadyForBuildListener mReadyForBuildListener =
new CollectionReadyForBuildListener() {
@Override
- public void onBuildList(Collection<NotificationEntry> entries) {
+ public void onBuildList(Collection<NotificationEntry> entries, String reason) {
Assert.isMainThread();
mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
- mLogger.logOnBuildList();
+ mLogger.logOnBuildList(reason);
mAllEntries = entries;
mChoreographer.schedule();
}
@@ -456,7 +456,8 @@
mLogger.logEndBuildList(
mIterationCount,
mReadOnlyNotifList.size(),
- countChildren(mReadOnlyNotifList));
+ countChildren(mReadOnlyNotifList),
+ /* enforcedVisualStability */ !mNotifStabilityManager.isEveryChangeAllowed());
if (mAlwaysLogList || mIterationCount % 10 == 0) {
Trace.beginSection("ShadeListBuilder.logFinalList");
mLogger.logFinalList(mNotifList);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index b923fdf..7293953 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -127,14 +127,6 @@
DismissedByUserStats dismissedByUserStats,
int reason
) {
- if (!mNotifPipeline.isNewPipelineEnabled()) {
- // The `entry` will be from whichever pipeline is active, so if the old pipeline is
- // running, make sure that we use the new pipeline's entry (if it still exists).
- NotificationEntry newPipelineEntry = mNotifPipeline.getEntry(entry.getKey());
- if (newPipelineEntry != null) {
- entry = newPipelineEntry;
- }
- }
if (isInterceptingDismissal(entry)) {
mInterceptedDismissalEntries.remove(entry.getKey());
mOnEndDismissInterception.onEndDismissInterception(mDismissInterceptor, entry,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index f9b8644..1399385 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -68,9 +68,7 @@
// pipeline, such as this DataStoreCoordinator which cannot be removed, as it's a critical
// glue between the pipeline and parts of SystemUI which depend on pipeline output via the
// NotifLiveDataStore.
- if (notifPipelineFlags.isNewPipelineEnabled()) {
- mCoordinators.add(dataStoreCoordinator)
- }
+ mCoordinators.add(dataStoreCoordinator)
// Attach normal coordinators.
mCoordinators.add(hideLocallyDismissedNotifsCoordinator)
@@ -93,18 +91,14 @@
if (notifPipelineFlags.isSmartspaceDedupingEnabled()) {
mCoordinators.add(smartspaceDedupingCoordinator)
}
- if (notifPipelineFlags.isNewPipelineEnabled()) {
- mCoordinators.add(headsUpCoordinator)
- mCoordinators.add(gutsCoordinator)
- mCoordinators.add(preparationCoordinator)
- mCoordinators.add(remoteInputCoordinator)
- }
+ mCoordinators.add(headsUpCoordinator)
+ mCoordinators.add(gutsCoordinator)
+ mCoordinators.add(preparationCoordinator)
+ mCoordinators.add(remoteInputCoordinator)
// Manually add Ordered Sections
// HeadsUp > FGS > People > Alerting > Silent > Minimized > Unknown/Default
- if (notifPipelineFlags.isNewPipelineEnabled()) {
- mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
- }
+ mOrderedSections.add(headsUpCoordinator.sectioner)
mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
mOrderedSections.add(conversationCoordinator.sectioner) // People
mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt
index 48f00ac..ce361ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt
@@ -65,13 +65,6 @@
statusBarStateController.addCallback(statusBarStateListener)
smartspaceController.addListener(this::onNewSmartspaceTargets)
- if (!pipeline.isNewPipelineEnabled) {
- // TODO (b/173126564): Remove this once the old pipeline is no longer necessary
- notificationLockscreenUserManager.addKeyguardNotificationSuppressor { entry ->
- isDupedWithSmartspaceContent(entry)
- }
- }
-
recordStatusBarState(statusBarStateController.state)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
index bae16fe..5a281b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -70,11 +70,9 @@
override fun attach(pipeline: NotifPipeline) {
mPipeline = pipeline
- if (pipeline.isNewPipelineEnabled) {
- mLockscreenUserManager.addUserChangedListener(mUserChangedListener)
- mConfigurationController.addCallback(this)
- mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback)
- }
+ mLockscreenUserManager.addUserChangedListener(mUserChangedListener)
+ mConfigurationController.addCallback(this)
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback)
}
override fun onDensityOrFontScaleChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
index d1ab62c..a34d033 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
@@ -24,7 +24,6 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -58,7 +57,6 @@
private final NotifInflaterImpl mNotifInflater;
private final DumpManager mDumpManager;
private final ShadeViewManagerFactory mShadeViewManagerFactory;
- private final NotifPipelineFlags mNotifPipelineFlags;
/* These are saved just for dumping. */
private ShadeViewManager mShadeViewManager;
@@ -74,8 +72,7 @@
NotifCoordinators notifCoordinators,
NotifInflaterImpl notifInflater,
DumpManager dumpManager,
- ShadeViewManagerFactory shadeViewManagerFactory,
- NotifPipelineFlags notifPipelineFlags
+ ShadeViewManagerFactory shadeViewManagerFactory
) {
mPipelineWrapper = pipelineWrapper;
mGroupCoalescer = groupCoalescer;
@@ -86,7 +83,6 @@
mDumpManager = dumpManager;
mNotifInflater = notifInflater;
mShadeViewManagerFactory = shadeViewManagerFactory;
- mNotifPipelineFlags = notifPipelineFlags;
}
/** Hooks the new pipeline up to NotificationManager */
@@ -100,25 +96,21 @@
mNotificationService = notificationService;
// Setup inflation
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- mNotifInflater.setRowBinder(rowBinder);
- }
+ mNotifInflater.setRowBinder(rowBinder);
// Wire up coordinators
mNotifPluggableCoordinators.attach(mPipelineWrapper);
// Wire up pipeline
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- mShadeViewManager = mShadeViewManagerFactory.create(listContainer, stackController);
- mShadeViewManager.attach(mRenderStageManager);
- }
+ mShadeViewManager = mShadeViewManagerFactory.create(listContainer, stackController);
+ mShadeViewManager.attach(mRenderStageManager);
mRenderStageManager.attach(mListBuilder);
mListBuilder.attach(mNotifCollection);
mNotifCollection.attach(mGroupCoalescer);
mGroupCoalescer.attach(mNotificationService);
Log.d(TAG, "Notif pipeline initialized."
- + " rendering=" + mNotifPipelineFlags.isNewPipelineEnabled());
+ + " rendering=" + true);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index 8d1759b..10a627d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -21,31 +21,42 @@
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.util.Compile
import javax.inject.Inject
class ShadeListBuilderLogger @Inject constructor(
+ notifPipelineFlags: NotifPipelineFlags,
@NotificationLog private val buffer: LogBuffer
) {
- fun logOnBuildList() {
+ fun logOnBuildList(reason: String?) {
buffer.log(TAG, INFO, {
+ str1 = reason
}, {
- "Request received from NotifCollection"
+ "Request received from NotifCollection for $str1"
})
}
- fun logEndBuildList(buildId: Int, topLevelEntries: Int, numChildren: Int) {
+ fun logEndBuildList(
+ buildId: Int,
+ topLevelEntries: Int,
+ numChildren: Int,
+ enforcedVisualStability: Boolean
+ ) {
buffer.log(TAG, INFO, {
long1 = buildId.toLong()
int1 = topLevelEntries
int2 = numChildren
+ bool1 = enforcedVisualStability
}, {
- "(Build $long1) Build complete ($int1 top-level entries, $int2 children)"
+ "(Build $long1) Build complete ($int1 top-level entries, $int2 children)" +
+ " enforcedVisualStability=$bool1"
})
}
@@ -280,6 +291,8 @@
})
}
+ val logRankInFinalList = Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled()
+
fun logFinalList(entries: List<ListEntry>) {
if (entries.isEmpty()) {
buffer.log(TAG, DEBUG, {}, { "(empty list)" })
@@ -289,16 +302,20 @@
buffer.log(TAG, DEBUG, {
int1 = i
str1 = entry.logKey
+ bool1 = logRankInFinalList
+ int2 = entry.representativeEntry!!.ranking.rank
}, {
- "[$int1] $str1"
+ "[$int1] $str1".let { if (bool1) "$it rank=$int2" else it }
})
if (entry is GroupEntry) {
entry.summary?.let {
buffer.log(TAG, DEBUG, {
str1 = it.logKey
+ bool1 = logRankInFinalList
+ int2 = it.ranking.rank
}, {
- " [*] $str1 (summary)"
+ " [*] $str1 (summary)".let { if (bool1) "$it rank=$int2" else it }
})
}
for (j in entry.children.indices) {
@@ -306,8 +323,10 @@
buffer.log(TAG, DEBUG, {
int1 = j
str1 = child.logKey
+ bool1 = logRankInFinalList
+ int2 = child.ranking.rank
}, {
- " [$int1] $str1"
+ " [$int1] $str1".let { if (bool1) "$it rank=$int2" else it }
})
}
}
@@ -318,4 +337,4 @@
buffer.log(TAG, INFO, {}) { "Suppressing pipeline run during animation." }
}
-private const val TAG = "ShadeListBuilder"
\ No newline at end of file
+private const val TAG = "ShadeListBuilder"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
index 4023474..941b2ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
@@ -29,5 +29,5 @@
* Called by the NotifCollection to indicate that something in the collection has changed and
* that the list builder should regenerate the list.
*/
- void onBuildList(Collection<NotificationEntry> entries);
+ void onBuildList(Collection<NotificationEntry> entries, String reason);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 8b1bed7..385fbb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -24,32 +24,23 @@
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.AnimatedImageNotificationManager
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.NotificationClicker
import com.android.systemui.statusbar.notification.NotificationEntryManager
import com.android.systemui.statusbar.notification.NotificationListController
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore
import com.android.systemui.statusbar.notification.collection.NotifPipeline
-import com.android.systemui.statusbar.notification.collection.NotificationRankingManager
import com.android.systemui.statusbar.notification.collection.TargetSdkResolver
-import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import com.android.systemui.statusbar.notification.collection.provider.DebugModeFilterProvider
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
-import com.android.systemui.statusbar.notification.interruption.HeadsUpController
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.logging.NotificationLogger
import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.policy.RemoteInputUriController
import com.android.wm.shell.bubbles.Bubbles
import dagger.Lazy
import java.io.PrintWriter
@@ -65,32 +56,23 @@
*/
@SysUISingleton
class NotificationsControllerImpl @Inject constructor(
- private val notifPipelineFlags: NotifPipelineFlags,
private val notificationListener: NotificationListener,
private val entryManager: NotificationEntryManager,
- private val debugModeFilterProvider: DebugModeFilterProvider,
- private val legacyRanker: NotificationRankingManager,
private val commonNotifCollection: Lazy<CommonNotifCollection>,
private val notifPipeline: Lazy<NotifPipeline>,
private val notifLiveDataStore: NotifLiveDataStore,
private val targetSdkResolver: TargetSdkResolver,
- private val newNotifPipelineInitializer: Lazy<NotifPipelineInitializer>,
+ private val notifPipelineInitializer: Lazy<NotifPipelineInitializer>,
private val notifBindPipelineInitializer: NotifBindPipelineInitializer,
private val notificationLogger: NotificationLogger,
private val deviceProvisionedController: DeviceProvisionedController,
private val notificationRowBinder: NotificationRowBinderImpl,
- private val bindEventManagerImpl: BindEventManagerImpl,
- private val remoteInputUriController: RemoteInputUriController,
- private val groupManagerLegacy: Lazy<NotificationGroupManagerLegacy>,
- private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper,
private val notificationsMediaManager: NotificationMediaManager,
- private val headsUpManager: HeadsUpManager,
- private val headsUpController: HeadsUpController,
private val headsUpViewBinder: HeadsUpViewBinder,
private val clickerBuilder: NotificationClicker.Builder,
private val animatedImageNotificationManager: AnimatedImageNotificationManager,
private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
- private val bubblesOptional: Optional<Bubbles>
+ private val bubblesOptional: Optional<Bubbles>,
) : NotificationsController {
override fun initialize(
@@ -122,7 +104,7 @@
notifBindPipelineInitializer.initialize()
animatedImageNotificationManager.bind()
- newNotifPipelineInitializer.get().initialize(
+ notifPipelineInitializer.get().initialize(
notificationListener,
notificationRowBinder,
listContainer,
@@ -170,9 +152,4 @@
override fun getActiveNotificationsCount(): Int =
notifLiveDataStore.activeNotifCount.value
-
- companion object {
- // NOTE: The new pipeline is always active, even if the old pipeline is *rendering*.
- private const val INITIALIZE_NEW_PIPELINE = true
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 4013254..6d513d0da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -24,8 +24,7 @@
import android.view.View;
import android.view.ViewGroup;
-import androidx.annotation.VisibleForTesting;
-
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.SystemBarUtils;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.R;
@@ -65,6 +64,9 @@
private int mPinnedZTranslationExtra;
private float mNotificationScrimPadding;
private int mMarginBottom;
+ private float mQuickQsOffsetHeight;
+ private float mSmallCornerRadius;
+ private float mLargeCornerRadius;
public StackScrollAlgorithm(
Context context,
@@ -74,10 +76,10 @@
}
public void initView(Context context) {
- initConstants(context);
+ updateResources(context);
}
- private void initConstants(Context context) {
+ private void updateResources(Context context) {
Resources res = context.getResources();
mPaddingBetweenElements = res.getDimensionPixelSize(
R.dimen.notification_divider_height);
@@ -93,6 +95,9 @@
R.dimen.notification_section_divider_height_lockscreen);
mNotificationScrimPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
mMarginBottom = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
+ mQuickQsOffsetHeight = SystemBarUtils.getQuickQsOffsetHeight(context);
+ mSmallCornerRadius = res.getDimension(R.dimen.notification_corner_radius_small);
+ mLargeCornerRadius = res.getDimension(R.dimen.notification_corner_radius);
}
/**
@@ -441,6 +446,15 @@
return false;
}
+ @VisibleForTesting
+ void maybeUpdateHeadsUpIsVisible(ExpandableViewState viewState, boolean isShadeExpanded,
+ boolean mustStayOnScreen, boolean topVisible, float viewEnd, float hunMax) {
+
+ if (isShadeExpanded && mustStayOnScreen && topVisible) {
+ viewState.headsUpIsVisible = viewEnd < hunMax;
+ }
+ }
+
// TODO(b/172289889) polish shade open from HUN
/**
* Populates the {@link ExpandableViewState} for a single child.
@@ -474,14 +488,6 @@
: ShadeInterpolation.getContentAlpha(expansion);
}
- if (ambientState.isShadeExpanded() && view.mustStayOnScreen()
- && viewState.yTranslation >= 0) {
- // Even if we're not scrolled away we're in view and we're also not in the
- // shelf. We can relax the constraints and let us scroll off the top!
- float end = viewState.yTranslation + viewState.height + ambientState.getStackY();
- viewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
- }
-
final float expansionFraction = getExpansionFractionWithoutShelf(
algorithmState, ambientState);
@@ -497,8 +503,15 @@
algorithmState.mCurrentExpandedYPosition += gap;
}
+ // Must set viewState.yTranslation _before_ use.
+ // Incoming views have yTranslation=0 by default.
viewState.yTranslation = algorithmState.mCurrentYPosition;
+ maybeUpdateHeadsUpIsVisible(viewState, ambientState.isShadeExpanded(),
+ view.mustStayOnScreen(), /* topVisible */ viewState.yTranslation >= 0,
+ /* viewEnd */ viewState.yTranslation + viewState.height + ambientState.getStackY(),
+ /* hunMax */ ambientState.getMaxHeadsUpTranslation()
+ );
if (view instanceof FooterView) {
final boolean shadeClosed = !ambientState.isShadeExpanded();
final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
@@ -682,7 +695,8 @@
if (row.mustStayOnScreen() && !childState.headsUpIsVisible
&& !row.showingPulsing()) {
// Ensure that the heads up is always visible even when scrolled off
- clampHunToTop(ambientState, row, childState);
+ clampHunToTop(mQuickQsOffsetHeight, ambientState.getStackTranslation(),
+ row.getCollapsedHeight(), childState);
if (isTopEntry && row.isAboveShelf()) {
// the first hun can't get off screen.
clampHunToMaxTranslation(ambientState, row, childState);
@@ -719,27 +733,62 @@
}
}
- private void clampHunToTop(AmbientState ambientState, ExpandableNotificationRow row,
- ExpandableViewState childState) {
- float newTranslation = Math.max(ambientState.getTopPadding()
- + ambientState.getStackTranslation(), childState.yTranslation);
- childState.height = (int) Math.max(childState.height - (newTranslation
- - childState.yTranslation), row.getCollapsedHeight());
- childState.yTranslation = newTranslation;
+ /**
+ * When shade is open and we are scrolled to the bottom of notifications,
+ * clamp incoming HUN in its collapsed form, right below qs offset.
+ * Transition pinned collapsed HUN to full height when scrolling back up.
+ */
+ @VisibleForTesting
+ void clampHunToTop(float quickQsOffsetHeight, float stackTranslation, float collapsedHeight,
+ ExpandableViewState viewState) {
+
+ final float newTranslation = Math.max(quickQsOffsetHeight + stackTranslation,
+ viewState.yTranslation);
+
+ // Transition from collapsed pinned state to fully expanded state
+ // when the pinned HUN approaches its actual location (when scrolling back to top).
+ final float distToRealY = newTranslation - viewState.yTranslation;
+ viewState.height = (int) Math.max(viewState.height - distToRealY, collapsedHeight);
+ viewState.yTranslation = newTranslation;
}
+ // Pin HUN to bottom of expanded QS
+ // while the rest of notifications are scrolled offscreen.
private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
ExpandableViewState childState) {
- float newTranslation;
float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation();
- float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
+ final float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
+ ambientState.getStackTranslation();
maxHeadsUpTranslation = Math.min(maxHeadsUpTranslation, maxShelfPosition);
- float bottomPosition = maxHeadsUpTranslation - row.getCollapsedHeight();
- newTranslation = Math.min(childState.yTranslation, bottomPosition);
+
+ final float bottomPosition = maxHeadsUpTranslation - row.getCollapsedHeight();
+ final float newTranslation = Math.min(childState.yTranslation, bottomPosition);
childState.height = (int) Math.min(childState.height, maxHeadsUpTranslation
- newTranslation);
childState.yTranslation = newTranslation;
+
+ // Animate pinned HUN bottom corners to and from original roundness.
+ final float originalCornerRadius =
+ row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius);
+ final float roundness = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
+ ambientState.getStackY(), getMaxAllowedChildHeight(row), originalCornerRadius);
+ row.setBottomRoundness(roundness, /* animate= */ false);
+ }
+
+ @VisibleForTesting
+ float computeCornerRoundnessForPinnedHun(float hostViewHeight, float stackY,
+ float viewMaxHeight, float originalCornerRadius) {
+
+ // Compute y where corner roundness should be in its original unpinned state.
+ // We use view max height because the pinned collapsed HUN expands to max height
+ // when it becomes unpinned.
+ final float originalRoundnessY = hostViewHeight - viewMaxHeight;
+
+ final float distToOriginalRoundness = Math.max(0f, stackY - originalRoundnessY);
+ final float progressToPinnedRoundness = Math.min(1f,
+ distToOriginalRoundness / viewMaxHeight);
+
+ return MathUtils.lerp(originalCornerRadius, 1f, progressToPinnedRoundness);
}
protected int getMaxAllowedChildHeight(View child) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 39620ac..a0f386f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -448,7 +448,6 @@
}
// During wake and unlock, we need to draw black before waking up to avoid abrupt
// brightness changes due to display state transitions.
- boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
Runnable wakeUp = ()-> {
if (!wasDeviceInteractive) {
if (DEBUG_BIO_WAKELOCK) {
@@ -659,7 +658,10 @@
mLatencyTracker.onActionCancel(action);
}
- if (biometricSourceType == BiometricSourceType.FINGERPRINT
+ if (!mVibratorHelper.hasVibrator()
+ && (!mUpdateMonitor.isDeviceInteractive() || mUpdateMonitor.isDreaming())) {
+ startWakeAndUnlock(MODE_SHOW_BOUNCER);
+ } else if (biometricSourceType == BiometricSourceType.FINGERPRINT
&& mUpdateMonitor.isUdfpsSupported()) {
long currUptimeMillis = SystemClock.uptimeMillis();
if (currUptimeMillis - mLastFpFailureUptimeMillis < mConsecutiveFpFailureThreshold) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 745b7d9..aea5360 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -138,6 +138,7 @@
import com.android.systemui.biometrics.AuthRippleController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.camera.CameraIntents;
+import com.android.systemui.charging.WiredChargingRippleController;
import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -196,7 +197,6 @@
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.charging.WiredChargingRippleController;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.core.StatusBarInitializer;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
@@ -1742,6 +1742,7 @@
// activity is exited.
if (mKeyguardStateController.isShowing()
&& !mKeyguardStateController.isKeyguardGoingAway()) {
+ Log.d(TAG, "Setting occluded = true in #startActivity.");
mKeyguardViewMediator.setOccluded(true /* isOccluded */,
true /* animate */);
}
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 1fcaeae..5810d64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -115,6 +115,7 @@
import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.LaunchAnimator;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.camera.CameraGestureHelper;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.controls.dagger.ControlsComponent;
@@ -691,6 +692,8 @@
}
};
+ private final CameraGestureHelper mCameraGestureHelper;
+
@Inject
public NotificationPanelViewController(NotificationPanelView view,
@Main Resources resources,
@@ -762,7 +765,8 @@
NotificationStackSizeCalculator notificationStackSizeCalculator,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
ShadeTransitionController shadeTransitionController,
- SystemClock systemClock) {
+ SystemClock systemClock,
+ CameraGestureHelper cameraGestureHelper) {
super(view,
falsingManager,
dozeLog,
@@ -947,6 +951,7 @@
}
}
});
+ mCameraGestureHelper = cameraGestureHelper;
}
@VisibleForTesting
@@ -3630,6 +3635,11 @@
/** Launches the camera. */
public void launchCamera(int source) {
+ if (!isFullyCollapsed()) {
+ setLaunchingAffordance(true);
+ }
+
+ mCameraGestureHelper.launchCamera(source);
}
public void onAffordanceLaunchEnded() {
@@ -3656,17 +3666,7 @@
* Whether the camera application can be launched for the camera launch gesture.
*/
public boolean canCameraGestureBeLaunched() {
- return false;
- }
-
- /**
- * Return true if the applications with the package name is running in foreground.
- *
- * @param pkgName application package name.
- */
- private boolean isForegroundApp(String pkgName) {
- List<ActivityManager.RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
- return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
+ return mCameraGestureHelper.canCameraGestureBeLaunched(mBarState);
}
public boolean hideStatusBarIconsWhenExpanded() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
index 911e750..a6160aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
@@ -20,6 +20,7 @@
import android.util.Log
import androidx.annotation.FloatRange
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.Compile
import javax.inject.Inject
/**
@@ -109,8 +110,8 @@
debugLog(
"panelExpansionChanged:" +
- "start state=${oldState.stateToString()} " +
- "end state=${state.stateToString()} " +
+ "start state=${oldState.panelStateToString()} " +
+ "end state=${state.panelStateToString()} " +
"f=$fraction " +
"expanded=$expanded " +
"tracking=$tracking" +
@@ -126,14 +127,15 @@
/** Updates the panel state if necessary. */
fun updateState(@PanelState state: Int) {
- debugLog("update state: ${this.state.stateToString()} -> ${state.stateToString()}")
+ debugLog(
+ "update state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}")
if (this.state != state) {
updateStateInternal(state)
}
}
private fun updateStateInternal(@PanelState state: Int) {
- debugLog("go state: ${this.state.stateToString()} -> ${state.stateToString()}")
+ debugLog("go state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}")
this.state = state
stateListeners.forEach { it.onPanelStateChanged(state) }
}
@@ -154,7 +156,7 @@
const val STATE_OPEN = 2
@PanelState
-private fun Int.stateToString(): String {
+fun Int.panelStateToString(): String {
return when (this) {
STATE_CLOSED -> "CLOSED"
STATE_OPENING -> "OPENING"
@@ -163,5 +165,5 @@
}
}
-private const val DEBUG = false
private val TAG = PanelExpansionStateManager::class.simpleName
+private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt
index 16f28e7..1b8afb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionController.kt
@@ -11,6 +11,8 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
+import com.android.systemui.statusbar.phone.panelstate.PanelState
+import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.LargeScreenUtils
import java.io.PrintWriter
@@ -30,8 +32,9 @@
private var inSplitShade = false
private var splitShadeScrimTransitionDistance = 0
- private var lastExpansionFraction: Float = 0f
+ private var lastExpansionFraction: Float? = null
private var lastExpansionEvent: PanelExpansionChangeEvent? = null
+ private var currentPanelState: Int? = null
init {
updateResources()
@@ -41,8 +44,8 @@
updateResources()
}
})
- dumpManager
- .registerDumpable(ScrimShadeTransitionController::class.java.simpleName, this::dump)
+ dumpManager.registerDumpable(
+ ScrimShadeTransitionController::class.java.simpleName, this::dump)
}
private fun updateResources() {
@@ -51,21 +54,38 @@
resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
}
- fun onPanelExpansionChanged(panelExpansionChangeEvent: PanelExpansionChangeEvent) {
- val expansionFraction = calculateScrimExpansionFraction(panelExpansionChangeEvent)
- scrimController.setRawPanelExpansionFraction(expansionFraction)
- lastExpansionFraction = expansionFraction
- lastExpansionEvent = panelExpansionChangeEvent
+ fun onPanelStateChanged(@PanelState state: Int) {
+ currentPanelState = state
+ onStateChanged()
}
- private fun calculateScrimExpansionFraction(expansionEvent: PanelExpansionChangeEvent): Float {
- return if (inSplitShade && isScreenUnlocked()) {
+ fun onPanelExpansionChanged(panelExpansionChangeEvent: PanelExpansionChangeEvent) {
+ lastExpansionEvent = panelExpansionChangeEvent
+ onStateChanged()
+ }
+
+ private fun onStateChanged() {
+ val expansionEvent = lastExpansionEvent ?: return
+ val panelState = currentPanelState
+ val expansionFraction = calculateScrimExpansionFraction(expansionEvent, panelState)
+ scrimController.setRawPanelExpansionFraction(expansionFraction)
+ lastExpansionFraction = expansionFraction
+ }
+
+ private fun calculateScrimExpansionFraction(
+ expansionEvent: PanelExpansionChangeEvent,
+ @PanelState panelState: Int?
+ ): Float {
+ return if (canUseCustomFraction(panelState)) {
constrain(expansionEvent.dragDownPxAmount / splitShadeScrimTransitionDistance, 0f, 1f)
} else {
expansionEvent.fraction
}
}
+ private fun canUseCustomFraction(panelState: Int?) =
+ inSplitShade && isScreenUnlocked() && panelState == STATE_OPENING
+
private fun isScreenUnlocked() =
statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE
@@ -78,9 +98,9 @@
isScreenUnlocked: ${isScreenUnlocked()}
splitShadeScrimTransitionDistance: $splitShadeScrimTransitionDistance
State:
+ currentPanelState: $currentPanelState
lastExpansionFraction: $lastExpansionFraction
lastExpansionEvent: $lastExpansionEvent
- """.trimIndent()
- )
+ """.trimIndent())
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt
index e967d4a..71c6159 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt
@@ -11,6 +11,7 @@
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
import com.android.systemui.statusbar.phone.panelstate.PanelState
+import com.android.systemui.statusbar.phone.panelstate.panelStateToString
import com.android.systemui.statusbar.policy.ConfigurationController
import java.io.PrintWriter
import javax.inject.Inject
@@ -34,6 +35,8 @@
lateinit var qs: QS
private var inSplitShade = false
+ private var currentPanelState: Int? = null
+ private var lastPanelExpansionChangeEvent: PanelExpansionChangeEvent? = null
private val splitShadeOverScroller by lazy {
splitShadeOverScrollerFactory.create({ qs }, { notificationStackScrollLayoutController })
@@ -66,10 +69,13 @@
}
private fun onPanelStateChanged(@PanelState state: Int) {
+ currentPanelState = state
shadeOverScroller.onPanelStateChanged(state)
+ scrimShadeTransitionController.onPanelStateChanged(state)
}
private fun onPanelExpansionChanged(event: PanelExpansionChangeEvent) {
+ lastPanelExpansionChangeEvent = event
shadeOverScroller.onDragDownAmountChanged(event.dragDownPxAmount)
scrimShadeTransitionController.onPanelExpansionChanged(event)
}
@@ -84,6 +90,8 @@
"""
ShadeTransitionController:
inSplitShade: $inSplitShade
+ currentPanelState: ${currentPanelState?.panelStateToString()}
+ lastPanelExpansionChangeEvent: $lastPanelExpansionChangeEvent
qs.isInitialized: ${this::qs.isInitialized}
npvc.isInitialized: ${this::notificationPanelViewController.isInitialized}
nssl.isInitialized: ${this::notificationStackScrollLayoutController.isInitialized}
diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
index cc6bf6a..f017126 100644
--- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
@@ -31,6 +31,7 @@
import com.android.settingslib.users.EditUserInfoController;
import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
import javax.inject.Inject;
@@ -55,15 +56,18 @@
private final UserCreator mUserCreator;
private final EditUserInfoController mEditUserInfoController;
private final IActivityManager mActivityManager;
+ private final ActivityStarter mActivityStarter;
private Dialog mSetupUserDialog;
@Inject
public CreateUserActivity(UserCreator userCreator,
- EditUserInfoController editUserInfoController, IActivityManager activityManager) {
+ EditUserInfoController editUserInfoController, IActivityManager activityManager,
+ ActivityStarter activityStarter) {
mUserCreator = userCreator;
mEditUserInfoController = editUserInfoController;
mActivityManager = activityManager;
+ mActivityStarter = activityStarter;
}
@Override
@@ -104,10 +108,7 @@
return mEditUserInfoController.createDialog(
this,
- (intent, requestCode) -> {
- mEditUserInfoController.startingActivityForResult();
- startActivityForResult(intent, requestCode);
- },
+ this::startActivity,
null,
defaultUserName,
getString(com.android.settingslib.R.string.user_add_user),
@@ -160,4 +161,17 @@
Log.e(TAG, "Couldn't switch user.", e);
}
}
+
+ /**
+ * Lambda to start activity from an intent. Ensures that device is unlocked first.
+ * @param intent
+ * @param requestCode
+ */
+ private void startActivity(Intent intent, int requestCode) {
+ mActivityStarter.dismissKeyguardThenExecute(() -> {
+ mEditUserInfoController.startingActivityForResult();
+ startActivityForResult(intent, requestCode);
+ return true;
+ }, /* cancel= */ null, /* afterKeyguardGone= */ true);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index 4e9030f..dac8a0b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -92,7 +92,15 @@
}
private void updateConditionMetState(Condition condition) {
- mConditions.get(condition).stream().forEach(token -> mSubscriptions.get(token).update());
+ final ArraySet<Subscription.Token> subscriptions = mConditions.get(condition);
+
+ // It's possible the condition was removed between the time the callback occurred and
+ // update was executed on the main thread.
+ if (subscriptions == null) {
+ return;
+ }
+
+ subscriptions.stream().forEach(token -> mSubscriptions.get(token).update());
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index e90775d..bca2a24 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -21,14 +21,10 @@
import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
-import static android.service.notification.NotificationListenerService.REASON_CLICK;
import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
-import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -55,7 +51,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
@@ -63,9 +58,7 @@
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationChannelHelper;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -114,7 +107,6 @@
private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final NotificationLockscreenUserManager mNotifUserManager;
private final NotificationGroupManagerLegacy mNotificationGroupManager;
- private final NotificationEntryManager mNotificationEntryManager;
private final CommonNotifCollection mCommonNotifCollection;
private final NotifPipeline mNotifPipeline;
private final Executor mSysuiMainExecutor;
@@ -142,11 +134,9 @@
ZenModeController zenModeController,
NotificationLockscreenUserManager notifUserManager,
NotificationGroupManagerLegacy groupManager,
- NotificationEntryManager entryManager,
CommonNotifCollection notifCollection,
NotifPipeline notifPipeline,
SysUiState sysUiState,
- NotifPipelineFlags notifPipelineFlags,
DumpManager dumpManager,
Executor sysuiMainExecutor) {
if (bubblesOptional.isPresent()) {
@@ -163,11 +153,9 @@
zenModeController,
notifUserManager,
groupManager,
- entryManager,
notifCollection,
notifPipeline,
sysUiState,
- notifPipelineFlags,
dumpManager,
sysuiMainExecutor);
} else {
@@ -189,11 +177,9 @@
ZenModeController zenModeController,
NotificationLockscreenUserManager notifUserManager,
NotificationGroupManagerLegacy groupManager,
- NotificationEntryManager entryManager,
CommonNotifCollection notifCollection,
NotifPipeline notifPipeline,
SysUiState sysUiState,
- NotifPipelineFlags notifPipelineFlags,
DumpManager dumpManager,
Executor sysuiMainExecutor) {
mContext = context;
@@ -205,7 +191,6 @@
mNotificationInterruptStateProvider = interruptionStateProvider;
mNotifUserManager = notifUserManager;
mNotificationGroupManager = groupManager;
- mNotificationEntryManager = entryManager;
mCommonNotifCollection = notifCollection;
mNotifPipeline = notifPipeline;
mSysuiMainExecutor = sysuiMainExecutor;
@@ -215,11 +200,7 @@
ServiceManager.getService(Context.STATUS_BAR_SERVICE))
: statusBarService;
- if (notifPipelineFlags.isNewPipelineEnabled()) {
- setupNotifPipeline();
- } else {
- setupNEM();
- }
+ setupNotifPipeline();
dumpManager.registerDumpable(TAG, this);
@@ -438,141 +419,6 @@
mBubbles.setSysuiProxy(mSysuiProxy);
}
- private void setupNEM() {
- mNotificationEntryManager.addNotificationEntryListener(
- new NotificationEntryListener() {
- @Override
- public void onPendingEntryAdded(NotificationEntry entry) {
- BubblesManager.this.onEntryAdded(entry);
- }
-
- @Override
- public void onPreEntryUpdated(NotificationEntry entry) {
- BubblesManager.this.onEntryUpdated(entry);
- }
-
- @Override
- public void onEntryRemoved(
- NotificationEntry entry,
- @Nullable NotificationVisibility visibility,
- boolean removedByUser,
- int reason) {
- BubblesManager.this.onEntryRemoved(entry);
- }
-
- @Override
- public void onNotificationRankingUpdated(RankingMap rankingMap) {
- BubblesManager.this.onRankingUpdate(rankingMap);
- }
-
- @Override
- public void onNotificationChannelModified(
- String pkgName,
- UserHandle user,
- NotificationChannel channel,
- int modificationType) {
- BubblesManager.this.onNotificationChannelModified(pkgName,
- user,
- channel,
- modificationType);
- }
- });
-
- // The new pipeline takes care of this as a NotifDismissInterceptor BubbleCoordinator
- mNotificationEntryManager.addNotificationRemoveInterceptor(
- (key, entry, dismissReason) -> {
- final boolean isClearAll = dismissReason == REASON_CANCEL_ALL;
- final boolean isUserDismiss = dismissReason == REASON_CANCEL
- || dismissReason == REASON_CLICK;
- final boolean isAppCancel = dismissReason == REASON_APP_CANCEL
- || dismissReason == REASON_APP_CANCEL_ALL;
- final boolean isSummaryCancel =
- dismissReason == REASON_GROUP_SUMMARY_CANCELED;
-
- // Need to check for !appCancel here because the notification may have
- // previously been dismissed & entry.isRowDismissed would still be true
- boolean userRemovedNotif =
- (entry != null && entry.isRowDismissed() && !isAppCancel)
- || isClearAll || isUserDismiss || isSummaryCancel;
-
- if (userRemovedNotif) {
- return handleDismissalInterception(entry);
- }
- return false;
- });
-
- mNotificationGroupManager.registerGroupChangeListener(
- new NotificationGroupManagerLegacy.OnGroupChangeListener() {
- @Override
- public void onGroupSuppressionChanged(
- NotificationGroupManagerLegacy.NotificationGroup group,
- boolean suppressed) {
- // More notifications could be added causing summary to no longer
- // be suppressed -- in this case need to remove the key.
- final String groupKey = group.summary != null
- ? group.summary.getSbn().getGroupKey()
- : null;
- if (!suppressed && groupKey != null) {
- mBubbles.removeSuppressedSummaryIfNecessary(groupKey, null, null);
- }
- }
- });
-
- addNotifCallback(new NotifCallback() {
- @Override
- public void removeNotification(NotificationEntry entry,
- DismissedByUserStats dismissedByUserStats, int reason) {
- mNotificationEntryManager.performRemoveNotification(entry.getSbn(),
- dismissedByUserStats, reason);
- }
-
- @Override
- public void invalidateNotifications(String reason) {
- mNotificationEntryManager.updateNotifications(reason);
- }
-
- @Override
- public void maybeCancelSummary(NotificationEntry entry) {
- // Check if removed bubble has an associated suppressed group summary that needs
- // to be removed now.
- final String groupKey = entry.getSbn().getGroupKey();
- mBubbles.removeSuppressedSummaryIfNecessary(groupKey, (summaryKey) -> {
- final NotificationEntry summary =
- mNotificationEntryManager.getActiveNotificationUnfiltered(summaryKey);
- if (summary != null) {
- mNotificationEntryManager.performRemoveNotification(
- summary.getSbn(),
- getDismissedByUserStats(summary, false),
- UNDEFINED_DISMISS_REASON);
- }
- }, mSysuiMainExecutor);
-
- // Check if we still need to remove the summary from NoManGroup because the summary
- // may not be in the mBubbleData.mSuppressedGroupKeys list and removed above.
- // For example:
- // 1. Bubbled notifications (group) is posted to shade and are visible bubbles
- // 2. User expands bubbles so now their respective notifications in the shade are
- // hidden, including the group summary
- // 3. User removes all bubbles
- // 4. We expect all the removed bubbles AND the summary (note: the summary was
- // never added to the suppressedSummary list in BubbleData, so we add this check)
- NotificationEntry summary = mNotificationGroupManager.getLogicalGroupSummary(entry);
- if (summary != null) {
- ArrayList<NotificationEntry> summaryChildren =
- mNotificationGroupManager.getLogicalChildren(summary.getSbn());
- boolean isSummaryThisNotif = summary.getKey().equals(entry.getKey());
- if (!isSummaryThisNotif && (summaryChildren == null
- || summaryChildren.isEmpty())) {
- mNotificationEntryManager.performRemoveNotification(
- summary.getSbn(),
- getDismissedByUserStats(summary, false),
- UNDEFINED_DISMISS_REASON);
- }
- }
- }
- });
- }
-
private void setupNotifPipeline() {
mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 90609fa..64a7986 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -1383,7 +1383,7 @@
mScreenDecorations.mDisplayListener.onDisplayChanged(1);
- verify(hwcLayer, times(1)).onDisplayChanged(1);
+ verify(hwcLayer, times(1)).onDisplayChanged(any());
}
@Test
@@ -1407,7 +1407,7 @@
mScreenDecorations.mDisplayListener.onDisplayChanged(1);
- verify(cutoutView, times(1)).onDisplayChanged(1);
+ verify(cutoutView, times(1)).onDisplayChanged(any());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index d0cf792..6978490 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.charging
+package com.android.systemui.charging
import android.testing.AndroidTestingRunner
import android.view.View
@@ -24,6 +24,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.ripple.RippleView
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -50,7 +51,7 @@
@Mock private lateinit var batteryController: BatteryController
@Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var configurationController: ConfigurationController
- @Mock private lateinit var rippleView: ChargingRippleView
+ @Mock private lateinit var rippleView: RippleView
@Mock private lateinit var windowManager: WindowManager
@Mock private lateinit var uiEventLogger: UiEventLogger
private val systemClock = FakeSystemClock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt
index 69366fa..8bf17d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.decor
+import android.graphics.Color
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.DisplayCutout
@@ -51,35 +52,45 @@
@Before
fun setUp() {
- decorProvider1 = spy(PrivacyDotCornerDecorProviderImpl(
+ decorProvider1 = spy(
+ PrivacyDotCornerDecorProviderImpl(
viewId = TEST_DECOR_VIEW_ID_1,
alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP,
alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
- layoutId = R.layout.privacy_dot_top_left))
- decorProvider2 = spy(PrivacyDotCornerDecorProviderImpl(
+ layoutId = R.layout.privacy_dot_top_left
+ )
+ )
+ decorProvider2 = spy(
+ PrivacyDotCornerDecorProviderImpl(
viewId = TEST_DECOR_VIEW_ID_2,
alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
- layoutId = R.layout.privacy_dot_bottom_left))
- decorProvider3 = spy(PrivacyDotCornerDecorProviderImpl(
+ layoutId = R.layout.privacy_dot_bottom_left
+ )
+ )
+ decorProvider3 = spy(
+ PrivacyDotCornerDecorProviderImpl(
viewId = TEST_DECOR_VIEW_ID_3,
alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT,
- layoutId = R.layout.privacy_dot_bottom_right))
-
+ layoutId = R.layout.privacy_dot_bottom_right
+ )
+ )
overlay = OverlayWindow(mContext)
}
@Test
fun testAddProvider() {
@Surface.Rotation val rotation = Surface.ROTATION_270
- overlay.addDecorProvider(decorProvider1, rotation)
- overlay.addDecorProvider(decorProvider2, rotation)
+ overlay.addDecorProvider(decorProvider1, rotation, Color.BLACK)
+ overlay.addDecorProvider(decorProvider2, rotation, Color.YELLOW)
verify(decorProvider1, times(1)).inflateView(
- mContext, overlay.rootView, rotation)
+ mContext, overlay.rootView, rotation, Color.BLACK
+ )
verify(decorProvider2, times(1)).inflateView(
- mContext, overlay.rootView, rotation)
+ mContext, overlay.rootView, rotation, Color.YELLOW
+ )
val view1FoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID_1)
Assert.assertNotNull(view1FoundFromRootView)
@@ -91,8 +102,8 @@
@Test
fun testRemoveView() {
- overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270)
- overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270)
+ overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270, Color.BLACK)
+ overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270, Color.BLACK)
overlay.removeView(TEST_DECOR_VIEW_ID_1)
val viewFoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID_1)
@@ -102,39 +113,47 @@
@Test
fun testOnReloadResAndMeasureWithoutIds() {
- overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0)
- overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0)
+ overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0, Color.BLACK)
+ overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0, Color.BLACK)
overlay.onReloadResAndMeasure(
- reloadToken = 1,
- rotation = Surface.ROTATION_90,
- displayUniqueId = null)
+ reloadToken = 1,
+ rotation = Surface.ROTATION_90,
+ tintColor = Color.BLACK,
+ displayUniqueId = null
+ )
verify(decorProvider1, times(1)).onReloadResAndMeasure(
- overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, null)
+ overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, Color.BLACK, null
+ )
verify(decorProvider2, times(1)).onReloadResAndMeasure(
- overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, null)
+ overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, Color.BLACK, null
+ )
}
@Test
fun testOnReloadResAndMeasureWithIds() {
- overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0)
- overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0)
+ overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0, Color.BLACK)
+ overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0, Color.BLACK)
overlay.onReloadResAndMeasure(
- filterIds = arrayOf(TEST_DECOR_VIEW_ID_2),
- reloadToken = 1,
- rotation = Surface.ROTATION_90,
- displayUniqueId = null)
+ filterIds = arrayOf(TEST_DECOR_VIEW_ID_2),
+ reloadToken = 1,
+ rotation = Surface.ROTATION_90,
+ tintColor = Color.BLACK,
+ displayUniqueId = null
+ )
verify(decorProvider1, never()).onReloadResAndMeasure(
- overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, null)
+ overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, Color.BLACK, null
+ )
verify(decorProvider2, times(1)).onReloadResAndMeasure(
- overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, null)
+ overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, Color.BLACK, null
+ )
}
@Test
fun testRemoveRedundantViewsWithNullParameter() {
- overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270)
- overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270)
+ overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270, Color.BLACK)
+ overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270, Color.BLACK)
overlay.removeRedundantViews(null)
@@ -146,13 +165,15 @@
@Test
fun testRemoveRedundantViewsWith2Providers() {
- overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270)
- overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270)
+ overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270, Color.BLACK)
+ overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270, Color.BLACK)
- overlay.removeRedundantViews(IntArray(2).apply {
- this[0] = TEST_DECOR_VIEW_ID_3
- this[1] = TEST_DECOR_VIEW_ID_1
- })
+ overlay.removeRedundantViews(
+ IntArray(2).apply {
+ this[0] = TEST_DECOR_VIEW_ID_3
+ this[1] = TEST_DECOR_VIEW_ID_1
+ }
+ )
Assert.assertNotNull(overlay.getView(TEST_DECOR_VIEW_ID_1))
Assert.assertNotNull(overlay.rootView.findViewById(TEST_DECOR_VIEW_ID_1))
@@ -167,16 +188,16 @@
Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2)))
Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2, decorProvider1)))
- overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0)
+ overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0, Color.BLACK)
Assert.assertFalse(overlay.hasSameProviders(emptyList()))
Assert.assertTrue(overlay.hasSameProviders(listOf(decorProvider1)))
Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2)))
Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2, decorProvider1)))
- overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0)
+ overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0, Color.BLACK)
Assert.assertFalse(overlay.hasSameProviders(emptyList()))
Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider1)))
Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2)))
Assert.assertTrue(overlay.hasSameProviders(listOf(decorProvider2, decorProvider1)))
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
deleted file mode 100644
index df506b4..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyObject;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Resources;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.AnimatableClockController;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.Utils;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.clocks.AnimatableClockView;
-import com.android.systemui.statusbar.policy.BatteryController;
-
-import org.junit.After;
-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.MockitoAnnotations;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class AnimatableClockControllerTest extends SysuiTestCase {
- @Mock
- private AnimatableClockView mClockView;
- @Mock
- private StatusBarStateController mStatusBarStateController;
- @Mock
- private BroadcastDispatcher mBroadcastDispatcher;
- @Mock
- private BatteryController mBatteryController;
- @Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- private Resources mResources;
-
- private MockitoSession mStaticMockSession;
- private AnimatableClockController mAnimatableClockController;
-
- // Capture listeners so that they can be used to send events
- @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
- ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
- private View.OnAttachStateChangeListener mAttachListener;
-
- @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor;
- private StatusBarStateController.StateListener mStatusBarStateCallback;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mStaticMockSession = mockitoSession()
- .mockStatic(Utils.class)
- .strictness(Strictness.LENIENT) // it's ok if mocked classes aren't used
- .startMocking();
- when(Utils.getColorAttrDefaultColor(anyObject(), anyInt())).thenReturn(0);
-
- mAnimatableClockController = new AnimatableClockController(
- mClockView,
- mStatusBarStateController,
- mBroadcastDispatcher,
- mBatteryController,
- mKeyguardUpdateMonitor,
- mResources
- );
- mAnimatableClockController.init();
- captureAttachListener();
- }
-
- @After
- public void tearDown() {
- mStaticMockSession.finishMocking();
- }
-
- @Test
- public void testOnAttachedUpdatesDozeStateToTrue() {
- // GIVEN dozing
- when(mStatusBarStateController.isDozing()).thenReturn(true);
- when(mStatusBarStateController.getDozeAmount()).thenReturn(1f);
-
- // WHEN the clock view gets attached
- mAttachListener.onViewAttachedToWindow(mClockView);
-
- // THEN the clock controller updated its dozing state to true
- assertTrue(mAnimatableClockController.isDozing());
- }
-
- @Test
- public void testOnAttachedUpdatesDozeStateToFalse() {
- // GIVEN not dozing
- when(mStatusBarStateController.isDozing()).thenReturn(false);
- when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
-
- // WHEN the clock view gets attached
- mAttachListener.onViewAttachedToWindow(mClockView);
-
- // THEN the clock controller updated its dozing state to false
- assertFalse(mAnimatableClockController.isDozing());
- }
-
- private void captureAttachListener() {
- verify(mClockView).addOnAttachStateChangeListener(mAttachCaptor.capture());
- mAttachListener = mAttachCaptor.getValue();
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt
index b979241..f56d42e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.media
-import org.mockito.Mockito.`when` as whenever
import android.animation.ValueAnimator
import android.graphics.Color
import android.testing.AndroidTestingRunner
@@ -34,6 +33,7 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
private const val DEFAULT_COLOR = Color.RED
@@ -147,8 +147,8 @@
@Test
fun testColorSchemeTransition_update() {
- colorSchemeTransition.updateColorScheme(colorScheme, true)
- verify(mockAnimatingTransition, times(10)).updateColorScheme(colorScheme)
+ colorSchemeTransition.updateColorScheme(colorScheme)
+ verify(mockAnimatingTransition, times(8)).updateColorScheme(colorScheme)
verify(gutsViewHolder).colorScheme = colorScheme
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 540f2a5..c13c30b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -21,7 +21,6 @@
import android.app.PendingIntent
import android.app.smartspace.SmartspaceAction
import android.content.Context
-import org.mockito.Mockito.`when` as whenever
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
@@ -69,8 +68,8 @@
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.KotlinArgumentCaptor
-import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.withArgCaptor
@@ -93,6 +92,7 @@
import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
private const val KEY = "TEST_KEY"
@@ -355,6 +355,7 @@
whenever(viewHolder.player).thenReturn(view)
whenever(viewHolder.appIcon).thenReturn(appIcon)
whenever(viewHolder.albumView).thenReturn(albumView)
+ whenever(albumView.foreground).thenReturn(mock(Drawable::class.java))
whenever(viewHolder.titleText).thenReturn(titleText)
whenever(viewHolder.artistText).thenReturn(artistText)
whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index 1527f0d..2eb4783 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -371,11 +371,9 @@
powerManager,
R.layout.media_ttt_chip
) {
- override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) {
-
- }
-
- override fun getIconSize(isAppIcon: Boolean): Int? = ICON_SIZE
+ override val windowLayoutParams = commonWindowLayoutParams
+ override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) {}
+ override fun getIconSize(isAppIcon: Boolean): Int = ICON_SIZE
}
inner class ChipInfo : ChipInfoCommon {
@@ -386,4 +384,4 @@
private const val PACKAGE_NAME = "com.android.systemui"
private const val APP_NAME = "Fake App Name"
private const val TIMEOUT_MS = 10000L
-private const val ICON_SIZE = 47
\ No newline at end of file
+private const val ICON_SIZE = 47
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
index 2927669..3dc10d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs;
+import static android.os.PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE;
+
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
@@ -34,6 +36,7 @@
import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -222,7 +225,41 @@
Assert.assertEquals(2, mFmc.getNumRunningPackages());
}
+ @Test
+ public void testButtonVisibilityOnShowAllowlistButtonFlagChange() throws Exception {
+ setUserProfiles(0);
+ setBackgroundRestrictionExemptionReason("pkg", 12345, REASON_ALLOWLISTED_PACKAGE);
+ final Binder binder = new Binder();
+ setShowStopButtonForUserAllowlistedApps(true);
+ mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true);
+ Assert.assertEquals(1, mFmc.getNumVisibleButtons());
+
+ mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, false);
+ Assert.assertEquals(0, mFmc.getNumVisibleButtons());
+
+ setShowStopButtonForUserAllowlistedApps(false);
+ mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true);
+ Assert.assertEquals(0, mFmc.getNumVisibleButtons());
+ }
+
+ private void setShowStopButtonForUserAllowlistedApps(boolean enable) {
+ mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+ enable ? "true" : "false", false);
+ mBackgroundExecutor.advanceClockToLast();
+ mBackgroundExecutor.runAllReady();
+ }
+
+ private void setBackgroundRestrictionExemptionReason(String pkgName, int uid, int reason)
+ throws Exception {
+ Mockito.doReturn(uid)
+ .when(mPackageManager)
+ .getPackageUidAsUser(pkgName, UserHandle.getUserId(uid));
+ Mockito.doReturn(reason)
+ .when(mIActivityManager)
+ .getBackgroundRestrictionExemptionReason(uid);
+ }
FgsManagerController createFgsManagerController() throws RemoteException {
ArgumentCaptor<IForegroundServiceObserver> iForegroundServiceObserverArgumentCaptor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index aad03e2..9d908fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -139,7 +139,6 @@
`when`(dialog.getButton(DialogInterface.BUTTON_NEUTRAL)).thenReturn(neutralButton)
clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL)
- verify(dialog).dismiss()
verify(activityStarter)
.postStartActivityDismissingKeyguard(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 2691ff9..34d13c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -16,12 +16,9 @@
package com.android.systemui.statusbar;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
@@ -30,19 +27,14 @@
import android.os.Looper;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.service.notification.NotificationListenerService;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.RemoteInputActiveExtender;
-import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.RemoteInputHistoryExtender;
-import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.SmartReplyHistoryExtender;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -52,8 +44,6 @@
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
-import com.google.android.collect.Sets;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -76,23 +66,15 @@
@Mock private NotificationRemoteInputManager.Callback mCallback;
@Mock private RemoteInputController mController;
@Mock private SmartReplyController mSmartReplyController;
- @Mock private NotificationListenerService.RankingMap mRanking;
@Mock private ExpandableNotificationRow mRow;
@Mock private StatusBarStateController mStateController;
@Mock private RemoteInputUriController mRemoteInputUriController;
@Mock private NotificationClickNotifier mClickNotifier;
-
- // Dependency mocks:
@Mock private NotificationEntryManager mEntryManager;
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
private TestableNotificationRemoteInputManager mRemoteInputManager;
private NotificationEntry mEntry;
- private RemoteInputHistoryExtender mRemoteInputHistoryExtender;
- private SmartReplyHistoryExtender mSmartReplyHistoryExtender;
- private RemoteInputActiveExtender mRemoteInputActiveExtender;
- private TestableNotificationRemoteInputManager.FakeLegacyRemoteInputLifetimeExtender
- mLegacyRemoteInputLifetimeExtender;
@Before
public void setUp() {
@@ -121,21 +103,7 @@
.build();
mEntry.setRow(mRow);
- mRemoteInputManager.setUpWithPresenterForTest(mCallback,
- mDelegate, mController);
- for (NotificationLifetimeExtender extender : mRemoteInputManager.getLifetimeExtenders()) {
- extender.setCallback(
- mock(NotificationLifetimeExtender.NotificationSafeToRemoveCallback.class));
- }
- }
-
- @Test
- public void testPerformOnRemoveNotification() {
- when(mController.isRemoteInputActive(mEntry)).thenReturn(true);
- mRemoteInputManager.onPerformRemoveNotification(mEntry, mEntry.getKey());
-
- assertFalse(mEntry.mRemoteEditImeVisible);
- verify(mController).removeRemoteInput(mEntry, null);
+ mRemoteInputManager.setUpWithPresenterForTest(mCallback, mDelegate, mController);
}
@Test
@@ -143,7 +111,6 @@
when(mController.isRemoteInputActive(mEntry)).thenReturn(true);
assertTrue(mRemoteInputManager.isRemoteInputActive(mEntry));
- assertTrue(mRemoteInputActiveExtender.shouldExtendLifetime(mEntry));
}
@Test
@@ -152,7 +119,6 @@
when(mController.isSpinning(mEntry.getKey())).thenReturn(true);
assertTrue(mRemoteInputManager.shouldKeepForRemoteInputHistory(mEntry));
- assertTrue(mRemoteInputHistoryExtender.shouldExtendLifetime(mEntry));
}
@Test
@@ -161,7 +127,6 @@
mEntry.lastRemoteInputSent = SystemClock.elapsedRealtime();
assertTrue(mRemoteInputManager.shouldKeepForRemoteInputHistory(mEntry));
- assertTrue(mRemoteInputHistoryExtender.shouldExtendLifetime(mEntry));
}
@Test
@@ -170,20 +135,6 @@
when(mSmartReplyController.isSendingSmartReply(mEntry.getKey())).thenReturn(true);
assertTrue(mRemoteInputManager.shouldKeepForSmartReplyHistory(mEntry));
- assertTrue(mSmartReplyHistoryExtender.shouldExtendLifetime(mEntry));
- }
-
- @Test
- public void testNotificationWithRemoteInputActiveIsRemovedOnCollapse() {
- mRemoteInputActiveExtender.setShouldManageLifetime(mEntry, true /* shouldManage */);
-
- assertEquals(mLegacyRemoteInputLifetimeExtender.getEntriesKeptForRemoteInputActive(),
- Sets.newArraySet(mEntry));
-
- mRemoteInputManager.onPanelCollapsed();
-
- assertTrue(
- mLegacyRemoteInputLifetimeExtender.getEntriesKeptForRemoteInputActive().isEmpty());
}
private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager {
@@ -227,28 +178,5 @@
mRemoteInputController = controller;
}
- @NonNull
- @Override
- protected LegacyRemoteInputLifetimeExtender createLegacyRemoteInputLifetimeExtender(
- Handler mainHandler,
- NotificationEntryManager notificationEntryManager,
- SmartReplyController smartReplyController) {
- mLegacyRemoteInputLifetimeExtender = new FakeLegacyRemoteInputLifetimeExtender();
- return mLegacyRemoteInputLifetimeExtender;
- }
-
- class FakeLegacyRemoteInputLifetimeExtender extends LegacyRemoteInputLifetimeExtender {
-
- @Override
- protected void addLifetimeExtenders() {
- mRemoteInputActiveExtender = new RemoteInputActiveExtender();
- mRemoteInputHistoryExtender = new RemoteInputHistoryExtender();
- mSmartReplyHistoryExtender = new SmartReplyHistoryExtender();
- mLifetimeExtenders.add(mRemoteInputHistoryExtender);
- mLifetimeExtenders.add(mSmartReplyHistoryExtender);
- mLifetimeExtenders.add(mRemoteInputActiveExtender);
- }
- }
-
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
index 3500e4d..c75aa81 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
@@ -17,15 +17,15 @@
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.Notification;
import android.os.Handler;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
@@ -39,14 +39,12 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.coordinator.RemoteInputCoordinator;
+import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.policy.RemoteInputUriController;
import org.junit.Before;
import org.junit.Test;
@@ -54,8 +52,6 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
@@ -67,57 +63,50 @@
private static final int TEST_CHOICE_COUNT = 4;
private static final int TEST_ACTION_COUNT = 3;
- private Notification mNotification;
private NotificationEntry mEntry;
private SmartReplyController mSmartReplyController;
- private NotificationRemoteInputManager mRemoteInputManager;
@Mock private NotificationVisibilityProvider mVisibilityProvider;
- @Mock private RemoteInputController.Delegate mDelegate;
- @Mock private NotificationRemoteInputManager.Callback mCallback;
@Mock private StatusBarNotification mSbn;
- @Mock private NotificationEntryManager mNotificationEntryManager;
@Mock private IStatusBarService mIStatusBarService;
- @Mock private StatusBarStateController mStatusBarStateController;
- @Mock private RemoteInputUriController mRemoteInputUriController;
@Mock private NotificationClickNotifier mClickNotifier;
+ @Mock private InternalNotifUpdater mInternalNotifUpdater;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mDependency.injectTestDependency(NotificationEntryManager.class,
- mNotificationEntryManager);
mSmartReplyController = new SmartReplyController(
mock(DumpManager.class),
mVisibilityProvider,
mIStatusBarService,
mClickNotifier);
- mDependency.injectTestDependency(SmartReplyController.class,
- mSmartReplyController);
-
- mRemoteInputManager = new NotificationRemoteInputManager(mContext,
- mock(NotifPipelineFlags.class),
- mock(NotificationLockscreenUserManager.class),
- mSmartReplyController,
- mVisibilityProvider,
- mNotificationEntryManager,
+ RemoteInputCoordinator remoteInputCoordinator = new RemoteInputCoordinator(
+ mock(DumpManager.class),
new RemoteInputNotificationRebuilder(mContext),
- () -> Optional.of(mock(CentralSurfaces.class)),
- mStatusBarStateController,
- Handler.createAsync(Looper.myLooper()),
- mRemoteInputUriController,
- mClickNotifier,
- mock(ActionClickLogger.class),
- mock(DumpManager.class));
- mRemoteInputManager.setUpWithCallback(mCallback, mDelegate);
- mNotification = new Notification.Builder(mContext, "")
+ mock(NotificationRemoteInputManager.class),
+ mock(Handler.class),
+ mSmartReplyController);
+ remoteInputCoordinator.setRemoteInputController(mock(RemoteInputController.class));
+ NotifPipeline notifPipeline = mock(NotifPipeline.class);
+ when(notifPipeline.getInternalNotifUpdater(anyString())).thenReturn(mInternalNotifUpdater);
+ remoteInputCoordinator.attach(notifPipeline);
+
+ Notification notification = new Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
.setContentTitle("Title")
.setContentText("Text").build();
-
- mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
- 0, mNotification, new UserHandle(ActivityManager.getCurrentUser()), null, 0);
+ mSbn = new StatusBarNotification(
+ TEST_PACKAGE_NAME,
+ TEST_PACKAGE_NAME,
+ 0,
+ null,
+ TEST_UID,
+ 0,
+ notification,
+ new UserHandle(ActivityManager.getCurrentUser()),
+ null,
+ 0);
mEntry = new NotificationEntryBuilder()
.setSbn(mSbn)
.build();
@@ -128,10 +117,9 @@
mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT,
MetricsEvent.LOCATION_UNKNOWN, false /* modifiedBeforeSending */);
- // Sending smart reply should make calls to NotificationEntryManager
- // to update the notification with reply and spinner.
- verify(mNotificationEntryManager).updateNotification(
- argThat(sbn -> sbn.getKey().equals(mSbn.getKey())), isNull());
+ // Sending smart reply should update the notification with reply and spinner.
+ verify(mInternalNotifUpdater).onInternalNotificationUpdate(
+ argThat(sbn -> sbn.getKey().equals(mSbn.getKey())), anyString());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index f286349..ef763d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -152,8 +152,6 @@
MockitoAnnotations.initMocks(this);
allowTestableLooperAsMainThread();
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(true);
-
when(mEulogizer.record(any(Exception.class))).thenAnswer(i -> i.getArguments()[0]);
mListenerInOrder = inOrder(mCollectionListener);
@@ -1684,9 +1682,9 @@
return new CollectionEvent(rawEvent, requireNonNull(mEntryCaptor.getValue()));
}
- private void verifyBuiltList(Collection<NotificationEntry> list) {
- verify(mBuildListener).onBuildList(mBuildListCaptor.capture());
- assertEquals(new ArraySet<>(list), new ArraySet<>(mBuildListCaptor.getValue()));
+ private void verifyBuiltList(Collection<NotificationEntry> expectedList) {
+ verify(mBuildListener).onBuildList(mBuildListCaptor.capture(), any());
+ assertThat(mBuildListCaptor.getValue()).containsExactly(expectedList.toArray());
}
private static class RecordingCollectionListener implements NotifCollectionListener {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 9546058..555adfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -570,7 +570,7 @@
assertTrue(entry.hasFinishedInitialization());
// WHEN the pipeline is kicked off
- mReadyForBuildListener.onBuildList(singletonList(entry));
+ mReadyForBuildListener.onBuildList(singletonList(entry), "test");
mPipelineChoreographer.runIfScheduled();
// THEN the entry's initialization time is reset
@@ -2092,7 +2092,7 @@
mPendingSet.clear();
}
- mReadyForBuildListener.onBuildList(mEntrySet);
+ mReadyForBuildListener.onBuildList(mEntrySet, "test");
mPipelineChoreographer.runIfScheduled();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
index 5ba926e..0830191 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
@@ -60,7 +60,6 @@
@Before
fun setUp() {
- whenever(pipeline.isNewPipelineEnabled).thenReturn(true)
whenever(pipeline.allNotifs).thenReturn(listOf(entry))
whenever(entry.row).thenReturn(row)
coordinator = ViewConfigCoordinator(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 275dbfd..8fd6842 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -15,6 +15,7 @@
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
+import junit.framework.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.mock
@@ -164,4 +165,178 @@
stackScrollAlgorithm.updateViewWithShelf(expandableView, expandableViewState, shelfStart)
assertFalse(expandableViewState.hidden)
}
+
+ @Test
+ fun maybeUpdateHeadsUpIsVisible_endVisible_true() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.headsUpIsVisible = false
+
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 0f,
+ /* maxHunY= */ 10f)
+
+ assertTrue(expandableViewState.headsUpIsVisible)
+ }
+
+ @Test
+ fun maybeUpdateHeadsUpIsVisible_endHidden_false() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.headsUpIsVisible = true
+
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 0f)
+
+ assertFalse(expandableViewState.headsUpIsVisible)
+ }
+
+ @Test
+ fun maybeUpdateHeadsUpIsVisible_shadeClosed_noUpdate() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.headsUpIsVisible = true
+
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
+ /* isShadeExpanded= */ false,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 1f)
+
+ assertTrue(expandableViewState.headsUpIsVisible)
+ }
+
+ @Test
+ fun maybeUpdateHeadsUpIsVisible_notHUN_noUpdate() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.headsUpIsVisible = true
+
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ false,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 1f)
+
+ assertTrue(expandableViewState.headsUpIsVisible)
+ }
+
+ @Test
+ fun maybeUpdateHeadsUpIsVisible_topHidden_noUpdate() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.headsUpIsVisible = true
+
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ false,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 1f)
+
+ assertTrue(expandableViewState.headsUpIsVisible)
+ }
+
+ @Test
+ fun clampHunToTop_viewYGreaterThanQqs_viewYUnchanged() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.yTranslation = 50f
+
+ stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 1f, expandableViewState)
+
+ // qqs (10 + 0) < viewY (50)
+ assertEquals(50f, expandableViewState.yTranslation)
+ }
+
+ @Test
+ fun clampHunToTop_viewYLessThanQqs_viewYChanged() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.yTranslation = -10f
+
+ stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 1f, expandableViewState)
+
+ // qqs (10 + 0) > viewY (-10)
+ assertEquals(10f, expandableViewState.yTranslation)
+ }
+
+
+ @Test
+ fun clampHunToTop_viewYFarAboveVisibleStack_heightCollapsed() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.height = 20
+ expandableViewState.yTranslation = -100f
+
+ stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 10f, expandableViewState)
+
+ // newTranslation = max(10, -100) = 10
+ // distToRealY = 10 - (-100f) = 110
+ // height = max(20 - 110, 10f)
+ assertEquals(10, expandableViewState.height)
+ }
+
+ @Test
+ fun clampHunToTop_viewYNearVisibleStack_heightTallerThanCollapsed() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.height = 20
+ expandableViewState.yTranslation = 5f
+
+ stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 10f, expandableViewState)
+
+ // newTranslation = max(10, 5) = 10
+ // distToRealY = 10 - 5 = 5
+ // height = max(20 - 5, 10) = 15
+ assertEquals(15, expandableViewState.height)
+ }
+
+ @Test
+ fun computeCornerRoundnessForPinnedHun_stackBelowScreen_round() {
+ val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ /* hostViewHeight= */ 100f,
+ /* stackY= */ 110f,
+ /* viewMaxHeight= */ 20f,
+ /* originalCornerRoundness= */ 0f)
+ assertEquals(1f, currentRoundness)
+ }
+
+ @Test
+ fun computeCornerRoundnessForPinnedHun_stackAboveScreenBelowPinPoint_halfRound() {
+ val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ /* hostViewHeight= */ 100f,
+ /* stackY= */ 90f,
+ /* viewMaxHeight= */ 20f,
+ /* originalCornerRoundness= */ 0f)
+ assertEquals(0.5f, currentRoundness)
+ }
+
+ @Test
+ fun computeCornerRoundnessForPinnedHun_stackAbovePinPoint_notRound() {
+ val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ /* hostViewHeight= */ 100f,
+ /* stackY= */ 0f,
+ /* viewMaxHeight= */ 20f,
+ /* originalCornerRoundness= */ 0f)
+ assertEquals(0f, currentRoundness)
+ }
+
+ @Test
+ fun computeCornerRoundnessForPinnedHun_originallyRoundAndStackAbovePinPoint_round() {
+ val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ /* hostViewHeight= */ 100f,
+ /* stackY= */ 0f,
+ /* viewMaxHeight= */ 20f,
+ /* originalCornerRoundness= */ 1f)
+ assertEquals(1f, currentRoundness)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index e5b6286..272ef3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -131,6 +131,7 @@
when(mKeyguardBypassController.onBiometricAuthenticated(any(), anyBoolean()))
.thenReturn(true);
when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
+ when(mVibratorHelper.hasVibrator()).thenReturn(true);
mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
mBiometricUnlockController = new BiometricUnlockController(mDozeScrimController,
mKeyguardViewMediator, mScrimController, mShadeController,
@@ -423,4 +424,35 @@
verify(mHandler).post(captor.capture());
captor.getValue().run();
}
+
+ @Test
+ public void onFPFailureNoHaptics_notDeviceInteractive_showBouncer() {
+ // GIVEN no vibrator and the screen is off
+ when(mVibratorHelper.hasVibrator()).thenReturn(false);
+ when(mUpdateMonitor.isDeviceInteractive()).thenReturn(false);
+ when(mUpdateMonitor.isDreaming()).thenReturn(false);
+
+ // WHEN FP fails
+ mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+
+ // after device is finished waking up
+ mBiometricUnlockController.mWakefulnessObserver.onFinishedWakingUp();
+
+ // THEN show the bouncer
+ verify(mStatusBarKeyguardViewManager).showBouncer(true);
+ }
+
+ @Test
+ public void onFPFailureNoHaptics_dreaming_showBouncer() {
+ // GIVEN no vibrator and device is dreaming
+ when(mVibratorHelper.hasVibrator()).thenReturn(false);
+ when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
+ when(mUpdateMonitor.isDreaming()).thenReturn(true);
+
+ // WHEN FP fails
+ mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+
+ // THEN show the bouncer
+ verify(mStatusBarKeyguardViewManager).showBouncer(true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 266f0e9..2e26a2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -84,6 +84,7 @@
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.charging.WiredChargingRippleController;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -117,7 +118,6 @@
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
-import com.android.systemui.statusbar.charging.WiredChargingRippleController;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
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 cec6a81..7fd61cf 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
@@ -87,6 +87,7 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.camera.CameraGestureHelper;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.controls.dagger.ControlsComponent;
@@ -546,7 +547,8 @@
mNotificationStackSizeCalculator,
mUnlockedScreenOffAnimationController,
mShadeTransitionController,
- mSystemClock);
+ mSystemClock,
+ mock(CameraGestureHelper.class));
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
() -> {},
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt
index cafe113..304a274 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -9,6 +9,9 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
+import com.android.systemui.statusbar.phone.panelstate.STATE_CLOSED
+import com.android.systemui.statusbar.phone.panelstate.STATE_OPEN
+import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
import com.android.systemui.statusbar.policy.FakeConfigurationController
import org.junit.Before
import org.junit.Test
@@ -39,8 +42,9 @@
dumpManager,
scrimController,
context.resources,
- statusBarStateController
- )
+ statusBarStateController)
+
+ controller.onPanelStateChanged(STATE_OPENING)
}
@Test
@@ -54,8 +58,7 @@
@Test
fun onPanelExpansionChanged_inSplitShade_unlockedShade_setsFractionBasedOnDragDownAmount() {
- whenever(statusBarStateController.currentOrUpcomingState)
- .thenReturn(StatusBarState.SHADE)
+ whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
val scrimShadeTransitionDistance =
context.resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
setSplitShadeEnabled(true)
@@ -68,23 +71,20 @@
@Test
fun onPanelExpansionChanged_inSplitShade_largeDragDownAmount_fractionIsNotGreaterThan1() {
- whenever(statusBarStateController.currentOrUpcomingState)
- .thenReturn(StatusBarState.SHADE)
+ whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
val scrimShadeTransitionDistance =
context.resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
setSplitShadeEnabled(true)
controller.onPanelExpansionChanged(
- EXPANSION_EVENT.copy(dragDownPxAmount = 100f * scrimShadeTransitionDistance)
- )
+ EXPANSION_EVENT.copy(dragDownPxAmount = 100f * scrimShadeTransitionDistance))
verify(scrimController).setRawPanelExpansionFraction(1f)
}
@Test
fun onPanelExpansionChanged_inSplitShade_negativeDragDownAmount_fractionIsNotLessThan0() {
- whenever(statusBarStateController.currentOrUpcomingState)
- .thenReturn(StatusBarState.SHADE)
+ whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
setSplitShadeEnabled(true)
controller.onPanelExpansionChanged(EXPANSION_EVENT.copy(dragDownPxAmount = -100f))
@@ -114,6 +114,30 @@
verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
}
+ @Test
+ fun onPanelExpansionChanged_inSplitShade_panelOpen_setsFractionEqualToEventFraction() {
+ controller.onPanelStateChanged(STATE_OPEN)
+ whenever(statusBarStateController.currentOrUpcomingState)
+ .thenReturn(StatusBarState.KEYGUARD)
+ setSplitShadeEnabled(true)
+
+ controller.onPanelExpansionChanged(EXPANSION_EVENT)
+
+ verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
+ }
+
+ @Test
+ fun onPanelExpansionChanged_inSplitShade_panelClosed_setsFractionEqualToEventFraction() {
+ controller.onPanelStateChanged(STATE_CLOSED)
+ whenever(statusBarStateController.currentOrUpcomingState)
+ .thenReturn(StatusBarState.KEYGUARD)
+ setSplitShadeEnabled(true)
+
+ controller.onPanelExpansionChanged(EXPANSION_EVENT)
+
+ verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
+ }
+
private fun setSplitShadeEnabled(enabled: Boolean) {
overrideResource(R.bool.config_use_split_notification_shade, enabled)
configurationController.notifyConfigurationChanged()
@@ -122,7 +146,6 @@
companion object {
val EXPANSION_EVENT =
PanelExpansionChangeEvent(
- fraction = 0.5f, expanded = true, tracking = true, dragDownPxAmount = 10f
- )
+ fraction = 0.5f, expanded = true, tracking = true, dragDownPxAmount = 10f)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt
index 85a8c6b..8b7e04b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt
@@ -8,6 +8,7 @@
import com.android.systemui.plugins.qs.QS
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.NotificationPanelViewController
+import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
import com.android.systemui.statusbar.policy.FakeConfigurationController
@@ -49,8 +50,7 @@
context,
splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller },
noOpOverScroller,
- scrimShadeTransitionController
- )
+ scrimShadeTransitionController)
// Resetting as they are notified upon initialization.
reset(noOpOverScroller, splitShadeOverScroller)
@@ -91,6 +91,16 @@
verifyZeroInteractions(splitShadeOverScroller)
}
+ @Test
+ fun onPanelStateChanged_forwardsToScrimTransitionController() {
+ initLateProperties()
+
+ startPanelExpansion()
+
+ verify(scrimShadeTransitionController).onPanelStateChanged(STATE_OPENING)
+ verify(scrimShadeTransitionController).onPanelExpansionChanged(DEFAULT_EXPANSION_EVENT)
+ }
+
private fun initLateProperties() {
controller.qs = qs
controller.notificationStackScrollLayoutController = nsslController
@@ -112,14 +122,20 @@
private fun startPanelExpansion() {
panelExpansionStateManager.onPanelExpansionChanged(
- fraction = 0.5f,
- expanded = true,
- tracking = true,
- dragDownPxAmount = DEFAULT_DRAG_DOWN_AMOUNT
+ DEFAULT_EXPANSION_EVENT.fraction,
+ DEFAULT_EXPANSION_EVENT.expanded,
+ DEFAULT_EXPANSION_EVENT.tracking,
+ DEFAULT_EXPANSION_EVENT.dragDownPxAmount,
)
}
companion object {
private const val DEFAULT_DRAG_DOWN_AMOUNT = 123f
+ private val DEFAULT_EXPANSION_EVENT =
+ PanelExpansionChangeEvent(
+ fraction = 0.5f,
+ expanded = true,
+ tracking = true,
+ dragDownPxAmount = DEFAULT_DRAG_DOWN_AMOUNT)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
index 1e35b0f..125b362 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
@@ -159,6 +159,24 @@
Mockito.clearInvocations(callback);
}
+ // Ensure that updating a callback that is removed doesn't result in an exception due to the
+ // absence of the condition.
+ @Test
+ public void testUpdateRemovedCallback() {
+ final Monitor.Callback callback1 =
+ mock(Monitor.Callback.class);
+ final Monitor.Subscription.Token subscription1 =
+ mConditionMonitor.addSubscription(getDefaultBuilder(callback1).build());
+ ArgumentCaptor<Condition.Callback> monitorCallback =
+ ArgumentCaptor.forClass(Condition.Callback.class);
+ mExecutor.runAllReady();
+ verify(mCondition1).addCallback(monitorCallback.capture());
+ // This will execute first before the handler for onConditionChanged.
+ mConditionMonitor.removeSubscription(subscription1);
+ monitorCallback.getValue().onConditionChanged(mCondition1);
+ mExecutor.runAllReady();
+ }
+
@Test
public void addCallback_addFirstCallback_addCallbackToAllConditions() {
final Monitor.Callback callback1 =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 7d4e27f..2e58fc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -21,12 +21,9 @@
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NOTIF_CANCEL;
import static com.google.common.truth.Truth.assertThat;
@@ -62,7 +59,6 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.hardware.display.AmbientDisplayConfiguration;
-import android.hardware.face.FaceManager;
import android.os.Handler;
import android.os.PowerManager;
import android.os.UserHandle;
@@ -90,18 +86,16 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationRemoveInterceptor;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
@@ -118,7 +112,6 @@
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
@@ -140,8 +133,6 @@
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
-import com.google.common.collect.ImmutableList;
-
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@@ -155,22 +146,17 @@
import java.util.List;
import java.util.Optional;
-/**
- * Tests the NotificationEntryManager setup with BubbleController.
- * The {@link NotifPipeline} setup with BubbleController is tested in
- * {@link NewNotifPipelineBubblesTest}.
- */
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class BubblesTest extends SysuiTestCase {
@Mock
- private NotificationEntryManager mNotificationEntryManager;
- @Mock
private CommonNotifCollection mCommonNotifCollection;
@Mock
private NotificationGroupManagerLegacy mNotificationGroupManager;
@Mock
+ private BubblesManager.NotifCallback mNotifCallback;
+ @Mock
private WindowManager mWindowManager;
@Mock
private IActivityManager mActivityManager;
@@ -183,8 +169,6 @@
@Mock
private ZenModeConfig mZenModeConfig;
@Mock
- private FaceManager mFaceManager;
- @Mock
private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock
private SysuiStatusBarStateController mStatusBarStateController;
@@ -196,15 +180,17 @@
private FloatingContentCoordinator mFloatingContentCoordinator;
@Mock
private BubbleDataRepository mDataRepository;
+ @Mock
+ private NotificationShadeWindowView mNotificationShadeWindowView;
+ @Mock
+ private AuthController mAuthController;
private SysUiState mSysUiState;
private boolean mSysUiStateBubblesExpanded;
private boolean mSysUiStateBubblesManageMenuExpanded;
@Captor
- private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
- @Captor
- private ArgumentCaptor<NotificationRemoveInterceptor> mRemoveInterceptorCaptor;
+ private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor;
@Captor
private ArgumentCaptor<List<Bubble>> mBubbleListCaptor;
@Captor
@@ -212,22 +198,16 @@
@Captor
private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
-
private BubblesManager mBubblesManager;
- // TODO(178618782): Move tests on the controller directly to the shell
private TestableBubbleController mBubbleController;
private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
- private NotificationEntryListener mEntryListener;
- private NotificationRemoveInterceptor mRemoveInterceptor;
-
+ private NotifCollectionListener mEntryListener;
private NotificationTestHelper mNotificationTestHelper;
private NotificationEntry mRow;
private NotificationEntry mRow2;
- private NotificationEntry mRow3;
private ExpandableNotificationRow mNonBubbleNotifRow;
private BubbleEntry mBubbleEntry;
private BubbleEntry mBubbleEntry2;
- private BubbleEntry mBubbleEntry3;
private BubbleEntry mBubbleEntryUser11;
private BubbleEntry mBubbleEntry2User11;
@@ -245,12 +225,8 @@
@Mock
private NotifPipeline mNotifPipeline;
@Mock
- private NotifPipelineFlags mNotifPipelineFlags;
- @Mock
private DumpManager mDumpManager;
@Mock
- private NotificationShadeWindowView mNotificationShadeWindowView;
- @Mock
private IStatusBarService mStatusBarService;
@Mock
private NotificationVisibilityProvider mVisibilityProvider;
@@ -269,8 +245,6 @@
@Mock
private ScreenOffAnimationController mScreenOffAnimationController;
@Mock
- private AuthController mAuthController;
- @Mock
private TaskViewTransitions mTaskViewTransitions;
@Mock
private Optional<OneHandedController> mOneHandedOptional;
@@ -290,7 +264,6 @@
// For the purposes of this test, just run everything synchronously
ShellExecutor syncExecutor = new SyncExecutor();
- mContext.addMockSystemService(FaceManager.class, mFaceManager);
when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
@@ -308,11 +281,9 @@
TestableLooper.get(this));
mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
- mRow3 = mNotificationTestHelper.createBubble(mDeleteIntent);
mNonBubbleNotifRow = mNotificationTestHelper.createRow();
mBubbleEntry = BubblesManager.notifToBubbleEntry(mRow);
mBubbleEntry2 = BubblesManager.notifToBubbleEntry(mRow2);
- mBubbleEntry3 = BubblesManager.notifToBubbleEntry(mRow3);
UserHandle handle = mock(UserHandle.class);
when(handle.getIdentifier()).thenReturn(11);
@@ -321,9 +292,6 @@
mBubbleEntry2User11 = BubblesManager.notifToBubbleEntry(
mNotificationTestHelper.createBubble(handle));
- // Return non-null notification data from the CommonNotifCollection
- when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
-
mZenModeConfig.suppressedVisualEffects = 0;
when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
@@ -336,7 +304,6 @@
(sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0;
});
- // TODO: Fix
mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
mPositioner.setMaxBubbles(5);
mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor);
@@ -355,8 +322,6 @@
mock(NotifPipelineFlags.class),
mock(KeyguardNotificationVisibilityProvider.class)
);
-
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
mBubbleController = new TestableBubbleController(
mContext,
@@ -396,23 +361,17 @@
mZenModeController,
mLockscreenUserManager,
mNotificationGroupManager,
- mNotificationEntryManager,
mCommonNotifCollection,
mNotifPipeline,
mSysUiState,
- mNotifPipelineFlags,
mDumpManager,
syncExecutor);
+ mBubblesManager.addNotifCallback(mNotifCallback);
- // XXX: Does *this* need to be changed?
// Get a reference to the BubbleController's entry listener
- verify(mNotificationEntryManager, atLeastOnce())
- .addNotificationEntryListener(mEntryListenerCaptor.capture());
- mEntryListener = mEntryListenerCaptor.getValue();
- // And the remove interceptor
- verify(mNotificationEntryManager, atLeastOnce())
- .addNotificationRemoveInterceptor(mRemoveInterceptorCaptor.capture());
- mRemoveInterceptor = mRemoveInterceptorCaptor.getValue();
+ verify(mNotifPipeline, atLeastOnce())
+ .addCollectionListener(mNotifListenerCaptor.capture());
+ mEntryListener = mNotifListenerCaptor.getValue();
}
@Test
@@ -433,90 +392,75 @@
@Test
public void testRemoveBubble() {
mBubbleController.updateBubble(mBubbleEntry);
- assertNotNull(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey()));
+ assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
assertTrue(mBubbleController.hasBubbles());
- verify(mNotificationEntryManager).updateNotifications(any());
+ verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
mBubbleController.removeBubble(
mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
- verify(mNotificationEntryManager, times(2)).updateNotifications(anyString());
+ verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
- public void testPromoteBubble_autoExpand() throws Exception {
- mBubbleController.updateBubble(mBubbleEntry2);
+ public void testRemoveBubble_withDismissedNotif_inOverflow() {
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
- when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
- when(mCommonNotifCollection.getEntry(mRow2.getKey())).thenReturn(mRow2);
- mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
- Bubble b = mBubbleData.getOverflowBubbleWithKey(mRow.getKey());
- assertThat(mBubbleData.getOverflowBubbles()).isEqualTo(ImmutableList.of(b));
- verify(mNotificationEntryManager, never()).performRemoveNotification(
- eq(mRow.getSbn()), any(), anyInt());
- assertThat(mRow.isBubble()).isFalse();
-
- Bubble b2 = mBubbleData.getBubbleInStackWithKey(mRow2.getKey());
- assertThat(mBubbleData.getSelectedBubble()).isEqualTo(b2);
-
- mBubbleController.promoteBubbleFromOverflow(b);
-
- assertThat(b.isBubble()).isTrue();
- assertThat(b.shouldAutoExpand()).isTrue();
- int flags = Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE
- | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
- verify(mStatusBarService, times(1)).onNotificationBubbleChanged(
- eq(b.getKey()), eq(true), eq(flags));
- }
-
- @Test
- public void testCancelOverflowBubble() {
- mBubbleController.updateBubble(mBubbleEntry2);
- mBubbleController.updateBubble(mBubbleEntry, /* suppressFlyout */
- false, /* showInShade */ true);
- when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
- when(mCommonNotifCollection.getEntry(mRow2.getKey())).thenReturn(mRow2);
- mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
-
- mBubbleController.removeBubble(
- mRow.getKey(), DISMISS_NOTIF_CANCEL);
- verify(mNotificationEntryManager, times(1)).performRemoveNotification(
- eq(mRow.getSbn()), any(), anyInt());
- assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
- assertFalse(mRow.isBubble());
- }
-
- @Test
- public void testUserChange_doesNotRemoveNotif() {
- mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
+ assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
+ // Make it look like dismissed notif
+ mBubbleData.getBubbleInStackWithKey(mRow.getKey()).setSuppressNotification(true);
+
+ // Now remove the bubble
mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_USER_CHANGED);
- verify(mNotificationEntryManager, never()).performRemoveNotification(
- eq(mRow.getSbn()), any(), anyInt());
+ mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ assertTrue(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey()));
+
+ // We don't remove the notification since the bubble is still in overflow.
+ verify(mNotifCallback, never()).removeNotification(eq(mRow), any(), anyInt());
assertFalse(mBubbleController.hasBubbles());
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- assertTrue(mRow.isBubble());
+ }
+
+ @Test
+ public void testRemoveBubble_withDismissedNotif_notInOverflow() {
+ mEntryListener.onEntryAdded(mRow);
+ mBubbleController.updateBubble(mBubbleEntry);
+ when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
+
+ assertTrue(mBubbleController.hasBubbles());
+ assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
+
+ // Make it look like dismissed notif
+ mBubbleData.getBubbleInStackWithKey(mRow.getKey()).setSuppressNotification(true);
+
+ // Now remove the bubble
+ mBubbleController.removeBubble(
+ mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
+ assertFalse(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey()));
+
+ // Since the notif is dismissed and not in overflow, once the bubble is removed,
+ // removeNotification gets called to really remove the notif
+ verify(mNotifCallback, times(1)).removeNotification(eq(mRow),
+ any(), anyInt());
+ assertFalse(mBubbleController.hasBubbles());
}
@Test
public void testDismissStack() {
mBubbleController.updateBubble(mBubbleEntry);
- verify(mNotificationEntryManager, times(1)).updateNotifications(any());
+ verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
mBubbleController.updateBubble(mBubbleEntry2);
- verify(mNotificationEntryManager, times(2)).updateNotifications(any());
+ verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
assertTrue(mBubbleController.hasBubbles());
mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
- verify(mNotificationEntryManager, times(3)).updateNotifications(any());
+ verify(mNotifCallback, times(3)).invalidateNotifications(anyString());
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
@@ -528,7 +472,7 @@
assertStackCollapsed();
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
// We should have bubbles & their notifs should not be suppressed
@@ -536,7 +480,6 @@
assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
// Expand the stack
- BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
@@ -556,8 +499,8 @@
@Ignore("Currently broken.")
public void testCollapseAfterChangingExpandedBubble() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow);
- mEntryListener.onPendingEntryAdded(mRow2);
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow2);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleController.updateBubble(mBubbleEntry2);
@@ -593,6 +536,7 @@
verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
true, mRow.getKey());
+
// Collapse
mBubbleController.collapseStack();
assertStackCollapsed();
@@ -602,7 +546,7 @@
@Test
public void testExpansionRemovesShowInShadeAndDot() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
// We should have bubbles & their notifs should not be suppressed
@@ -627,7 +571,7 @@
@Test
public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
// We should have bubbles & their notifs should not be suppressed
@@ -649,7 +593,7 @@
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
// Send update
- mEntryListener.onPreEntryUpdated(mRow);
+ mEntryListener.onEntryUpdated(mRow);
// Nothing should have changed
// Notif is suppressed after expansion
@@ -661,8 +605,8 @@
@Test
public void testRemoveLastExpanded_collapses() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow);
- mEntryListener.onPendingEntryAdded(mRow2);
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow2);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleController.updateBubble(mBubbleEntry2);
@@ -707,7 +651,7 @@
@Test
public void testRemoveLastExpandedEmptyOverflow_collapses() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
// Expand
@@ -732,6 +676,7 @@
assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
+
@Test
public void testAutoExpand_fails_noFlag() {
assertStackCollapsed();
@@ -739,7 +684,7 @@
Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
// Add the auto expand bubble
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
// Expansion shouldn't change
@@ -755,7 +700,7 @@
Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
// Add the auto expand bubble
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
// Expansion should change
@@ -771,7 +716,7 @@
Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
// Add the suppress notif bubble
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
// Notif should be suppressed because we were foreground
@@ -805,22 +750,8 @@
}
@Test
- public void testExpandStackAndSelectBubble_removedFirst() {
- mEntryListener.onPendingEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // Simulate notification cancellation.
- mRemoveInterceptor.onNotificationRemoveRequested(
- mRow.getKey(), mRow, REASON_APP_CANCEL);
-
- mBubbleController.expandStackAndSelectBubble(mBubbleEntry);
-
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
public void testMarkNewNotificationAsShowInShade() {
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
mTestableLooper.processAllMessages();
@@ -829,8 +760,8 @@
@Test
public void testAddNotif_notBubble() {
- mEntryListener.onPendingEntryAdded(mNonBubbleNotifRow.getEntry());
- mEntryListener.onPreEntryUpdated(mNonBubbleNotifRow.getEntry());
+ mEntryListener.onEntryAdded(mNonBubbleNotifRow.getEntry());
+ mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry());
assertThat(mBubbleController.hasBubbles()).isFalse();
}
@@ -868,48 +799,33 @@
NotificationListenerService.Ranking ranking = new RankingBuilder(
mRow.getRanking()).setCanBubble(false).build();
mRow.setRanking(ranking);
- mEntryListener.onPreEntryUpdated(mRow);
+ mEntryListener.onEntryUpdated(mRow);
assertFalse(mBubbleController.hasBubbles());
verify(mDeleteIntent, never()).send();
}
@Test
- public void testRemoveBubble_succeeds_appCancel() {
- mEntryListener.onPendingEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- assertTrue(mBubbleController.hasBubbles());
-
- boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
- mRow.getKey(), mRow, REASON_APP_CANCEL);
-
- // Cancels always remove so no need to intercept
- assertFalse(intercepted);
- }
-
- @Test
public void testRemoveBubble_entryListenerRemove() {
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
// Removes the notification
- mEntryListener.onEntryRemoved(mRow, null, false, REASON_APP_CANCEL);
+ mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL);
assertFalse(mBubbleController.hasBubbles());
}
@Test
- public void removeBubble_clearAllIntercepted() {
- mEntryListener.onPendingEntryAdded(mRow);
+ public void removeBubble_intercepted() {
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
- boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
- mRow.getKey(), mRow, REASON_CANCEL_ALL);
+ boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
// Intercept!
assertTrue(intercepted);
@@ -918,99 +834,51 @@
}
@Test
- public void removeBubble_userDismissNotifIntercepted() {
- mEntryListener.onPendingEntryAdded(mRow);
+ public void removeBubble_dismissIntoOverflow_intercepted() {
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
- boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
- mRow.getKey(), mRow, REASON_CANCEL);
-
- // Intercept!
- assertTrue(intercepted);
- // Should update show in shade state
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
- }
-
- @Test
- public void removeNotif_inOverflow_intercepted() {
- // Get bubble with notif in shade.
- mEntryListener.onPendingEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- // Dismiss the bubble into overflow.
- mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ // Dismiss the bubble
+ mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
assertFalse(mBubbleController.hasBubbles());
- boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
- mRow.getKey(), mRow, REASON_CANCEL);
+ // Dismiss the notification
+ boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
- // Notif is no longer a bubble, but still in overflow, so we intercept removal.
+ // Intercept dismissal since bubble is going into overflow
assertTrue(intercepted);
}
@Test
- public void removeNotif_notInOverflow_notIntercepted() {
- // Get bubble with notif in shade.
- mEntryListener.onPendingEntryAdded(mRow);
+ public void removeBubble_notIntercepted() {
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
- mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_NO_LONGER_BUBBLE);
+ // Dismiss the bubble
+ mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
assertFalse(mBubbleController.hasBubbles());
- boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
- mRow.getKey(), mRow, REASON_CANCEL);
+ // Dismiss the notification
+ boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
- // Notif is no longer a bubble, so we should not intercept removal.
+ // Not a bubble anymore so we don't intercept dismissal.
assertFalse(intercepted);
}
@Test
- public void testOverflowBubble_maxReached_notInShade_bubbleRemoved() {
- mBubbleController.updateBubble(
- mBubbleEntry, /* suppressFlyout */ false, /* showInShade */ false);
- mBubbleController.updateBubble(
- mBubbleEntry2, /* suppressFlyout */ false, /* showInShade */ false);
- mBubbleController.updateBubble(
- mBubbleEntry3, /* suppressFlyout */ false, /* showInShade */ false);
- when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
- when(mCommonNotifCollection.getEntry(mRow2.getKey())).thenReturn(mRow2);
- when(mCommonNotifCollection.getEntry(mRow3.getKey())).thenReturn(mRow3);
- assertEquals(mBubbleData.getBubbles().size(), 3);
-
- mBubbleData.setMaxOverflowBubbles(1);
- mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
- assertEquals(mBubbleData.getBubbles().size(), 2);
- assertEquals(mBubbleData.getOverflowBubbles().size(), 1);
-
- mBubbleController.removeBubble(
- mRow2.getKey(), Bubbles.DISMISS_USER_GESTURE);
- // Overflow max of 1 is reached; mRow is oldest, so it gets removed
- verify(mNotificationEntryManager, times(1)).performRemoveNotification(
- eq(mRow.getSbn()), any(), eq(REASON_CANCEL));
- assertEquals(mBubbleData.getBubbles().size(), 1);
- assertEquals(mBubbleData.getOverflowBubbles().size(), 1);
- }
-
- @Test
public void testNotifyShadeSuppressionChange_notificationDismiss() {
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
assertTrue(mBubbleController.hasBubbles());
assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
- mRemoveInterceptor.onNotificationRemoveRequested(
- mRow.getKey(), mRow, REASON_CANCEL);
+ mBubblesManager.handleDismissalInterception(mRow);
// Should update show in shade state
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -1022,7 +890,7 @@
@Test
public void testNotifyShadeSuppressionChange_bubbleExpanded() {
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
assertTrue(mBubbleController.hasBubbles());
assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
@@ -1042,9 +910,9 @@
// GIVEN a group summary with a bubble child
ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
+ mEntryListener.onEntryAdded(groupedBubble.getEntry());
when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
.thenReturn(groupedBubble.getEntry());
- mEntryListener.onPendingEntryAdded(groupedBubble.getEntry());
groupSummary.addChildNotification(groupedBubble);
assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
@@ -1054,10 +922,10 @@
// THEN the summary and bubbled child are suppressed from the shade
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
groupedBubble.getEntry().getKey(),
- groupSummary.getEntry().getSbn().getGroupKey()));
+ groupedBubble.getEntry().getSbn().getGroupKey()));
assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
groupedBubble.getEntry().getKey(),
- groupSummary.getEntry().getSbn().getGroupKey()));
+ groupedBubble.getEntry().getSbn().getGroupKey()));
assertTrue(mBubbleData.isSummarySuppressed(groupSummary.getEntry().getSbn().getGroupKey()));
}
@@ -1066,7 +934,7 @@
// GIVEN a group summary with a bubble child
ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
- mEntryListener.onPendingEntryAdded(groupedBubble.getEntry());
+ mEntryListener.onEntryAdded(groupedBubble.getEntry());
when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
.thenReturn(groupedBubble.getEntry());
groupSummary.addChildNotification(groupedBubble);
@@ -1076,7 +944,7 @@
mBubblesManager.handleDismissalInterception(groupSummary.getEntry());
// WHEN the summary is cancelled by the app
- mEntryListener.onEntryRemoved(groupSummary.getEntry(), null, false, REASON_APP_CANCEL);
+ mEntryListener.onEntryRemoved(groupSummary.getEntry(), REASON_APP_CANCEL);
// THEN the summary and its children are removed from bubble data
assertFalse(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
@@ -1085,14 +953,14 @@
}
@Test
- public void testSummaryDismissal_marksBubblesHiddenFromShadeAndDismissesNonBubbledChildren()
+ public void testSummaryDismissalMarksBubblesHiddenFromShadeAndDismissesNonBubbledChildren()
throws Exception {
// GIVEN a group summary with two (non-bubble) children and one bubble child
ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2);
ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
+ mEntryListener.onEntryAdded(groupedBubble.getEntry());
when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
.thenReturn(groupedBubble.getEntry());
- mEntryListener.onPendingEntryAdded(groupedBubble.getEntry());
groupSummary.addChildNotification(groupedBubble);
// WHEN the summary is dismissed
@@ -1100,16 +968,15 @@
// THEN only the NON-bubble children are dismissed
List<ExpandableNotificationRow> childrenRows = groupSummary.getAttachedChildren();
- verify(mNotificationEntryManager, times(1)).performRemoveNotification(
- eq(childrenRows.get(0).getEntry().getSbn()), any(),
- eq(REASON_GROUP_SUMMARY_CANCELED));
- verify(mNotificationEntryManager, times(1)).performRemoveNotification(
- eq(childrenRows.get(1).getEntry().getSbn()), any(),
- eq(REASON_GROUP_SUMMARY_CANCELED));
- verify(mNotificationEntryManager, never()).performRemoveNotification(
- eq(groupedBubble.getEntry().getSbn()), any(), anyInt());
+ verify(mNotifCallback, times(1)).removeNotification(
+ eq(childrenRows.get(0).getEntry()), any(), eq(REASON_GROUP_SUMMARY_CANCELED));
+ verify(mNotifCallback, times(1)).removeNotification(
+ eq(childrenRows.get(1).getEntry()), any(), eq(REASON_GROUP_SUMMARY_CANCELED));
+ verify(mNotifCallback, never()).removeNotification(eq(groupedBubble.getEntry()),
+ any(), anyInt());
- // THEN the bubble child is suppressed from the shade
+ // THEN the bubble child still exists as a bubble and is suppressed from the shade
+ assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
groupedBubble.getEntry().getKey(),
groupedBubble.getEntry().getSbn().getGroupKey()));
@@ -1117,34 +984,17 @@
groupedBubble.getEntry().getKey(),
groupedBubble.getEntry().getSbn().getGroupKey()));
- // THEN the summary is removed from GroupManager
- verify(mNotificationGroupManager, times(1)).onEntryRemoved(groupSummary.getEntry());
+ // THEN the summary is also suppressed from the shade
+ assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+ groupSummary.getEntry().getKey(),
+ groupSummary.getEntry().getSbn().getGroupKey()));
+ assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
+ groupSummary.getEntry().getKey(),
+ groupSummary.getEntry().getSbn().getGroupKey()));
}
/**
- * Verifies that when a non visually interruptive update occurs for a bubble in the overflow,
- * the that bubble does not get promoted from the overflow.
- */
- @Test
- public void test_notVisuallyInterruptive_updateOverflowBubble_notAdded() {
- // Setup
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.updateBubble(mBubbleEntry2);
- assertTrue(mBubbleController.hasBubbles());
-
- // Overflow it
- mBubbleData.dismissBubbleWithKey(mRow.getKey(),
- Bubbles.DISMISS_USER_GESTURE);
- assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getKey())).isFalse();
- assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey())).isTrue();
-
- // Test
- mBubbleController.updateBubble(mBubbleEntry);
- assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getKey())).isFalse();
- }
-
- /**
* Verifies that when the user changes, the bubbles in the overflow list is cleared. Doesn't
* test the loading from the repository which would be a nice thing to add.
*/
@@ -1185,15 +1035,17 @@
*/
@Test
public void testOverflowLoadedOnce() {
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.updateBubble(mBubbleEntry2);
- mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
- assertThat(mBubbleData.getOverflowBubbles().isEmpty()).isFalse();
+ // XXX
+ when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
+ when(mCommonNotifCollection.getEntry(mRow2.getKey())).thenReturn(mRow2);
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.updateBubble(mBubbleEntry2);
- mBubbleController.removeBubble(mBubbleEntry.getKey(), DISMISS_NOTIF_CANCEL);
- mBubbleController.removeBubble(mBubbleEntry2.getKey(), DISMISS_NOTIF_CANCEL);
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow2);
+ mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
+ assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty();
+
+ mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL);
+ mEntryListener.onEntryRemoved(mRow2, REASON_APP_CANCEL);
assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
verify(mDataRepository, times(1)).loadBubbles(anyInt(), any());
@@ -1376,6 +1228,7 @@
assertStackCollapsed();
}
+
@Test
public void testRegisterUnregisterBroadcastListener() {
spyOn(mContext);
@@ -1455,7 +1308,7 @@
@Test
public void testSetShouldAutoExpand_notifiesFlagChanged() {
- mEntryListener.onPendingEntryAdded(mRow);
+ mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
Bubble b = mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey());
@@ -1551,7 +1404,7 @@
}
/**
- * Sets the bubble metadata flags for this entry. These ]flags are normally set by
+ * Sets the bubble metadata flags for this entry. These flags are normally set by
* NotificationManagerService when the notification is sent, however, these tests do not
* go through that path so we set them explicitly when testing.
*/
@@ -1570,12 +1423,15 @@
private Notification.BubbleMetadata getMetadata() {
Intent target = new Intent(mContext, BubblesTestActivity.class);
PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, FLAG_MUTABLE);
-
- return new Notification.BubbleMetadata.Builder(bubbleIntent,
- Icon.createWithResource(mContext, R.drawable.bubble_ic_create_bubble))
+ return new Notification.BubbleMetadata.Builder(
+ bubbleIntent,
+ Icon.createWithResource(
+ mContext,
+ com.android.wm.shell.R.drawable.bubble_ic_create_bubble))
.build();
}
+
/**
* Asserts that the bubble stack is expanded and also validates the cached state is updated.
*/
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
deleted file mode 100644
index a6327b9..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ /dev/null
@@ -1,1401 +0,0 @@
-/*
- * Copyright (C) 2020 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.wmshell;
-
-import static android.app.Notification.FLAG_BUBBLE;
-import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
-import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
-import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
-import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
-import static com.google.common.truth.Truth.assertThat;
-
-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 static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.IActivityManager;
-import android.app.INotificationManager;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.LauncherApps;
-import android.content.pm.UserInfo;
-import android.hardware.display.AmbientDisplayConfiguration;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.service.dreams.IDreamManager;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.ZenModeConfig;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.util.Pair;
-import android.util.SparseArray;
-import android.view.View;
-import android.view.WindowManager;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.RankingBuilder;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationFilter;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskViewTransitions;
-import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.bubbles.Bubble;
-import com.android.wm.shell.bubbles.BubbleData;
-import com.android.wm.shell.bubbles.BubbleDataRepository;
-import com.android.wm.shell.bubbles.BubbleEntry;
-import com.android.wm.shell.bubbles.BubbleLogger;
-import com.android.wm.shell.bubbles.BubbleStackView;
-import com.android.wm.shell.bubbles.Bubbles;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.FloatingContentCoordinator;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.onehanded.OneHandedController;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Optional;
-
-/**
- * Tests the NotifPipeline setup with BubbleController.
- * The NotificationEntryManager setup with BubbleController is tested in
- * {@link BubblesTest}.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class NewNotifPipelineBubblesTest extends SysuiTestCase {
- @Mock
- private NotificationEntryManager mNotificationEntryManager;
- @Mock
- private CommonNotifCollection mCommonNotifCollection;
- @Mock
- private NotificationGroupManagerLegacy mNotificationGroupManager;
- @Mock
- private BubblesManager.NotifCallback mNotifCallback;
- @Mock
- private WindowManager mWindowManager;
- @Mock
- private IActivityManager mActivityManager;
- @Mock
- private DozeParameters mDozeParameters;
- @Mock
- private ConfigurationController mConfigurationController;
- @Mock
- private ZenModeController mZenModeController;
- @Mock
- private ZenModeConfig mZenModeConfig;
- @Mock
- private NotificationLockscreenUserManager mLockscreenUserManager;
- @Mock
- private SysuiStatusBarStateController mStatusBarStateController;
- @Mock
- private KeyguardViewMediator mKeyguardViewMediator;
- @Mock
- private KeyguardBypassController mKeyguardBypassController;
- @Mock
- private FloatingContentCoordinator mFloatingContentCoordinator;
- @Mock
- private BubbleDataRepository mDataRepository;
- @Mock
- private NotificationShadeWindowView mNotificationShadeWindowView;
- @Mock
- private AuthController mAuthController;
-
- private SysUiState mSysUiState;
- private boolean mSysUiStateBubblesExpanded;
- private boolean mSysUiStateBubblesManageMenuExpanded;
-
- @Captor
- private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor;
- @Captor
- private ArgumentCaptor<List<Bubble>> mBubbleListCaptor;
- @Captor
- private ArgumentCaptor<IntentFilter> mFilterArgumentCaptor;
- @Captor
- private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
-
- private BubblesManager mBubblesManager;
- private TestableBubbleController mBubbleController;
- private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
- private NotifCollectionListener mEntryListener;
- private NotificationTestHelper mNotificationTestHelper;
- private NotificationEntry mRow;
- private NotificationEntry mRow2;
- private ExpandableNotificationRow mNonBubbleNotifRow;
- private BubbleEntry mBubbleEntry;
- private BubbleEntry mBubbleEntry2;
-
- private BubbleEntry mBubbleEntryUser11;
- private BubbleEntry mBubbleEntry2User11;
-
- @Mock
- private Bubbles.BubbleExpandListener mBubbleExpandListener;
- @Mock
- private PendingIntent mDeleteIntent;
- @Mock
- private SysuiColorExtractor mColorExtractor;
- @Mock
- ColorExtractor.GradientColors mGradientColors;
- @Mock
- private ShadeController mShadeController;
- @Mock
- private NotifPipeline mNotifPipeline;
- @Mock
- private NotifPipelineFlags mNotifPipelineFlags;
- @Mock
- private DumpManager mDumpManager;
- @Mock
- private IStatusBarService mStatusBarService;
- @Mock
- private NotificationVisibilityProvider mVisibilityProvider;
- @Mock
- private LauncherApps mLauncherApps;
- @Mock
- private WindowManagerShellWrapper mWindowManagerShellWrapper;
- @Mock
- private BubbleLogger mBubbleLogger;
- @Mock
- private TaskStackListenerImpl mTaskStackListener;
- @Mock
- private ShellTaskOrganizer mShellTaskOrganizer;
- @Mock
- private KeyguardStateController mKeyguardStateController;
- @Mock
- private ScreenOffAnimationController mScreenOffAnimationController;
- @Mock
- private TaskViewTransitions mTaskViewTransitions;
- @Mock
- private Optional<OneHandedController> mOneHandedOptional;
-
- private TestableBubblePositioner mPositioner;
-
- private BubbleData mBubbleData;
-
- private TestableLooper mTestableLooper;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mTestableLooper = TestableLooper.get(this);
-
- // For the purposes of this test, just run everything synchronously
- ShellExecutor syncExecutor = new SyncExecutor();
-
- when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
-
- mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
- mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
- mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
- mColorExtractor, mDumpManager, mKeyguardStateController,
- mScreenOffAnimationController, mAuthController);
- mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
- mNotificationShadeWindowController.attach();
-
- // Need notifications for bubbles
- mNotificationTestHelper = new NotificationTestHelper(
- mContext,
- mDependency,
- TestableLooper.get(this));
- mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
- mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
- mNonBubbleNotifRow = mNotificationTestHelper.createRow();
- mBubbleEntry = BubblesManager.notifToBubbleEntry(mRow);
- mBubbleEntry2 = BubblesManager.notifToBubbleEntry(mRow2);
-
- UserHandle handle = mock(UserHandle.class);
- when(handle.getIdentifier()).thenReturn(11);
- mBubbleEntryUser11 = BubblesManager.notifToBubbleEntry(
- mNotificationTestHelper.createBubble(handle));
- mBubbleEntry2User11 = BubblesManager.notifToBubbleEntry(
- mNotificationTestHelper.createBubble(handle));
-
- mZenModeConfig.suppressedVisualEffects = 0;
- when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
-
- mSysUiState = new SysUiState();
- mSysUiState.addCallback(sysUiFlags -> {
- mSysUiStateBubblesManageMenuExpanded =
- (sysUiFlags
- & QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
- mSysUiStateBubblesExpanded =
- (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0;
- });
-
- mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
- mPositioner.setMaxBubbles(5);
- mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor);
-
- TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
- new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
- mock(PowerManager.class),
- mock(IDreamManager.class),
- mock(AmbientDisplayConfiguration.class),
- mock(NotificationFilter.class),
- mock(StatusBarStateController.class),
- mock(BatteryController.class),
- mock(HeadsUpManager.class),
- mock(NotificationInterruptLogger.class),
- mock(Handler.class),
- mock(NotifPipelineFlags.class),
- mock(KeyguardNotificationVisibilityProvider.class)
- );
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(true);
- when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
- mBubbleController = new TestableBubbleController(
- mContext,
- mBubbleData,
- mFloatingContentCoordinator,
- mDataRepository,
- mStatusBarService,
- mWindowManager,
- mWindowManagerShellWrapper,
- mock(UserManager.class),
- mLauncherApps,
- mBubbleLogger,
- mTaskStackListener,
- mShellTaskOrganizer,
- mPositioner,
- mock(DisplayController.class),
- mOneHandedOptional,
- mock(DragAndDropController.class),
- syncExecutor,
- mock(Handler.class),
- mTaskViewTransitions,
- mock(SyncTransactionQueue.class));
- mBubbleController.setExpandListener(mBubbleExpandListener);
- spyOn(mBubbleController);
-
- mBubblesManager = new BubblesManager(
- mContext,
- mBubbleController.asBubbles(),
- mNotificationShadeWindowController,
- mock(KeyguardStateController.class),
- mShadeController,
- mConfigurationController,
- mStatusBarService,
- mock(INotificationManager.class),
- mVisibilityProvider,
- interruptionStateProvider,
- mZenModeController,
- mLockscreenUserManager,
- mNotificationGroupManager,
- mNotificationEntryManager,
- mCommonNotifCollection,
- mNotifPipeline,
- mSysUiState,
- mNotifPipelineFlags,
- mDumpManager,
- syncExecutor);
- mBubblesManager.addNotifCallback(mNotifCallback);
-
- // Get a reference to the BubbleController's entry listener
- verify(mNotifPipeline, atLeastOnce())
- .addCollectionListener(mNotifListenerCaptor.capture());
- mEntryListener = mNotifListenerCaptor.getValue();
- }
-
- @Test
- public void testAddBubble() {
- mBubbleController.updateBubble(mBubbleEntry);
- assertTrue(mBubbleController.hasBubbles());
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testHasBubbles() {
- assertFalse(mBubbleController.hasBubbles());
- mBubbleController.updateBubble(mBubbleEntry);
- assertTrue(mBubbleController.hasBubbles());
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testRemoveBubble() {
- mBubbleController.updateBubble(mBubbleEntry);
- assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
- assertTrue(mBubbleController.hasBubbles());
- verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
-
- mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
- assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
- verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
-
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testRemoveBubble_withDismissedNotif_inOverflow() {
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- // Make it look like dismissed notif
- mBubbleData.getBubbleInStackWithKey(mRow.getKey()).setSuppressNotification(true);
-
- // Now remove the bubble
- mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
- assertTrue(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey()));
-
- // We don't remove the notification since the bubble is still in overflow.
- verify(mNotifCallback, never()).removeNotification(eq(mRow), any(), anyInt());
- assertFalse(mBubbleController.hasBubbles());
- }
-
- @Test
- public void testRemoveBubble_withDismissedNotif_notInOverflow() {
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
- when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
-
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- // Make it look like dismissed notif
- mBubbleData.getBubbleInStackWithKey(mRow.getKey()).setSuppressNotification(true);
-
- // Now remove the bubble
- mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
- assertFalse(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey()));
-
- // Since the notif is dismissed and not in overflow, once the bubble is removed,
- // removeNotification gets called to really remove the notif
- verify(mNotifCallback, times(1)).removeNotification(eq(mRow),
- any(), anyInt());
- assertFalse(mBubbleController.hasBubbles());
- }
-
- @Test
- public void testDismissStack() {
- mBubbleController.updateBubble(mBubbleEntry);
- verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
- assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
- mBubbleController.updateBubble(mBubbleEntry2);
- verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
- assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
- assertTrue(mBubbleController.hasBubbles());
-
- mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
- verify(mNotifCallback, times(3)).invalidateNotifications(anyString());
- assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
- assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
-
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testExpandCollapseStack() {
- assertStackCollapsed();
-
- // Mark it as a bubble and add it explicitly
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // We should have bubbles & their notifs should not be suppressed
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- // Expand the stack
- mBubbleData.setExpanded(true);
- assertStackExpanded();
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
- // Make sure the notif is suppressed
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-
- // Collapse
- mBubbleController.collapseStack();
- verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
- assertStackCollapsed();
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- @Ignore("Currently broken.")
- public void testCollapseAfterChangingExpandedBubble() {
- // Mark it as a bubble and add it explicitly
- mEntryListener.onEntryAdded(mRow);
- mEntryListener.onEntryAdded(mRow2);
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.updateBubble(mBubbleEntry2);
-
- // We should have bubbles & their notifs should not be suppressed
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry2);
-
- // Expand
- BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleData.setExpanded(true);
- assertStackExpanded();
- verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
- true, mRow2.getKey());
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
- // Last added is the one that is expanded
- assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey());
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry2);
-
- // Switch which bubble is expanded
- mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey(
- mRow.getKey()));
- mBubbleData.setExpanded(true);
- assertEquals(mRow.getKey(), mBubbleData.getBubbleInStackWithKey(
- stackView.getExpandedBubble().getKey()).getKey());
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-
- // collapse for previous bubble
- verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
- false, mRow2.getKey());
- // expand for selected bubble
- verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
- true, mRow.getKey());
-
-
- // Collapse
- mBubbleController.collapseStack();
- assertStackCollapsed();
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testExpansionRemovesShowInShadeAndDot() {
- // Mark it as a bubble and add it explicitly
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // We should have bubbles & their notifs should not be suppressed
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- mTestableLooper.processAllMessages();
- assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
-
- // Expand
- mBubbleData.setExpanded(true);
- assertStackExpanded();
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
- // Notif is suppressed after expansion
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
- // Notif shouldn't show dot after expansion
- assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
- }
-
- @Test
- public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
- // Mark it as a bubble and add it explicitly
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // We should have bubbles & their notifs should not be suppressed
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- mTestableLooper.processAllMessages();
- assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
-
- // Expand
- mBubbleData.setExpanded(true);
- assertStackExpanded();
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
- // Notif is suppressed after expansion
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
- // Notif shouldn't show dot after expansion
- assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
-
- // Send update
- mEntryListener.onEntryUpdated(mRow);
-
- // Nothing should have changed
- // Notif is suppressed after expansion
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
- // Notif shouldn't show dot after expansion
- assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
- }
-
- @Test
- public void testRemoveLastExpanded_collapses() {
- // Mark it as a bubble and add it explicitly
- mEntryListener.onEntryAdded(mRow);
- mEntryListener.onEntryAdded(mRow2);
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.updateBubble(mBubbleEntry2);
-
- // Expand
- BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleData.setExpanded(true);
-
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
- assertStackExpanded();
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey());
-
- // Last added is the one that is expanded
- assertEquals(mRow2.getKey(), mBubbleData.getBubbleInStackWithKey(
- stackView.getExpandedBubble().getKey()).getKey());
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry2);
-
- // Dismiss currently expanded
- mBubbleController.removeBubble(
- mBubbleData.getBubbleInStackWithKey(
- stackView.getExpandedBubble().getKey()).getKey(),
- Bubbles.DISMISS_USER_GESTURE);
- verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getKey());
-
- // Make sure first bubble is selected
- assertEquals(mRow.getKey(), mBubbleData.getBubbleInStackWithKey(
- stackView.getExpandedBubble().getKey()).getKey());
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-
- // Dismiss that one
- mBubbleController.removeBubble(
- mBubbleData.getBubbleInStackWithKey(
- stackView.getExpandedBubble().getKey()).getKey(),
- Bubbles.DISMISS_USER_GESTURE);
-
- // We should be collapsed
- verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
- assertFalse(mBubbleController.hasBubbles());
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testRemoveLastExpandedEmptyOverflow_collapses() {
- // Mark it as a bubble and add it explicitly
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // Expand
- BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleData.setExpanded(true);
-
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
- assertStackExpanded();
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-
- // Block the bubble so it won't be in the overflow
- mBubbleController.removeBubble(
- mBubbleData.getBubbleInStackWithKey(
- stackView.getExpandedBubble().getKey()).getKey(),
- Bubbles.DISMISS_BLOCKED);
-
- verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
-
- // We should be collapsed
- verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
- assertFalse(mBubbleController.hasBubbles());
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
-
- @Test
- public void testAutoExpand_fails_noFlag() {
- assertStackCollapsed();
- setMetadataFlags(mRow,
- Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
-
- // Add the auto expand bubble
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // Expansion shouldn't change
- verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
- mRow.getKey());
- assertStackCollapsed();
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testAutoExpand_succeeds_withFlag() {
- setMetadataFlags(mRow,
- Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
-
- // Add the auto expand bubble
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // Expansion should change
- verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
- mRow.getKey());
- assertStackExpanded();
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testSuppressNotif_onInitialNotif() {
- setMetadataFlags(mRow,
- Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
-
- // Add the suppress notif bubble
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // Notif should be suppressed because we were foreground
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
- // Dot + flyout is hidden because notif is suppressed
- assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
- assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testSuppressNotif_onUpdateNotif() {
- mBubbleController.updateBubble(mBubbleEntry);
-
- // Should not be suppressed
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
- // Should show dot
- assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
-
- // Update to suppress notif
- setMetadataFlags(mRow,
- Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // Notif should be suppressed
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
- // Dot + flyout is hidden because notif is suppressed
- assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
- assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testMarkNewNotificationAsShowInShade() {
- mEntryListener.onEntryAdded(mRow);
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- mTestableLooper.processAllMessages();
- assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
- }
-
- @Test
- public void testAddNotif_notBubble() {
- mEntryListener.onEntryAdded(mNonBubbleNotifRow.getEntry());
- mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry());
-
- assertThat(mBubbleController.hasBubbles()).isFalse();
- }
-
- @Test
- public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_AGED);
- verify(mDeleteIntent, never()).send();
- }
-
- @Test
- public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException {
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
- verify(mDeleteIntent, times(1)).send();
- }
-
- @Test
- public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.updateBubble(mBubbleEntry2);
- mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
- verify(mDeleteIntent, times(2)).send();
- }
-
- @Test
- public void testRemoveBubble_noLongerBubbleAfterUpdate()
- throws PendingIntent.CanceledException {
- mBubbleController.updateBubble(mBubbleEntry);
- assertTrue(mBubbleController.hasBubbles());
-
- mRow.getSbn().getNotification().flags &= ~FLAG_BUBBLE;
- NotificationListenerService.Ranking ranking = new RankingBuilder(
- mRow.getRanking()).setCanBubble(false).build();
- mRow.setRanking(ranking);
- mEntryListener.onEntryUpdated(mRow);
-
- assertFalse(mBubbleController.hasBubbles());
- verify(mDeleteIntent, never()).send();
- }
-
- @Test
- public void testRemoveBubble_entryListenerRemove() {
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- assertTrue(mBubbleController.hasBubbles());
-
- // Removes the notification
- mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL);
- assertFalse(mBubbleController.hasBubbles());
- }
-
- @Test
- public void removeBubble_intercepted() {
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
-
- // Intercept!
- assertTrue(intercepted);
- // Should update show in shade state
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
- }
-
- @Test
- public void removeBubble_dismissIntoOverflow_intercepted() {
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- // Dismiss the bubble
- mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
- assertFalse(mBubbleController.hasBubbles());
-
- // Dismiss the notification
- boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
-
- // Intercept dismissal since bubble is going into overflow
- assertTrue(intercepted);
- }
-
- @Test
- public void removeBubble_notIntercepted() {
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- // Dismiss the bubble
- mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
- assertFalse(mBubbleController.hasBubbles());
-
- // Dismiss the notification
- boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
-
- // Not a bubble anymore so we don't intercept dismissal.
- assertFalse(intercepted);
- }
-
- @Test
- public void testNotifyShadeSuppressionChange_notificationDismiss() {
- mEntryListener.onEntryAdded(mRow);
-
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- mBubblesManager.handleDismissalInterception(mRow);
-
- // Should update show in shade state
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-
- // Should notify delegate that shade state changed
- verify(mBubbleController).onBubbleMetadataFlagChanged(
- mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
- }
-
- @Test
- public void testNotifyShadeSuppressionChange_bubbleExpanded() {
- mEntryListener.onEntryAdded(mRow);
-
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- mBubbleData.setExpanded(true);
-
- // Once a bubble is expanded the notif is suppressed
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-
- // Should notify delegate that shade state changed
- verify(mBubbleController).onBubbleMetadataFlagChanged(
- mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
- }
-
- @Test
- public void testBubbleSummaryDismissal_suppressesSummaryAndBubbleFromShade() throws Exception {
- // GIVEN a group summary with a bubble child
- ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
- ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
- mEntryListener.onEntryAdded(groupedBubble.getEntry());
- when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
- .thenReturn(groupedBubble.getEntry());
- groupSummary.addChildNotification(groupedBubble);
- assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
-
- // WHEN the summary is dismissed
- mBubblesManager.handleDismissalInterception(groupSummary.getEntry());
-
- // THEN the summary and bubbled child are suppressed from the shade
- assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
- groupedBubble.getEntry().getKey(),
- groupedBubble.getEntry().getSbn().getGroupKey()));
- assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
- groupedBubble.getEntry().getKey(),
- groupedBubble.getEntry().getSbn().getGroupKey()));
- assertTrue(mBubbleData.isSummarySuppressed(groupSummary.getEntry().getSbn().getGroupKey()));
- }
-
- @Test
- public void testAppRemovesSummary_removesAllBubbleChildren() throws Exception {
- // GIVEN a group summary with a bubble child
- ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
- ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
- mEntryListener.onEntryAdded(groupedBubble.getEntry());
- when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
- .thenReturn(groupedBubble.getEntry());
- groupSummary.addChildNotification(groupedBubble);
- assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
-
- // GIVEN the summary is dismissed
- mBubblesManager.handleDismissalInterception(groupSummary.getEntry());
-
- // WHEN the summary is cancelled by the app
- mEntryListener.onEntryRemoved(groupSummary.getEntry(), REASON_APP_CANCEL);
-
- // THEN the summary and its children are removed from bubble data
- assertFalse(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
- assertFalse(mBubbleData.isSummarySuppressed(
- groupSummary.getEntry().getSbn().getGroupKey()));
- }
-
- @Test
- public void testSummaryDismissalMarksBubblesHiddenFromShadeAndDismissesNonBubbledChildren()
- throws Exception {
- // GIVEN a group summary with two (non-bubble) children and one bubble child
- ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2);
- ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
- mEntryListener.onEntryAdded(groupedBubble.getEntry());
- when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
- .thenReturn(groupedBubble.getEntry());
- groupSummary.addChildNotification(groupedBubble);
-
- // WHEN the summary is dismissed
- mBubblesManager.handleDismissalInterception(groupSummary.getEntry());
-
- // THEN only the NON-bubble children are dismissed
- List<ExpandableNotificationRow> childrenRows = groupSummary.getAttachedChildren();
- verify(mNotifCallback, times(1)).removeNotification(
- eq(childrenRows.get(0).getEntry()), any(), eq(REASON_GROUP_SUMMARY_CANCELED));
- verify(mNotifCallback, times(1)).removeNotification(
- eq(childrenRows.get(1).getEntry()), any(), eq(REASON_GROUP_SUMMARY_CANCELED));
- verify(mNotifCallback, never()).removeNotification(eq(groupedBubble.getEntry()),
- any(), anyInt());
-
- // THEN the bubble child still exists as a bubble and is suppressed from the shade
- assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
- assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
- groupedBubble.getEntry().getKey(),
- groupedBubble.getEntry().getSbn().getGroupKey()));
- assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
- groupedBubble.getEntry().getKey(),
- groupedBubble.getEntry().getSbn().getGroupKey()));
-
- // THEN the summary is also suppressed from the shade
- assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
- groupSummary.getEntry().getKey(),
- groupSummary.getEntry().getSbn().getGroupKey()));
- assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
- groupSummary.getEntry().getKey(),
- groupSummary.getEntry().getSbn().getGroupKey()));
- }
-
-
- /**
- * Verifies that when the user changes, the bubbles in the overflow list is cleared. Doesn't
- * test the loading from the repository which would be a nice thing to add.
- */
- @Test
- public void testOnUserChanged_overflowState() {
- int firstUserId = mBubbleEntry.getStatusBarNotification().getUser().getIdentifier();
- int secondUserId = mBubbleEntryUser11.getStatusBarNotification().getUser().getIdentifier();
-
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.updateBubble(mBubbleEntry2);
- assertTrue(mBubbleController.hasBubbles());
- mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
-
- // Verify these are in the overflow
- assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry.getKey())).isNotNull();
- assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2.getKey())).isNotNull();
-
- // Switch users
- mBubbleController.onUserChanged(secondUserId);
- assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
-
- // Give this user some bubbles
- mBubbleController.updateBubble(mBubbleEntryUser11);
- mBubbleController.updateBubble(mBubbleEntry2User11);
- assertTrue(mBubbleController.hasBubbles());
- mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
-
- // Verify these are in the overflow
- assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntryUser11.getKey())).isNotNull();
- assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2User11.getKey())).isNotNull();
-
- // Would have loaded bubbles twice because of user switch
- verify(mDataRepository, times(2)).loadBubbles(anyInt(), any());
- }
-
- /**
- * Verifies we only load the overflow data once.
- */
- @Test
- public void testOverflowLoadedOnce() {
- // XXX
- when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
- when(mCommonNotifCollection.getEntry(mRow2.getKey())).thenReturn(mRow2);
-
- mEntryListener.onEntryAdded(mRow);
- mEntryListener.onEntryAdded(mRow2);
- mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
- assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty();
-
- mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL);
- mEntryListener.onEntryRemoved(mRow2, REASON_APP_CANCEL);
- assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
-
- verify(mDataRepository, times(1)).loadBubbles(anyInt(), any());
- }
-
- /**
- * Verifies that shortcut deletions triggers that bubble being removed from XML.
- */
- @Test
- public void testDeleteShortcutsDeletesXml() throws Exception {
- ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId");
- BubbleEntry shortcutBubbleEntry = BubblesManager.notifToBubbleEntry(row.getEntry());
- mBubbleController.updateBubble(shortcutBubbleEntry);
-
- mBubbleData.dismissBubbleWithKey(shortcutBubbleEntry.getKey(),
- Bubbles.DISMISS_SHORTCUT_REMOVED);
-
- verify(mDataRepository, atLeastOnce()).removeBubbles(anyInt(), mBubbleListCaptor.capture());
- assertThat(mBubbleListCaptor.getValue().get(0).getKey()).isEqualTo(
- shortcutBubbleEntry.getKey());
- }
-
- @Test
- public void testShowManageMenuChangesSysuiState() {
- mBubbleController.updateBubble(mBubbleEntry);
- assertTrue(mBubbleController.hasBubbles());
-
- // Expand the stack
- BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleData.setExpanded(true);
- assertStackExpanded();
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
- // Show the menu
- stackView.showManageMenu(true);
- assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
- }
-
- @Test
- public void testHideManageMenuChangesSysuiState() {
- mBubbleController.updateBubble(mBubbleEntry);
- assertTrue(mBubbleController.hasBubbles());
-
- // Expand the stack
- BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleData.setExpanded(true);
- assertStackExpanded();
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
- // Show the menu
- stackView.showManageMenu(true);
- assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
-
- // Hide the menu
- stackView.showManageMenu(false);
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testCollapseBubbleManageMenuChangesSysuiState() {
- mBubbleController.updateBubble(mBubbleEntry);
- assertTrue(mBubbleController.hasBubbles());
-
- // Expand the stack
- BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleData.setExpanded(true);
- assertStackExpanded();
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
- // Show the menu
- stackView.showManageMenu(true);
- assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
-
- // Collapse the stack
- mBubbleData.setExpanded(false);
-
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testNotificationChannelModified_channelUpdated_removesOverflowBubble()
- throws Exception {
- // Setup
- ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId");
- NotificationEntry entry = row.getEntry();
- entry.getChannel().setConversationId(
- row.getEntry().getChannel().getParentChannelId(),
- "shortcutId");
- mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry()));
- assertTrue(mBubbleController.hasBubbles());
-
- // Overflow it
- mBubbleData.dismissBubbleWithKey(entry.getKey(),
- Bubbles.DISMISS_USER_GESTURE);
- assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isTrue();
-
- // Test
- entry.getChannel().setDeleted(true);
- mBubbleController.onNotificationChannelModified(entry.getSbn().getPackageName(),
- entry.getSbn().getUser(),
- entry.getChannel(),
- NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
- assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse();
- }
-
- @Test
- public void testNotificationChannelModified_channelDeleted_removesOverflowBubble()
- throws Exception {
- // Setup
- ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId");
- NotificationEntry entry = row.getEntry();
- entry.getChannel().setConversationId(
- row.getEntry().getChannel().getParentChannelId(),
- "shortcutId");
- mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry()));
- assertTrue(mBubbleController.hasBubbles());
-
- // Overflow it
- mBubbleData.dismissBubbleWithKey(entry.getKey(),
- Bubbles.DISMISS_USER_GESTURE);
- assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isTrue();
-
- // Test
- entry.getChannel().setDeleted(true);
- mBubbleController.onNotificationChannelModified(entry.getSbn().getPackageName(),
- entry.getSbn().getUser(),
- entry.getChannel(),
- NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
- assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse();
- }
-
- @Test
- public void testStackViewOnBackPressed_updatesBubbleDataExpandState() {
- mBubbleController.updateBubble(mBubbleEntry);
-
- // Expand the stack
- mBubbleData.setExpanded(true);
- assertStackExpanded();
-
- // Hit back
- BubbleStackView stackView = mBubbleController.getStackView();
- stackView.onBackPressed();
-
- // Make sure we're collapsed
- assertStackCollapsed();
- }
-
-
- @Test
- public void testRegisterUnregisterBroadcastListener() {
- spyOn(mContext);
- mBubbleController.updateBubble(mBubbleEntry);
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture());
- assertThat(mFilterArgumentCaptor.getValue().getAction(0)).isEqualTo(
- Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- assertThat(mFilterArgumentCaptor.getValue().getAction(1)).isEqualTo(
- Intent.ACTION_SCREEN_OFF);
-
- mBubbleData.dismissBubbleWithKey(mBubbleEntry.getKey(), REASON_APP_CANCEL);
- // TODO: not certain why this isn't called normally when tests are run, perhaps because
- // it's after an animation in BSV. This calls BubbleController#removeFromWindowManagerMaybe
- mBubbleController.onAllBubblesAnimatedOut();
-
- verify(mContext).unregisterReceiver(eq(mBroadcastReceiverArgumentCaptor.getValue()));
- }
-
- @Test
- public void testBroadcastReceiverCloseDialogs_notGestureNav() {
- spyOn(mContext);
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleData.setExpanded(true);
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture());
- Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
-
- assertStackExpanded();
- }
-
- @Test
- public void testBroadcastReceiverCloseDialogs_reasonGestureNav() {
- spyOn(mContext);
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleData.setExpanded(true);
-
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture());
- Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- i.putExtra("reason", "gestureNav");
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
- assertStackCollapsed();
- }
-
- @Test
- public void testBroadcastReceiver_screenOff() {
- spyOn(mContext);
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleData.setExpanded(true);
-
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture());
-
- Intent i = new Intent(Intent.ACTION_SCREEN_OFF);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
- assertStackCollapsed();
- }
-
- @Test
- public void testOnStatusBarStateChanged() {
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleData.setExpanded(true);
- assertStackExpanded();
- BubbleStackView stackView = mBubbleController.getStackView();
- assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE);
-
- mBubbleController.onStatusBarStateChanged(false);
-
- assertStackCollapsed();
- assertThat(stackView.getVisibility()).isEqualTo(View.INVISIBLE);
-
- mBubbleController.onStatusBarStateChanged(true);
- assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- public void testSetShouldAutoExpand_notifiesFlagChanged() {
- mBubbleController.updateBubble(mBubbleEntry);
-
- assertTrue(mBubbleController.hasBubbles());
- Bubble b = mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey());
- assertThat(b.shouldAutoExpand()).isFalse();
-
- // Set it to the same thing
- b.setShouldAutoExpand(false);
-
- // Verify it doesn't notify
- verify(mBubbleController, never()).onBubbleMetadataFlagChanged(any());
-
- // Set it to something different
- b.setShouldAutoExpand(true);
- verify(mBubbleController).onBubbleMetadataFlagChanged(b);
- }
-
- @Test
- public void testUpdateBubble_skipsDndSuppressListNotifs() {
- mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
- mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
- mRow.shouldSuppressPeek());
- mBubbleEntry.getBubbleMetadata().setFlags(
- Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
-
- mBubbleController.updateBubble(mBubbleEntry);
-
- Bubble b = mBubbleData.getPendingBubbleWithKey(mBubbleEntry.getKey());
- assertThat(b.shouldAutoExpand()).isFalse();
- assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey())).isNull();
- }
-
- @Test
- public void testOnRankingUpdate_DndSuppressListNotif() {
- // It's in the stack
- mBubbleController.updateBubble(mBubbleEntry);
- assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isTrue();
-
- // Set current user profile
- SparseArray<UserInfo> userInfos = new SparseArray<>();
- userInfos.put(mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(),
- mock(UserInfo.class));
- mBubbleController.onCurrentProfilesChanged(userInfos);
-
- // Send ranking update that the notif is suppressed from the list.
- HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey = new HashMap<>();
- mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
- mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
- mRow.shouldSuppressPeek());
- Pair<BubbleEntry, Boolean> pair = new Pair(mBubbleEntry, true);
- entryDataByKey.put(mBubbleEntry.getKey(), pair);
-
- NotificationListenerService.RankingMap rankingMap =
- mock(NotificationListenerService.RankingMap.class);
- when(rankingMap.getOrderedKeys()).thenReturn(new String[] { mBubbleEntry.getKey() });
- mBubbleController.onRankingUpdated(rankingMap, entryDataByKey);
-
- // Should no longer be in the stack
- assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse();
- }
-
- /**
- * Sets the bubble metadata flags for this entry. These flags are normally set by
- * NotificationManagerService when the notification is sent, however, these tests do not
- * go through that path so we set them explicitly when testing.
- */
- private void setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag) {
- Notification.BubbleMetadata bubbleMetadata =
- entry.getSbn().getNotification().getBubbleMetadata();
- int flags = bubbleMetadata.getFlags();
- if (enableFlag) {
- flags |= flag;
- } else {
- flags &= ~flag;
- }
- bubbleMetadata.setFlags(flags);
- }
-
- /**
- * Asserts that the bubble stack is expanded and also validates the cached state is updated.
- */
- private void assertStackExpanded() {
- assertTrue(mBubbleController.isStackExpanded());
- assertTrue(mBubbleController.getImplCachedState().isStackExpanded());
- }
-
- /**
- * Asserts that the bubble stack is collapsed and also validates the cached state is updated.
- */
- private void assertStackCollapsed() {
- assertFalse(mBubbleController.isStackExpanded());
- assertFalse(mBubbleController.getImplCachedState().isStackExpanded());
- }
-
- /**
- * Asserts that a bubble notification is suppressed from the shade and also validates the cached
- * state is updated.
- */
- private void assertBubbleNotificationSuppressedFromShade(BubbleEntry entry) {
- assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
- entry.getKey(), entry.getGroupKey()));
- assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
- entry.getKey(), entry.getGroupKey()));
- }
-
- /**
- * Asserts that a bubble notification is not suppressed from the shade and also validates the
- * cached state is updated.
- */
- private void assertBubbleNotificationNotSuppressedFromShade(BubbleEntry entry) {
- assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
- entry.getKey(), entry.getGroupKey()));
- assertFalse(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
- entry.getKey(), entry.getGroupKey()));
- }
-
- /**
- * Asserts that the system ui states associated to bubbles are in the correct state.
- */
- private void assertSysuiStates(boolean stackExpanded, boolean manageMenuExpanded) {
- assertThat(mSysUiStateBubblesExpanded).isEqualTo(stackExpanded);
- assertThat(mSysUiStateBubblesManageMenuExpanded).isEqualTo(manageMenuExpanded);
- }
-}
diff --git a/services/accessibility/OWNERS b/services/accessibility/OWNERS
index a31cfae..2b9f179 100644
--- a/services/accessibility/OWNERS
+++ b/services/accessibility/OWNERS
@@ -1,4 +1,4 @@
-svetoslavganov@google.com
pweaver@google.com
-rhedjao@google.com
+sallyyuen@google.com
ryanlwlin@google.com
+fuego@google.com
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 99c8495..79f590f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -199,9 +199,6 @@
private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE =
"registerUiTestAutomationService";
- private static final String TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED =
- "temporaryEnableAccessibilityStateUntilKeyguardRemoved";
-
private static final String GET_WINDOW_TOKEN = "getWindowToken";
private static final String SET_PIP_ACTION_REPLACEMENT =
@@ -1235,46 +1232,6 @@
}
@Override
- public void temporaryEnableAccessibilityStateUntilKeyguardRemoved(
- ComponentName service, boolean touchExplorationEnabled) {
- if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
- mTraceManager.logTrace(
- LOG_TAG + ".temporaryEnableAccessibilityStateUntilKeyguardRemoved",
- FLAGS_ACCESSIBILITY_MANAGER,
- "service=" + service + ";touchExplorationEnabled=" + touchExplorationEnabled);
- }
-
- mSecurityPolicy.enforceCallingPermission(
- Manifest.permission.TEMPORARY_ENABLE_ACCESSIBILITY,
- TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED);
- if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_WINDOW_MANAGER_INTERNAL)) {
- mTraceManager.logTrace("WindowManagerInternal.isKeyguardLocked",
- FLAGS_WINDOW_MANAGER_INTERNAL);
- }
- if (!mWindowManagerService.isKeyguardLocked()) {
- return;
- }
- synchronized (mLock) {
- // Set the temporary state.
- AccessibilityUserState userState = getCurrentUserStateLocked();
-
- userState.setTouchExplorationEnabledLocked(touchExplorationEnabled);
- userState.setDisplayMagnificationEnabledLocked(false);
- userState.disableShortcutMagnificationLocked();
- userState.setAutoclickEnabledLocked(false);
- userState.mEnabledServices.clear();
- userState.mEnabledServices.add(service);
- userState.getBindingServicesLocked().clear();
- userState.getCrashedServicesLocked().clear();
- userState.mTouchExplorationGrantedServices.clear();
- userState.mTouchExplorationGrantedServices.add(service);
-
- // User the current state instead settings.
- onUserStateChangedLocked(userState);
- }
- }
-
- @Override
public IBinder getWindowToken(int windowId, int userId) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".getWindowToken",
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 966d887..dc39b01 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -184,7 +184,12 @@
mPanningScalingState.mScrollGestureDetector.onTouchEvent(event);
mPanningScalingState.mScaleGestureDetector.onTouchEvent(event);
- stateHandler.onMotionEvent(event, rawEvent, policyFlags);
+ try {
+ stateHandler.onMotionEvent(event, rawEvent, policyFlags);
+ } catch (GestureException e) {
+ Slog.e(mLogTag, "Error processing motion event", e);
+ clearAndTransitionToStateDetecting();
+ }
}
@Override
@@ -281,7 +286,8 @@
}
interface State {
- void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags);
+ void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)
+ throws GestureException;
default void clear() {}
@@ -439,7 +445,8 @@
private boolean mLastMoveOutsideMagnifiedRegion;
@Override
- public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)
+ throws GestureException {
final int action = event.getActionMasked();
switch (action) {
case ACTION_POINTER_DOWN: {
@@ -449,7 +456,7 @@
break;
case ACTION_MOVE: {
if (event.getPointerCount() != 1) {
- throw new IllegalStateException("Should have one pointer down.");
+ throw new GestureException("Should have one pointer down.");
}
final float eventX = event.getX();
final float eventY = event.getY();
@@ -475,7 +482,7 @@
case ACTION_DOWN:
case ACTION_POINTER_UP: {
- throw new IllegalArgumentException(
+ throw new GestureException(
"Unexpected event type: " + MotionEvent.actionToString(action));
}
}
@@ -1087,4 +1094,13 @@
mGestureHandler.mDetectingState.setShortcutTriggered(false);
}
}
+
+ /**
+ * Indicates an error with a gesture handler or state.
+ */
+ private static class GestureException extends Exception {
+ GestureException(String message) {
+ super(message);
+ }
+ }
}
diff --git a/services/companion/Android.bp b/services/companion/Android.bp
index d3ef6dc..cdeb2dc 100644
--- a/services/companion/Android.bp
+++ b/services/companion/Android.bp
@@ -11,7 +11,6 @@
name: "services.companion-sources",
srcs: [
"java/**/*.java",
- "java/**/*.proto",
],
path: "java",
visibility: ["//frameworks/base/services"],
@@ -20,9 +19,6 @@
java_library_static {
name: "services.companion",
defaults: ["platform_service_defaults"],
- proto: {
- type: "stream",
- },
srcs: [":services.companion-sources"],
libs: [
"app-compat-annotations",
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index 2ab1aa8..2acb65e 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -244,29 +244,6 @@
primaryServiceConnector.postOnDeviceDisappeared(association);
}
- /** Pass an encrypted secure message to the companion application for transporting. */
- public void dispatchMessage(@UserIdInt int userId, @NonNull String packageName,
- int associationId, int messageId, @NonNull byte[] message) {
- if (DEBUG) {
- Log.i(TAG, "dispatchMessage() u" + userId + "/" + packageName
- + " associationId=" + associationId);
- }
-
- final CompanionDeviceServiceConnector primaryServiceConnector =
- getPrimaryServiceConnector(userId, packageName);
- if (primaryServiceConnector == null) {
- if (DEBUG) {
- Log.e(TAG, "dispatchMessage(): "
- + "u" + userId + "/" + packageName + " is NOT bound.");
- Log.d(TAG, "Stacktrace", new Throwable());
- }
- return;
- }
-
- primaryServiceConnector.postOnMessageDispatchedFromSystem(associationId, messageId,
- message);
- }
-
void dump(@NonNull PrintWriter out) {
out.append("Companion Device Application Controller: \n");
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index c354561..fa043f8 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -106,11 +106,10 @@
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
-import com.android.server.companion.datatransfer.CompanionMessageProcessor;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
-import com.android.server.companion.securechannel.CompanionSecureCommunicationsManager;
+import com.android.server.companion.transport.CompanionTransportManager;
import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -151,10 +150,9 @@
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
private AssociationRequestsProcessor mAssociationRequestsProcessor;
private SystemDataTransferProcessor mSystemDataTransferProcessor;
- private CompanionMessageProcessor mCompanionMessageProcessor;
private CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private CompanionApplicationController mCompanionAppController;
- private CompanionSecureCommunicationsManager mSecureCommsManager;
+ private CompanionTransportManager mTransportManager;
private final ActivityTaskManagerInternal mAtmInternal;
private final ActivityManagerInternal mAmInternal;
@@ -238,11 +236,9 @@
/* cdmService */this, mAssociationStore);
mCompanionAppController = new CompanionApplicationController(
context, mApplicationControllerCallback);
- mSecureCommsManager = new CompanionSecureCommunicationsManager(
- mAssociationStore, mCompanionAppController);
- mCompanionMessageProcessor = new CompanionMessageProcessor(mSecureCommsManager);
+ mTransportManager = new CompanionTransportManager(context);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mAssociationStore,
- mSystemDataTransferRequestStore, mCompanionMessageProcessor);
+ mSystemDataTransferRequestStore, mTransportManager);
// Publish "binder" service.
final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl();
@@ -319,18 +315,32 @@
MINUTES.toMillis(10));
}
- @Nullable
+ @NonNull
AssociationInfo getAssociationWithCallerChecks(
@UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
- final AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
+ AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
userId, packageName, macAddress);
- return sanitizeWithCallerChecks(getContext(), association);
+ association = sanitizeWithCallerChecks(getContext(), association);
+ if (association != null) {
+ return association;
+ } else {
+ throw new IllegalArgumentException("Association does not exist "
+ + "or the caller does not have permissions to manage it "
+ + "(ie. it belongs to a different package or a different user).");
+ }
}
- @Nullable
+ @NonNull
AssociationInfo getAssociationWithCallerChecks(int associationId) {
- final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- return sanitizeWithCallerChecks(getContext(), association);
+ AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ association = sanitizeWithCallerChecks(getContext(), association);
+ if (association != null) {
+ return association;
+ } else {
+ throw new IllegalArgumentException("Association does not exist "
+ + "or the caller does not have permissions to manage it "
+ + "(ie. it belongs to a different package or a different user).");
+ }
}
private void onDeviceAppearedInternal(int associationId) {
@@ -609,12 +619,6 @@
final AssociationInfo association =
getAssociationWithCallerChecks(userId, packageName, deviceMacAddress);
- if (association == null) {
- throw new IllegalArgumentException("Association does not exist "
- + "or the caller does not have permissions to manage it "
- + "(ie. it belongs to a different package or a different user).");
- }
-
disassociateInternal(association.getId());
}
@@ -622,15 +626,9 @@
public void disassociate(int associationId) {
if (DEBUG) Log.i(TAG, "disassociate() associationId=" + associationId);
- final AssociationInfo association = getAssociationWithCallerChecks(associationId);
- if (association == null) {
- throw new IllegalArgumentException("Association with ID " + associationId + " "
- + "does not exist "
- + "or belongs to a different package "
- + "or belongs to a different user");
- }
-
- disassociateInternal(associationId);
+ final AssociationInfo association =
+ getAssociationWithCallerChecks(associationId);
+ disassociateInternal(association.getId());
}
@Override
@@ -698,27 +696,6 @@
}
@Override
- public void dispatchMessage(int messageId, int associationId, @NonNull byte[] message) {
- if (DEBUG) {
- Log.i(TAG, "dispatchMessage() associationId=" + associationId + "\n"
- + " message(Base64)=" + Base64.encodeToString(message, 0));
- }
-
- getContext().enforceCallingOrSelfPermission(DELIVER_COMPANION_MESSAGES,
- "dispatchMessage");
-
- AssociationInfo association = getAssociationWithCallerChecks(associationId);
- if (association == null) {
- throw new IllegalArgumentException("Association with ID " + associationId + " "
- + "does not exist "
- + "or belongs to a different package "
- + "or belongs to a different user");
- }
-
- mSecureCommsManager.receiveSecureMessage(messageId, associationId, message);
- }
-
- @Override
public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
int userId, int associationId) {
return mSystemDataTransferProcessor.buildPermissionTransferUserConsentIntent(
@@ -735,14 +712,14 @@
@Override
public void attachSystemDataTransport(String packageName, int userId, int associationId,
ParcelFileDescriptor fd) {
- mSystemDataTransferProcessor.attachSystemDataTransport(packageName, userId,
- associationId, fd);
+ getAssociationWithCallerChecks(associationId);
+ mTransportManager.attachSystemDataTransport(packageName, userId, associationId, fd);
}
@Override
public void detachSystemDataTransport(String packageName, int userId, int associationId) {
- mSystemDataTransferProcessor.detachSystemDataTransport(packageName, userId,
- associationId);
+ getAssociationWithCallerChecks(associationId);
+ mTransportManager.detachSystemDataTransport(packageName, userId, associationId);
}
@Override
@@ -750,13 +727,6 @@
if (DEBUG) Log.i(TAG, "notifyDevice_Appeared() id=" + associationId);
AssociationInfo association = getAssociationWithCallerChecks(associationId);
- if (association == null) {
- throw new IllegalArgumentException("Association with ID " + associationId + " "
- + "does not exist "
- + "or belongs to a different package "
- + "or belongs to a different user");
- }
-
if (!association.isSelfManaged()) {
throw new IllegalArgumentException("Association with ID " + associationId
+ " is not self-managed. notifyDeviceAppeared(int) can only be called for"
@@ -777,13 +747,6 @@
if (DEBUG) Log.i(TAG, "notifyDevice_Disappeared() id=" + associationId);
final AssociationInfo association = getAssociationWithCallerChecks(associationId);
- if (association == null) {
- throw new IllegalArgumentException("Association with ID " + associationId + " "
- + "does not exist "
- + "or belongs to a different package "
- + "or belongs to a different user");
- }
-
if (!association.isSelfManaged()) {
throw new IllegalArgumentException("Association with ID " + associationId
+ " is not self-managed. notifyDeviceAppeared(int) can only be called for"
@@ -892,7 +855,6 @@
final CompanionDeviceShellCommand cmd = new CompanionDeviceShellCommand(
CompanionDeviceManagerService.this,
mAssociationStore,
- mSecureCommsManager,
mDevicePresenceMonitor);
cmd.exec(this, in, out, err, args, callback, resultReceiver);
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
index 7360f08..a288f7b 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
@@ -99,12 +99,6 @@
post(companionService -> companionService.onDeviceDisappeared(associationInfo));
}
- void postOnMessageDispatchedFromSystem(int associationId, int messageId,
- @NonNull byte[] message) {
- post(companionService ->
- companionService.onMessageDispatchedFromSystem(messageId, associationId, message));
- }
-
/**
* Post "unbind" job, which will run *after* all previously posted jobs complete.
*
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 0b7bc03..322ad46 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -16,14 +16,9 @@
package com.android.server.companion;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
import android.companion.AssociationInfo;
import android.os.Binder;
import android.os.ShellCommand;
-import android.util.Base64;
-
-import com.android.server.companion.securechannel.CompanionSecureCommunicationsManager;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
@@ -35,16 +30,13 @@
private final CompanionDeviceManagerService mService;
private final AssociationStore mAssociationStore;
- private final CompanionSecureCommunicationsManager mSecureCommsManager;
private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
CompanionDeviceShellCommand(CompanionDeviceManagerService service,
AssociationStore associationStore,
- CompanionSecureCommunicationsManager secureCommsManager,
CompanionDevicePresenceMonitor devicePresenceMonitor) {
mService = service;
mAssociationStore = associationStore;
- mSecureCommsManager = secureCommsManager;
mDevicePresenceMonitor = devicePresenceMonitor;
}
@@ -92,32 +84,6 @@
mService.loadAssociationsFromDisk();
break;
- case "send-secure-message":
- associationId = getNextIntArgRequired();
- final byte[] message;
-
- // The message should be either a UTF-8 String or Base64-encoded data.
- final boolean isBase64 = "--base64".equals(getNextOption());
- if (isBase64) {
- final String base64encodedMessage = getNextArgRequired();
- message = Base64.decode(base64encodedMessage, 0);
- } else {
- // We treat the rest of the command as the message, which should contain at
- // least one word (hence getNextArg_Required() below), but there may be
- // more.
- final StringBuilder sb = new StringBuilder(getNextArgRequired());
- // Pick up the rest.
- for (String word : peekRemainingArgs()) {
- sb.append(" ").append(word);
- }
- // And now convert to byte[]...
- message = sb.toString().getBytes(UTF_8);
- }
-
- mSecureCommsManager.sendSecureMessage(associationId, /* messageId */ 0,
- message);
- break;
-
case "simulate-device-appeared":
associationId = getNextIntArgRequired();
mDevicePresenceMonitor.simulateDeviceAppeared(associationId);
@@ -167,8 +133,6 @@
pw.println(" Create a new Association.");
pw.println(" disassociate USER_ID PACKAGE MAC_ADDRESS");
pw.println(" Remove an existing Association.");
- pw.println(" send-secure-message ASSOCIATION_ID [--base64] MESSAGE");
- pw.println(" Send a secure message to an associated companion device.");
pw.println(" clear-association-memory-cache");
pw.println(" Clear the in-memory association cache and reload all association ");
pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
diff --git a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageInfo.java b/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageInfo.java
deleted file mode 100644
index 91ad93c..0000000
--- a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageInfo.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2022 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.datatransfer;
-
-class CompanionMessageInfo {
-
- private final long mId;
- private final int mPage;
- private final int mTotal;
- private final int mType;
- private final byte[] mData;
-
- CompanionMessageInfo(long id, int page, int total, int type, byte[] data) {
- mId = id;
- mPage = page;
- mTotal = total;
- mType = type;
- mData = data;
- }
-
- public long getId() {
- return mId;
- }
-
- public int getPage() {
- return mPage;
- }
-
- public int getType() {
- return mType;
- }
-
- public byte[] getData() {
- return mData;
- }
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageProcessor.java
deleted file mode 100644
index 98a00aa6..0000000
--- a/services/companion/java/com/android/server/companion/datatransfer/CompanionMessageProcessor.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2022 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.datatransfer;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.util.Slog;
-import android.util.proto.ProtoInputStream;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.server.companion.proto.CompanionMessage;
-import com.android.server.companion.securechannel.CompanionSecureCommunicationsManager;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * This class builds and reads CompanionMessage. And also paginate and combine messages.
- */
-public class CompanionMessageProcessor {
-
- private static final String LOG_TAG = CompanionMessageProcessor.class.getSimpleName();
-
- /** Listener for incoming complete messages. */
- interface Listener {
- /** When a complete message is received from the companion app. */
- void onCompleteMessageReceived(@NonNull CompanionMessageInfo message);
- }
-
- // Rough size for each CompanionMessage, each message can exceed 50K for a little, but not
- // too much. Hard limit is 100K, WCS data processing limit. Closer to 100K, less stable at
- // the WCS data processing layer. Refer to
- // https://developers.google.com/android/reference/com/google/android/gms/wearable/MessageClient
- // #public-abstract-taskinteger-sendmessage-string-nodeid,-string-path,-byte[]-data
- private static final int MESSAGE_SIZE_IN_BYTES = 50000;
-
- private final CompanionSecureCommunicationsManager mSecureCommsManager;
-
- @Nullable
- private Listener mListener;
-
- // Association id -> (parent id -> received messages)
- private final Map<Integer, Map<Integer, List<CompanionMessageInfo>>> mAssociationsMessagesMap =
- new HashMap<>();
- // Association id -> next parent id
- private final Map<Integer, Integer> mNextParentId = new HashMap<>();
-
- public CompanionMessageProcessor(CompanionSecureCommunicationsManager secureCommsManager) {
- mSecureCommsManager = secureCommsManager;
- mSecureCommsManager.setListener(this::onDecryptedMessageReceived);
- }
-
- public void setListener(@NonNull Listener listener) {
- mListener = listener;
- }
-
- /**
- * Paginate the data into multiple messages with size limit. And dispatch the messages to the
- * companion app.
- */
- public void paginateAndDispatchMessagesToApp(byte[] data, int messageType,
- String packageName, int userId, int associationId) {
- Slog.i(LOG_TAG, "Paginating " + data.length + " bytes.");
-
- final int totalMessageCount = (data.length / MESSAGE_SIZE_IN_BYTES)
- + ((data.length % MESSAGE_SIZE_IN_BYTES == 0) ? 0 : 1);
- int parentMessageId = findNextParentId(associationId, totalMessageCount);
-
- for (int i = 0; i < totalMessageCount; i++) {
- ProtoOutputStream proto = new ProtoOutputStream();
- int messageId = parentMessageId + i + 1;
- proto.write(CompanionMessage.ID, messageId);
-
- long paginationInfoToken = proto.start(CompanionMessage.PAGINATION_INFO);
- proto.write(CompanionMessage.PaginationInfo.PARENT_ID, parentMessageId);
- proto.write(CompanionMessage.PaginationInfo.PAGE, i + 1);
- proto.write(CompanionMessage.PaginationInfo.TOTAL, totalMessageCount);
- proto.end(paginationInfoToken);
-
- proto.write(CompanionMessage.TYPE, messageType);
- byte[] currentData = Arrays.copyOfRange(data, i * MESSAGE_SIZE_IN_BYTES,
- Math.min((i + 1) * MESSAGE_SIZE_IN_BYTES, data.length));
- proto.write(CompanionMessage.DATA, currentData);
-
- byte[] message = proto.getBytes();
-
- Slog.i(LOG_TAG, "Sending [" + message.length + "] bytes to " + packageName);
-
- mSecureCommsManager.sendSecureMessage(associationId, messageId, message);
- }
- }
-
- /**
- * Process the message and store it. If all the messages with the same parent id have been
- * received, return the message with combined message data. Otherwise, return null if there's
- * still data parts missing.
- */
- public CompanionMessageInfo onDecryptedMessageReceived(int messageId, int associationId,
- byte[] message) {
- Slog.i(LOG_TAG, "Partial message received, size [" + message.length
- + "], reading from protobuf.");
-
- ProtoInputStream proto = new ProtoInputStream(message);
- try {
- int id = 0;
- int parentId = 0;
- int page = 0;
- int total = 0;
- int type = CompanionMessage.UNKNOWN;
- byte[] data = null;
-
- // Read proto data
- while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- switch (proto.getFieldNumber()) {
- case (int) CompanionMessage.ID:
- id = proto.readInt(CompanionMessage.ID);
- break;
- case (int) CompanionMessage.PAGINATION_INFO:
- long paginationToken = proto.start(CompanionMessage.PAGINATION_INFO);
- while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
- switch (proto.getFieldNumber()) {
- case (int) CompanionMessage.PaginationInfo.PARENT_ID:
- parentId = proto.readInt(
- CompanionMessage.PaginationInfo.PARENT_ID);
- break;
- case (int) CompanionMessage.PaginationInfo.PAGE:
- page = proto.readInt(CompanionMessage.PaginationInfo.PAGE);
- break;
- case (int) CompanionMessage.PaginationInfo.TOTAL:
- total = proto.readInt(CompanionMessage.PaginationInfo.TOTAL);
- break;
- default:
- Slog.e(LOG_TAG, "Unexpected field id "
- + proto.getFieldNumber() + " for PaginationInfo.");
- break;
- }
- }
- proto.end(paginationToken);
- break;
- case (int) CompanionMessage.TYPE:
- type = proto.readInt(CompanionMessage.TYPE);
- break;
- case (int) CompanionMessage.DATA:
- data = proto.readBytes(CompanionMessage.DATA);
- break;
- default:
- Slog.e(LOG_TAG, "Unexpected field id " + proto.getFieldNumber()
- + " for CompanionMessage.");
- break;
- }
- }
-
- if (id == messageId) {
- CompanionMessageInfo messageInfo = new CompanionMessageInfo(id, page, total, type,
- data);
- // Add the message into mAssociationsMessagesMap
- Map<Integer, List<CompanionMessageInfo>> associationMessages =
- mAssociationsMessagesMap.getOrDefault(associationId, new HashMap<>());
- List<CompanionMessageInfo> childMessages = associationMessages.getOrDefault(
- parentId, new ArrayList<>());
- childMessages.add(messageInfo);
- associationMessages.put(parentId, childMessages);
- mAssociationsMessagesMap.put(associationId, associationMessages);
- // Check if all the messages with the same parentId are received.
- if (childMessages.size() == total) {
- Slog.i(LOG_TAG, "All [" + total + "] messages are received for parentId ["
- + parentId + "]. Processing.");
-
- childMessages.sort(Comparator.comparing(CompanionMessageInfo::getPage));
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- for (int i = 0; i < childMessages.size(); i++) {
- stream.write(childMessages.get(i).getData());
- }
- mAssociationsMessagesMap.remove(parentId);
- mListener.onCompleteMessageReceived(
- new CompanionMessageInfo(parentId, 0, total, type,
- stream.toByteArray()));
- } else {
- Slog.i(LOG_TAG, "[" + childMessages.size() + "/" + total
- + "] messages are received for parentId [" + parentId + "]");
- }
- } else {
- Slog.e(LOG_TAG, "Message id mismatch.");
- return null;
- }
- } catch (IOException e) {
- Slog.e(LOG_TAG, "Can't read proto from the message.");
- return null;
- }
- return null;
- }
-
- /**
- * Find the next parent id from [1, Integer.MAX_VALUE].
- * The parent and child ids are incremental.
- */
- private int findNextParentId(int associationId, int totalMessageCount) {
- int nextParentId = mNextParentId.getOrDefault(associationId, 1);
-
- // If the last child message id exceeds the Integer range, start from 1 again.
- if (nextParentId > Integer.MAX_VALUE - totalMessageCount - 1) {
- nextParentId = 1;
- }
-
- mNextParentId.put(associationId, nextParentId + totalMessageCount + 1);
-
- return nextParentId;
- }
-}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 7eede55..88ebb97 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -25,6 +25,7 @@
import static com.android.server.companion.Utils.prepareForIpc;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
@@ -38,34 +39,27 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
-import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
import android.permission.PermissionControllerManager;
import android.util.Slog;
-import android.util.SparseArray;
-import com.android.internal.annotations.GuardedBy;
import com.android.server.companion.AssociationStore;
import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.PermissionsUtils;
-import com.android.server.companion.proto.CompanionMessage;
+import com.android.server.companion.transport.CompanionTransportManager;
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
/**
- * This processor builds user consent intent for a given SystemDataTransferRequest and processes the
- * request when the system is ready (a secure channel is established between the handhold and the
- * companion device).
+ * This processor builds user consent intent for a
+ * {@link SystemDataTransferRequest} and processes the request once a channel is
+ * established between the local and remote companion device.
*/
public class SystemDataTransferProcessor {
@@ -85,21 +79,19 @@
private final Context mContext;
private final AssociationStore mAssociationStore;
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
- private final CompanionMessageProcessor mCompanionMessageProcessor;
+ private final CompanionTransportManager mTransportManager;
private final PermissionControllerManager mPermissionControllerManager;
private final ExecutorService mExecutor;
- @GuardedBy("mTransports")
- private final SparseArray<Transport> mTransports = new SparseArray<>();
public SystemDataTransferProcessor(CompanionDeviceManagerService service,
AssociationStore associationStore,
SystemDataTransferRequestStore systemDataTransferRequestStore,
- CompanionMessageProcessor companionMessageProcessor) {
+ CompanionTransportManager transportManager) {
mContext = service.getContext();
mAssociationStore = associationStore;
mSystemDataTransferRequestStore = systemDataTransferRequestStore;
- mCompanionMessageProcessor = companionMessageProcessor;
- mCompanionMessageProcessor.setListener(this::onCompleteMessageReceived);
+ mTransportManager = transportManager;
+ mTransportManager.setListener(this::onReceivePermissionRestore);
mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
mExecutor = Executors.newSingleThreadExecutor();
}
@@ -194,77 +186,55 @@
return;
}
- // TODO: Establish a secure channel
-
// Start permission sync
final long callingIdentityToken = Binder.clearCallingIdentity();
try {
+ // TODO: refactor to work with streams of data
mPermissionControllerManager.getRuntimePermissionBackup(UserHandle.of(userId),
- mExecutor,
- backup -> mCompanionMessageProcessor.paginateAndDispatchMessagesToApp(backup,
- CompanionMessage.PERMISSION_SYNC, packageName, userId, associationId));
+ mExecutor, backup -> {
+ Future<?> future = mTransportManager
+ .requestPermissionRestore(associationId, backup);
+ translateFutureToCallback(future, callback);
+ });
} finally {
Binder.restoreCallingIdentity(callingIdentityToken);
}
}
- public void attachSystemDataTransport(String packageName, int userId, int associationId,
- ParcelFileDescriptor fd) {
- synchronized (mTransports) {
- // TODO: restore once testing has evolved
- // resolveAssociation(packageName, userId, associationId);
-
- if (mTransports.contains(associationId)) {
- detachSystemDataTransport(packageName, userId, associationId);
- }
-
- final Transport transport = new Transport(fd);
- transport.start();
- mTransports.put(associationId, transport);
- }
- }
-
- public void detachSystemDataTransport(String packageName, int userId, int associationId) {
- synchronized (mTransports) {
- // TODO: restore once testing has evolved
- // resolveAssociation(packageName, userId, associationId);
-
- final Transport transport = mTransports.get(associationId);
- if (transport != null) {
- mTransports.delete(associationId);
- transport.stop();
- }
- }
- }
-
- /**
- * Process a complete decrypted message reported by the companion app.
- */
- public void onCompleteMessageReceived(@NonNull CompanionMessageInfo completeMessage) {
- switch (completeMessage.getType()) {
- case CompanionMessage.PERMISSION_SYNC:
- processPermissionSyncMessage(completeMessage);
- break;
- default:
- Slog.e(LOG_TAG, "Unknown message type [" + completeMessage.getType()
- + "]. Unable to process.");
- }
- }
-
- private void processPermissionSyncMessage(CompanionMessageInfo messageInfo) {
+ private void onReceivePermissionRestore(byte[] message) {
Slog.i(LOG_TAG, "Applying permissions.");
// Start applying permissions
UserHandle user = mContext.getUser();
final long callingIdentityToken = Binder.clearCallingIdentity();
try {
+ // TODO: refactor to work with streams of data
mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup(
- messageInfo.getData(), user);
+ message, user);
} finally {
- Slog.i(LOG_TAG, "Permissions applied.");
Binder.restoreCallingIdentity(callingIdentityToken);
}
}
+ private static void translateFutureToCallback(@NonNull Future<?> future,
+ @Nullable ISystemDataTransferCallback callback) {
+ try {
+ future.get(15, TimeUnit.SECONDS);
+ try {
+ if (callback != null) {
+ callback.onResult();
+ }
+ } catch (RemoteException ignored) {
+ }
+ } catch (Exception e) {
+ try {
+ if (callback != null) {
+ callback.onError(e.getMessage());
+ }
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
private final ResultReceiver mOnSystemDataTransferRequestConfirmationReceiver =
new ResultReceiver(Handler.getMain()) {
@Override
@@ -291,74 +261,4 @@
Slog.e(LOG_TAG, "Unknown result code:" + resultCode);
}
};
-
- private class Transport {
- private final InputStream mRemoteIn;
- private final OutputStream mRemoteOut;
-
- private volatile boolean mStopped;
-
- public Transport(ParcelFileDescriptor fd) {
- mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd);
- mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
- }
-
- public void start() {
- new Thread(() -> {
- try {
- while (!mStopped) {
- processNextCommand();
- }
- } catch (IOException e) {
- if (!mStopped) {
- Slog.w(LOG_TAG, "Trouble during transport", e);
- stop();
- }
- }
- }).start();
- }
-
- public void stop() {
- mStopped = true;
-
- IoUtils.closeQuietly(mRemoteIn);
- IoUtils.closeQuietly(mRemoteOut);
- }
-
- private void processNextCommand() throws IOException {
- Slog.d(LOG_TAG, "Waiting for next command...");
-
- // Read message header
- final byte[] headerBytes = new byte[8];
- Streams.readFully(mRemoteIn, headerBytes);
- final ByteBuffer header = ByteBuffer.wrap(headerBytes);
- final int command = header.getInt();
- final int length = header.getInt();
-
- Slog.d(LOG_TAG, "Received command 0x" + Integer.toHexString(command)
- + " length " + length);
- switch (command) {
- case 0x50490000: // PI(NG) version 0
- // Repeat back the given payload, within reason
- final int target = Math.min(length, 1_000_000);
- final byte[] payload = new byte[target];
- Streams.readFully(mRemoteIn, payload);
- Streams.skipByReading(mRemoteIn, length - target);
-
- // Respond with PO(NG) version 0
- header.rewind();
- header.putInt(0x504F0000);
- header.putInt(target);
- mRemoteOut.write(header.array());
- mRemoteOut.write(payload);
- break;
-
- default:
- // Emit local warning, and skip message to
- // handle next one
- Slog.w(LOG_TAG, "Unknown command 0x" + Integer.toHexString(command));
- mRemoteIn.skip(length);
- }
- }
- }
}
diff --git a/services/companion/java/com/android/server/companion/proto/companion_message.proto b/services/companion/java/com/android/server/companion/proto/companion_message.proto
deleted file mode 100644
index 893a0db..0000000
--- a/services/companion/java/com/android/server/companion/proto/companion_message.proto
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 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.
- */
-
-syntax = "proto3";
-
-option java_multiple_files = true;
-
-package com.android.server.companion.proto;
-
-/* Represents a message between companion devices */
-message CompanionMessage {
- int32 id = 1;
-
- PaginationInfo paginationInfo = 2;
-
- Type type = 3;
-
- // message body data
- bytes data = 4;
-
- /* Message pagination info */
- message PaginationInfo {
- // id of the parent message, which should be the same for all the paginated messages
- int32 parentId = 1;
-
- // page number of the current message in [1, total]
- int32 page = 2;
-
- // total number of messages
- int32 total = 3;
- }
-
- /* Message type */
- enum Type {
- // default value for proto3
- UNKNOWN = 0;
-
- // handshake message to establish secure channel
- SECURE_CHANNEL_HANDSHAKE = 1;
-
- // permission sync
- PERMISSION_SYNC = 2;
- }
-}
diff --git a/services/companion/java/com/android/server/companion/securechannel/CompanionSecureCommunicationsManager.java b/services/companion/java/com/android/server/companion/securechannel/CompanionSecureCommunicationsManager.java
deleted file mode 100644
index 16a74ec..0000000
--- a/services/companion/java/com/android/server/companion/securechannel/CompanionSecureCommunicationsManager.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2022 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.securechannel;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.companion.AssociationInfo;
-import android.util.Base64;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.server.companion.AssociationStore;
-import com.android.server.companion.CompanionApplicationController;
-
-/** Secure Comms Manager */
-@SuppressLint("LongLogTag")
-public class CompanionSecureCommunicationsManager {
- static final String TAG = "CompanionDevice_SecureComms";
- static final boolean DEBUG = false;
-
- /** Listener for incoming decrypted messages. */
- public interface Listener {
- /** When an incoming message is decrypted. */
- void onDecryptedMessageReceived(int messageId, int associationId, byte[] message);
- }
-
- private final AssociationStore mAssociationStore;
- private final CompanionApplicationController mCompanionAppController;
-
- @Nullable
- private Listener mListener;
-
- /** Constructor */
- public CompanionSecureCommunicationsManager(AssociationStore associationStore,
- CompanionApplicationController companionApplicationController) {
- mAssociationStore = associationStore;
- mCompanionAppController = companionApplicationController;
- }
-
- public void setListener(@NonNull Listener listener) {
- mListener = listener;
- }
-
- /**
- * Send a data to the associated companion device via secure channel (establishing one if
- * needed).
- * @param associationId associationId of the "recipient" companion device.
- * @param messageId id of the message
- * @param message data to be sent securely.
- */
- public void sendSecureMessage(int associationId, int messageId, @NonNull byte[] message) {
- if (DEBUG) {
- Log.d(TAG, "sendSecureMessage() associationId=" + associationId + "\n"
- + " message (Base64)=\"" + Base64.encodeToString(message, 0) + "\"");
- }
-
- final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- if (association == null) {
- throw new IllegalArgumentException(
- "Association with ID " + associationId + " does not exist");
- }
- if (DEBUG) Log.d(TAG, " association=" + association);
-
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
-
- // Bind to the app if it hasn't been bound.
- if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
- Slog.d(TAG, "userId [" + userId + "] packageName [" + packageName
- + "] is not bound. Binding it now to send a secure message.");
- mCompanionAppController.bindCompanionApplication(userId, packageName,
- association.isSelfManaged());
-
- // TODO(b/202926196): implement: encrypt and pass on the companion application for
- // transporting
- mCompanionAppController.dispatchMessage(userId, packageName, associationId, messageId,
- message);
-
- Slog.d(TAG, "Unbinding userId [" + userId + "] packageName [" + packageName
- + "]");
- mCompanionAppController.unbindCompanionApplication(userId, packageName);
- }
-
- // TODO(b/202926196): implement: encrypt and pass on the companion application for
- // transporting
- mCompanionAppController.dispatchMessage(userId, packageName, associationId, messageId,
- message);
- }
-
- /**
- * Decrypt and dispatch message received from an associated companion device.
- * @param associationId associationId of the "sender" companion device.
- * @param encryptedMessage data.
- */
- public void receiveSecureMessage(int messageId, int associationId,
- @NonNull byte[] encryptedMessage) {
- if (DEBUG) {
- Log.d(TAG, "sendSecureMessage() associationId=" + associationId + "\n"
- + " message (Base64)=\"" + Base64.encodeToString(encryptedMessage, 0) + "\"");
- }
-
- // TODO(b/202926196): implement: decrypt and dispatch
-
- mListener.onDecryptedMessageReceived(messageId, associationId, encryptedMessage);
- }
-}
diff --git a/services/companion/java/com/android/server/companion/securechannel/OWNERS b/services/companion/java/com/android/server/companion/securechannel/OWNERS
deleted file mode 100644
index ecb97f4..0000000
--- a/services/companion/java/com/android/server/companion/securechannel/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-set noparent
-
-sergeynv@google.com
-ewol@google.com
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
new file mode 100644
index 0000000..77d51ea
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2022 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.transport;
+
+import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.ActivityManagerInternal;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Binder;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+import libcore.util.EmptyArray;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@SuppressLint("LongLogTag")
+public class CompanionTransportManager {
+ private static final String TAG = "CompanionTransportManager";
+ // TODO: flip to false
+ private static final boolean DEBUG = true;
+
+ private static final int HEADER_LENGTH = 12;
+ // TODO: refactor message processing to use streams to remove this limit
+ private static final int MAX_PAYLOAD_LENGTH = 1_000_000;
+
+ private static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
+ private static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
+
+ private static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
+ private static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI
+
+ private static boolean isRequest(int message) {
+ return (message & 0xFF000000) == 0x63000000;
+ }
+
+ private static boolean isResponse(int message) {
+ return (message & 0xFF000000) == 0x33000000;
+ }
+
+ public interface Listener {
+ void onRequestPermissionRestore(byte[] data);
+ }
+
+ private final Context mContext;
+
+ @GuardedBy("mTransports")
+ private final SparseArray<Transport> mTransports = new SparseArray<>();
+
+ @Nullable
+ private Listener mListener;
+
+ public CompanionTransportManager(Context context) {
+ mContext = context;
+ }
+
+ public void setListener(@NonNull Listener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * For the moment, we only offer transporting of system data to built-in
+ * companion apps; future work will improve the security model to support
+ * third-party companion apps.
+ */
+ private void enforceCallerCanTransportSystemData(String packageName, int userId) {
+ mContext.enforceCallingOrSelfPermission(DELIVER_COMPANION_MESSAGES, TAG);
+
+ try {
+ final ApplicationInfo info = mContext.getPackageManager().getApplicationInfoAsUser(
+ packageName, 0, userId);
+ final int instrumentationUid = LocalServices.getService(ActivityManagerInternal.class)
+ .getInstrumentationSourceUid(Binder.getCallingUid());
+ if (!Build.isDebuggable() && !info.isSystemApp()
+ && instrumentationUid == android.os.Process.INVALID_UID) {
+ throw new SecurityException("Transporting of system data currently only available "
+ + "to built-in companion apps or tests");
+ }
+ } catch (NameNotFoundException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public void attachSystemDataTransport(String packageName, int userId, int associationId,
+ ParcelFileDescriptor fd) {
+ enforceCallerCanTransportSystemData(packageName, userId);
+ synchronized (mTransports) {
+ if (mTransports.contains(associationId)) {
+ detachSystemDataTransport(packageName, userId, associationId);
+ }
+
+ final Transport transport = new Transport(associationId, fd);
+ transport.start();
+ mTransports.put(associationId, transport);
+ }
+ }
+
+ public void detachSystemDataTransport(String packageName, int userId, int associationId) {
+ enforceCallerCanTransportSystemData(packageName, userId);
+ synchronized (mTransports) {
+ final Transport transport = mTransports.get(associationId);
+ if (transport != null) {
+ mTransports.delete(associationId);
+ transport.stop();
+ }
+ }
+ }
+
+ public Future<?> requestPermissionRestore(int associationId, byte[] data) {
+ synchronized (mTransports) {
+ final Transport transport = mTransports.get(associationId);
+ if (transport != null) {
+ return transport.requestForResponse(MESSAGE_REQUEST_PERMISSION_RESTORE, data);
+ } else {
+ return CompletableFuture.failedFuture(new IOException("Missing transport"));
+ }
+ }
+ }
+
+ private class Transport {
+ private final int mAssociationId;
+
+ private final InputStream mRemoteIn;
+ private final OutputStream mRemoteOut;
+
+ private final AtomicInteger mNextSequence = new AtomicInteger();
+
+ @GuardedBy("mPendingRequests")
+ private final SparseArray<CompletableFuture<byte[]>> mPendingRequests = new SparseArray<>();
+
+ private volatile boolean mStopped;
+
+ public Transport(int associationId, ParcelFileDescriptor fd) {
+ mAssociationId = associationId;
+ mRemoteIn = new ParcelFileDescriptor.AutoCloseInputStream(fd);
+ mRemoteOut = new ParcelFileDescriptor.AutoCloseOutputStream(fd);
+ }
+
+ public void start() {
+ new Thread(() -> {
+ try {
+ while (!mStopped) {
+ receiveMessage();
+ }
+ } catch (IOException e) {
+ if (!mStopped) {
+ Slog.w(TAG, "Trouble during transport", e);
+ stop();
+ }
+ }
+ }).start();
+ }
+
+ public void stop() {
+ mStopped = true;
+
+ IoUtils.closeQuietly(mRemoteIn);
+ IoUtils.closeQuietly(mRemoteOut);
+ }
+
+ public Future<byte[]> requestForResponse(int message, byte[] data) {
+ final int sequence = mNextSequence.incrementAndGet();
+ final CompletableFuture<byte[]> pending = new CompletableFuture<>();
+ synchronized (mPendingRequests) {
+ mPendingRequests.put(sequence, pending);
+ }
+ try {
+ sendMessage(message, sequence, data);
+ } catch (IOException e) {
+ synchronized (mPendingRequests) {
+ mPendingRequests.remove(sequence);
+ }
+ pending.completeExceptionally(e);
+ }
+ return pending;
+ }
+
+ private void sendMessage(int message, int sequence, @NonNull byte[] data)
+ throws IOException {
+ if (DEBUG) {
+ Slog.d(TAG, "Sending message 0x" + Integer.toHexString(message)
+ + " sequence " + sequence + " length " + data.length
+ + " to association " + mAssociationId);
+ }
+
+ synchronized (mRemoteOut) {
+ final ByteBuffer header = ByteBuffer.allocate(HEADER_LENGTH)
+ .putInt(message)
+ .putInt(sequence)
+ .putInt(data.length);
+ mRemoteOut.write(header.array());
+ mRemoteOut.write(data);
+ mRemoteOut.flush();
+ }
+ }
+
+ private void receiveMessage() throws IOException {
+ if (DEBUG) {
+ Slog.d(TAG, "Waiting for next message...");
+ }
+
+ final byte[] headerBytes = new byte[HEADER_LENGTH];
+ Streams.readFully(mRemoteIn, headerBytes);
+ final ByteBuffer header = ByteBuffer.wrap(headerBytes);
+ final int message = header.getInt();
+ final int sequence = header.getInt();
+ final int length = header.getInt();
+
+ if (DEBUG) {
+ Slog.d(TAG, "Received message 0x" + Integer.toHexString(message)
+ + " sequence " + sequence + " length " + length
+ + " from association " + mAssociationId);
+ }
+ if (length > MAX_PAYLOAD_LENGTH) {
+ Slog.w(TAG, "Ignoring message 0x" + Integer.toHexString(message)
+ + " sequence " + sequence + " length " + length
+ + " from association " + mAssociationId + " beyond maximum length");
+ Streams.skipByReading(mRemoteIn, length);
+ return;
+ }
+
+ final byte[] data = new byte[length];
+ Streams.readFully(mRemoteIn, data);
+
+ if (isRequest(message)) {
+ processRequest(message, sequence, data);
+ } else if (isResponse(message)) {
+ processResponse(message, sequence, data);
+ } else {
+ Slog.w(TAG, "Unknown message 0x" + Integer.toHexString(message));
+ }
+ }
+
+ private void processRequest(int message, int sequence, byte[] data)
+ throws IOException {
+ switch (message) {
+ case MESSAGE_REQUEST_PING: {
+ sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data);
+ break;
+ }
+ case MESSAGE_REQUEST_PERMISSION_RESTORE: {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+ && !Build.isDebuggable()) {
+ Slog.w(TAG, "Restoring permissions only supported on watches");
+ sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
+ break;
+ }
+ try {
+ mListener.onRequestPermissionRestore(data);
+ sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE);
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed to restore permissions");
+ sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
+ }
+ break;
+ }
+ default: {
+ Slog.w(TAG, "Unknown request 0x" + Integer.toHexString(message));
+ sendMessage(MESSAGE_RESPONSE_FAILURE, sequence, EmptyArray.BYTE);
+ break;
+ }
+ }
+ }
+
+ private void processResponse(int message, int sequence, byte[] data) {
+ final CompletableFuture<byte[]> future;
+ synchronized (mPendingRequests) {
+ future = mPendingRequests.removeReturnOld(sequence);
+ }
+ if (future == null) {
+ Slog.w(TAG, "Ignoring unknown sequence " + sequence);
+ return;
+ }
+
+ switch (message) {
+ case MESSAGE_RESPONSE_SUCCESS: {
+ future.complete(data);
+ break;
+ }
+ case MESSAGE_RESPONSE_FAILURE: {
+ future.completeExceptionally(new RuntimeException("Remote failure"));
+ break;
+ }
+ default: {
+ Slog.w(TAG, "Ignoring unknown response 0x" + Integer.toHexString(message));
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/VpnManagerService.java b/services/core/java/com/android/server/VpnManagerService.java
index d3ef6be..07b6843 100644
--- a/services/core/java/com/android/server/VpnManagerService.java
+++ b/services/core/java/com/android/server/VpnManagerService.java
@@ -45,6 +45,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.INetworkManagementService;
+import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.ServiceManager;
@@ -131,6 +132,12 @@
return INetworkManagementService.Stub.asInterface(
ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
}
+
+ /** Create a VPN. */
+ public Vpn createVpn(Looper looper, Context context, INetworkManagementService nms,
+ INetd netd, int userId) {
+ return new Vpn(looper, context, nms, netd, userId, new VpnProfileStore());
+ }
}
public VpnManagerService(Context context, Dependencies deps) {
@@ -688,6 +695,7 @@
// Listen to package add and removal events for all users.
intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
intentFilter.addDataScheme("package");
@@ -738,6 +746,10 @@
final boolean isReplacing = intent.getBooleanExtra(
Intent.EXTRA_REPLACING, false);
onPackageRemoved(packageName, uid, isReplacing);
+ } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
+ final boolean isReplacing = intent.getBooleanExtra(
+ Intent.EXTRA_REPLACING, false);
+ onPackageAdded(packageName, uid, isReplacing);
} else {
Log.wtf(TAG, "received unexpected intent: " + action);
}
@@ -757,15 +769,15 @@
}
};
- private void onUserStarted(int userId) {
+ @VisibleForTesting
+ void onUserStarted(int userId) {
synchronized (mVpns) {
Vpn userVpn = mVpns.get(userId);
if (userVpn != null) {
loge("Starting user already has a VPN");
return;
}
- userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId,
- new VpnProfileStore());
+ userVpn = mDeps.createVpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId);
mVpns.put(userId, userVpn);
if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) {
updateLockdownVpn();
@@ -842,7 +854,8 @@
}
}
- private void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
+ @VisibleForTesting
+ void onPackageRemoved(String packageName, int uid, boolean isReplacing) {
if (TextUtils.isEmpty(packageName) || uid < 0) {
Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid);
return;
@@ -851,15 +864,34 @@
final int userId = UserHandle.getUserId(uid);
synchronized (mVpns) {
final Vpn vpn = mVpns.get(userId);
- if (vpn == null) {
+ if (vpn == null || isReplacing) {
return;
}
// Legacy always-on VPN won't be affected since the package name is not set.
- if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
+ if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
log("Removing always-on VPN package " + packageName + " for user "
+ userId);
vpn.setAlwaysOnPackage(null, false, null);
}
+
+ vpn.refreshPlatformVpnAppExclusionList();
+ }
+ }
+
+ @VisibleForTesting
+ void onPackageAdded(String packageName, int uid, boolean isReplacing) {
+ if (TextUtils.isEmpty(packageName) || uid < 0) {
+ Log.wtf(TAG, "Invalid package in onPackageAdded: " + packageName + " | " + uid);
+ return;
+ }
+
+ final int userId = UserHandle.getUserId(uid);
+ synchronized (mVpns) {
+ final Vpn vpn = mVpns.get(userId);
+
+ if (vpn != null && !isReplacing) {
+ vpn.refreshPlatformVpnAppExclusionList();
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5c13921..60286be 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8755,7 +8755,7 @@
private static String processClass(ProcessRecord process) {
if (process == null || process.getPid() == MY_PID) {
return "system_server";
- } else if ((process.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ } else if (process.info.isSystemApp() || process.info.isSystemExt()) {
return "system_app";
} else {
return "data_app";
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 2ec744f..a3ae35e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -24,6 +24,7 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.INVALID_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW;
@@ -175,6 +176,7 @@
private String mAgent; // Agent to attach on startup.
private boolean mAttachAgentDuringBind; // Whether agent should be attached late.
private int mDisplayId;
+ private int mTaskDisplayAreaFeatureId;
private int mWindowingMode;
private int mActivityType;
private int mTaskId;
@@ -368,6 +370,7 @@
mStreaming = false;
mUserId = defUser;
mDisplayId = INVALID_DISPLAY;
+ mTaskDisplayAreaFeatureId = FEATURE_UNDEFINED;
mWindowingMode = WINDOWING_MODE_UNDEFINED;
mActivityType = ACTIVITY_TYPE_UNDEFINED;
mTaskId = INVALID_TASK_ID;
@@ -423,6 +426,8 @@
mReceiverPermission = getNextArgRequired();
} else if (opt.equals("--display")) {
mDisplayId = Integer.parseInt(getNextArgRequired());
+ } else if (opt.equals("--task-display-area-feature-id")) {
+ mTaskDisplayAreaFeatureId = Integer.parseInt(getNextArgRequired());
} else if (opt.equals("--windowingMode")) {
mWindowingMode = Integer.parseInt(getNextArgRequired());
} else if (opt.equals("--activityType")) {
@@ -554,6 +559,12 @@
options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(mDisplayId);
}
+ if (mTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
+ if (options == null) {
+ options = ActivityOptions.makeBasic();
+ }
+ options.setLaunchTaskDisplayAreaFeatureId(mTaskDisplayAreaFeatureId);
+ }
if (mWindowingMode != WINDOWING_MODE_UNDEFINED) {
if (options == null) {
options = ActivityOptions.makeBasic();
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 178b6bb..132309e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -5129,6 +5129,7 @@
private static final String EXTRA_REQUESTER = "requester";
private static final String DROPBOX_TAG_IMPERCEPTIBLE_KILL = "imperceptible_app_kill";
+ private static final boolean LOG_TO_DROPBOX = false;
// uid -> killing information mapping
private SparseArray<List<Bundle>> mWorkItems = new SparseArray<List<Bundle>>();
@@ -5234,7 +5235,7 @@
private void handleDeviceIdle() {
final DropBoxManager dbox = mService.mContext.getSystemService(DropBoxManager.class);
- final boolean logToDropbox = dbox != null
+ final boolean logToDropbox = LOG_TO_DROPBOX && dbox != null
&& dbox.isTagEnabled(DROPBOX_TAG_IMPERCEPTIBLE_KILL);
synchronized (mService) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index fb4b683..42d8778 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -365,6 +365,8 @@
private static final int MSG_REMOVE_ASSISTANT_SERVICE_UID = 45;
private static final int MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID = 46;
private static final int MSG_DISPATCH_DEVICE_VOLUME_BEHAVIOR = 47;
+ private static final int MSG_ROTATION_UPDATE = 48;
+ private static final int MSG_FOLD_UPDATE = 49;
// start of messages handled under wakelock
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
@@ -1251,7 +1253,9 @@
intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
if (mMonitorRotation) {
- RotationHelper.init(mContext, mAudioHandler);
+ RotationHelper.init(mContext, mAudioHandler,
+ rotationParam -> onRotationUpdate(rotationParam),
+ foldParam -> onFoldUpdate(foldParam));
}
intentFilter.addAction(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
@@ -1398,6 +1402,20 @@
}
//-----------------------------------------------------------------
+ // rotation/fold updates coming from RotationHelper
+ void onRotationUpdate(String rotationParameter) {
+ // use REPLACE as only the last rotation matters
+ sendMsg(mAudioHandler, MSG_ROTATION_UPDATE, SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0,
+ /*obj*/ rotationParameter, /*delay*/ 0);
+ }
+
+ void onFoldUpdate(String foldParameter) {
+ // use REPLACE as only the last fold state matters
+ sendMsg(mAudioHandler, MSG_FOLD_UPDATE, SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0,
+ /*obj*/ foldParameter, /*delay*/ 0);
+ }
+
+ //-----------------------------------------------------------------
// monitoring requests for volume range initialization
@Override // AudioSystemAdapter.OnVolRangeInitRequestListener
public void onVolumeRangeInitRequestFromNative() {
@@ -8327,6 +8345,16 @@
case MSG_DISPATCH_DEVICE_VOLUME_BEHAVIOR:
dispatchDeviceVolumeBehavior((AudioDeviceAttributes) msg.obj, msg.arg1);
break;
+
+ case MSG_ROTATION_UPDATE:
+ // rotation parameter format: "rotation=x" where x is one of 0, 90, 180, 270
+ mAudioSystem.setParameters((String) msg.obj);
+ break;
+
+ case MSG_FOLD_UPDATE:
+ // fold parameter format: "device_folded=x" where x is one of on, off
+ mAudioSystem.setParameters((String) msg.obj);
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java
index eb8387f..5cdf58b 100644
--- a/services/core/java/com/android/server/audio/RotationHelper.java
+++ b/services/core/java/com/android/server/audio/RotationHelper.java
@@ -21,13 +21,14 @@
import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
-import android.media.AudioSystem;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.util.Log;
import android.view.Display;
import android.view.Surface;
+import java.util.function.Consumer;
+
/**
* Class to handle device rotation events for AudioService, and forward device rotation
* and folded state to the audio HALs through AudioSystem.
@@ -53,6 +54,10 @@
private static AudioDisplayListener sDisplayListener;
private static FoldStateListener sFoldStateListener;
+ /** callback to send rotation updates to AudioSystem */
+ private static Consumer<String> sRotationUpdateCb;
+ /** callback to send folded state updates to AudioSystem */
+ private static Consumer<String> sFoldUpdateCb;
private static final Object sRotationLock = new Object();
private static final Object sFoldStateLock = new Object();
@@ -67,13 +72,16 @@
* - sDisplayListener != null
* - sContext != null
*/
- static void init(Context context, Handler handler) {
+ static void init(Context context, Handler handler,
+ Consumer<String> rotationUpdateCb, Consumer<String> foldUpdateCb) {
if (context == null) {
throw new IllegalArgumentException("Invalid null context");
}
sContext = context;
sHandler = handler;
sDisplayListener = new AudioDisplayListener();
+ sRotationUpdateCb = rotationUpdateCb;
+ sFoldUpdateCb = foldUpdateCb;
enable();
}
@@ -115,21 +123,26 @@
if (DEBUG_ROTATION) {
Log.i(TAG, "publishing device rotation =" + rotation + " (x90deg)");
}
+ String rotationParam;
switch (rotation) {
case Surface.ROTATION_0:
- AudioSystem.setParameters("rotation=0");
+ rotationParam = "rotation=0";
break;
case Surface.ROTATION_90:
- AudioSystem.setParameters("rotation=90");
+ rotationParam = "rotation=90";
break;
case Surface.ROTATION_180:
- AudioSystem.setParameters("rotation=180");
+ rotationParam = "rotation=180";
break;
case Surface.ROTATION_270:
- AudioSystem.setParameters("rotation=270");
+ rotationParam = "rotation=270";
break;
default:
Log.e(TAG, "Unknown device rotation");
+ rotationParam = null;
+ }
+ if (rotationParam != null) {
+ sRotationUpdateCb.accept(rotationParam);
}
}
@@ -140,11 +153,13 @@
synchronized (sFoldStateLock) {
if (sDeviceFold != newFolded) {
sDeviceFold = newFolded;
+ String foldParam;
if (newFolded) {
- AudioSystem.setParameters("device_folded=on");
+ foldParam = "device_folded=on";
} else {
- AudioSystem.setParameters("device_folded=off");
+ foldParam = "device_folded=off";
}
+ sFoldUpdateCb.accept(foldParam);
}
}
}
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 5b26672..dd44af1 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -280,18 +280,13 @@
}
// for both transaural / binaural, we are not forcing enablement as the init() method
// could have been called another time after boot in case of audioserver restart
- if (mTransauralSupported) {
- // not force-enabling as this device might already be in the device list
- addCompatibleAudioDevice(
- new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
- false /*forceEnable*/);
- }
- if (mBinauralSupported) {
- // not force-enabling as this device might already be in the device list
- addCompatibleAudioDevice(
- new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""),
- false /*forceEnable*/);
- }
+ addCompatibleAudioDevice(
+ new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
+ false /*forceEnable*/);
+ // not force-enabling as this device might already be in the device list
+ addCompatibleAudioDevice(
+ new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""),
+ false /*forceEnable*/);
} catch (RemoteException e) {
resetCapabilities();
} finally {
@@ -497,10 +492,9 @@
synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() {
// build unionOf(mCompatibleAudioDevices, mEnabledDevice) - mDisabledAudioDevices
ArrayList<AudioDeviceAttributes> compatList = new ArrayList<>();
- for (SADeviceState dev : mSADevices) {
- if (dev.mEnabled) {
- compatList.add(new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
- dev.mDeviceType, dev.mDeviceAddress == null ? "" : dev.mDeviceAddress));
+ for (SADeviceState deviceState : mSADevices) {
+ if (deviceState.mEnabled) {
+ compatList.add(deviceState.getAudioDeviceAttributes());
}
}
return compatList;
@@ -521,15 +515,15 @@
*/
private void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada,
boolean forceEnable) {
+ if (!isDeviceCompatibleWithSpatializationModes(ada)) {
+ return;
+ }
loglogi("addCompatibleAudioDevice: dev=" + ada);
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
boolean isInList = false;
SADeviceState deviceUpdated = null; // non-null on update.
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
isInList = true;
if (forceEnable) {
deviceState.mEnabled = true;
@@ -539,11 +533,10 @@
}
}
if (!isInList) {
- final SADeviceState dev = new SADeviceState(deviceType,
- wireless ? ada.getAddress() : "");
- dev.mEnabled = true;
- mSADevices.add(dev);
- deviceUpdated = dev;
+ final SADeviceState deviceState = new SADeviceState(ada.getType(), ada.getAddress());
+ deviceState.mEnabled = true;
+ mSADevices.add(deviceState);
+ deviceUpdated = deviceState;
}
if (deviceUpdated != null) {
onRoutingUpdated();
@@ -574,13 +567,10 @@
synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
loglogi("removeCompatibleAudioDevice: dev=" + ada);
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
SADeviceState deviceUpdated = null; // non-null on update.
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
deviceState.mEnabled = false;
deviceUpdated = deviceState;
break;
@@ -602,10 +592,9 @@
// if not a wireless device, this value will be overwritten to map the type
// to TYPE_BUILTIN_SPEAKER or TYPE_WIRED_HEADPHONES
@AudioDeviceInfo.AudioDeviceType int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
// if not a wireless device: find if media device is in the speaker, wired headphones
- if (!wireless) {
+ if (!isWireless(deviceType)) {
// is the device type capable of doing SA?
if (!mSACapableDeviceTypes.contains(deviceType)) {
Log.i(TAG, "Device incompatible with Spatial Audio dev:" + ada);
@@ -640,9 +629,7 @@
boolean enabled = false;
boolean available = false;
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
available = true;
enabled = deviceState.mEnabled;
break;
@@ -652,11 +639,12 @@
}
private synchronized void addWirelessDeviceIfNew(@NonNull AudioDeviceAttributes ada) {
+ if (!isDeviceCompatibleWithSpatializationModes(ada)) {
+ return;
+ }
boolean knownDevice = false;
for (SADeviceState deviceState : mSADevices) {
- // wireless device so always check address
- if (ada.getType() == deviceState.mDeviceType
- && ada.getAddress().equals(deviceState.mDeviceAddress)) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
knownDevice = true;
break;
}
@@ -704,13 +692,8 @@
if (ada.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
return false;
}
-
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
return true;
}
}
@@ -719,12 +702,19 @@
private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes,
@NonNull AudioFormat format, @NonNull AudioDeviceAttributes[] devices) {
- final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(devices[0].getType(),
+ if (isDeviceCompatibleWithSpatializationModes(devices[0])) {
+ return AudioSystem.canBeSpatialized(attributes, format, devices);
+ }
+ return false;
+ }
+
+ private boolean isDeviceCompatibleWithSpatializationModes(@NonNull AudioDeviceAttributes ada) {
+ final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(),
/*default when type not found*/ SpatializationMode.SPATIALIZER_BINAURAL);
if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
|| (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL
&& mTransauralSupported)) {
- return AudioSystem.canBeSpatialized(attributes, format, devices);
+ return true;
}
return false;
}
@@ -1089,13 +1079,8 @@
Log.v(TAG, "no headtracking support, ignoring setHeadTrackerEnabled to " + enabled
+ " for " + ada);
}
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
-
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
if (!deviceState.mHasHeadTracker) {
Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled
+ " device:" + ada + " on a device without headtracker");
@@ -1109,7 +1094,7 @@
}
}
// check current routing to see if it affects the headtracking mode
- if (ROUTING_DEVICES[0].getType() == deviceType
+ if (ROUTING_DEVICES[0].getType() == ada.getType()
&& ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) {
setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled
: Spatializer.HEAD_TRACKING_MODE_DISABLED);
@@ -1121,13 +1106,8 @@
Log.v(TAG, "no headtracking support, hasHeadTracker always false for " + ada);
return false;
}
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
-
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
return deviceState.mHasHeadTracker;
}
}
@@ -1144,13 +1124,8 @@
Log.v(TAG, "no headtracking support, setHasHeadTracker always false for " + ada);
return false;
}
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
-
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
if (!deviceState.mHasHeadTracker) {
deviceState.mHasHeadTracker = true;
mAudioService.persistSpatialAudioDeviceSettings();
@@ -1168,13 +1143,8 @@
Log.v(TAG, "no headtracking support, isHeadTrackerEnabled always false for " + ada);
return false;
}
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
-
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
+ if (deviceState.matchesAudioDeviceAttributes(ada)) {
if (!deviceState.mHasHeadTracker) {
return false;
}
@@ -1531,7 +1501,7 @@
SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @NonNull String address) {
mDeviceType = deviceType;
- mDeviceAddress = Objects.requireNonNull(address);
+ mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : "";
}
@Override
@@ -1599,6 +1569,18 @@
return null;
}
}
+
+ public AudioDeviceAttributes getAudioDeviceAttributes() {
+ return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+ mDeviceType, mDeviceAddress == null ? "" : mDeviceAddress);
+ }
+
+ public boolean matchesAudioDeviceAttributes(AudioDeviceAttributes ada) {
+ final int deviceType = ada.getType();
+ final boolean wireless = isWireless(deviceType);
+ return (deviceType == mDeviceType)
+ && (!wireless || ada.getAddress().equals(mDeviceAddress));
+ }
}
/*package*/ synchronized String getSADeviceSettings() {
@@ -1619,7 +1601,9 @@
// small list, not worth overhead of Arrays.stream(devSettings)
for (String setting : devSettings) {
SADeviceState devState = SADeviceState.fromPersistedString(setting);
- if (devState != null) {
+ if (devState != null
+ && isDeviceCompatibleWithSpatializationModes(
+ devState.getAudioDeviceAttributes())) {
mSADevices.add(devState);
logDeviceState(devState, "setSADeviceSettings");
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 5b282ce..4e5ce8a 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -4085,6 +4085,20 @@
@NonNull List<String> excludedApps) {
enforceNotRestrictedUser();
if (!storeAppExclusionList(packageName, excludedApps)) return false;
+
+ updateAppExclusionList(excludedApps);
+
+ return true;
+ }
+
+ /**
+ * Triggers an update of the VPN network's excluded UIDs if a VPN is running.
+ */
+ public synchronized void refreshPlatformVpnAppExclusionList() {
+ updateAppExclusionList(getAppExclusionList(mPackage));
+ }
+
+ private synchronized void updateAppExclusionList(@NonNull List<String> excludedApps) {
// Re-build and update NetworkCapabilities via NetworkAgent.
if (mNetworkAgent != null) {
// Only update the platform VPN
@@ -4097,8 +4111,6 @@
mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
}
}
-
- return true;
}
/**
diff --git a/services/core/java/com/android/server/dreams/OWNERS b/services/core/java/com/android/server/dreams/OWNERS
index 426f002..7302f6e 100644
--- a/services/core/java/com/android/server/dreams/OWNERS
+++ b/services/core/java/com/android/server/dreams/OWNERS
@@ -1,3 +1,4 @@
+brycelee@google.com
dsandler@android.com
michaelwr@google.com
roosa@google.com
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 157057d..aaa9ee5 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -337,6 +337,8 @@
static final int AUDIO_CODEC_WMAPRO = 0xE; // Support WMA-Pro
static final int AUDIO_CODEC_MAX = 0xF;
+ static final int AUDIO_FORMAT_MASK = 0b0111_1000;
+
@StringDef({
AUDIO_DEVICE_ARC_IN,
AUDIO_DEVICE_SPDIF,
diff --git a/services/core/java/com/android/server/hdmi/RequestSadAction.java b/services/core/java/com/android/server/hdmi/RequestSadAction.java
index 23aaf32..0188e96 100644
--- a/services/core/java/com/android/server/hdmi/RequestSadAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestSadAction.java
@@ -200,8 +200,14 @@
}
private boolean isValidCodec(byte codec) {
- return Constants.AUDIO_CODEC_NONE < (codec & 0xFF)
- && (codec & 0xFF) <= Constants.AUDIO_CODEC_MAX;
+ // Bit 7 needs to be 0.
+ if ((codec & (1 << 7)) != 0) {
+ return false;
+ }
+ // Bit [6, 3] is the audio format code.
+ int audioFormatCode = (codec & Constants.AUDIO_FORMAT_MASK) >> 3;
+ return Constants.AUDIO_CODEC_NONE < audioFormatCode
+ && audioFormatCode <= Constants.AUDIO_CODEC_MAX;
}
private void updateResult(byte[] sad) {
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index 4ffad91..6c75dbf 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -108,12 +108,17 @@
}
@AnyThread
- void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
- int configChanges, boolean stylusHwSupported,
- @InputMethodNavButtonFlags int navButtonFlags) {
+ void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privilegedOperations,
+ int configChanges, boolean stylusHandWritingSupported,
+ @InputMethodNavButtonFlags int navigationBarFlags) {
+ final IInputMethod.InitParams params = new IInputMethod.InitParams();
+ params.token = token;
+ params.privilegedOperations = privilegedOperations;
+ params.configChanges = configChanges;
+ params.stylusHandWritingSupported = stylusHandWritingSupported;
+ params.navigationBarFlags = navigationBarFlags;
try {
- mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported,
- navButtonFlags);
+ mTarget.initializeInternal(params);
} catch (RemoteException e) {
logRemoteException(e);
}
@@ -148,13 +153,19 @@
}
@AnyThread
- void startInput(IBinder startInputToken, IRemoteInputConnection inputConnection,
+ void startInput(IBinder startInputToken, IRemoteInputConnection remoteInputConnection,
EditorInfo editorInfo, boolean restarting,
@InputMethodNavButtonFlags int navButtonFlags,
@NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+ final IInputMethod.StartInputParams params = new IInputMethod.StartInputParams();
+ params.startInputToken = startInputToken;
+ params.remoteInputConnection = remoteInputConnection;
+ params.editorInfo = editorInfo;
+ params.restarting = restarting;
+ params.navigationBarFlags = navButtonFlags;
+ params.imeDispatcher = imeDispatcher;
try {
- mTarget.startInput(startInputToken, inputConnection, editorInfo, restarting,
- navButtonFlags, imeDispatcher);
+ mTarget.startInput(params);
} catch (RemoteException e) {
logRemoteException(e);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 3fceba7..b45dc7f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2108,9 +2108,24 @@
}
@Override
- public boolean isStylusHandwritingAvailable() {
+ public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
+ if (UserHandle.getCallingUserId() != userId) {
+ mContext.enforceCallingPermission(
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
+ }
+
synchronized (ImfLock.class) {
- return mBindingController.supportsStylusHandwriting();
+ if (userId == mSettings.getCurrentUserId()) {
+ return mBindingController.supportsStylusHandwriting();
+ }
+
+ //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
+ //TODO(b/210039666): use cache.
+ final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId);
+ final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(),
+ mContext.getContentResolver(), methodMap, userId, true);
+ final InputMethodInfo imi = methodMap.get(settings.getSelectedInputMethod());
+ return imi != null && imi.supportsStylusHandwriting();
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 1de3e37..ee8a23c 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -73,7 +73,6 @@
import android.database.ContentObserver;
import android.database.sqlite.SQLiteDatabase;
import android.hardware.authsecret.V1_0.IAuthSecret;
-import android.hardware.biometrics.BiometricManager;
import android.hardware.face.Face;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.Fingerprint;
@@ -89,7 +88,6 @@
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
-import android.os.StrictMode;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -110,7 +108,6 @@
import android.security.keystore.recovery.WrappedApplicationKey;
import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
import android.security.keystore2.AndroidKeyStoreProvider;
-import android.service.gatekeeper.GateKeeperResponse;
import android.service.gatekeeper.IGateKeeperService;
import android.system.keystore2.Domain;
import android.text.TextUtils;
@@ -141,7 +138,6 @@
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
-import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
@@ -163,7 +159,6 @@
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStoreException;
-import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
@@ -517,11 +512,6 @@
return new RebootEscrowManager(mContext, callbacks, storage);
}
- public boolean hasEnrolledBiometrics(int userId) {
- BiometricManager bm = mContext.getSystemService(BiometricManager.class);
- return bm.hasEnrolledBiometrics(userId);
- }
-
public int binderGetCallingUid() {
return Binder.getCallingUid();
}
@@ -1285,11 +1275,8 @@
return mStorage.getString(key, defaultValue, userId);
}
- private void setKeyguardStoredQuality(int quality, int userId) {
- if (DEBUG) Slog.d(TAG, "setKeyguardStoredQuality: user=" + userId + " quality=" + quality);
- mStorage.setLong(LockPatternUtils.PASSWORD_TYPE_KEY, quality, userId);
- }
-
+ // Not relevant for new devices, but some legacy devices still have PASSWORD_TYPE_KEY around to
+ // distinguish between credential types.
private int getKeyguardStoredQuality(int userId) {
return (int) mStorage.getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId);
@@ -1326,18 +1313,6 @@
return pinOrPasswordQualityToCredentialType(getKeyguardStoredQuality(userId));
}
}
- // Intentional duplication of the getKeyguardStoredQuality() call above since this is a
- // unlikely code path (device with pre-synthetic password credential). We want to skip
- // calling getKeyguardStoredQuality whenever possible.
- final int savedQuality = getKeyguardStoredQuality(userId);
- if (savedQuality == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
- && mStorage.hasPattern(userId)) {
- return CREDENTIAL_TYPE_PATTERN;
- }
- if (savedQuality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
- && mStorage.hasPassword(userId)) {
- return pinOrPasswordQualityToCredentialType(savedQuality);
- }
return CREDENTIAL_TYPE_NONE;
}
@@ -1742,33 +1717,15 @@
LockscreenCredential savedCredential, int userId, boolean isLockTiedToParent) {
Objects.requireNonNull(credential);
Objects.requireNonNull(savedCredential);
+ if (DEBUG) Slog.d(TAG, "setLockCredentialInternal: user=" + userId);
synchronized (mSpManager) {
- if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- return spBasedSetLockCredentialInternalLocked(credential, savedCredential, userId,
- isLockTiedToParent);
- }
- }
-
- if (credential.isNone()) {
- clearUserKeyProtection(userId, null);
- gateKeeperClearSecureUserId(userId);
- mStorage.writeCredentialHash(CredentialHash.createEmptyHash(), userId);
- // Still update PASSWORD_TYPE_KEY if we are running in pre-synthetic password code path,
- // since it forms part of the state that determines the credential type
- // @see getCredentialTypeInternal
- setKeyguardStoredQuality(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId);
- setKeystorePassword(null, userId);
- fixateNewestUserKeyAuth(userId);
- synchronizeUnifiedWorkChallengeForProfiles(userId, null);
- setUserPasswordMetrics(LockscreenCredential.createNone(), userId);
- sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
- return true;
- }
-
- CredentialHash currentHandle = mStorage.readCredentialHash(userId);
- if (isProfileWithUnifiedLock(userId)) {
- // get credential from keystore when managed/clone profile has unified lock
- if (savedCredential.isNone()) {
+ if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
+ if (!savedCredential.isNone()) {
+ throw new IllegalStateException("Saved credential given, but user has no SP");
+ }
+ initializeSyntheticPasswordLocked(savedCredential, userId);
+ } else if (savedCredential.isNone() && isProfileWithUnifiedLock(userId)) {
+ // get credential from keystore when profile has unified lock
try {
//TODO: remove as part of b/80170828
savedCredential = getDecryptedPasswordForTiedProfile(userId);
@@ -1781,19 +1738,31 @@
Slog.e(TAG, "Failed to decrypt child profile key", e);
}
}
- } else {
- if (currentHandle.hash == null) {
- if (!savedCredential.isNone()) {
- Slog.w(TAG, "Saved credential provided, but none stored");
+ final long origHandle = getSyntheticPasswordHandleLocked(userId);
+ AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
+ getGateKeeperService(), origHandle, savedCredential, userId, null);
+ VerifyCredentialResponse response = authResult.gkResponse;
+ AuthenticationToken auth = authResult.authToken;
+
+ if (auth == null) {
+ if (response == null
+ || response.getResponseCode() == VerifyCredentialResponse.RESPONSE_ERROR) {
+ Slog.w(TAG, "Failed to enroll: incorrect credential.");
+ return false;
}
- savedCredential.close();
- savedCredential = LockscreenCredential.createNone();
+ if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
+ Slog.w(TAG, "Failed to enroll: rate limit exceeded.");
+ return false;
+ }
+ // Should not be reachable, but just in case.
+ throw new IllegalStateException("password change failed");
}
- }
- synchronized (mSpManager) {
- initializeSyntheticPasswordLocked(currentHandle.hash, savedCredential, userId);
- return spBasedSetLockCredentialInternalLocked(credential, savedCredential, userId,
- isLockTiedToParent);
+
+ onAuthTokenKnownForUser(userId, auth);
+ setLockCredentialWithAuthTokenLocked(credential, auth, userId);
+ mSpManager.destroyPasswordBasedSyntheticPassword(origHandle, userId);
+ sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
+ return true;
}
}
@@ -1912,10 +1881,6 @@
return getUserManagerFromCache(userId).isCredentialSharableWithParent();
}
- private VerifyCredentialResponse convertResponse(GateKeeperResponse gateKeeperResponse) {
- return VerifyCredentialResponse.fromGateKeeperResponse(gateKeeperResponse);
- }
-
private void setCredentialRequiredToDecrypt(boolean required) {
if (isDeviceEncryptionEnabled()) {
Settings.Global.putInt(mContext.getContentResolver(),
@@ -2089,21 +2054,6 @@
}
}
- private static byte[] secretFromCredential(LockscreenCredential credential) {
- try {
- MessageDigest digest = MessageDigest.getInstance("SHA-512");
- // Personalize the hash
- byte[] personalization = "Android FBE credential hash".getBytes();
- // Pad it to the block size of the hash function
- personalization = Arrays.copyOf(personalization, 128);
- digest.update(personalization);
- digest.update(credential.getCredential());
- return digest.digest();
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalStateException("NoSuchAlgorithmException for SHA-512");
- }
- }
-
private boolean isUserKeyUnlocked(int userId) {
try {
return mStorageManager.isUserKeyUnlocked(userId);
@@ -2262,9 +2212,8 @@
}
}
- /*
- * Verify user credential and unlock the user. Fix pattern bug by deprecating the old base zero
- * format.
+ /**
+ * Verify user credential and unlock the user.
* @param credential User's lockscreen credential
* @param userId User to verify the credential for
* @param progressCallback Receive progress callbacks
@@ -2282,36 +2231,59 @@
Slog.e(TAG, "FRP credential can only be verified prior to provisioning.");
return VerifyCredentialResponse.ERROR;
}
+ Slog.d(TAG, "doVerifyCredential: user=" + userId);
- VerifyCredentialResponse response = spBasedDoVerifyCredential(credential, userId,
- progressCallback, flags);
+ final AuthenticationResult authResult;
+ VerifyCredentialResponse response;
- // The user employs synthetic password based credential.
- if (response != null) {
- if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
- sendCredentialsOnUnlockIfRequired(credential, userId);
+ synchronized (mSpManager) {
+ if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
+ Slog.wtf(TAG, "Unexpected credential type, should be SP based.");
+ return VerifyCredentialResponse.ERROR;
}
- return response;
+ if (userId == USER_FRP) {
+ return mSpManager.verifyFrpCredential(getGateKeeperService(), credential,
+ progressCallback);
+ }
+
+ long handle = getSyntheticPasswordHandleLocked(userId);
+ authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
+ getGateKeeperService(), handle, credential, userId, progressCallback);
+ response = authResult.gkResponse;
+
+ if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+ // credential has matched
+ mBiometricDeferredQueue.addPendingLockoutResetForUser(userId,
+ authResult.authToken.deriveGkPassword());
+
+ // perform verifyChallenge with synthetic password which generates the real GK auth
+ // token and response for the current user
+ response = mSpManager.verifyChallenge(getGateKeeperService(), authResult.authToken,
+ 0L /* challenge */, userId);
+ if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
+ // This shouldn't really happen: the unwrapping of SP succeeds, but SP doesn't
+ // match the recorded GK password handle.
+ Slog.wtf(TAG, "verifyChallenge with SP failed.");
+ return VerifyCredentialResponse.ERROR;
+ }
+ }
}
-
- if (userId == USER_FRP) {
- Slog.wtf(TAG, "Unexpected FRP credential type, should be SP based.");
- return VerifyCredentialResponse.ERROR;
- }
-
- final CredentialHash storedHash = mStorage.readCredentialHash(userId);
- if (!credential.checkAgainstStoredType(storedHash.type)) {
- Slog.wtf(TAG, "doVerifyCredential type mismatch with stored credential??"
- + " stored: " + storedHash.type + " passed in: " + credential.getType());
- return VerifyCredentialResponse.ERROR;
- }
-
- response = verifyCredential(userId, storedHash, credential, progressCallback);
-
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
- mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
+ onCredentialVerified(authResult.authToken,
+ PasswordMetrics.computeForCredential(credential), userId);
+ if ((flags & VERIFY_FLAG_REQUEST_GK_PW_HANDLE) != 0) {
+ final long gkHandle = storeGatekeeperPasswordTemporarily(
+ authResult.authToken.deriveGkPassword());
+ response = new VerifyCredentialResponse.Builder()
+ .setGatekeeperPasswordHandle(gkHandle)
+ .build();
+ }
+ sendCredentialsOnUnlockIfRequired(credential, userId);
+ } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
+ if (response.getTimeout() > 0) {
+ requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
+ }
}
-
return response;
}
@@ -2351,83 +2323,6 @@
}
/**
- * Lowest-level credential verification routine that talks to GateKeeper. If verification
- * passes, unlock the corresponding user and keystore. Also handles the migration from legacy
- * hash to GK.
- */
- private VerifyCredentialResponse verifyCredential(int userId, CredentialHash storedHash,
- LockscreenCredential credential, ICheckCredentialProgressCallback progressCallback) {
- if ((storedHash == null || storedHash.hash.length == 0) && credential.isNone()) {
- // don't need to pass empty credentials to GateKeeper
- return VerifyCredentialResponse.OK;
- }
-
- if (storedHash == null || storedHash.hash.length == 0 || credential.isNone()) {
- return VerifyCredentialResponse.ERROR;
- }
-
- // We're potentially going to be doing a bunch of disk I/O below as part
- // of unlocking the user, so yell if calling from the main thread.
- StrictMode.noteDiskRead();
-
- GateKeeperResponse gateKeeperResponse;
- try {
- gateKeeperResponse = getGateKeeperService().verifyChallenge(
- userId, 0L /* challenge */, storedHash.hash, credential.getCredential());
- } catch (RemoteException e) {
- Slog.e(TAG, "gatekeeper verify failed", e);
- gateKeeperResponse = GateKeeperResponse.ERROR;
- }
- VerifyCredentialResponse response = convertResponse(gateKeeperResponse);
- boolean shouldReEnroll = gateKeeperResponse.getShouldReEnroll();
-
- if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-
- // credential has matched
-
- if (progressCallback != null) {
- try {
- progressCallback.onCredentialVerified();
- } catch (RemoteException e) {
- Slog.w(TAG, "progressCallback throws exception", e);
- }
- }
- setUserPasswordMetrics(credential, userId);
- unlockKeystore(credential.getCredential(), userId);
-
- Slog.i(TAG, "Unlocking user " + userId);
- unlockUser(userId, secretFromCredential(credential));
-
- if (isProfileWithSeparatedLock(userId)) {
- setDeviceUnlockedForUser(userId);
- }
- if (shouldReEnroll) {
- setLockCredentialInternal(credential, credential,
- userId, /* isLockTiedToParent= */ false);
- } else {
- // Now that we've cleared of all required GK migration, let's do the final
- // migration to synthetic password.
- synchronized (mSpManager) {
- if (shouldMigrateToSyntheticPasswordLocked(userId)) {
- AuthenticationToken auth = initializeSyntheticPasswordLocked(
- storedHash.hash, credential, userId);
- activateEscrowTokens(auth, userId);
- }
- }
- }
- // Use credentials to create recoverable keystore snapshot.
- sendCredentialsOnUnlockIfRequired(credential, userId);
-
- } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
- if (response.getTimeout() > 0) {
- requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
- }
- }
-
- return response;
- }
-
- /**
* Keep track of the given user's latest password metric. This should be called
* when the user is authenticating or when a new password is being set. In comparison,
* {@link #notifyPasswordChanged} only needs to be called when the user changes the password.
@@ -2779,7 +2674,9 @@
* Precondition: vold and keystore unlocked.
*
* Create new synthetic password, set up synthetic password blob protected by the supplied
- * user credential, and make the newly-created SP blob active.
+ * user credential, and make the newly-created SP blob active. This is called just once in the
+ * lifetime of the user: the first time that a user credential is set (!credential.isNone()), or
+ * when an escrow token is activated on an unsecured device (credential.isNone()).
*
* The invariant under a synthetic password is:
* 1. If user credential exists, then both vold and keystore and protected with keys derived
@@ -2793,47 +2690,21 @@
* protected by a default PIN.
* 4. The user SID is linked with synthetic password, but its cleared/re-created when the user
* clears/re-creates their lockscreen PIN.
- *
- *
- * Different cases of calling this method:
- * 1. credentialHash != null
- * This implies credential != null, a new SP blob will be provisioned, and existing SID
- * migrated to associate with the new SP.
- * This happens during a normal migration case when the user currently has password.
- *
- * 2. credentialhash == null and credential == null
- * A new SP blob and will be created, while the user has no credentials.
- * This can happens when we are activating an escrow token on a unsecured device, during
- * which we want to create the SP structure with an empty user credential.
- * This could also happen during an untrusted reset to clear password.
- *
- * 3. credentialhash == null and credential != null
- * The user sets a new lockscreen password FOR THE FIRST TIME on a SP-enabled device.
- * New credential and new SID will be created
*/
@GuardedBy("mSpManager")
@VisibleForTesting
- protected AuthenticationToken initializeSyntheticPasswordLocked(byte[] credentialHash,
- LockscreenCredential credential, int userId) {
+ AuthenticationToken initializeSyntheticPasswordLocked(LockscreenCredential credential,
+ int userId) {
Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
Preconditions.checkState(
getSyntheticPasswordHandleLocked(userId) == SyntheticPasswordManager.DEFAULT_HANDLE,
"Cannot reinitialize SP");
- final AuthenticationToken auth = mSpManager.newSyntheticPasswordAndSid(
- getGateKeeperService(), credentialHash, credential, userId);
- if (auth == null) {
- Slog.wtf(TAG, "initializeSyntheticPasswordLocked returns null auth token");
- return null;
- }
+ final AuthenticationToken auth = mSpManager.newSyntheticPassword(userId);
long handle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
credential, auth, userId);
if (!credential.isNone()) {
- if (credentialHash == null) {
- // Since when initializing SP, we didn't provide an existing password handle
- // for it to migrate SID, we need to create a new SID for the user.
- mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
- }
+ mSpManager.newSidForUser(getGateKeeperService(), auth, userId);
mSpManager.verifyChallenge(getGateKeeperService(), auth, 0L, userId);
setUserKeyProtection(userId, auth.deriveDiskEncryptionKey());
setKeystorePassword(auth.deriveKeyStorePassword(), userId);
@@ -2878,73 +2749,6 @@
return handle != SyntheticPasswordManager.DEFAULT_HANDLE;
}
- @VisibleForTesting
- protected boolean shouldMigrateToSyntheticPasswordLocked(int userId) {
- return getSyntheticPasswordHandleLocked(userId) == SyntheticPasswordManager.DEFAULT_HANDLE;
- }
-
- private VerifyCredentialResponse spBasedDoVerifyCredential(LockscreenCredential userCredential,
- int userId, ICheckCredentialProgressCallback progressCallback,
- @LockPatternUtils.VerifyFlag int flags) {
- final boolean hasEnrolledBiometrics = mInjector.hasEnrolledBiometrics(userId);
-
- Slog.d(TAG, "spBasedDoVerifyCredential: user=" + userId
- + " hasEnrolledBiometrics=" + hasEnrolledBiometrics);
-
- final AuthenticationResult authResult;
- VerifyCredentialResponse response;
- final boolean requestGkPw = (flags & VERIFY_FLAG_REQUEST_GK_PW_HANDLE) != 0;
-
- synchronized (mSpManager) {
- if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
- return null;
- }
- if (userId == USER_FRP) {
- return mSpManager.verifyFrpCredential(getGateKeeperService(),
- userCredential, progressCallback);
- }
-
- long handle = getSyntheticPasswordHandleLocked(userId);
- authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
- getGateKeeperService(), handle, userCredential, userId, progressCallback);
- response = authResult.gkResponse;
-
- // credential has matched
- if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
- mBiometricDeferredQueue.addPendingLockoutResetForUser(userId,
- authResult.authToken.deriveGkPassword());
-
- // perform verifyChallenge with synthetic password which generates the real GK auth
- // token and response for the current user
- response = mSpManager.verifyChallenge(getGateKeeperService(), authResult.authToken,
- 0L /* challenge */, userId);
- if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
- // This shouldn't really happen: the unwrapping of SP succeeds, but SP doesn't
- // match the recorded GK password handle.
- Slog.wtf(TAG, "verifyChallenge with SP failed.");
- return VerifyCredentialResponse.ERROR;
- }
- }
- }
- if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
- onCredentialVerified(authResult.authToken,
- PasswordMetrics.computeForCredential(userCredential), userId);
- } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
- if (response.getTimeout() > 0) {
- requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId);
- }
- }
-
- if (response.isMatched() && requestGkPw) {
- final long handle = storeGatekeeperPasswordTemporarily(
- authResult.authToken.deriveGkPassword());
- return new VerifyCredentialResponse.Builder().setGatekeeperPasswordHandle(handle)
- .build();
- } else {
- return response;
- }
- }
-
/**
* Stores the gatekeeper password temporarily.
* @param gatekeeperPassword unlocked upon successful Synthetic Password
@@ -3149,56 +2953,6 @@
}
/**
- * @param savedCredential if the user is a profile with unified challenge and
- * savedCredential is empty, LSS will try to re-derive the profile password internally.
- * TODO (b/80170828): Fix this so profile password is always passed in.
- */
- @GuardedBy("mSpManager")
- private boolean spBasedSetLockCredentialInternalLocked(LockscreenCredential credential,
- LockscreenCredential savedCredential, int userId, boolean isLockTiedToParent) {
- if (DEBUG) Slog.d(TAG, "spBasedSetLockCredentialInternalLocked: user=" + userId);
- if (savedCredential.isNone() && isProfileWithUnifiedLock(userId)) {
- // get credential from keystore when profile has unified lock
- try {
- //TODO: remove as part of b/80170828
- savedCredential = getDecryptedPasswordForTiedProfile(userId);
- } catch (FileNotFoundException e) {
- Slog.i(TAG, "Child profile key not found");
- } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
- | NoSuchAlgorithmException | NoSuchPaddingException
- | InvalidAlgorithmParameterException | IllegalBlockSizeException
- | BadPaddingException | CertificateException | IOException e) {
- Slog.e(TAG, "Failed to decrypt child profile key", e);
- }
- }
- long handle = getSyntheticPasswordHandleLocked(userId);
- AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
- getGateKeeperService(), handle, savedCredential, userId, null);
- VerifyCredentialResponse response = authResult.gkResponse;
- AuthenticationToken auth = authResult.authToken;
-
- if (auth == null) {
- if (response == null
- || response.getResponseCode() == VerifyCredentialResponse.RESPONSE_ERROR) {
- Slog.w(TAG, "Failed to enroll: incorrect credential.");
- return false;
- }
- if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
- Slog.w(TAG, "Failed to enroll: rate limit exceeded.");
- return false;
- }
- // Should not be reachable, but just in case.
- throw new IllegalStateException("password change failed");
- }
-
- onAuthTokenKnownForUser(userId, auth);
- setLockCredentialWithAuthTokenLocked(credential, auth, userId);
- mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId);
- sendCredentialsOnChangeIfRequired(credential, userId, isLockTiedToParent);
- return true;
- }
-
- /**
* Returns a fixed pseudorandom byte string derived from the user's synthetic password.
* This is used to salt the password history hash to protect the hash against offline
* bruteforcing, since rederiving this value requires a successful authentication.
@@ -3244,20 +2998,18 @@
// the token can then be activated immediately.
AuthenticationToken auth = null;
if (!isUserSecure(userId)) {
- if (shouldMigrateToSyntheticPasswordLocked(userId)) {
- auth = initializeSyntheticPasswordLocked(
- /* credentialHash */ null, LockscreenCredential.createNone(), userId);
- } else /* isSyntheticPasswordBasedCredentialLocked(userId) */ {
- long pwdHandle = getSyntheticPasswordHandleLocked(userId);
+ long handle = getSyntheticPasswordHandleLocked(userId);
+ if (handle == SyntheticPasswordManager.DEFAULT_HANDLE) {
+ auth = initializeSyntheticPasswordLocked(LockscreenCredential.createNone(),
+ userId);
+ } else {
auth = mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(),
- pwdHandle, LockscreenCredential.createNone(), userId, null).authToken;
+ handle, LockscreenCredential.createNone(), userId, null).authToken;
}
}
- if (isSyntheticPasswordBasedCredentialLocked(userId)) {
- disableEscrowTokenOnNonManagedDevicesIfNeeded(userId);
- if (!mSpManager.hasEscrowData(userId)) {
- throw new SecurityException("Escrow token is disabled on the current user");
- }
+ disableEscrowTokenOnNonManagedDevicesIfNeeded(userId);
+ if (!mSpManager.hasEscrowData(userId)) {
+ throw new SecurityException("Escrow token is disabled on the current user");
}
long handle = mSpManager.createTokenBasedSyntheticPassword(token, type, userId,
callback);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index f9db5cf..9ddf1ef 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -85,8 +85,6 @@
};
private static final String SYSTEM_DIRECTORY = "/system/";
- private static final String LOCK_PATTERN_FILE = "gatekeeper.pattern.key";
- private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
private static final String REBOOT_ESCROW_FILE = "reboot.escrow.key";
@@ -248,38 +246,6 @@
}
cursor.close();
}
-
- // Populate cache by reading the password and pattern files.
- readCredentialHash(userId);
- }
-
- private CredentialHash readPasswordHashIfExists(int userId) {
- byte[] stored = readFile(getLockPasswordFilename(userId));
- if (!ArrayUtils.isEmpty(stored)) {
- return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN);
- }
- return null;
- }
-
- private CredentialHash readPatternHashIfExists(int userId) {
- byte[] stored = readFile(getLockPatternFilename(userId));
- if (!ArrayUtils.isEmpty(stored)) {
- return new CredentialHash(stored, LockPatternUtils.CREDENTIAL_TYPE_PATTERN);
- }
- return null;
- }
-
- public CredentialHash readCredentialHash(int userId) {
- CredentialHash passwordHash = readPasswordHashIfExists(userId);
- if (passwordHash != null) {
- return passwordHash;
- }
-
- CredentialHash patternHash = readPatternHashIfExists(userId);
- if (patternHash != null) {
- return patternHash;
- }
- return CredentialHash.createEmptyHash();
}
public void removeChildProfileLock(int userId) {
@@ -336,14 +302,6 @@
deleteFile(getRebootEscrowServerBlob());
}
- public boolean hasPassword(int userId) {
- return hasFile(getLockPasswordFilename(userId));
- }
-
- public boolean hasPattern(int userId) {
- return hasFile(getLockPatternFilename(userId));
- }
-
private boolean hasFile(String name) {
byte[] contents = readFile(name);
return contents != null && contents.length > 0;
@@ -429,33 +387,6 @@
}
}
- public void writeCredentialHash(CredentialHash hash, int userId) {
- byte[] patternHash = null;
- byte[] passwordHash = null;
- if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN
- || hash.type == LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
- || hash.type == LockPatternUtils.CREDENTIAL_TYPE_PIN) {
- passwordHash = hash.hash;
- } else if (hash.type == LockPatternUtils.CREDENTIAL_TYPE_PATTERN) {
- patternHash = hash.hash;
- } else {
- Preconditions.checkArgument(hash.type == LockPatternUtils.CREDENTIAL_TYPE_NONE,
- "Unknown credential type");
- }
- writeFile(getLockPasswordFilename(userId), passwordHash);
- writeFile(getLockPatternFilename(userId), patternHash);
- }
-
- @VisibleForTesting
- String getLockPatternFilename(int userId) {
- return getLockCredentialFilePathForUser(userId, LOCK_PATTERN_FILE);
- }
-
- @VisibleForTesting
- String getLockPasswordFilename(int userId) {
- return getLockCredentialFilePathForUser(userId, LOCK_PASSWORD_FILE);
- }
-
@VisibleForTesting
String getChildProfileLockFile(int userId) {
return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE);
@@ -567,12 +498,7 @@
if (parentInfo == null) {
// This user owns its lock settings files - safe to delete them
- synchronized (mFileWriteLock) {
- deleteFilesAndRemoveCache(
- getLockPasswordFilename(userId),
- getLockPatternFilename(userId),
- getRebootEscrowFile(userId));
- }
+ deleteFile(getRebootEscrowFile(userId));
} else {
// Managed profile
removeChildProfileLock(userId);
@@ -594,17 +520,6 @@
dispatchChange(this);
}
- private void deleteFilesAndRemoveCache(String... names) {
- for (String name : names) {
- File file = new File(name);
- if (file.exists()) {
- file.delete();
- mCache.putFile(name, null);
- dispatchChange(this);
- }
- }
- }
-
public void setBoolean(String key, boolean value, int userId) {
setString(key, value ? "1" : "0", userId);
}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
index 3386408..c8f1cb2 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -99,8 +99,8 @@
return outputStream.toByteArray();
}
- public static byte[] encrypt(byte[] keyBytes, byte[] personalisation, byte[] message) {
- byte[] keyHash = personalisedHash(personalisation, keyBytes);
+ public static byte[] encrypt(byte[] keyBytes, byte[] personalization, byte[] message) {
+ byte[] keyHash = personalizedHash(personalization, keyBytes);
SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
KeyProperties.KEY_ALGORITHM_AES);
try {
@@ -113,8 +113,8 @@
}
}
- public static byte[] decrypt(byte[] keyBytes, byte[] personalisation, byte[] ciphertext) {
- byte[] keyHash = personalisedHash(personalisation, keyBytes);
+ public static byte[] decrypt(byte[] keyBytes, byte[] personalization, byte[] ciphertext) {
+ byte[] keyHash = personalizedHash(personalization, keyBytes);
SecretKeySpec key = new SecretKeySpec(Arrays.copyOf(keyHash, AES_KEY_LENGTH),
KeyProperties.KEY_ALGORITHM_AES);
try {
@@ -220,17 +220,17 @@
}
}
- protected static byte[] personalisedHash(byte[] personalisation, byte[]... message) {
+ protected static byte[] personalizedHash(byte[] personalization, byte[]... message) {
try {
final int PADDING_LENGTH = 128;
MessageDigest digest = MessageDigest.getInstance("SHA-512");
- if (personalisation.length > PADDING_LENGTH) {
- throw new IllegalArgumentException("Personalisation too long");
+ if (personalization.length > PADDING_LENGTH) {
+ throw new IllegalArgumentException("Personalization too long");
}
// Personalize the hash
// Pad it to the block size of the hash function
- personalisation = Arrays.copyOf(personalisation, PADDING_LENGTH);
- digest.update(personalisation);
+ personalization = Arrays.copyOf(personalization, PADDING_LENGTH);
+ digest.update(personalization);
for (byte[] data : message) {
digest.update(data);
}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 3edbfe0..f5151c4 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -129,7 +129,7 @@
private static final int PASSWORD_TOKEN_LENGTH = 32;
private static final String TAG = "SyntheticPasswordManager";
- private static final byte[] PERSONALISATION_SECDISCARDABLE = "secdiscardable-transform".getBytes();
+ private static final byte[] PERSONALIZATION_SECDISCARDABLE = "secdiscardable-transform".getBytes();
private static final byte[] PERSONALIZATION_KEY_STORE_PASSWORD = "keystore-password".getBytes();
private static final byte[] PERSONALIZATION_USER_GK_AUTH = "user-gk-authentication".getBytes();
private static final byte[] PERSONALIZATION_SP_GK_AUTH = "sp-gk-authentication".getBytes();
@@ -138,11 +138,11 @@
private static final byte[] PERSONALIZATION_SP_SPLIT = "sp-split".getBytes();
private static final byte[] PERSONALIZATION_PASSWORD_HASH = "pw-hash".getBytes();
private static final byte[] PERSONALIZATION_E0 = "e0-encryption".getBytes();
- private static final byte[] PERSONALISATION_WEAVER_PASSWORD = "weaver-pwd".getBytes();
- private static final byte[] PERSONALISATION_WEAVER_KEY = "weaver-key".getBytes();
- private static final byte[] PERSONALISATION_WEAVER_TOKEN = "weaver-token".getBytes();
+ private static final byte[] PERSONALIZATION_WEAVER_PASSWORD = "weaver-pwd".getBytes();
+ private static final byte[] PERSONALIZATION_WEAVER_KEY = "weaver-key".getBytes();
+ private static final byte[] PERSONALIZATION_WEAVER_TOKEN = "weaver-token".getBytes();
private static final byte[] PERSONALIZATION_PASSWORD_METRICS = "password-metrics".getBytes();
- private static final byte[] PERSONALISATION_CONTEXT =
+ private static final byte[] PERSONALIZATION_CONTEXT =
"android-synthetic-password-personalization-context".getBytes();
static class AuthenticationResult {
@@ -199,9 +199,9 @@
private byte[] deriveSubkey(byte[] personalization) {
if (mVersion == SYNTHETIC_PASSWORD_VERSION_V3) {
return (new SP800Derive(mSyntheticPassword))
- .withContext(personalization, PERSONALISATION_CONTEXT);
+ .withContext(personalization, PERSONALIZATION_CONTEXT);
} else {
- return SyntheticPasswordCrypto.personalisedHash(personalization,
+ return SyntheticPasswordCrypto.personalizedHash(personalization,
mSyntheticPassword);
}
}
@@ -278,7 +278,7 @@
* AuthenticationToken.mSyntheticPassword for details on what each block means.
*/
private void recreate(byte[] escrowSplit0, byte[] escrowSplit1) {
- mSyntheticPassword = bytesToHex(SyntheticPasswordCrypto.personalisedHash(
+ mSyntheticPassword = bytesToHex(SyntheticPasswordCrypto.personalizedHash(
PERSONALIZATION_SP_SPLIT, escrowSplit0, escrowSplit1));
}
@@ -599,47 +599,19 @@
}
/**
- * Initializing a new Authentication token, possibly from an existing credential and hash.
+ * Initializes a new Authentication token for the given user.
*
- * The authentication token would bear a randomly-generated synthetic password.
+ * The authentication token will bear a randomly-generated synthetic password.
*
- * This method has the side effect of rebinding the SID of the given user to the
- * newly-generated SP.
- *
- * If the existing credential hash is non-null, the existing SID mill be migrated so
- * the synthetic password in the authentication token will produce the same SID
- * (the corresponding synthetic password handle is persisted by SyntheticPasswordManager
- * in a per-user data storage.)
- *
- * If the existing credential hash is null, it means the given user should have no SID so
- * SyntheticPasswordManager will nuke any SP handle previously persisted. In this case,
- * the supplied credential parameter is also ignored.
+ * Any existing SID for the user is cleared.
*
* Also saves the escrow information necessary to re-generate the synthetic password under
* an escrow scheme. This information can be removed with {@link #destroyEscrowData} if
* password escrow should be disabled completely on the given user.
- *
*/
- public AuthenticationToken newSyntheticPasswordAndSid(IGateKeeperService gatekeeper,
- byte[] hash, LockscreenCredential credential, int userId) {
+ AuthenticationToken newSyntheticPassword(int userId) {
+ clearSidForUser(userId);
AuthenticationToken result = AuthenticationToken.create();
- GateKeeperResponse response;
- if (hash != null) {
- try {
- response = gatekeeper.enroll(userId, hash, credential.getCredential(),
- result.deriveGkPassword());
- } catch (RemoteException e) {
- throw new IllegalStateException("Failed to enroll credential duing SP init", e);
- }
- if (response.getResponseCode() != GateKeeperResponse.RESPONSE_OK) {
- Slog.w(TAG, "Fail to migrate SID, assuming no SID, user " + userId);
- clearSidForUser(userId);
- } else {
- saveSyntheticPasswordHandle(response.getPayload(), userId);
- }
- } else {
- clearSidForUser(userId);
- }
saveEscrowData(result, userId);
return result;
}
@@ -948,7 +920,7 @@
if (isWeaverAvailable()) {
tokenData.weaverSecret = secureRandom(mWeaverConfig.valueSize);
tokenData.secdiscardableOnDisk = SyntheticPasswordCrypto.encrypt(tokenData.weaverSecret,
- PERSONALISATION_WEAVER_TOKEN, secdiscardable);
+ PERSONALIZATION_WEAVER_TOKEN, secdiscardable);
} else {
tokenData.secdiscardableOnDisk = secdiscardable;
tokenData.weaverSecret = null;
@@ -1191,7 +1163,7 @@
return result;
}
secdiscardable = SyntheticPasswordCrypto.decrypt(response.getGatekeeperHAT(),
- PERSONALISATION_WEAVER_TOKEN, secdiscardable);
+ PERSONALIZATION_WEAVER_TOKEN, secdiscardable);
}
byte[] applicationId = transformUnderSecdiscardable(token, secdiscardable);
result.authToken = unwrapSyntheticPasswordBlob(handle, type, applicationId, 0L, userId);
@@ -1357,8 +1329,8 @@
}
private byte[] transformUnderWeaverSecret(byte[] data, byte[] secret) {
- byte[] weaverSecret = SyntheticPasswordCrypto.personalisedHash(
- PERSONALISATION_WEAVER_PASSWORD, secret);
+ byte[] weaverSecret = SyntheticPasswordCrypto.personalizedHash(
+ PERSONALIZATION_WEAVER_PASSWORD, secret);
byte[] result = new byte[data.length + weaverSecret.length];
System.arraycopy(data, 0, result, 0, data.length);
System.arraycopy(weaverSecret, 0, result, data.length, weaverSecret.length);
@@ -1366,8 +1338,8 @@
}
private byte[] transformUnderSecdiscardable(byte[] data, byte[] rawSecdiscardable) {
- byte[] secdiscardable = SyntheticPasswordCrypto.personalisedHash(
- PERSONALISATION_SECDISCARDABLE, rawSecdiscardable);
+ byte[] secdiscardable = SyntheticPasswordCrypto.personalizedHash(
+ PERSONALIZATION_SECDISCARDABLE, rawSecdiscardable);
byte[] result = new byte[data.length + secdiscardable.length];
System.arraycopy(data, 0, result, 0, data.length);
System.arraycopy(secdiscardable, 0, result, data.length, secdiscardable.length);
@@ -1487,11 +1459,11 @@
}
private byte[] passwordTokenToGkInput(byte[] token) {
- return SyntheticPasswordCrypto.personalisedHash(PERSONALIZATION_USER_GK_AUTH, token);
+ return SyntheticPasswordCrypto.personalizedHash(PERSONALIZATION_USER_GK_AUTH, token);
}
private byte[] passwordTokenToWeaverKey(byte[] token) {
- byte[] key = SyntheticPasswordCrypto.personalisedHash(PERSONALISATION_WEAVER_KEY, token);
+ byte[] key = SyntheticPasswordCrypto.personalizedHash(PERSONALIZATION_WEAVER_KEY, token);
if (key.length < mWeaverConfig.keySize) {
throw new IllegalArgumentException("weaver key length too small");
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index de9102a..6135fe8 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -340,7 +340,8 @@
int newRuleInstanceCount = getCurrentInstanceCount(automaticZenRule.getOwner())
+ getCurrentInstanceCount(automaticZenRule.getConfigurationActivity())
+ 1;
- if (newRuleInstanceCount > RULE_LIMIT_PER_PACKAGE
+ int newPackageRuleCount = getPackageRuleCount(pkg) + 1;
+ if (newPackageRuleCount > RULE_LIMIT_PER_PACKAGE
|| (ruleInstanceLimit > 0 && ruleInstanceLimit < newRuleInstanceCount)) {
throw new IllegalArgumentException("Rule instance limit exceeded");
}
@@ -521,6 +522,23 @@
return count;
}
+ // Equivalent method to getCurrentInstanceCount, but for all rules associated with a specific
+ // package rather than a condition provider service or activity.
+ private int getPackageRuleCount(String pkg) {
+ if (pkg == null) {
+ return 0;
+ }
+ int count = 0;
+ synchronized (mConfig) {
+ for (ZenRule rule : mConfig.automaticRules.values()) {
+ if (pkg.equals(rule.getPkg())) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
public boolean canManageAutomaticZenRule(ZenRule rule) {
final int callingUid = Binder.getCallingUid();
if (callingUid == 0 || callingUid == Process.SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index b0b9e61c..075fb47 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -163,7 +163,8 @@
return PackageManager.DELETE_FAILED_INTERNAL_ERROR;
}
- if (PackageManagerServiceUtils.isSystemApp(uninstalledPs)) {
+ if (PackageManagerServiceUtils.isUpdatedSystemApp(uninstalledPs)
+ && ((deleteFlags & PackageManager.DELETE_SYSTEM_APP) == 0)) {
UserInfo userInfo = mUserManagerInternal.getUserInfo(userId);
if (userInfo == null || (!userInfo.isAdmin() && !mUserManagerInternal.getUserInfo(
mUserManagerInternal.getProfileParentId(userId)).isAdmin())) {
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 622de57..270891f 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -56,6 +56,10 @@
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__NOT_LETTERBOXED_POSITION;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
import static com.android.internal.util.FrameworkStatsLog.CAMERA_COMPAT_CONTROL_EVENT_REPORTED__EVENT__APPEARED_APPLY_TREATMENT;
@@ -1376,7 +1380,7 @@
return;
}
- logAppCompatStateInternal(activity, state, packageUid, compatStateInfo);
+ logAppCompatStateInternal(activity, state, compatStateInfo);
}
/**
@@ -1416,18 +1420,61 @@
}
}
if (activityToLog != null && stateToLog != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE) {
- logAppCompatStateInternal(activityToLog, stateToLog, packageUid, compatStateInfo);
+ logAppCompatStateInternal(activityToLog, stateToLog, compatStateInfo);
}
}
+ private static boolean isAppCompateStateChangedToLetterboxed(int state) {
+ return state == APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO
+ || state == APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION
+ || state == APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
+ }
+
private void logAppCompatStateInternal(@NonNull ActivityRecord activity, int state,
- int packageUid, PackageCompatStateInfo compatStateInfo) {
+ PackageCompatStateInfo compatStateInfo) {
compatStateInfo.mLastLoggedState = state;
compatStateInfo.mLastLoggedActivity = activity;
- FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED, packageUid, state);
+ int packageUid = activity.info.applicationInfo.uid;
+
+ int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__NOT_LETTERBOXED_POSITION;
+ if (isAppCompateStateChangedToLetterboxed(state)) {
+ positionToLog = activity.mLetterboxUiController.getLetterboxPositionForLogging();
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED,
+ packageUid, state, positionToLog);
if (DEBUG_METRICS) {
- Slog.i(TAG, String.format("APP_COMPAT_STATE_CHANGED(%s, %s)", packageUid, state));
+ Slog.i(TAG, String.format("APP_COMPAT_STATE_CHANGED(%s, %s, %s)",
+ packageUid, state, positionToLog));
+ }
+ }
+
+ /**
+ * Logs the changing of the letterbox position along with its package UID
+ */
+ void logLetterboxPositionChange(@NonNull ActivityRecord activity, int position) {
+ int packageUid = activity.info.applicationInfo.uid;
+ FrameworkStatsLog.write(FrameworkStatsLog.LETTERBOX_POSITION_CHANGED, packageUid, position);
+
+ if (!mPackageUidToCompatStateInfo.contains(packageUid)) {
+ // There is no last logged activity for this packageUid so we should not log the
+ // position change as we can only log the position change for the current activity
+ return;
+ }
+ final PackageCompatStateInfo compatStateInfo = mPackageUidToCompatStateInfo.get(packageUid);
+ final ActivityRecord lastLoggedActivity = compatStateInfo.mLastLoggedActivity;
+ if (activity != lastLoggedActivity) {
+ // Only log the position change for the current activity to be consistent with
+ // findAppCompatStateToLog and ensure that metrics for the state changes are computed
+ // correctly
+ return;
+ }
+ int state = activity.getAppCompatState();
+ logAppCompatStateInternal(activity, state, compatStateInfo);
+
+ if (DEBUG_METRICS) {
+ Slog.i(TAG, String.format("LETTERBOX_POSITION_CHANGED(%s, %s)",
+ packageUid, position));
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 406b9ee..85f6f0c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4752,6 +4752,8 @@
if (mPendingRemoteAnimation != null) {
mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(
mPendingRemoteAnimation);
+ mTransitionController.setStatusBarTransitionDelay(
+ mPendingRemoteAnimation.getStatusBarTransitionDelay());
} else {
if (mPendingOptions == null
|| mPendingOptions.getAnimationType() == ANIM_SCENE_TRANSITION) {
@@ -7813,11 +7815,15 @@
newParentConfiguration.windowConfiguration.getWindowingMode();
final boolean isFixedOrientationLetterboxAllowed =
parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
- || parentWindowingMode == WINDOWING_MODE_FULLSCREEN;
+ || parentWindowingMode == WINDOWING_MODE_FULLSCREEN
+ // Switching from PiP to fullscreen.
+ || (parentWindowingMode == WINDOWING_MODE_PINNED
+ && resolvedConfig.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN);
// TODO(b/181207944): Consider removing the if condition and always run
// resolveFixedOrientationConfiguration() since this should be applied for all cases.
if (isFixedOrientationLetterboxAllowed) {
- resolveFixedOrientationConfiguration(newParentConfiguration, parentWindowingMode);
+ resolveFixedOrientationConfiguration(newParentConfiguration);
}
if (mCompatDisplayInsets != null) {
@@ -8109,8 +8115,7 @@
* <p>If letterboxed due to fixed orientation then aspect ratio restrictions are also applied
* in this method.
*/
- private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig,
- int windowingMode) {
+ private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) {
mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
mIsEligibleForFixedOrientationLetterbox = false;
final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
@@ -8130,11 +8135,6 @@
if (organizedTf != null && !organizedTf.fillsParent()) {
return;
}
- if (windowingMode == WINDOWING_MODE_PINNED) {
- // PiP bounds have higher priority than the requested orientation. Otherwise the
- // activity may be squeezed into a small piece.
- return;
- }
final Rect resolvedBounds =
getResolvedOverrideConfiguration().windowConfiguration.getBounds();
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 9637aca4c..c92cf33 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -79,6 +79,10 @@
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
+import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import android.annotation.NonNull;
@@ -132,6 +136,7 @@
import com.android.server.uri.NeededUriGrants;
import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
import com.android.server.wm.LaunchParamsController.LaunchParams;
+import com.android.server.wm.TaskFragment.EmbeddingCheckResult;
import java.io.PrintWriter;
import java.text.DateFormat;
@@ -2074,24 +2079,6 @@
}
}
- if (mInTaskFragment != null && !canEmbedActivity(mInTaskFragment, r, newTask, targetTask)) {
- final StringBuilder errorMsg = new StringBuilder("Permission denied: Cannot embed " + r
- + " to " + mInTaskFragment.getTask() + ". newTask=" + newTask + ", targetTask= "
- + targetTask);
- if (newTask && isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE,
- LAUNCH_SINGLE_INSTANCE_PER_TASK, LAUNCH_SINGLE_TASK)) {
- errorMsg.append("\nActivity tries to launch on a new task because the launch mode"
- + " is " + launchModeToString(mLaunchMode));
- } else if (newTask && (mLaunchFlags & (FLAG_ACTIVITY_NEW_DOCUMENT
- | FLAG_ACTIVITY_NEW_TASK)) != 0) {
- errorMsg.append("\nActivity tries to launch on a new task because the launch flags"
- + " contains FLAG_ACTIVITY_NEW_DOCUMENT or FLAG_ACTIVITY_NEW_TASK. "
- + "mLaunchFlag=" + mLaunchFlags);
- }
- Slog.e(TAG, errorMsg.toString());
- return START_PERMISSION_DENIED;
- }
-
// Do not start the activity if target display's DWPC does not allow it.
// We can't return fatal error code here because it will crash the caller of
// startActivity() if they don't catch the exception. We don't expect 3P apps to make
@@ -2118,19 +2105,21 @@
}
/**
- * Return {@code true} if an activity can be embedded to the TaskFragment.
+ * Returns whether embedding of {@code starting} is allowed.
+ *
* @param taskFragment the TaskFragment for embedding.
* @param starting the starting activity.
- * @param newTask whether the starting activity is going to be launched on a new task.
* @param targetTask the target task for launching activity, which could be different from
* the one who hosting the embedding.
*/
- private boolean canEmbedActivity(@NonNull TaskFragment taskFragment,
- @NonNull ActivityRecord starting, boolean newTask, Task targetTask) {
+ @VisibleForTesting
+ @EmbeddingCheckResult
+ static int canEmbedActivity(@NonNull TaskFragment taskFragment,
+ @NonNull ActivityRecord starting, @NonNull Task targetTask) {
final Task hostTask = taskFragment.getTask();
// Not allowed embedding a separate task or without host task.
- if (hostTask == null || newTask || targetTask != hostTask) {
- return false;
+ if (hostTask == null || targetTask != hostTask) {
+ return EMBEDDING_DISALLOWED_NEW_TASK;
}
return taskFragment.isAllowedToEmbedActivity(starting);
@@ -2970,19 +2959,16 @@
mIntentDelivered = true;
}
+ /** Places {@link #mStartActivity} in {@code task} or an embedded {@link TaskFragment}. */
private void addOrReparentStartingActivity(@NonNull Task task, String reason) {
TaskFragment newParent = task;
if (mInTaskFragment != null) {
- // TODO(b/234351413): remove remaining embedded Task logic.
- // mInTaskFragment is created and added to the leaf task by task fragment organizer's
- // request. If the task was resolved and different than mInTaskFragment, reparent the
- // task to mInTaskFragment for embedding.
- if (mInTaskFragment.getTask() != task) {
- if (shouldReparentInTaskFragment(task)) {
- task.reparent(mInTaskFragment, POSITION_TOP);
- }
- } else {
+ int embeddingCheckResult = canEmbedActivity(mInTaskFragment, mStartActivity, task);
+ if (embeddingCheckResult == EMBEDDING_ALLOWED) {
newParent = mInTaskFragment;
+ } else {
+ // Start mStartActivity to task instead if it can't be embedded to mInTaskFragment.
+ sendCanNotEmbedActivityError(mInTaskFragment, embeddingCheckResult);
}
} else {
TaskFragment candidateTf = mAddingToTaskFragment != null ? mAddingToTaskFragment : null;
@@ -2994,20 +2980,12 @@
}
}
if (candidateTf != null && candidateTf.isEmbedded()
- && canEmbedActivity(candidateTf, mStartActivity, false /* newTask */, task)) {
+ && canEmbedActivity(candidateTf, mStartActivity, task) == EMBEDDING_ALLOWED) {
// Use the embedded TaskFragment of the top activity as the new parent if the
// activity can be embedded.
newParent = candidateTf;
}
}
- // Start Activity to the Task if mStartActivity's min dimensions are not satisfied.
- if (newParent.isEmbedded() && newParent.smallerThanMinDimension(mStartActivity)) {
- reason += " - MinimumDimensionViolation";
- mService.mWindowOrganizerController.sendMinimumDimensionViolation(
- newParent, mStartActivity.getMinDimensions(), mRequest.errorCallbackToken,
- reason);
- newParent = task;
- }
if (mStartActivity.getTaskFragment() == null
|| mStartActivity.getTaskFragment() == newParent) {
newParent.addChild(mStartActivity, POSITION_TOP);
@@ -3016,16 +2994,41 @@
}
}
- private boolean shouldReparentInTaskFragment(Task task) {
- // The task has not been embedded. We should reparent the task to TaskFragment.
- if (!task.isEmbedded()) {
- return true;
+ /**
+ * Notifies the client side that {@link #mStartActivity} cannot be embedded to
+ * {@code taskFragment}.
+ */
+ private void sendCanNotEmbedActivityError(TaskFragment taskFragment,
+ @EmbeddingCheckResult int result) {
+ final String errMsg;
+ switch(result) {
+ case EMBEDDING_DISALLOWED_NEW_TASK: {
+ errMsg = "Cannot embed " + mStartActivity + " that launched on another task"
+ + ",mLaunchMode=" + launchModeToString(mLaunchMode)
+ + ",mLaunchFlag=" + Integer.toHexString(mLaunchFlags);
+ break;
+ }
+ case EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION: {
+ errMsg = "Cannot embed " + mStartActivity
+ + ". TaskFragment's bounds:" + taskFragment.getBounds()
+ + ", minimum dimensions:" + mStartActivity.getMinDimensions();
+ break;
+ }
+ case EMBEDDING_DISALLOWED_UNTRUSTED_HOST: {
+ errMsg = "The app:" + mCallingUid + "is not trusted to " + mStartActivity;
+ break;
+ }
+ default:
+ errMsg = "Unhandled embed result:" + result;
}
- WindowContainer<?> parent = task.getParent();
- // If the Activity is going to launch on top of embedded Task in the same TaskFragment,
- // we don't need to reparent the Task. Otherwise, the embedded Task should reparent to
- // another TaskFragment.
- return parent.asTaskFragment() != mInTaskFragment;
+ if (taskFragment.isOrganized()) {
+ mService.mWindowOrganizerController.sendTaskFragmentOperationFailure(
+ taskFragment.getTaskFragmentOrganizer(), mRequest.errorCallbackToken,
+ new SecurityException(errMsg));
+ } else {
+ // If the taskFragment is not organized, just dump error message as warning logs.
+ Slog.w(TAG, errMsg);
+ }
}
private int adjustLaunchFlagsToDocumentMode(ActivityRecord r, boolean launchSingleInstance,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a790ca5..6de1b95 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3660,7 +3660,8 @@
}
@Override
- public TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution) {
+ public TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution,
+ boolean takeSnapshotIfNeeded) {
mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
final long ident = Binder.clearCallingIdentity();
try {
@@ -3674,8 +3675,12 @@
}
}
// Don't call this while holding the lock as this operation might hit the disk.
- return mWindowManager.mTaskSnapshotController.getSnapshot(taskId, task.mUserId,
- true /* restoreFromDisk */, isLowResolution);
+ TaskSnapshot taskSnapshot = mWindowManager.mTaskSnapshotController.getSnapshot(taskId,
+ task.mUserId, true /* restoreFromDisk */, isLowResolution);
+ if (taskSnapshot == null && takeSnapshotIfNeeded) {
+ taskSnapshot = takeTaskSnapshot(taskId);
+ }
+ return taskSnapshot;
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -6648,7 +6653,8 @@
@Override
public TaskSnapshot getTaskSnapshotBlocking(
int taskId, boolean isLowResolution) {
- return ActivityTaskManagerService.this.getTaskSnapshot(taskId, isLowResolution);
+ return ActivityTaskManagerService.this.getTaskSnapshot(taskId, isLowResolution,
+ false /* takeSnapshotIfNeeded */);
}
@Override
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index e3de18b..2e1d3b1 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -357,7 +357,7 @@
* or seamless transformation in a rotated display.
*/
boolean shouldFreezeInsetsPosition(WindowState w) {
- return mTransitionOp == OP_APP_SWITCH && w.mTransitionController.inTransition()
+ return mTransitionOp != OP_LEGACY && w.mTransitionController.inTransition()
&& isTargetToken(w.mToken);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 68f6015..6619f98 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1611,7 +1611,8 @@
if (mTransitionController.useShellTransitionsRotation()) {
return ROTATION_UNDEFINED;
}
- if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM) {
+ if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM
+ || getIgnoreOrientationRequest()) {
return ROTATION_UNDEFINED;
}
if (r.mOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 08715b1..91b2fb6 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -687,6 +687,24 @@
}
}
+ /*
+ * Gets the horizontal position of the letterboxed app window when horizontal reachability is
+ * enabled.
+ */
+ @LetterboxHorizontalReachabilityPosition
+ int getLetterboxPositionForHorizontalReachability() {
+ return mLetterboxPositionForHorizontalReachability;
+ }
+
+ /*
+ * Gets the vertical position of the letterboxed app window when vertical reachability is
+ * enabled.
+ */
+ @LetterboxVerticalReachabilityPosition
+ int getLetterboxPositionForVerticalReachability() {
+ return mLetterboxPositionForVerticalReachability;
+ }
+
/** Returns a string representing the given {@link LetterboxHorizontalReachabilityPosition}. */
static String letterboxHorizontalReachabilityPositionToString(
@LetterboxHorizontalReachabilityPosition int position) {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index f849d28..d652767 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -21,6 +21,20 @@
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
import static com.android.server.wm.ActivityRecord.computeAspectRatio;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -28,6 +42,12 @@
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTypeToString;
@@ -259,12 +279,26 @@
return;
}
+ int letterboxPositionForHorizontalReachability = mLetterboxConfiguration
+ .getLetterboxPositionForHorizontalReachability();
if (mLetterbox.getInnerFrame().left > x) {
// Moving to the next stop on the left side of the app window: right > center > left.
mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextLeftStop();
+ int changeToLog =
+ letterboxPositionForHorizontalReachability
+ == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
+ logLetterboxPositionChange(changeToLog);
} else if (mLetterbox.getInnerFrame().right < x) {
// Moving to the next stop on the right side of the app window: left > center > right.
mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop();
+ int changeToLog =
+ letterboxPositionForHorizontalReachability
+ == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
+ logLetterboxPositionChange(changeToLog);
}
// TODO(197549949): Add animation for transition.
@@ -280,13 +314,26 @@
// Only react to clicks at the top and bottom of the letterboxed app window.
return;
}
-
+ int letterboxPositionForVerticalReachability = mLetterboxConfiguration
+ .getLetterboxPositionForVerticalReachability();
if (mLetterbox.getInnerFrame().top > y) {
// Moving to the next stop on the top side of the app window: bottom > center > top.
mLetterboxConfiguration.movePositionForVerticalReachabilityToNextTopStop();
+ int changeToLog =
+ letterboxPositionForVerticalReachability
+ == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
+ logLetterboxPositionChange(changeToLog);
} else if (mLetterbox.getInnerFrame().bottom < y) {
// Moving to the next stop on the bottom side of the app window: top > center > bottom.
mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop();
+ int changeToLog =
+ letterboxPositionForVerticalReachability
+ == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
+ logLetterboxPositionChange(changeToLog);
}
// TODO(197549949): Add animation for transition.
@@ -577,4 +624,63 @@
return "UNKNOWN_REASON";
}
+ private int letterboxHorizontalReachabilityPositionToLetterboxPosition(
+ @LetterboxConfiguration.LetterboxHorizontalReachabilityPosition int position) {
+ switch (position) {
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox horizontal reachability position type: "
+ + position);
+ }
+ }
+
+ private int letterboxVerticalReachabilityPositionToLetterboxPosition(
+ @LetterboxConfiguration.LetterboxVerticalReachabilityPosition int position) {
+ switch (position) {
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox vertical reachability position type: "
+ + position);
+ }
+ }
+
+ int getLetterboxPositionForLogging() {
+ int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+ if (isHorizontalReachabilityEnabled()) {
+ int letterboxPositionForHorizontalReachability = getLetterboxConfiguration()
+ .getLetterboxPositionForHorizontalReachability();
+ positionToLog = letterboxHorizontalReachabilityPositionToLetterboxPosition(
+ letterboxPositionForHorizontalReachability);
+ } else if (isVerticalReachabilityEnabled()) {
+ int letterboxPositionForVerticalReachability = getLetterboxConfiguration()
+ .getLetterboxPositionForVerticalReachability();
+ positionToLog = letterboxVerticalReachabilityPositionToLetterboxPosition(
+ letterboxPositionForVerticalReachability);
+ }
+ return positionToLog;
+ }
+
+ private LetterboxConfiguration getLetterboxConfiguration() {
+ return mLetterboxConfiguration;
+ }
+
+ /**
+ * Logs letterbox position changes via {@link ActivityMetricsLogger#logLetterboxPositionChange}.
+ */
+ private void logLetterboxPositionChange(int letterboxPositionChange) {
+ mActivityRecord.mTaskSupervisor.getActivityMetricsLogger()
+ .logLetterboxPositionChange(mActivityRecord, letterboxPositionChange);
+ }
}
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index baa31a0..927604e 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -26,7 +26,9 @@
import static android.app.WindowConfiguration.activityTypeToString;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -249,8 +251,25 @@
}
// Check if the caller is allowed to launch on the specified display area.
final WindowContainerToken daToken = options.getLaunchTaskDisplayArea();
- final TaskDisplayArea taskDisplayArea = daToken != null
+ TaskDisplayArea taskDisplayArea = daToken != null
? (TaskDisplayArea) WindowContainer.fromBinder(daToken.asBinder()) : null;
+
+ // If we do not have a task display area token, check if the launch task display area
+ // feature id is specified.
+ if (taskDisplayArea == null) {
+ final int launchTaskDisplayAreaFeatureId = options.getLaunchTaskDisplayAreaFeatureId();
+ if (launchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
+ final int launchDisplayId = options.getLaunchDisplayId() == INVALID_DISPLAY
+ ? DEFAULT_DISPLAY : options.getLaunchDisplayId();
+ final DisplayContent dc = supervisor.mRootWindowContainer
+ .getDisplayContent(launchDisplayId);
+ if (dc != null) {
+ taskDisplayArea = dc.getItemFromTaskDisplayAreas(tda ->
+ tda.mFeatureId == launchTaskDisplayAreaFeatureId ? tda : null);
+ }
+ }
+ }
+
if (aInfo != null && taskDisplayArea != null
&& !supervisor.isCallerAllowedToLaunchOnTaskDisplayArea(callingPid, callingUid,
taskDisplayArea, aInfo)) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index a1c22d6..3e6546e 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -140,6 +140,45 @@
static final boolean SHOW_APP_STARTING_PREVIEW = true;
/**
+ * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
+ * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
+ * indicate that an Activity can be embedded successfully.
+ */
+ static final int EMBEDDING_ALLOWED = 0;
+ /**
+ * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
+ * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
+ * indicate that an Activity can't be embedded because either the Activity does not allow
+ * untrusted embedding, and the embedding host app is not trusted.
+ */
+ static final int EMBEDDING_DISALLOWED_UNTRUSTED_HOST = 1;
+ /**
+ * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
+ * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
+ * indicate that an Activity can't be embedded because this taskFragment's bounds are
+ * {@link #smallerThanMinDimension(ActivityRecord)}.
+ */
+ static final int EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION = 2;
+ /**
+ * An embedding check result of
+ * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
+ * indicate that an Activity can't be embedded because the Activity is started on a new task.
+ */
+ static final int EMBEDDING_DISALLOWED_NEW_TASK = 3;
+
+ /**
+ * Embedding check results of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
+ * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}.
+ */
+ @IntDef(prefix = {"EMBEDDING_"}, value = {
+ EMBEDDING_ALLOWED,
+ EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
+ EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
+ EMBEDDING_DISALLOWED_NEW_TASK,
+ })
+ @interface EmbeddingCheckResult {}
+
+ /**
* Indicate that the minimal width/height should use the default value.
*
* @see #mMinWidth
@@ -509,20 +548,29 @@
return false;
}
- boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a) {
+ @EmbeddingCheckResult
+ int isAllowedToEmbedActivity(@NonNull ActivityRecord a) {
return isAllowedToEmbedActivity(a, mTaskFragmentOrganizerUid);
}
/**
* Checks if the organized task fragment is allowed to have the specified activity, which is
- * allowed if an activity allows embedding in untrusted mode, or if the trusted mode can be
- * enabled.
- * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord)
+ * allowed if an activity allows embedding in untrusted mode, if the trusted mode can be
+ * enabled, or if the organized task fragment bounds are not
+ * {@link #smallerThanMinDimension(ActivityRecord)}.
+ *
* @param uid uid of the TaskFragment organizer.
+ * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord)
*/
- boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a, int uid) {
- return isAllowedToEmbedActivityInUntrustedMode(a)
- || isAllowedToEmbedActivityInTrustedMode(a, uid);
+ @EmbeddingCheckResult
+ int isAllowedToEmbedActivity(@NonNull ActivityRecord a, int uid) {
+ if (!isAllowedToEmbedActivityInUntrustedMode(a)
+ && !isAllowedToEmbedActivityInTrustedMode(a, uid)) {
+ return EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
+ } else if (smallerThanMinDimension(a)) {
+ return EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
+ }
+ return EMBEDDING_ALLOWED;
}
boolean smallerThanMinDimension(@NonNull ActivityRecord activity) {
@@ -539,9 +587,8 @@
}
final int minWidth = minDimensions.x;
final int minHeight = minDimensions.y;
- final boolean smaller = taskFragBounds.width() < minWidth
+ return taskFragBounds.width() < minWidth
|| taskFragBounds.height() < minHeight;
- return smaller;
}
/**
@@ -598,7 +645,7 @@
// The system is trusted to embed other apps securely and for all users.
return UserHandle.getAppId(uid) == SYSTEM_UID
// Activities from the same UID can be embedded freely by the host.
- || uid == a.getUid();
+ || a.isUid(uid);
}
/**
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 9aff23d..392d4c2 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -19,6 +19,7 @@
import static android.window.TaskFragmentOrganizer.putExceptionInBundle;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer;
import android.annotation.IntDef;
@@ -235,7 +236,7 @@
+ " is not in a task belong to the organizer app.");
return;
}
- if (!task.isAllowedToEmbedActivity(activity, mOrganizerUid)) {
+ if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) {
Slog.d(TAG, "Reparent activity=" + activity.token
+ " is not allowed to be embedded.");
return;
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 9bb0271..4141156 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -35,6 +35,7 @@
import static android.util.DisplayMetrics.DENSITY_DEFAULT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import static com.android.server.wm.ActivityStarter.Request;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
@@ -307,7 +308,8 @@
TaskDisplayArea taskDisplayArea = suggestedDisplayArea;
// If launch task display area is set in options we should just use it. We assume the
// suggestedDisplayArea has the right one in this case.
- if (options == null || options.getLaunchTaskDisplayArea() == null) {
+ if (options == null || (options.getLaunchTaskDisplayArea() == null
+ && options.getLaunchTaskDisplayAreaFeatureId() == FEATURE_UNDEFINED)) {
final int activityType =
mSupervisor.mRootWindowContainer.resolveActivityType(root, options, task);
display.forAllTaskDisplayAreas(displayArea -> {
@@ -391,7 +393,22 @@
if (optionLaunchTaskDisplayAreaToken != null) {
taskDisplayArea = (TaskDisplayArea) WindowContainer.fromBinder(
optionLaunchTaskDisplayAreaToken.asBinder());
- if (DEBUG) appendLog("display-area-from-option=" + taskDisplayArea);
+ if (DEBUG) appendLog("display-area-token-from-option=" + taskDisplayArea);
+ }
+
+ if (taskDisplayArea == null && options != null) {
+ final int launchTaskDisplayAreaFeatureId = options.getLaunchTaskDisplayAreaFeatureId();
+ if (launchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
+ final int launchDisplayId = options.getLaunchDisplayId() == INVALID_DISPLAY
+ ? DEFAULT_DISPLAY : options.getLaunchDisplayId();
+ final DisplayContent dc = mSupervisor.mRootWindowContainer
+ .getDisplayContent(launchDisplayId);
+ if (dc != null) {
+ taskDisplayArea = dc.getItemFromTaskDisplayAreas(tda ->
+ tda.mFeatureId == launchTaskDisplayAreaFeatureId ? tda : null);
+ if (DEBUG) appendLog("display-area-feature-from-option=" + taskDisplayArea);
+ }
+ }
}
// If task display area is not specified in options - try display id
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 9da78e1..27d181f 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -36,6 +36,7 @@
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
+import android.util.ArrayMap;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
@@ -56,7 +57,6 @@
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.WeakHashMap;
@@ -94,7 +94,7 @@
* lifecycle order since we may be updating the visibility of task surface controls in a pending
* transaction before they are presented to the task org.
*/
- private class TaskOrganizerCallbacks {
+ private static class TaskOrganizerCallbacks {
final ITaskOrganizer mTaskOrganizer;
final Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
@@ -123,7 +123,6 @@
}
}
-
void onTaskVanished(Task task) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task vanished taskId=%d", task.mTaskId);
final RunningTaskInfo taskInfo = task.getTaskInfo();
@@ -173,11 +172,160 @@
}
}
+ /**
+ * Maintains a list of all the pending events for a given {@link android.window.TaskOrganizer}
+ */
+ static final class TaskOrganizerPendingEventsQueue {
+ private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>();
+ private final TaskOrganizerState mOrganizerState;
+ private RunningTaskInfo mTmpTaskInfo;
+ // Pending task events due to layout deferred.
+ private final ArrayList<PendingTaskEvent> mPendingTaskEvents = new ArrayList<>();
+
+ TaskOrganizerPendingEventsQueue(TaskOrganizerState taskOrganizerState) {
+ mOrganizerState = taskOrganizerState;
+ }
+
+ @VisibleForTesting
+ public ArrayList<PendingTaskEvent> getPendingEventList() {
+ return mPendingTaskEvents;
+ }
+
+ int numPendingTaskEvents() {
+ return mPendingTaskEvents.size();
+ }
+
+ void clearPendingTaskEvents() {
+ mPendingTaskEvents.clear();
+ }
+
+ void addPendingTaskEvent(PendingTaskEvent event) {
+ mPendingTaskEvents.add(event);
+ }
+
+ void removePendingTaskEvent(PendingTaskEvent event) {
+ mPendingTaskEvents.remove(event);
+ }
+
+ /**
+ * Removes all the pending task events for the given {@code task}.
+ *
+ * @param task
+ * @return true if a {@link PendingTaskEvent#EVENT_APPEARED} is still pending for the given
+ * {code task}.
+ */
+ boolean removePendingTaskEvents(Task task) {
+ boolean foundPendingAppearedEvents = false;
+ for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
+ PendingTaskEvent entry = mPendingTaskEvents.get(i);
+ if (task.mTaskId == entry.mTask.mTaskId) {
+ // This task is vanished so remove all pending event of it.
+ mPendingTaskEvents.remove(i);
+
+ if (entry.mEventType == PendingTaskEvent.EVENT_APPEARED) {
+ foundPendingAppearedEvents = true;
+ }
+ }
+ }
+ return foundPendingAppearedEvents;
+ }
+
+ @Nullable
+ private PendingTaskEvent getPendingTaskEvent(Task task, int type) {
+ for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
+ PendingTaskEvent entry = mPendingTaskEvents.get(i);
+ if (task.mTaskId == entry.mTask.mTaskId && type == entry.mEventType) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ @VisibleForTesting
+ @Nullable
+ PendingTaskEvent getPendingLifecycleTaskEvent(Task task) {
+ for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
+ PendingTaskEvent entry = mPendingTaskEvents.get(i);
+ if (task.mTaskId == entry.mTask.mTaskId && entry.isLifecycleEvent()) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ void dispatchPendingEvents() {
+ if (mPendingTaskEvents.isEmpty()) {
+ return;
+ }
+ for (int i = 0, n = mPendingTaskEvents.size(); i < n; i++) {
+ dispatchPendingEvent(mPendingTaskEvents.get(i));
+ }
+ mPendingTaskEvents.clear();
+ }
+
+ private void dispatchPendingEvent(PendingTaskEvent event) {
+ final Task task = event.mTask;
+ switch (event.mEventType) {
+ case PendingTaskEvent.EVENT_APPEARED:
+ if (task.taskAppearedReady()) {
+ mOrganizerState.mOrganizer.onTaskAppeared(task);
+ }
+ break;
+ case PendingTaskEvent.EVENT_VANISHED:
+ mOrganizerState.mOrganizer.onTaskVanished(task);
+ mLastSentTaskInfos.remove(task);
+ break;
+ case PendingTaskEvent.EVENT_INFO_CHANGED:
+ dispatchTaskInfoChanged(event.mTask, event.mForce);
+ break;
+ case PendingTaskEvent.EVENT_ROOT_BACK_PRESSED:
+ mOrganizerState.mOrganizer.onBackPressedOnTaskRoot(task);
+ break;
+ }
+ }
+
+ private void dispatchTaskInfoChanged(Task task, boolean force) {
+ RunningTaskInfo lastInfo = mLastSentTaskInfos.get(task);
+ if (mTmpTaskInfo == null) {
+ mTmpTaskInfo = new RunningTaskInfo();
+ }
+ mTmpTaskInfo.configuration.unset();
+ task.fillTaskInfo(mTmpTaskInfo);
+
+ boolean changed = !mTmpTaskInfo
+ .equalsForTaskOrganizer(lastInfo)
+ || !configurationsAreEqualForOrganizer(
+ mTmpTaskInfo.configuration,
+ lastInfo.configuration);
+ if (!(changed || force)) {
+ // mTmpTaskInfo will be reused next time.
+ return;
+ }
+ final RunningTaskInfo newInfo = mTmpTaskInfo;
+ mLastSentTaskInfos.put(task,
+ mTmpTaskInfo);
+ // Since we've stored this, clean up the reference so a new one will be created next
+ // time.
+ // Transferring it this way means we only have to construct new RunningTaskInfos when
+ // they change.
+ mTmpTaskInfo = null;
+
+ if (task.isOrganized()) {
+ // Because we defer sending taskAppeared() until the app has drawn, we may receive a
+ // configuration change before the state actually has the task registered. As such
+ // we should ignore these change events to the organizer until taskAppeared(). If
+ // the task was created by the organizer, then we always send the info change.
+ mOrganizerState.mOrganizer.onTaskInfoChanged(task, newInfo);
+ }
+ }
+ }
+
@VisibleForTesting
class TaskOrganizerState {
private final TaskOrganizerCallbacks mOrganizer;
private final DeathRecipient mDeathRecipient;
private final ArrayList<Task> mOrganizedTasks = new ArrayList<>();
+ private final TaskOrganizerPendingEventsQueue mPendingEventsQueue;
private final int mUid;
TaskOrganizerState(ITaskOrganizer organizer, int uid) {
@@ -187,6 +335,7 @@
: mService.mWindowManager.mAnimator::addAfterPrepareSurfacesRunnable;
mOrganizer = new TaskOrganizerCallbacks(organizer, deferTaskOrgCallbacksConsumer);
mDeathRecipient = new DeathRecipient(organizer);
+ mPendingEventsQueue = new TaskOrganizerPendingEventsQueue(this);
try {
organizer.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
@@ -200,6 +349,11 @@
return mDeathRecipient;
}
+ @VisibleForTesting
+ TaskOrganizerPendingEventsQueue getPendingEventsQueue() {
+ return mPendingEventsQueue;
+ }
+
/**
* Register this task with this state, but doesn't trigger the task appeared callback to
* the organizer.
@@ -263,8 +417,7 @@
// updateTaskOrganizerState should remove the task from the list, but still
// check it again to avoid while-loop isn't terminate.
if (removeTask(t, t.mRemoveWithTaskOrganizer)) {
- TaskOrganizerController.this.onTaskVanishedInternal(
- mOrganizer.mTaskOrganizer, t);
+ TaskOrganizerController.this.onTaskVanishedInternal(this, t);
}
}
if (mService.getTransitionController().isShellTransitionsEnabled()) {
@@ -278,8 +431,9 @@
}
}
- // Remove organizer state after removing tasks so we get a chance to send
- // onTaskVanished.
+ // Pending events queue for this organizer need to be cleared because this organizer
+ // has either died or unregistered itself.
+ mPendingEventsQueue.clearPendingTaskEvents();
mTaskOrganizerStates.remove(mOrganizer.getBinder());
}
@@ -320,14 +474,10 @@
// List of task organizers by priority
private final ArrayDeque<ITaskOrganizer> mTaskOrganizers = new ArrayDeque<>();
- private final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap<>();
- private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>();
- // Pending task events due to layout deferred.
- private final ArrayList<PendingTaskEvent> mPendingTaskEvents = new ArrayList<>();
+ private final ArrayMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new ArrayMap<>();
// Set of organized tasks (by taskId) that dispatch back pressed to their organizers
private final HashSet<Integer> mInterceptBackPressedOnRootTasks = new HashSet<>();
- private RunningTaskInfo mTmpTaskInfo;
private Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
TaskOrganizerController(ActivityTaskManagerService atm) {
@@ -354,11 +504,6 @@
mDeferTaskOrgCallbacksConsumer = consumer;
}
- @VisibleForTesting
- ArrayList<PendingTaskEvent> getPendingEventList() {
- return mPendingTaskEvents;
- }
-
/**
* Register a TaskOrganizer to manage tasks as they enter the a supported windowing mode.
*/
@@ -592,10 +737,13 @@
void onTaskAppeared(ITaskOrganizer organizer, Task task) {
final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
if (state != null && state.addTask(task)) {
- PendingTaskEvent pending = getPendingTaskEvent(task, PendingTaskEvent.EVENT_APPEARED);
+ final TaskOrganizerPendingEventsQueue pendingEvents =
+ state.mPendingEventsQueue;
+ PendingTaskEvent pending = pendingEvents.getPendingTaskEvent(task,
+ PendingTaskEvent.EVENT_APPEARED);
if (pending == null) {
- pending = new PendingTaskEvent(task, PendingTaskEvent.EVENT_APPEARED);
- mPendingTaskEvents.add(pending);
+ pendingEvents.addPendingTaskEvent(new PendingTaskEvent(task,
+ PendingTaskEvent.EVENT_APPEARED));
}
}
}
@@ -603,26 +751,25 @@
void onTaskVanished(ITaskOrganizer organizer, Task task) {
final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
if (state != null && state.removeTask(task, task.mRemoveWithTaskOrganizer)) {
- onTaskVanishedInternal(organizer, task);
+ onTaskVanishedInternal(state, task);
}
}
- private void onTaskVanishedInternal(ITaskOrganizer organizer, Task task) {
- for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
- PendingTaskEvent entry = mPendingTaskEvents.get(i);
- if (task.mTaskId == entry.mTask.mTaskId && entry.mTaskOrg == organizer) {
- // This task is vanished so remove all pending event of it.
- mPendingTaskEvents.remove(i);
- if (entry.mEventType == PendingTaskEvent.EVENT_APPEARED) {
- // If task appeared callback still pend, ignore this callback too.
- return;
- }
- }
+ private void onTaskVanishedInternal(TaskOrganizerState organizerState, Task task) {
+ if (organizerState == null) {
+ Slog.i(TAG, "cannot send onTaskVanished because organizer state is not "
+ + "present for this organizer");
+ return;
}
-
- PendingTaskEvent pending =
- new PendingTaskEvent(task, organizer, PendingTaskEvent.EVENT_VANISHED);
- mPendingTaskEvents.add(pending);
+ TaskOrganizerPendingEventsQueue pendingEventsQueue =
+ organizerState.mPendingEventsQueue;
+ boolean hadPendingAppearedEvents =
+ pendingEventsQueue.removePendingTaskEvents(task);
+ if (hadPendingAppearedEvents) {
+ return;
+ }
+ pendingEventsQueue.addPendingTaskEvent(new PendingTaskEvent(task,
+ organizerState.mOrganizer.mTaskOrganizer, PendingTaskEvent.EVENT_VANISHED));
}
@Override
@@ -690,48 +837,13 @@
}
void dispatchPendingEvents() {
- if (mService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()
- || mPendingTaskEvents.isEmpty()) {
+ if (mService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()) {
return;
}
-
- for (int i = 0, n = mPendingTaskEvents.size(); i < n; i++) {
- PendingTaskEvent event = mPendingTaskEvents.get(i);
- final Task task = event.mTask;
- final TaskOrganizerState state;
- switch (event.mEventType) {
- case PendingTaskEvent.EVENT_APPEARED:
- state = mTaskOrganizerStates.get(event.mTaskOrg.asBinder());
- if (state != null && task.taskAppearedReady()) {
- state.mOrganizer.onTaskAppeared(task);
- }
- break;
- case PendingTaskEvent.EVENT_VANISHED:
- // TaskOrganizerState cannot be used here because it might have already been
- // removed.
- // The state is removed when an organizer dies or is unregistered. In order to
- // send the pending vanished task events, the mTaskOrg from event is used.
- // These events should not ideally be sent and will be removed as part of
- // b/224812558.
- try {
- event.mTaskOrg.onTaskVanished(task.getTaskInfo());
- } catch (RemoteException ex) {
- Slog.e(TAG, "Exception sending onTaskVanished callback", ex);
- }
- mLastSentTaskInfos.remove(task);
- break;
- case PendingTaskEvent.EVENT_INFO_CHANGED:
- dispatchTaskInfoChanged(event.mTask, event.mForce);
- break;
- case PendingTaskEvent.EVENT_ROOT_BACK_PRESSED:
- state = mTaskOrganizerStates.get(event.mTaskOrg.asBinder());
- if (state != null) {
- state.mOrganizer.onBackPressedOnTaskRoot(task);
- }
- break;
- }
+ for (int taskOrgIdx = 0; taskOrgIdx < mTaskOrganizerStates.size(); taskOrgIdx++) {
+ TaskOrganizerState taskOrganizerState = mTaskOrganizerStates.valueAt(taskOrgIdx);
+ taskOrganizerState.mPendingEventsQueue.dispatchPendingEvents();
}
- mPendingTaskEvents.clear();
}
void reportImeDrawnOnTask(Task task) {
@@ -750,20 +862,30 @@
// Skip if task still not appeared.
return;
}
- if (force && mPendingTaskEvents.isEmpty()) {
+ final TaskOrganizerState taskOrganizerState =
+ mTaskOrganizerStates.get(task.mTaskOrganizer.asBinder());
+ final TaskOrganizerPendingEventsQueue pendingEventsQueue =
+ taskOrganizerState.mPendingEventsQueue;
+ if (pendingEventsQueue == null) {
+ Slog.i(TAG, "cannot send onTaskInfoChanged because pending events queue is not "
+ + "present for this organizer");
+ return;
+ }
+ if (force && pendingEventsQueue.numPendingTaskEvents() == 0) {
// There are task-info changed events do not result in
// - RootWindowContainer#performSurfacePlacementNoTrace OR
// - WindowAnimator#animate
// For instance, when an app requesting aspect ratio change when in PiP mode.
// To solve this, we directly dispatch the pending event if there are no events queued (
// otherwise, all pending events should be dispatched on next drawn).
- dispatchTaskInfoChanged(task, true /* force */);
+ pendingEventsQueue.dispatchTaskInfoChanged(task, true /* force */);
return;
}
// Defer task info reporting while layout is deferred. This is because layout defer
// blocks tend to do lots of re-ordering which can mess up animations in receivers.
- PendingTaskEvent pending = getPendingLifecycleTaskEvent(task);
+ PendingTaskEvent pending = pendingEventsQueue
+ .getPendingLifecycleTaskEvent(task);
if (pending == null) {
pending = new PendingTaskEvent(task, PendingTaskEvent.EVENT_INFO_CHANGED);
} else {
@@ -774,45 +896,10 @@
return;
}
// Remove and add for re-ordering.
- mPendingTaskEvents.remove(pending);
+ pendingEventsQueue.removePendingTaskEvent(pending);
}
pending.mForce |= force;
- mPendingTaskEvents.add(pending);
- }
-
- private void dispatchTaskInfoChanged(Task task, boolean force) {
- RunningTaskInfo lastInfo = mLastSentTaskInfos.get(task);
- if (mTmpTaskInfo == null) {
- mTmpTaskInfo = new RunningTaskInfo();
- }
- mTmpTaskInfo.configuration.unset();
- task.fillTaskInfo(mTmpTaskInfo);
-
- boolean changed = !mTmpTaskInfo.equalsForTaskOrganizer(lastInfo)
- || !configurationsAreEqualForOrganizer(
- mTmpTaskInfo.configuration, lastInfo.configuration);
- if (!(changed || force)) {
- // mTmpTaskInfo will be reused next time.
- return;
- }
- final RunningTaskInfo newInfo = mTmpTaskInfo;
- mLastSentTaskInfos.put(task, mTmpTaskInfo);
- // Since we've stored this, clean up the reference so a new one will be created next time.
- // Transferring it this way means we only have to construct new RunningTaskInfos when they
- // change.
- mTmpTaskInfo = null;
-
- if (task.isOrganized()) {
- // Because we defer sending taskAppeared() until the app has drawn, we may receive a
- // configuration change before the state actually has the task registered. As such we
- // should ignore these change events to the organizer until taskAppeared(). If the task
- // was created by the organizer, then we always send the info change.
- final TaskOrganizerState state = mTaskOrganizerStates.get(
- task.mTaskOrganizer.asBinder());
- if (state != null) {
- state.mOrganizer.onTaskInfoChanged(task, newInfo);
- }
- }
+ pendingEventsQueue.addPendingTaskEvent(pending);
}
@Override
@@ -1018,50 +1105,36 @@
|| !mInterceptBackPressedOnRootTasks.contains(task.mTaskId)) {
return false;
}
+ final TaskOrganizerPendingEventsQueue pendingEventsQueue =
+ mTaskOrganizerStates.get(task.mTaskOrganizer.asBinder())
+ .mPendingEventsQueue;
+ if (pendingEventsQueue == null) {
+ Slog.w(TAG, "cannot get handle BackPressedOnTaskRoot because organizerState is "
+ + "not present");
+ return false;
+ }
PendingTaskEvent pendingVanished =
- getPendingTaskEvent(task, PendingTaskEvent.EVENT_VANISHED);
+ pendingEventsQueue.getPendingTaskEvent(task,
+ PendingTaskEvent.EVENT_VANISHED);
if (pendingVanished != null) {
// This task will vanish before this callback so just ignore.
return false;
}
- PendingTaskEvent pending = getPendingTaskEvent(
+ PendingTaskEvent pending = pendingEventsQueue.getPendingTaskEvent(
task, PendingTaskEvent.EVENT_ROOT_BACK_PRESSED);
if (pending == null) {
pending = new PendingTaskEvent(task, PendingTaskEvent.EVENT_ROOT_BACK_PRESSED);
} else {
// Pending already exist, remove and add for re-ordering.
- mPendingTaskEvents.remove(pending);
+ pendingEventsQueue.removePendingTaskEvent(pending);
}
- mPendingTaskEvents.add(pending);
+ pendingEventsQueue.addPendingTaskEvent(pending);
mService.mWindowManager.mWindowPlacerLocked.requestTraversal();
return true;
}
- @Nullable
- private PendingTaskEvent getPendingTaskEvent(Task task, int type) {
- for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
- PendingTaskEvent entry = mPendingTaskEvents.get(i);
- if (task.mTaskId == entry.mTask.mTaskId && type == entry.mEventType) {
- return entry;
- }
- }
- return null;
- }
-
- @VisibleForTesting
- @Nullable
- PendingTaskEvent getPendingLifecycleTaskEvent(Task task) {
- for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
- PendingTaskEvent entry = mPendingTaskEvents.get(i);
- if (task.mTaskId == entry.mTask.mTaskId && entry.isLifecycleEvent()) {
- return entry;
- }
- }
- return null;
- }
-
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.print(prefix); pw.println("TaskOrganizerController:");
@@ -1084,4 +1157,9 @@
TaskOrganizerState getTaskOrganizerState(IBinder taskOrganizer) {
return mTaskOrganizerStates.get(taskOrganizer);
}
+
+ @VisibleForTesting
+ TaskOrganizerPendingEventsQueue getTaskOrganizerPendingEvents(IBinder taskOrganizer) {
+ return mTaskOrganizerStates.get(taskOrganizer).mPendingEventsQueue;
+ }
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 3c0cac0..09f6110 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -149,9 +149,6 @@
final @TransitionType int mType;
private int mSyncId = -1;
- // Used for tracking a Transition throughout a lifecycle (i.e. from STATE_COLLECTING to
- // STATE_FINISHED or STATE_ABORT), and should only be used for testing and debugging.
- private int mDebugId = -1;
private @TransitionFlags int mFlags;
private final TransitionController mController;
private final BLASTSyncEngine mSyncEngine;
@@ -202,6 +199,9 @@
private boolean mNavBarAttachedToApp = false;
private int mRecentsDisplayId = INVALID_DISPLAY;
+ /** The delay for light bar appearance animation. */
+ long mStatusBarTransitionDelay;
+
/** @see #setCanPipOnFinish */
private boolean mCanPipOnFinish = true;
@@ -295,11 +295,6 @@
return mSyncId;
}
- @VisibleForTesting
- int getDebugId() {
- return mDebugId;
- }
-
@TransitionFlags
int getFlags() {
return mFlags;
@@ -315,6 +310,10 @@
return mFinishTransaction;
}
+ private boolean isCollecting() {
+ return mState == STATE_COLLECTING || mState == STATE_STARTED;
+ }
+
/** Starts collecting phase. Once this starts, all relevant surface operations are sync. */
void startCollecting(long timeoutMs) {
if (mState != STATE_PENDING) {
@@ -322,7 +321,6 @@
}
mState = STATE_COLLECTING;
mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG);
- mDebugId = mSyncId;
mController.mTransitionTracer.logState(this);
}
@@ -353,7 +351,10 @@
if (mState < STATE_COLLECTING) {
throw new IllegalStateException("Transition hasn't started collecting.");
}
- if (mSyncId < 0) return;
+ if (!isCollecting()) {
+ // Too late, transition already started playing, so don't collect.
+ return;
+ }
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
mSyncId, wc);
// "snapshot" all parents (as potential promotion targets). Do this before checking
@@ -403,7 +404,10 @@
* or waiting until after the animation to close).
*/
void collectExistenceChange(@NonNull WindowContainer wc) {
- if (mSyncId < 0) return;
+ if (mState >= STATE_PLAYING) {
+ // Too late to collect. Don't check too-early here since `collect` will check that.
+ return;
+ }
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Existence Changed in transition %d:"
+ " %s", mSyncId, wc);
collect(wc);
@@ -437,7 +441,7 @@
*/
void setOverrideAnimation(TransitionInfo.AnimationOptions options,
@Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
- if (mSyncId < 0) return;
+ if (!isCollecting()) return;
mOverrideOptions = options;
sendRemoteCallback(mClientAnimationStartCallback);
mClientAnimationStartCallback = startCallback;
@@ -455,7 +459,7 @@
* The transition will wait for all groups to be ready.
*/
void setReady(WindowContainer wc, boolean ready) {
- if (mSyncId < 0) return;
+ if (!isCollecting() || mSyncId < 0) return;
mReadyTracker.setReadyFrom(wc, ready);
applyReady();
}
@@ -473,7 +477,7 @@
* @see ReadyTracker#setAllReady.
*/
void setAllReady() {
- if (mSyncId < 0) return;
+ if (!isCollecting() || mSyncId < 0) return;
mReadyTracker.setAllReady();
applyReady();
}
@@ -672,7 +676,7 @@
SurfaceControl.Transaction inputSinkTransaction = null;
for (int i = 0; i < mParticipants.size(); ++i) {
final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
- if (ar == null || !ar.isVisible()) continue;
+ if (ar == null || !ar.isVisible() || ar.getParent() == null) continue;
if (inputSinkTransaction == null) {
inputSinkTransaction = new SurfaceControl.Transaction();
}
@@ -870,7 +874,7 @@
}
buildFinishTransaction(mFinishTransaction, info.getRootLeash());
if (mController.getTransitionPlayer() != null) {
- mController.dispatchLegacyAppTransitionStarting(info);
+ mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay);
try {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Calling onTransitionReady: %s", info);
@@ -889,7 +893,6 @@
// No player registered, so just finish/apply immediately
cleanUpOnFailure();
}
- mSyncId = -1;
mOverrideOptions = null;
reportStartReasonsToLogger();
@@ -1614,7 +1617,7 @@
}
boolean getLegacyIsReady() {
- return (mState == STATE_STARTED || mState == STATE_COLLECTING) && mSyncId >= 0;
+ return isCollecting() && mSyncId >= 0;
}
static Transition fromBinder(IBinder binder) {
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 6d31e93..dbc2c5f 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -462,6 +462,12 @@
}, true /* traverseTopToBottom */);
}
+ /** @see Transition#mStatusBarTransitionDelay */
+ void setStatusBarTransitionDelay(long delay) {
+ if (mCollectingTransition == null) return;
+ mCollectingTransition.mStatusBarTransitionDelay = delay;
+ }
+
/** @see Transition#setOverrideAnimation */
void setOverrideAnimation(TransitionInfo.AnimationOptions options,
@Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
@@ -600,13 +606,14 @@
}
}
- void dispatchLegacyAppTransitionStarting(TransitionInfo info) {
+ void dispatchLegacyAppTransitionStarting(TransitionInfo info, long statusBarTransitionDelay) {
final boolean keyguardGoingAway = info.isKeyguardGoingAway();
for (int i = 0; i < mLegacyListeners.size(); ++i) {
// TODO(shell-transitions): handle (un)occlude transition.
mLegacyListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway,
false /* keyguardOcclude */, 0 /* durationHint */,
- SystemClock.uptimeMillis(), AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
+ SystemClock.uptimeMillis() + statusBarTransitionDelay,
+ AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
}
}
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index b1951e0..c1927d8 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -79,7 +79,7 @@
final ProtoOutputStream outputStream = new ProtoOutputStream();
final long transitionEntryToken = outputStream.start(TRANSITION);
- outputStream.write(ID, transition.getDebugId());
+ outputStream.write(ID, transition.getSyncId());
outputStream.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos());
outputStream.write(TRANSITION_TYPE, transition.mType);
outputStream.write(STATE, transition.getState());
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 7f1096b..00ee94a 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2818,6 +2818,10 @@
* snapshot from {@link #getFreezeSnapshotTarget()}.
*/
void initializeChangeTransition(Rect startBounds, @Nullable SurfaceControl freezeTarget) {
+ if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
+ // TODO(b/207070762): request shell transition for activityEmbedding change.
+ return;
+ }
mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
mDisplayContent.mChangingContainers.add(this);
// Calculate the relative position in parent container.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d7c85d4..bcf05a6f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2082,7 +2082,7 @@
if (win.mAttrs.type == TYPE_WALLPAPER) {
dc.mWallpaperController.clearLastWallpaperTimeoutTime();
dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
- } else if (win.hasWallpaper()) {
+ } else if (dc.mWallpaperController.isWallpaperTarget(win)) {
dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 1d93c89..97dcb75 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -46,6 +46,7 @@
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
+import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -814,7 +815,7 @@
sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
break;
}
- if (!parent.isAllowedToEmbedActivity(activity)) {
+ if (parent.isAllowedToEmbedActivity(activity) != EMBEDDING_ALLOWED) {
final Throwable exception = new SecurityException(
"The task fragment is not trusted to embed the given activity.");
sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
@@ -1057,7 +1058,7 @@
}
/** A helper method to send minimum dimension violation error to the client. */
- void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions,
+ private void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions,
IBinder errorCallbackToken, String reason) {
if (taskFragment == null || taskFragment.getTaskFragmentOrganizer() == null) {
return;
@@ -1672,7 +1673,7 @@
// We are reparenting activities to a new embedded TaskFragment, this operation is only
// allowed if the new parent is trusted by all reparent activities.
final boolean isEmbeddingDisallowed = oldParent.forAllActivities(activity ->
- !newParentTF.isAllowedToEmbedActivity(activity));
+ newParentTF.isAllowedToEmbedActivity(activity) == EMBEDDING_ALLOWED);
if (isEmbeddingDisallowed) {
final Throwable exception = new SecurityException(
"The new parent is not trusted to embed the activities.");
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index ec38f77..4078996 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -119,7 +119,7 @@
"libutils",
"libui",
"libvibratorservice",
- "PlatformProperties",
+ "libPlatformProperties",
"libinput",
"libinputflinger",
"libinputflinger_base",
diff --git a/services/smartspace/OWNERS b/services/smartspace/OWNERS
index 19ef9d7..4d9a633 100644
--- a/services/smartspace/OWNERS
+++ b/services/smartspace/OWNERS
@@ -1,2 +1 @@
-srazdan@google.com
-alexmang@google.com
\ No newline at end of file
+include /core/java/android/service/smartspace/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
index 69584e0..7ccd6d9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt
@@ -70,13 +70,12 @@
@Test
fun deleteSystemPackageFailsIfNotAdminAndNotProfile() {
val ps = mPms.mSettings.getPackageLPr("a.data.package")
- whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true)
+ whenever(PackageManagerServiceUtils.isUpdatedSystemApp(ps)).thenReturn(true)
whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(UserInfo(1, "test", 0))
whenever(mUserManagerInternal.getProfileParentId(1)).thenReturn(1)
val dph = DeletePackageHelper(mPms)
- val result = dph.deletePackageX("a.data.package", 1L, 1,
- PackageManager.DELETE_SYSTEM_APP, false)
+ val result = dph.deletePackageX("a.data.package", 1L, 1, 0, false)
assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED)
}
@@ -86,7 +85,7 @@
val userId = 1
val parentId = 5
val ps = mPms.mSettings.getPackageLPr("a.data.package")
- whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true)
+ whenever(PackageManagerServiceUtils.isUpdatedSystemApp(ps)).thenReturn(true)
whenever(mUserManagerInternal.getUserInfo(userId)).thenReturn(
UserInfo(userId, "test", UserInfo.FLAG_PROFILE))
whenever(mUserManagerInternal.getProfileParentId(userId)).thenReturn(parentId)
@@ -94,8 +93,7 @@
UserInfo(userId, "testparent", 0))
val dph = DeletePackageHelper(mPms)
- val result = dph.deletePackageX("a.data.package", 1L, userId,
- PackageManager.DELETE_SYSTEM_APP, false)
+ val result = dph.deletePackageX("a.data.package", 1L, userId, 0, false)
assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_USER_RESTRICTED)
}
@@ -132,4 +130,18 @@
assertThat(result).isEqualTo(PackageManager.DELETE_SUCCEEDED)
}
-}
\ No newline at end of file
+
+ @Test
+ fun deleteSystemPackageSucceedsIfNotAdminButDeleteSystemAppSpecified() {
+ val ps = mPms.mSettings.getPackageLPr("a.data.package")
+ whenever(PackageManagerServiceUtils.isUpdatedSystemApp(ps)).thenReturn(true)
+ whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(UserInfo(1, "test", 0))
+ whenever(mUserManagerInternal.getProfileParentId(1)).thenReturn(1)
+
+ val dph = DeletePackageHelper(mPms)
+ val result = dph.deletePackageX("a.data.package", 1L, 1,
+ PackageManager.DELETE_SYSTEM_APP, false)
+
+ assertThat(result).isEqualTo(PackageManager.DELETE_SUCCEEDED)
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index 8b314cd..c2519caa 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -295,10 +295,10 @@
mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_1 = new byte[]{
- 0x01, 0x18, 0x4A,
- 0x02, 0x64, 0x5A,
- 0x03, 0x4B, 0x00,
- 0x04, 0x20, 0x0A};
+ 0x0F, 0x18, 0x4A,
+ 0x17, 0x64, 0x5A,
+ 0x1F, 0x4B, 0x00,
+ 0x27, 0x20, 0x0A};
HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1);
assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
@@ -310,10 +310,10 @@
mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_2 = new byte[]{
- 0x05, 0x18, 0x4A,
- 0x06, 0x64, 0x5A,
- 0x07, 0x4B, 0x00,
- 0x08, 0x20, 0x0A};
+ 0x2F, 0x18, 0x4A,
+ 0x37, 0x64, 0x5A,
+ 0x3F, 0x4B, 0x00,
+ 0x47, 0x20, 0x0A};
HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_2);
assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
@@ -325,10 +325,10 @@
mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_3 = new byte[]{
- 0x09, 0x18, 0x4A,
- 0x0A, 0x64, 0x5A,
- 0x0B, 0x4B, 0x00,
- 0x0C, 0x20, 0x0A};
+ 0x4F, 0x18, 0x4A,
+ 0x57, 0x64, 0x5A,
+ 0x5F, 0x4B, 0x00,
+ 0x67, 0x20, 0x0A};
HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_3);
assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
@@ -340,9 +340,9 @@
mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_4 = new byte[]{
- 0x0D, 0x18, 0x4A,
- 0x0E, 0x64, 0x5A,
- 0x0F, 0x4B, 0x00};
+ 0x6F, 0x18, 0x4A,
+ 0x77, 0x64, 0x5A,
+ 0x7F, 0x4B, 0x00};
HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_4);
assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
@@ -371,9 +371,9 @@
mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_1 = new byte[]{
- 0x01, 0x18, 0x4A,
- 0x03, 0x4B, 0x00,
- 0x04, 0x20, 0x0A};
+ 0x0F, 0x18, 0x4A,
+ 0x1F, 0x4B, 0x00,
+ 0x27, 0x20, 0x0A};
HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1);
assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
@@ -385,7 +385,7 @@
mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_2 = new byte[]{
- 0x08, 0x20, 0x0A};
+ 0x2F, 0x20, 0x0A};
HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_2);
assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
@@ -397,10 +397,10 @@
mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_3 = new byte[]{
- 0x09, 0x18, 0x4A,
- 0x0A, 0x64, 0x5A,
- 0x0B, 0x4B, 0x00,
- 0x0C, 0x20, 0x0A};
+ 0x4F, 0x18, 0x4A,
+ 0x57, 0x64, 0x5A,
+ 0x5F, 0x4B, 0x00,
+ 0x67, 0x20, 0x0A};
HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_3);
assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
@@ -412,7 +412,7 @@
mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_4 = new byte[]{
- 0x0F, 0x4B, 0x00};
+ 0x7F, 0x4B, 0x00};
HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_4);
assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
@@ -440,11 +440,12 @@
HdmiCecMessage expected1 = HdmiCecMessageBuilder.buildRequestShortAudioDescriptor(
mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
+ // Negative values require explicit casting
byte[] sadsToRespond_1 = new byte[]{
- 0x20, 0x18, 0x4A,
- 0x21, 0x64, 0x5A,
- 0x22, 0x4B, 0x00,
- 0x23, 0x20, 0x0A};
+ (byte) 0x80, 0x18, 0x4A,
+ (byte) 0x82, 0x64, 0x5A,
+ (byte) 0x87, 0x4B, 0x00,
+ (byte) 0x8F, 0x20, 0x0A};
HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1);
assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
@@ -456,10 +457,10 @@
mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_2.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_2 = new byte[]{
- 0x24, 0x18, 0x4A,
- 0x25, 0x64, 0x5A,
- 0x26, 0x4B, 0x00,
- 0x27, 0x20, 0x0A};
+ (byte) 0x92, 0x18, 0x4A,
+ (byte) 0x98, 0x64, 0x5A,
+ (byte) 0xA1, 0x4B, 0x00,
+ (byte) 0xA4, 0x20, 0x0A};
HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_2);
assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
@@ -471,10 +472,10 @@
mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_3.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_3 = new byte[]{
- 0x28, 0x18, 0x4A,
- 0x29, 0x64, 0x5A,
- 0x2A, 0x4B, 0x00,
- 0x2B, 0x20, 0x0A};
+ (byte) 0xB3, 0x18, 0x4A,
+ (byte) 0xBA, 0x64, 0x5A,
+ (byte) 0xCB, 0x4B, 0x00,
+ (byte) 0xCF, 0x20, 0x0A};
HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_3);
assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
@@ -486,9 +487,9 @@
mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_4.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_4 = new byte[]{
- 0x2C, 0x18, 0x4A,
- 0x2D, 0x64, 0x5A,
- 0x2E, 0x4B, 0x00};
+ (byte) 0xF0, 0x18, 0x4A,
+ (byte) 0xF1, 0x64, 0x5A,
+ (byte) 0xF3, 0x4B, 0x00};
HdmiCecMessage response4 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_4);
assertThat(mNativeWrapper.getResultMessages()).contains(expected4);
@@ -510,10 +511,10 @@
mTvLogicalAddress, Constants.ADDR_AUDIO_SYSTEM,
CODECS_TO_QUERY_1.stream().mapToInt(i -> i).toArray());
byte[] sadsToRespond_1 = new byte[]{
- 0x01, 0x18,
- 0x02, 0x64, 0x5A,
- 0x03, 0x4B, 0x00,
- 0x04, 0x20, 0x0A};
+ 0x0F, 0x18,
+ 0x17, 0x64, 0x5A,
+ 0x1F, 0x4B, 0x00,
+ 0x27, 0x20, 0x0A};
HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1);
assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
@@ -580,10 +581,10 @@
new int[]{Constants.AUDIO_CODEC_LPCM, Constants.AUDIO_CODEC_DD,
Constants.AUDIO_CODEC_MP3, Constants.AUDIO_CODEC_MPEG2});
byte[] sadsToRespond_1 = new byte[]{
- 0x01, 0x18, 0x4A,
- 0x02, 0x64, 0x5A,
- 0x04, 0x20, 0x0A,
- 0x05, 0x18, 0x4A};
+ 0x0F, 0x18, 0x4A,
+ 0x17, 0x64, 0x5A,
+ 0x27, 0x20, 0x0A,
+ 0x2F, 0x18, 0x4A};
HdmiCecMessage response1 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_1);
assertThat(mNativeWrapper.getResultMessages()).contains(expected1);
@@ -596,10 +597,10 @@
new int[]{Constants.AUDIO_CODEC_AAC, Constants.AUDIO_CODEC_DTS,
Constants.AUDIO_CODEC_ATRAC, Constants.AUDIO_CODEC_DDP});
byte[] sadsToRespond_2 = new byte[]{
- 0x06, 0x64, 0x5A,
- 0x07, 0x4B, 0x00,
- 0x08, 0x20, 0x0A,
- 0x09, 0x18, 0x4A};
+ 0x37, 0x64, 0x5A,
+ 0x3F, 0x4B, 0x00,
+ 0x47, 0x20, 0x0A,
+ 0x4F, 0x18, 0x4A};
HdmiCecMessage response2 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_2);
assertThat(mNativeWrapper.getResultMessages()).contains(expected2);
@@ -612,10 +613,10 @@
new int[]{Constants.AUDIO_CODEC_DTSHD, Constants.AUDIO_CODEC_TRUEHD,
Constants.AUDIO_CODEC_DST, Constants.AUDIO_CODEC_MAX});
byte[] sadsToRespond_3 = new byte[]{
- 0x0B, 0x4B, 0x00,
- 0x0C, 0x20, 0x0A,
- 0x0D, 0x18, 0x4A,
- 0x0F, 0x4B, 0x00};
+ 0x5F, 0x4B, 0x00,
+ 0x67, 0x20, 0x0A,
+ 0x6F, 0x18, 0x4A,
+ 0x7F, 0x4B, 0x00};
HdmiCecMessage response3 = HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
Constants.ADDR_AUDIO_SYSTEM, mTvLogicalAddress, sadsToRespond_3);
assertThat(mNativeWrapper.getResultMessages()).contains(expected3);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 1d10b8a..85db23c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -136,11 +136,6 @@
}
@Override
- public boolean hasEnrolledBiometrics(int userId) {
- return false;
- }
-
- @Override
public int binderGetCallingUid() {
return Process.SYSTEM_UID;
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index e7f4d3d..20cc42c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -42,11 +42,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.server.locksettings.FakeGateKeeperService.VerifyHandle;
-import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
import org.junit.Before;
import org.junit.Test;
@@ -96,17 +93,16 @@
@Test
public void testChangePasswordFailPrimaryUser() throws RemoteException {
- final long sid = 1234;
- initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("password"), sid);
+ initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("password"));
assertFalse(mService.setLockCredential(newPassword("newpwd"), newPassword("badpwd"),
PRIMARY_USER_ID));
- assertVerifyCredentials(PRIMARY_USER_ID, newPassword("password"), sid);
+ assertVerifyCredentials(PRIMARY_USER_ID, newPassword("password"));
}
@Test
public void testClearPasswordPrimaryUser() throws RemoteException {
- initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("password"), 1234);
+ initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("password"));
assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"),
PRIMARY_USER_ID));
assertEquals(CREDENTIAL_TYPE_NONE, mService.getCredentialType(PRIMARY_USER_ID));
@@ -264,10 +260,7 @@
public void testSetLockCredential_forProfileWithSeparateChallenge_updatesCredentials()
throws Exception {
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, true, null);
- initializeStorageWithCredential(
- MANAGED_PROFILE_USER_ID,
- newPattern("12345"),
- 1234);
+ initializeStorageWithCredential(MANAGED_PROFILE_USER_ID, newPattern("12345"));
assertTrue(mService.setLockCredential(
newPassword("newPassword"),
@@ -300,8 +293,7 @@
throws Exception {
final LockscreenCredential oldCredential = newPassword("oldPassword");
final LockscreenCredential newCredential = newPassword("newPassword");
- initializeStorageWithCredential(
- PRIMARY_USER_ID, oldCredential, 1234);
+ initializeStorageWithCredential(PRIMARY_USER_ID, oldCredential);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
assertTrue(mService.setLockCredential(
@@ -321,7 +313,7 @@
public void
testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_removesBothCredentials()
throws Exception {
- initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("oldPassword"), 1234);
+ initializeStorageWithCredential(PRIMARY_USER_ID, newPassword("oldPassword"));
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
assertTrue(mService.setLockCredential(
@@ -337,10 +329,7 @@
@Test
public void testSetLockCredential_nullCredential_removeBiometrics() throws RemoteException {
- initializeStorageWithCredential(
- PRIMARY_USER_ID,
- newPattern("123654"),
- 1234);
+ initializeStorageWithCredential(PRIMARY_USER_ID, newPattern("123654"));
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
mService.setLockCredential(nonePassword(), newPattern("123654"), PRIMARY_USER_ID);
@@ -358,7 +347,7 @@
throws Exception {
final LockscreenCredential parentPassword = newPassword("parentPassword");
final LockscreenCredential profilePassword = newPassword("profilePassword");
- initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword, 1234);
+ initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
assertTrue(mService.setLockCredential(
@@ -377,7 +366,7 @@
throws Exception {
final LockscreenCredential parentPassword = newPassword("parentPassword");
final LockscreenCredential profilePassword = newPattern("12345");
- initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword, 1234);
+ initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword);
// Create and verify separate profile credentials.
testCreateCredential(MANAGED_PROFILE_USER_ID, profilePassword);
@@ -393,7 +382,7 @@
@Test
public void testVerifyCredential_forPrimaryUser_sendsCredentials() throws Exception {
final LockscreenCredential password = newPassword("password");
- initializeStorageWithCredential(PRIMARY_USER_ID, password, 1234);
+ initializeStorageWithCredential(PRIMARY_USER_ID, password);
reset(mRecoverableKeyStoreManager);
mService.verifyCredential(password, PRIMARY_USER_ID, 0 /* flags */);
@@ -424,7 +413,7 @@
public void verifyCredential_forPrimaryUserWithUnifiedChallengeProfile_sendsCredentialsForBoth()
throws Exception {
final LockscreenCredential pattern = newPattern("12345");
- initializeStorageWithCredential(PRIMARY_USER_ID, pattern, 1234);
+ initializeStorageWithCredential(PRIMARY_USER_ID, pattern);
mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
reset(mRecoverableKeyStoreManager);
@@ -464,7 +453,7 @@
private void testCreateCredential(int userId, LockscreenCredential credential)
throws RemoteException {
assertTrue(mService.setLockCredential(credential, nonePassword(), userId));
- assertVerifyCredentials(userId, credential, -1);
+ assertVerifyCredentials(userId, credential);
}
private void testCreateCredentialFailsWithoutLockScreen(
@@ -483,19 +472,17 @@
private void testChangeCredentials(int userId, LockscreenCredential newCredential,
LockscreenCredential oldCredential) throws RemoteException {
- final long sid = 1234;
- initializeStorageWithCredential(userId, oldCredential, sid);
+ initializeStorageWithCredential(userId, oldCredential);
assertTrue(mService.setLockCredential(newCredential, oldCredential, userId));
- assertVerifyCredentials(userId, newCredential, sid);
+ assertVerifyCredentials(userId, newCredential);
}
- private void assertVerifyCredentials(int userId, LockscreenCredential credential, long sid)
+ private void assertVerifyCredentials(int userId, LockscreenCredential credential)
throws RemoteException{
VerifyCredentialResponse response = mService.verifyCredential(credential, userId,
0 /* flags */);
assertEquals(GateKeeperResponse.RESPONSE_OK, response.getResponseCode());
- if (sid != -1) assertEquals(sid, mGateKeeperService.getSecureUserId(userId));
if (credential.isPassword()) {
assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(userId));
} else if (credential.isPin()) {
@@ -517,19 +504,11 @@
badCredential, userId, 0 /* flags */).getResponseCode());
}
- private void initializeStorageWithCredential(int userId, LockscreenCredential credential,
- long sid) throws RemoteException {
- byte[] oldHash = new VerifyHandle(credential.getCredential(), sid).toBytes();
- if (mService.shouldMigrateToSyntheticPasswordLocked(userId)) {
- mService.initializeSyntheticPasswordLocked(oldHash, credential, userId);
- } else {
- if (credential.isPassword() || credential.isPin()) {
- mStorage.writeCredentialHash(CredentialHash.create(oldHash,
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), userId);
- } else {
- mStorage.writeCredentialHash(CredentialHash.create(oldHash,
- LockPatternUtils.CREDENTIAL_TYPE_PATTERN), userId);
- }
- }
+ @SuppressWarnings("GuardedBy") // for initializeSyntheticPasswordLocked
+ private void initializeStorageWithCredential(int userId, LockscreenCredential credential)
+ throws RemoteException {
+ assertEquals(0, mGateKeeperService.getSecureUserId(userId));
+ mService.initializeSyntheticPasswordLocked(credential, userId);
+ assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
index f2bb1d6..c30af4c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -64,18 +64,6 @@
}
@Override
- String getLockPatternFilename(int userId) {
- return makeDirs(mStorageDir,
- super.getLockPatternFilename(userId)).getAbsolutePath();
- }
-
- @Override
- String getLockPasswordFilename(int userId) {
- return makeDirs(mStorageDir,
- super.getLockPasswordFilename(userId)).getAbsolutePath();
- }
-
- @Override
String getChildProfileLockFile(int userId) {
return makeDirs(mStorageDir,
super.getChildProfileLockFile(userId)).getAbsolutePath();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 5f38a35..609c05c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -48,9 +48,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.widget.LockPatternUtils;
import com.android.server.PersistentDataBlockManagerInternal;
-import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
import com.android.server.locksettings.LockSettingsStorage.PersistentData;
import org.junit.After;
@@ -72,10 +70,7 @@
@RunWith(AndroidJUnit4.class)
public class LockSettingsStorageTests {
private static final int SOME_USER_ID = 1034;
- private final byte[] PASSWORD_0 = "thepassword0".getBytes();
- private final byte[] PASSWORD_1 = "password1".getBytes();
- private final byte[] PATTERN_0 = "123654".getBytes();
- private final byte[] PATTERN_1 = "147852369".getBytes();
+ private final byte[] PASSWORD = "thepassword".getBytes();
public static final byte[] PAYLOAD = new byte[] {1, 2, -1, -2, 33};
@@ -216,161 +211,64 @@
@Test
public void testRemoveUser() {
mStorage.writeKeyValue("key", "value", 0);
- writePasswordBytes(PASSWORD_0, 0);
- writePatternBytes(PATTERN_0, 0);
-
mStorage.writeKeyValue("key", "value", 1);
- writePasswordBytes(PASSWORD_1, 1);
- writePatternBytes(PATTERN_1, 1);
mStorage.removeUser(0);
assertEquals("value", mStorage.readKeyValue("key", "default", 1));
assertEquals("default", mStorage.readKeyValue("key", "default", 0));
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_NONE, mStorage.readCredentialHash(0).type);
- assertPatternBytes(PATTERN_1, 1);
- }
-
- @Test
- public void testCredential_Default() {
- assertEquals(mStorage.readCredentialHash(0).type, LockPatternUtils.CREDENTIAL_TYPE_NONE);
- }
-
- @Test
- public void testPassword_Write() {
- writePasswordBytes(PASSWORD_0, 0);
-
- assertPasswordBytes(PASSWORD_0, 0);
- mStorage.clearCache();
- assertPasswordBytes(PASSWORD_0, 0);
- }
-
- @Test
- public void testPassword_WriteProfileWritesParent() {
- writePasswordBytes(PASSWORD_0, 1);
- writePasswordBytes(PASSWORD_1, 2);
-
- assertPasswordBytes(PASSWORD_0, 1);
- assertPasswordBytes(PASSWORD_1, 2);
- mStorage.clearCache();
- assertPasswordBytes(PASSWORD_0, 1);
- assertPasswordBytes(PASSWORD_1, 2);
- }
-
- @Test
- public void testLockType_WriteProfileWritesParent() {
- writePasswordBytes(PASSWORD_0, 10);
- writePatternBytes(PATTERN_0, 20);
-
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN,
- mStorage.readCredentialHash(10).type);
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
- mStorage.readCredentialHash(20).type);
- mStorage.clearCache();
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN,
- mStorage.readCredentialHash(10).type);
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
- mStorage.readCredentialHash(20).type);
- }
-
- @Test
- public void testPassword_WriteParentWritesProfile() {
- writePasswordBytes(PASSWORD_0, 2);
- writePasswordBytes(PASSWORD_1, 1);
-
- assertPasswordBytes(PASSWORD_1, 1);
- assertPasswordBytes(PASSWORD_0, 2);
- mStorage.clearCache();
- assertPasswordBytes(PASSWORD_1, 1);
- assertPasswordBytes(PASSWORD_0, 2);
}
@Test
public void testProfileLock_ReadWriteChildProfileLock() {
assertFalse(mStorage.hasChildProfileLock(20));
- mStorage.writeChildProfileLock(20, PASSWORD_0);
- assertArrayEquals(PASSWORD_0, mStorage.readChildProfileLock(20));
+ mStorage.writeChildProfileLock(20, PASSWORD);
+ assertArrayEquals(PASSWORD, mStorage.readChildProfileLock(20));
assertTrue(mStorage.hasChildProfileLock(20));
mStorage.clearCache();
- assertArrayEquals(PASSWORD_0, mStorage.readChildProfileLock(20));
+ assertArrayEquals(PASSWORD, mStorage.readChildProfileLock(20));
assertTrue(mStorage.hasChildProfileLock(20));
}
@Test
- public void testPattern_Write() {
- writePatternBytes(PATTERN_0, 0);
-
- assertPatternBytes(PATTERN_0, 0);
- mStorage.clearCache();
- assertPatternBytes(PATTERN_0, 0);
- }
-
- @Test
- public void testPattern_WriteProfileWritesParent() {
- writePatternBytes(PATTERN_0, 1);
- writePatternBytes(PATTERN_1, 2);
-
- assertPatternBytes(PATTERN_0, 1);
- assertPatternBytes(PATTERN_1, 2);
- mStorage.clearCache();
- assertPatternBytes(PATTERN_0, 1);
- assertPatternBytes(PATTERN_1, 2);
- }
-
- @Test
- public void testPattern_WriteParentWritesProfile() {
- writePatternBytes(PATTERN_1, 2);
- writePatternBytes(PATTERN_0, 1);
-
- assertPatternBytes(PATTERN_0, 1);
- assertPatternBytes(PATTERN_1, 2);
- mStorage.clearCache();
- assertPatternBytes(PATTERN_0, 1);
- assertPatternBytes(PATTERN_1, 2);
- }
-
- @Test
public void testPrefetch() {
mStorage.writeKeyValue("key", "toBeFetched", 0);
- writePatternBytes(PATTERN_0, 0);
mStorage.clearCache();
mStorage.prefetchUser(0);
assertEquals("toBeFetched", mStorage.readKeyValue("key", "default", 0));
- assertPatternBytes(PATTERN_0, 0);
}
@Test
public void testFileLocation_Owner() {
LockSettingsStorage storage = new LockSettingsStorage(InstrumentationRegistry.getContext());
- assertEquals("/data/system/gatekeeper.pattern.key", storage.getLockPatternFilename(0));
- assertEquals("/data/system/gatekeeper.password.key", storage.getLockPasswordFilename(0));
+ assertEquals("/data/system/gatekeeper.profile.key", storage.getChildProfileLockFile(0));
}
@Test
public void testFileLocation_SecondaryUser() {
LockSettingsStorage storage = new LockSettingsStorage(InstrumentationRegistry.getContext());
- assertEquals("/data/system/users/1/gatekeeper.pattern.key", storage.getLockPatternFilename(1));
- assertEquals("/data/system/users/1/gatekeeper.password.key", storage.getLockPasswordFilename(1));
+ assertEquals("/data/system/users/1/gatekeeper.profile.key",
+ storage.getChildProfileLockFile(1));
}
@Test
public void testFileLocation_ProfileToSecondary() {
LockSettingsStorage storage = new LockSettingsStorage(InstrumentationRegistry.getContext());
- assertEquals("/data/system/users/2/gatekeeper.pattern.key", storage.getLockPatternFilename(2));
- assertEquals("/data/system/users/2/gatekeeper.password.key", storage.getLockPasswordFilename(2));
+ assertEquals("/data/system/users/2/gatekeeper.profile.key",
+ storage.getChildProfileLockFile(2));
}
@Test
public void testFileLocation_ProfileToOwner() {
LockSettingsStorage storage = new LockSettingsStorage(InstrumentationRegistry.getContext());
- assertEquals("/data/system/users/3/gatekeeper.pattern.key", storage.getLockPatternFilename(3));
- assertEquals("/data/system/users/3/gatekeeper.password.key", storage.getLockPasswordFilename(3));
+ assertEquals("/data/system/users/3/gatekeeper.profile.key",
+ storage.getChildProfileLockFile(3));
}
@Test
@@ -483,28 +381,6 @@
}
}
- private void writePasswordBytes(byte[] password, int userId) {
- mStorage.writeCredentialHash(CredentialHash.create(
- password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), userId);
- }
-
- private void writePatternBytes(byte[] pattern, int userId) {
- mStorage.writeCredentialHash(CredentialHash.create(
- pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN), userId);
- }
-
- private void assertPasswordBytes(byte[] password, int userId) {
- CredentialHash cred = mStorage.readCredentialHash(userId);
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN, cred.type);
- assertArrayEquals(password, cred.hash);
- }
-
- private void assertPatternBytes(byte[] pattern, int userId) {
- CredentialHash cred = mStorage.readCredentialHash(userId);
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PATTERN, cred.type);
- assertArrayEquals(pattern, cred.hash);
- }
-
/**
* Suppresses reporting of the WTF to system_server, so we don't pollute the dropbox with
* intentionally caused WTFs.
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 09aa345..6d1df2c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -19,7 +19,6 @@
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD_OR_PIN;
-import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_ENABLED_KEY;
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
import static org.junit.Assert.assertEquals;
@@ -38,7 +37,6 @@
import android.app.admin.PasswordMetrics;
import android.app.PropertyInvalidatedCache;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -82,8 +80,7 @@
final LockscreenCredential badPassword = newPassword("bad-password");
MockSyntheticPasswordManager manager = new MockSyntheticPasswordManager(mContext, mStorage,
mGateKeeperService, mUserManager, mPasswordSlotManager);
- AuthenticationToken authToken = manager.newSyntheticPasswordAndSid(mGateKeeperService, null,
- null, USER_ID);
+ AuthenticationToken authToken = manager.newSyntheticPassword(USER_ID);
long handle = manager.createPasswordBasedSyntheticPassword(mGateKeeperService,
password, authToken, USER_ID);
@@ -97,32 +94,23 @@
assertNull(result.authToken);
}
- private void disableSyntheticPassword() throws RemoteException {
- mService.setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM);
- }
-
- private void enableSyntheticPassword() throws RemoteException {
- mService.setLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 1, UserHandle.USER_SYSTEM);
- }
-
private boolean hasSyntheticPassword(int userId) throws RemoteException {
return mService.getLong(SYNTHETIC_PASSWORD_HANDLE_KEY, 0, userId) != 0;
}
- protected void initializeCredentialUnderSP(LockscreenCredential password, int userId)
+ private void initializeCredential(LockscreenCredential password, int userId)
throws RemoteException {
- enableSyntheticPassword();
assertTrue(mService.setLockCredential(password, nonePassword(), userId));
assertEquals(CREDENTIAL_TYPE_PASSWORD, mService.getCredentialType(userId));
assertTrue(mService.isSyntheticPasswordBasedCredential(userId));
}
@Test
- public void testSyntheticPasswordChangeCredential() throws RemoteException {
+ public void testChangeCredential() throws RemoteException {
final LockscreenCredential password = newPassword("password");
final LockscreenCredential newPassword = newPassword("newpassword");
- initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+ initializeCredential(password, PRIMARY_USER_ID);
long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
mService.setLockCredential(newPassword, password, PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
@@ -131,11 +119,11 @@
}
@Test
- public void testSyntheticPasswordVerifyCredential() throws RemoteException {
+ public void testVerifyCredential() throws RemoteException {
LockscreenCredential password = newPassword("password");
LockscreenCredential badPassword = newPassword("badpassword");
- initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+ initializeCredential(password, PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
@@ -144,11 +132,11 @@
}
@Test
- public void testSyntheticPasswordClearCredential() throws RemoteException {
+ public void testClearCredential() throws RemoteException {
LockscreenCredential password = newPassword("password");
LockscreenCredential badPassword = newPassword("newpassword");
- initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+ initializeCredential(password, PRIMARY_USER_ID);
long sid = mGateKeeperService.getSecureUserId(PRIMARY_USER_ID);
// clear password
mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID);
@@ -162,11 +150,11 @@
}
@Test
- public void testSyntheticPasswordChangeCredentialKeepsAuthSecret() throws RemoteException {
+ public void testChangeCredentialKeepsAuthSecret() throws RemoteException {
LockscreenCredential password = newPassword("password");
LockscreenCredential badPassword = newPassword("new");
- initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+ initializeCredential(password, PRIMARY_USER_ID);
mService.setLockCredential(badPassword, password, PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
badPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
@@ -178,11 +166,10 @@
}
@Test
- public void testSyntheticPasswordVerifyPassesPrimaryUserAuthSecret() throws RemoteException {
+ public void testVerifyPassesPrimaryUserAuthSecret() throws RemoteException {
LockscreenCredential password = newPassword("password");
- LockscreenCredential newPassword = newPassword("new");
- initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+ initializeCredential(password, PRIMARY_USER_ID);
reset(mAuthSecretService);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
@@ -193,7 +180,7 @@
public void testSecondaryUserDoesNotPassAuthSecret() throws RemoteException {
LockscreenCredential password = newPassword("password");
- initializeCredentialUnderSP(password, SECONDARY_USER_ID);
+ initializeCredential(password, SECONDARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
password, SECONDARY_USER_ID, 0 /* flags */).getResponseCode());
verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
@@ -207,9 +194,9 @@
}
@Test
- public void testSyntheticPasswordAndCredentialDoesNotPassAuthSecret() throws RemoteException {
- LockscreenCredential password = newPassword("passwordForASyntheticPassword");
- initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+ public void testCredentialDoesNotPassAuthSecret() throws RemoteException {
+ LockscreenCredential password = newPassword("password");
+ initializeCredential(password, PRIMARY_USER_ID);
reset(mAuthSecretService);
mService.onUnlockUser(PRIMARY_USER_ID);
@@ -219,8 +206,8 @@
@Test
public void testSyntheticPasswordButNoCredentialPassesAuthSecret() throws RemoteException {
- LockscreenCredential password = newPassword("getASyntheticPassword");
- initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+ LockscreenCredential password = newPassword("password");
+ initializeCredential(password, PRIMARY_USER_ID);
mService.setLockCredential(nonePassword(), password, PRIMARY_USER_ID);
reset(mAuthSecretService);
@@ -234,7 +221,7 @@
LockscreenCredential password = newPassword("password");
LockscreenCredential pattern = newPattern("123654");
byte[] token = "some-high-entropy-secure-token".getBytes();
- initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+ initializeCredential(password, PRIMARY_USER_ID);
// Disregard any reportPasswordChanged() invocations as part of credential setup.
flushHandlerTasks();
reset(mDevicePolicyManager);
@@ -269,7 +256,7 @@
LockscreenCredential password = newPassword("password");
LockscreenCredential pattern = newPattern("123654");
byte[] token = "some-high-entropy-secure-token".getBytes();
- initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+ initializeCredential(password, PRIMARY_USER_ID);
byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
@@ -295,7 +282,7 @@
LockscreenCredential pattern = newPattern("123654");
LockscreenCredential newPassword = newPassword("password");
byte[] token = "some-high-entropy-secure-token".getBytes();
- initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+ initializeCredential(password, PRIMARY_USER_ID);
byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID);
long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
@@ -318,7 +305,6 @@
public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration()
throws RemoteException {
final byte[] token = "some-high-entropy-secure-token".getBytes();
- enableSyntheticPassword();
long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID));
@@ -331,7 +317,7 @@
final byte[] token = "some-high-entropy-secure-token".getBytes();
// By first setting a password and then clearing it, we enter the state where SP is
// initialized but the user currently has no password
- initializeCredentialUnderSP(newPassword("password"), PRIMARY_USER_ID);
+ initializeCredential(newPassword("password"), PRIMARY_USER_ID);
assertTrue(mService.setLockCredential(nonePassword(), newPassword("password"),
PRIMARY_USER_ID));
assertTrue(mService.isSyntheticPasswordBasedCredential(PRIMARY_USER_ID));
@@ -343,19 +329,15 @@
}
@Test
- public void testEscrowTokenActivatedLaterWithUserPasswordNeedsMigration()
- throws RemoteException {
+ public void testEscrowTokenActivatedLaterWithUserPassword() throws RemoteException {
byte[] token = "some-high-entropy-secure-token".getBytes();
LockscreenCredential password = newPassword("password");
- // Set up pre-SP user password
- disableSyntheticPassword();
mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
- enableSyntheticPassword();
long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
// Token not activated immediately since user password exists
assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
- // Activate token (password gets migrated to SP at the same time)
+ // Activate token
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
// Verify token is activated
@@ -383,7 +365,7 @@
LockscreenCredential password = newPassword("password");
LockscreenCredential pattern = newPattern("123654");
- initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+ initializeCredential(password, PRIMARY_USER_ID);
long handle0 = mLocalService.addEscrowToken(token0, PRIMARY_USER_ID, null);
long handle1 = mLocalService.addEscrowToken(token1, PRIMARY_USER_ID, null);
@@ -412,7 +394,6 @@
byte[] token = "some-high-entropy-secure-token".getBytes();
mService.mHasSecureLockScreen = false;
- enableSyntheticPassword();
long handle = mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
@@ -515,7 +496,7 @@
LockscreenCredential password = newPassword("testGsiDisablesAuthSecret-password");
- initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+ initializeCredential(password, PRIMARY_USER_ID);
assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
@@ -525,7 +506,7 @@
public void testUnlockUserWithToken() throws Exception {
LockscreenCredential password = newPassword("password");
byte[] token = "some-high-entropy-secure-token".getBytes();
- initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+ initializeCredential(password, PRIMARY_USER_ID);
// Disregard any reportPasswordChanged() invocations as part of credential setup.
flushHandlerTasks();
reset(mDevicePolicyManager);
@@ -546,7 +527,7 @@
@Test
public void testPasswordChange_NoOrphanedFilesLeft() throws Exception {
LockscreenCredential password = newPassword("password");
- initializeCredentialUnderSP(password, PRIMARY_USER_ID);
+ initializeCredential(password, PRIMARY_USER_ID);
assertTrue(mService.setLockCredential(password, password, PRIMARY_USER_ID));
assertNoOrphanedFilesLeft(PRIMARY_USER_ID);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index fd1536c..4550b56 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -1622,7 +1622,9 @@
ZenModeConfig.toScheduleConditionId(si),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test");
+ // We need the package name to be something that's not "android" so there aren't any
+ // existing rules under that package.
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
assertNotNull(id);
}
try {
@@ -1632,12 +1634,41 @@
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test");
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
fail("allowed too many rules to be created");
} catch (IllegalArgumentException e) {
// yay
}
+ }
+ @Test
+ public void testAddAutomaticZenRule_beyondSystemLimit_differentComponents() {
+ // Make sure the system limit is enforced per-package even with different component provider
+ // names.
+ for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) {
+ ScheduleInfo si = new ScheduleInfo();
+ si.startHour = i;
+ AutomaticZenRule zenRule = new AutomaticZenRule("name" + i,
+ null,
+ new ComponentName("android", "ScheduleConditionProvider" + i),
+ ZenModeConfig.toScheduleConditionId(si),
+ new ZenPolicy.Builder().build(),
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+ assertNotNull(id);
+ }
+ try {
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ null,
+ new ComponentName("android", "ScheduleConditionProviderFinal"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ new ZenPolicy.Builder().build(),
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+ fail("allowed too many rules to be created");
+ } catch (IllegalArgumentException e) {
+ // yay
+ }
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 5be1ece..fe3c26a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -37,6 +37,7 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
+import static android.content.pm.ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
@@ -52,6 +53,11 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.ActivityStarter.canEmbedActivity;
+import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK;
+import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -59,6 +65,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -87,6 +94,7 @@
import android.platform.test.annotations.Presubmit;
import android.service.voice.IVoiceInteractionSession;
import android.util.Pair;
+import android.util.Size;
import android.view.Gravity;
import android.window.TaskFragmentOrganizerToken;
@@ -1171,6 +1179,7 @@
null /* inTask */, taskFragment);
assertFalse(taskFragment.hasChild());
+ assertNotNull("Target record must be started on Task.", targetRecord.getParent().asTask());
}
@Test
@@ -1341,6 +1350,58 @@
any());
}
+ @Test
+ public void testCanEmbedActivity() {
+ final Size minDimensions = new Size(1000, 1000);
+ final WindowLayout windowLayout = new WindowLayout(0, 0, 0, 0, 0,
+ minDimensions.getWidth(), minDimensions.getHeight());
+ final ActivityRecord starting = new ActivityBuilder(mAtm)
+ .setUid(UNIMPORTANT_UID)
+ .setWindowLayout(windowLayout)
+ .build();
+
+ // Task fragment hasn't attached to a task yet. Start activity to a new task.
+ TaskFragment taskFragment = new TaskFragmentBuilder(mAtm).build();
+ final Task task = new TaskBuilder(mSupervisor).build();
+
+ assertEquals(EMBEDDING_DISALLOWED_NEW_TASK,
+ canEmbedActivity(taskFragment, starting, task));
+
+ // Starting activity is going to be started on a task different from task fragment's parent
+ // task. Start activity to a new task.
+ task.addChild(taskFragment, POSITION_TOP);
+ final Task newTask = new TaskBuilder(mSupervisor).build();
+
+ assertEquals(EMBEDDING_DISALLOWED_NEW_TASK,
+ canEmbedActivity(taskFragment, starting, newTask));
+
+ // Make task fragment bounds exceed task bounds.
+ final Rect taskBounds = task.getBounds();
+ taskFragment.setBounds(taskBounds.left, taskBounds.top, taskBounds.right + 1,
+ taskBounds.bottom + 1);
+
+ assertEquals(EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
+ canEmbedActivity(taskFragment, starting, task));
+
+ taskFragment.setBounds(taskBounds);
+ starting.info.flags |= FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
+
+ assertEquals(EMBEDDING_ALLOWED, canEmbedActivity(taskFragment, starting, task));
+
+ starting.info.flags &= ~FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING;
+ // Set task fragment's uid as the same as starting activity's uid.
+ taskFragment.setTaskFragmentOrganizer(mock(TaskFragmentOrganizerToken.class),
+ UNIMPORTANT_UID, "test");
+
+ assertEquals(EMBEDDING_ALLOWED, canEmbedActivity(taskFragment, starting, task));
+
+ // Make task fragment bounds smaller than starting activity's minimum dimensions
+ taskFragment.setBounds(0, 0, minDimensions.getWidth() - 1, minDimensions.getHeight() - 1);
+
+ assertEquals(EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
+ canEmbedActivity(taskFragment, starting, task));
+ }
+
private static void startActivityInner(ActivityStarter starter, ActivityRecord target,
ActivityRecord source, ActivityOptions options, Task inTask,
TaskFragment inTaskFragment) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d737963..40e266c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1699,6 +1699,13 @@
assertFalse(displayContent.mPinnedTaskController.isFreezingTaskConfig(pinnedTask));
assertEquals(pinnedActivity.getConfiguration().orientation,
displayContent.getConfiguration().orientation);
+
+ // No need to apply rotation if the display ignores orientation request.
+ doCallRealMethod().when(displayContent).rotationForActivityInDifferentOrientation(any());
+ pinnedActivity.mOrientation = SCREEN_ORIENTATION_LANDSCAPE;
+ displayContent.setIgnoreOrientationRequest(true);
+ assertEquals(WindowConfiguration.ROTATION_UNDEFINED,
+ displayContent.rotationForActivityInDifferentOrientation(pinnedActivity));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 1708ed7..83f375f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1332,7 +1332,7 @@
});
assertSecurityException(expectCallable,
() -> mAtm.startActivityFromRecents(0, new Bundle()));
- assertSecurityException(expectCallable, () -> mAtm.getTaskSnapshot(0, true));
+ assertSecurityException(expectCallable, () -> mAtm.getTaskSnapshot(0, true, false));
assertSecurityException(expectCallable, () -> mAtm.registerTaskStackListener(null));
assertSecurityException(expectCallable,
() -> mAtm.unregisterTaskStackListener(null));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 1c3b869..e47bcc9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -21,6 +21,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.testing.Assert.assertThrows;
@@ -530,7 +531,7 @@
mWindowOrganizerController.mLaunchTaskFragments
.put(mFragmentToken, mTaskFragment);
mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token);
- doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity);
+ doReturn(EMBEDDING_ALLOWED).when(mTaskFragment).isAllowedToEmbedActivity(activity);
clearInvocations(mAtm.mRootWindowContainer);
mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
@@ -920,7 +921,6 @@
.setOrganizer(mOrganizer)
.setBounds(mTaskFragBounds)
.build();
- doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity);
mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
// Reparent activity to mTaskFragment, which is smaller than activity's
@@ -956,7 +956,6 @@
.setOrganizer(mOrganizer)
.setBounds(mTaskFragBounds)
.build();
- doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity);
mWindowOrganizerController.mLaunchTaskFragments.put(oldFragToken, oldTaskFrag);
mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index c0759c1..22101c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -196,6 +196,28 @@
}
@Test
+ public void testUsesOptionsDisplayAreaFeatureIdIfSet() {
+ final TestDisplayContent freeformDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FREEFORM);
+ final TestDisplayContent fullscreenDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FULLSCREEN);
+
+ mCurrent.mPreferredTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
+ ActivityRecord source = createSourceActivity(freeformDisplay);
+
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(fullscreenDisplay.mDisplayId);
+ options.setLaunchTaskDisplayAreaFeatureId(
+ fullscreenDisplay.getDefaultTaskDisplayArea().mFeatureId);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setSource(source).setOptions(options).calculate());
+
+ assertEquals(fullscreenDisplay.getDefaultTaskDisplayArea(),
+ mResult.mPreferredTaskDisplayArea);
+ }
+
+ @Test
public void testUsesSourcesDisplayAreaIdPriorToTaskIfSet() {
final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
@@ -453,7 +475,7 @@
}
@Test
- public void testNotOverrideDisplayAreaWhenActivityOptionsHasDisplayArea() {
+ public void testNotOverrideDisplayAreaWhenActivityOptionsHasDisplayAreaToken() {
final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(mDefaultDisplay,
mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST);
final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN,
@@ -475,6 +497,52 @@
}
@Test
+ public void testNotOverrideDisplayAreaWhenActivityOptionsHasDisplayAreaFeatureId() {
+ final TaskDisplayArea secondaryDisplayArea = createTaskDisplayArea(mDefaultDisplay,
+ mWm, "SecondaryDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST);
+ final Task launchRoot = createTask(secondaryDisplayArea, WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD);
+ launchRoot.mCreatedByOrganizer = true;
+
+ secondaryDisplayArea.setLaunchRootTask(launchRoot, new int[] { WINDOWING_MODE_FULLSCREEN },
+ new int[] { ACTIVITY_TYPE_STANDARD });
+
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchTaskDisplayAreaFeatureId(
+ mDefaultDisplay.getDefaultTaskDisplayArea().mFeatureId);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setOptions(options).calculate());
+
+ assertEquals(
+ mDefaultDisplay.getDefaultTaskDisplayArea(), mResult.mPreferredTaskDisplayArea);
+ }
+
+ @Test
+ public void testUsesOptionsDisplayAreaFeatureIdDisplayIdNotSet() {
+ final TestDisplayContent secondaryDisplay = createNewDisplayContent(
+ WINDOWING_MODE_FULLSCREEN);
+ final TaskDisplayArea tdaOnSecondaryDisplay = createTaskDisplayArea(secondaryDisplay,
+ mWm, "TestTaskDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST);
+
+ final TaskDisplayArea tdaOnDefaultDisplay = createTaskDisplayArea(mDefaultDisplay,
+ mWm, "TestTaskDisplayArea", FEATURE_RUNTIME_TASK_CONTAINER_FIRST);
+
+ mCurrent.mPreferredTaskDisplayArea = tdaOnSecondaryDisplay;
+ ActivityRecord source = createSourceActivity(tdaOnSecondaryDisplay,
+ WINDOWING_MODE_FULLSCREEN);
+
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchTaskDisplayAreaFeatureId(tdaOnSecondaryDisplay.mFeatureId);
+
+ assertEquals(RESULT_CONTINUE,
+ new CalculateRequestBuilder().setSource(source).setOptions(options).calculate());
+ // Display id wasn't specified in ActivityOptions - the activity should be placed on the
+ // default display, into the TaskDisplayArea with the same feature id.
+ assertEquals(tdaOnDefaultDisplay, mResult.mPreferredTaskDisplayArea);
+ }
+
+ @Test
public void testRecalculateFreeformInitialBoundsWithOverrideDisplayArea() {
final TestDisplayContent freeformDisplay = createNewDisplayContent(
WINDOWING_MODE_FREEFORM);
@@ -1822,6 +1890,13 @@
return new ActivityBuilder(mAtm).setTask(rootTask).build();
}
+ private ActivityRecord createSourceActivity(TaskDisplayArea taskDisplayArea,
+ int windowingMode) {
+ final Task rootTask = taskDisplayArea.createRootTask(windowingMode, ACTIVITY_TYPE_STANDARD,
+ true);
+ return new ActivityBuilder(mAtm).setTask(rootTask).build();
+ }
+
private void addFreeformTaskTo(TestDisplayContent display, Rect bounds) {
final Task rootTask = display.getDefaultTaskDisplayArea()
.createRootTask(display.getWindowingMode(), ACTIVITY_TYPE_STANDARD, true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 53595a5..67e0a35 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -686,6 +686,7 @@
assertTrue(ime.mToken.inTransition());
assertTrue(task.inTransition());
assertTrue(asyncRotationController.isTargetToken(decorToken));
+ assertTrue(asyncRotationController.shouldFreezeInsetsPosition(navBar));
screenDecor.setOrientationChanging(false);
// Status bar finishes drawing before the start transaction. Its fade-in animation will be
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 9963827..fba4ff1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -246,6 +246,22 @@
assertEquals(otherWindowInitialZoom, wallpaperWindow.mWallpaperZoomOut, .01f);
}
+ @Test
+ public void testUpdateWallpaperTarget() {
+ final DisplayContent dc = mDisplayContent;
+ final WindowState homeWin = createWallpaperTargetWindow(dc);
+ final WindowState appWin = createWindow(null, TYPE_BASE_APPLICATION, "app");
+ final RecentsAnimationController recentsController = mock(RecentsAnimationController.class);
+ doReturn(true).when(recentsController).isWallpaperVisible(eq(appWin));
+ mWm.setRecentsAnimationController(recentsController);
+
+ dc.mWallpaperController.adjustWallpaperWindows();
+ assertEquals(appWin, dc.mWallpaperController.getWallpaperTarget());
+ // The wallpaper target is gone, so it should adjust to the next target.
+ appWin.removeImmediately();
+ assertEquals(homeWin, dc.mWallpaperController.getWallpaperTarget());
+ }
+
/**
* Tests that the windowing mode of the wallpaper window must always be fullscreen.
*/
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 08bad70..9c2aac0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -223,7 +223,7 @@
mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
- assertTaskVanished(organizer, false /* expectVanished */, rootTask);
+ verify(organizer, times(0)).onTaskVanished(any());
assertFalse(rootTask.isOrganized());
}
@@ -297,7 +297,7 @@
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
- assertTaskVanished(organizer, true /* expectVanished */, rootTask);
+ verify(organizer, times(0)).onTaskVanished(any());
assertFalse(rootTask.isOrganized());
}
@@ -341,7 +341,7 @@
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(3))
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
- assertTaskVanished(organizer2, true /* expectVanished */, rootTask, rootTask2, rootTask3);
+ verify(organizer2, times(0)).onTaskVanished(any());
}
@Test
@@ -395,6 +395,7 @@
assertFalse(task2.isAttached());
// Normal task should keep.
assertTrue(task.isAttached());
+ verify(organizer2, times(0)).onTaskVanished(any());
}
@Test
@@ -439,7 +440,7 @@
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(3))
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
- assertTaskVanished(organizer2, true /* expectVanished */, rootTask, rootTask2, rootTask3);
+ verify(organizer2, times(0)).onTaskVanished(any());
}
@Test
@@ -1264,7 +1265,7 @@
record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription"));
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_APPEARED, pendingEvents.get(0).mEventType);
assertEquals("TestDescription",
@@ -1284,7 +1285,7 @@
rootTask.removeImmediately();
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(0, pendingEvents.size());
}
@@ -1302,7 +1303,7 @@
record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription"));
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_INFO_CHANGED, pendingEvents.get(0).mEventType);
assertEquals("TestDescription",
@@ -1311,7 +1312,7 @@
record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription2"));
waitUntilHandlersIdle();
- pendingEvents = getTaskPendingEvent(rootTask);
+ pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_INFO_CHANGED, pendingEvents.get(0).mEventType);
assertEquals("TestDescription2",
@@ -1334,7 +1335,7 @@
rootTask.removeImmediately();
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType);
assertEquals("TestDescription",
@@ -1355,7 +1356,7 @@
rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType);
}
@@ -1375,14 +1376,16 @@
new IRequestFinishCallback.Default());
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType);
}
- private ArrayList<PendingTaskEvent> getTaskPendingEvent(Task task) {
+ private ArrayList<PendingTaskEvent> getTaskPendingEvent(ITaskOrganizer organizer, Task task) {
ArrayList<PendingTaskEvent> total =
- mWm.mAtmService.mTaskOrganizerController.getPendingEventList();
+ mWm.mAtmService.mTaskOrganizerController
+ .getTaskOrganizerPendingEvents(organizer.asBinder())
+ .getPendingEventList();
ArrayList<PendingTaskEvent> result = new ArrayList();
for (int i = 0; i < total.size(); i++) {
diff --git a/telephony/java/android/telephony/AccessNetworkUtils.java b/telephony/java/android/telephony/AccessNetworkUtils.java
index b5d97ab..c2a9864 100644
--- a/telephony/java/android/telephony/AccessNetworkUtils.java
+++ b/telephony/java/android/telephony/AccessNetworkUtils.java
@@ -4,8 +4,8 @@
import static android.telephony.ServiceState.DUPLEX_MODE_TDD;
import static android.telephony.ServiceState.DUPLEX_MODE_UNKNOWN;
-import android.telephony.AccessNetworkConstants.EutranBandArfcnFrequency;
import android.telephony.AccessNetworkConstants.EutranBand;
+import android.telephony.AccessNetworkConstants.EutranBandArfcnFrequency;
import android.telephony.AccessNetworkConstants.GeranBand;
import android.telephony.AccessNetworkConstants.GeranBandArfcnFrequency;
import android.telephony.AccessNetworkConstants.NgranArfcnFrequency;
@@ -13,7 +13,6 @@
import android.telephony.AccessNetworkConstants.UtranBand;
import android.telephony.AccessNetworkConstants.UtranBandArfcnFrequency;
import android.telephony.ServiceState.DuplexMode;
-import android.util.Log;
import java.util.Arrays;
import java.util.HashSet;
@@ -232,6 +231,108 @@
}
/**
+ * Gets the NR Operating band for a given downlink NRARFCN.
+ *
+ * <p>See 3GPP TS 38.104 Table 5.2-1 NR operating bands in FR1 and
+ * Table 5.2-2 NR operating bands in FR2
+ *
+ * @param nrarfcn The downlink NRARFCN
+ * @return Operating band number, or {@link #INVALID_BAND} if no corresponding band exists
+ */
+ public static int getOperatingBandForNrarfcn(int nrarfcn) {
+ if (nrarfcn >= 422000 && nrarfcn <= 434000) {
+ return NgranBands.BAND_1;
+ } else if (nrarfcn >= 386000 && nrarfcn <= 398000) {
+ return NgranBands.BAND_2;
+ } else if (nrarfcn >= 361000 && nrarfcn <= 376000) {
+ return NgranBands.BAND_3;
+ } else if (nrarfcn >= 173800 && nrarfcn <= 178800) {
+ return NgranBands.BAND_5;
+ } else if (nrarfcn >= 524000 && nrarfcn <= 538000) {
+ return NgranBands.BAND_7;
+ } else if (nrarfcn >= 185000 && nrarfcn <= 192000) {
+ return NgranBands.BAND_8;
+ } else if (nrarfcn >= 145800 && nrarfcn <= 149200) {
+ return NgranBands.BAND_12;
+ } else if (nrarfcn >= 151600 && nrarfcn <= 153600) {
+ return NgranBands.BAND_14;
+ } else if (nrarfcn >= 172000 && nrarfcn <= 175000) {
+ return NgranBands.BAND_18;
+ } else if (nrarfcn >= 158200 && nrarfcn <= 164200) {
+ return NgranBands.BAND_20;
+ } else if (nrarfcn >= 386000 && nrarfcn <= 399000) {
+ return NgranBands.BAND_25;
+ } else if (nrarfcn >= 171800 && nrarfcn <= 178800) {
+ return NgranBands.BAND_26;
+ } else if (nrarfcn >= 151600 && nrarfcn <= 160600) {
+ return NgranBands.BAND_28;
+ } else if (nrarfcn >= 143400 && nrarfcn <= 145600) {
+ return NgranBands.BAND_29;
+ } else if (nrarfcn >= 470000 && nrarfcn <= 472000) {
+ return NgranBands.BAND_30;
+ } else if (nrarfcn >= 402000 && nrarfcn <= 405000) {
+ return NgranBands.BAND_34;
+ } else if (nrarfcn >= 514000 && nrarfcn <= 524000) {
+ return NgranBands.BAND_38;
+ } else if (nrarfcn >= 376000 && nrarfcn <= 384000) {
+ return NgranBands.BAND_39;
+ } else if (nrarfcn >= 460000 && nrarfcn <= 480000) {
+ return NgranBands.BAND_40;
+ } else if (nrarfcn >= 499200 && nrarfcn <= 537999) {
+ return NgranBands.BAND_41;
+ } else if (nrarfcn >= 743334 && nrarfcn <= 795000) {
+ return NgranBands.BAND_46;
+ } else if (nrarfcn >= 636667 && nrarfcn <= 646666) {
+ return NgranBands.BAND_48;
+ } else if (nrarfcn >= 286400 && nrarfcn <= 303400) {
+ return NgranBands.BAND_50;
+ } else if (nrarfcn >= 285400 && nrarfcn <= 286400) {
+ return NgranBands.BAND_51;
+ } else if (nrarfcn >= 496700 && nrarfcn <= 499000) {
+ return NgranBands.BAND_53;
+ } else if (nrarfcn >= 422000 && nrarfcn <= 440000) {
+ return NgranBands.BAND_65; // BAND_66 has the same channels
+ } else if (nrarfcn >= 399000 && nrarfcn <= 404000) {
+ return NgranBands.BAND_70;
+ } else if (nrarfcn >= 123400 && nrarfcn <= 130400) {
+ return NgranBands.BAND_71;
+ } else if (nrarfcn >= 295000 && nrarfcn <= 303600) {
+ return NgranBands.BAND_74;
+ } else if (nrarfcn >= 286400 && nrarfcn <= 303400) {
+ return NgranBands.BAND_75;
+ } else if (nrarfcn >= 285400 && nrarfcn <= 286400) {
+ return NgranBands.BAND_76;
+ } else if (nrarfcn >= 620000 && nrarfcn <= 680000) {
+ return NgranBands.BAND_77;
+ } else if (nrarfcn >= 620000 && nrarfcn <= 653333) {
+ return NgranBands.BAND_78;
+ } else if (nrarfcn >= 693334 && nrarfcn <= 733333) {
+ return NgranBands.BAND_79;
+ } else if (nrarfcn >= 499200 && nrarfcn <= 538000) {
+ return NgranBands.BAND_90;
+ } else if (nrarfcn >= 285400 && nrarfcn <= 286400) {
+ return NgranBands.BAND_91;
+ } else if (nrarfcn >= 286400 && nrarfcn <= 303400) {
+ return NgranBands.BAND_92;
+ } else if (nrarfcn >= 285400 && nrarfcn <= 286400) {
+ return NgranBands.BAND_93;
+ } else if (nrarfcn >= 286400 && nrarfcn <= 303400) {
+ return NgranBands.BAND_94;
+ } else if (nrarfcn >= 795000 && nrarfcn <= 875000) {
+ return NgranBands.BAND_96;
+ } else if (nrarfcn >= 2054166 && nrarfcn <= 2104165) {
+ return NgranBands.BAND_257;
+ } else if (nrarfcn >= 2016667 && nrarfcn <= 2070832) {
+ return NgranBands.BAND_258;
+ } else if (nrarfcn >= 2229166 && nrarfcn <= 2279165) {
+ return NgranBands.BAND_260;
+ } else if (nrarfcn >= 2070833 && nrarfcn <= 2084999) {
+ return NgranBands.BAND_261;
+ }
+ return INVALID_BAND;
+ }
+
+ /**
* Gets the GERAN Operating band for a given ARFCN.
*
* <p>See 3GPP TS 45.005 clause 2 for calculation.
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
index cc2ab19..06c098d 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/CallMigrators.kt
@@ -19,6 +19,7 @@
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.LintFix
import com.android.tools.lint.detector.api.Location
+import com.intellij.psi.PsiArrayType
import com.intellij.psi.PsiCallExpression
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiIntersectionType
@@ -26,8 +27,8 @@
import com.intellij.psi.PsiType
import com.intellij.psi.PsiTypeParameter
import com.intellij.psi.PsiWildcardType
-import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.jetbrains.uast.UCallExpression
+import org.jetbrains.uast.UElement
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.UVariable
@@ -42,11 +43,11 @@
) {
open fun report(context: JavaContext, call: UCallExpression, method: PsiMethod) {
val location = context.getLocation(call)
- val itemType = getBoundingClass(context, call, method)
+ val itemType = filter(getBoundingClass(context, call, method))
val fix = (itemType as? PsiClassType)?.let { type ->
getParcelFix(location, this.method.name, getArgumentSuffix(type))
}
- val message = "Unsafe `Parcel.${this.method.name}()` API usage"
+ val message = "Unsafe `${this.method.className}.${this.method.name}()` API usage"
context.report(SaferParcelChecker.ISSUE_UNSAFE_API_USAGE, call, location, message, fix)
}
@@ -73,14 +74,14 @@
}
/**
- * Tries to obtain the type expected by the "receiving" end given a certain {@link UExpression}.
+ * Tries to obtain the type expected by the "receiving" end given a certain {@link UElement}.
*
* This could be an assignment, an argument passed to a method call, to a constructor call, a
* type cast, etc. If no receiving end is found, the type of the UExpression itself is returned.
*/
- protected fun getReceivingType(expression: UExpression): PsiType? {
+ protected fun getReceivingType(expression: UElement): PsiType? {
val parent = expression.uastParent
- val type = when (parent) {
+ var type = when (parent) {
is UCallExpression -> {
val i = parent.valueArguments.indexOf(expression)
val psiCall = parent.sourcePsi as? PsiCallExpression ?: return null
@@ -92,10 +93,13 @@
is UExpression -> parent.getExpressionType()
else -> null
}
- return filter(type ?: expression.getExpressionType())
+ if (type == null && expression is UExpression) {
+ type = expression.getExpressionType()
+ }
+ return type
}
- private fun filter(type: PsiType?): PsiType? {
+ protected fun filter(type: PsiType?): PsiType? {
// It's important that PsiIntersectionType case is above the one that check the type in
// rejects, because for intersect types, the canonicalText is one of the terms.
if (type is PsiIntersectionType) {
@@ -169,7 +173,7 @@
override fun getBoundingClass(
context: JavaContext, call: UCallExpression, method: PsiMethod
): PsiType? {
- val type = getReceivingType(call.uastParent as UExpression) ?: return null
+ val type = getReceivingType(call.uastParent!!) ?: return null
return getItemType(type, container)
}
}
@@ -184,7 +188,7 @@
override fun getBoundingClass(
context: JavaContext, call: UCallExpression, method: PsiMethod
): PsiType? {
- return getReceivingType(call.uastParent as UExpression)
+ return getReceivingType(call.uastParent!!)
}
}
@@ -199,11 +203,27 @@
override fun getBoundingClass(
context: JavaContext, call: UCallExpression, method: PsiMethod
): PsiType? {
- return getReceivingType(call.uastParent as UExpression)
+ return getReceivingType(call.uastParent!!)
}
override fun getArgumentSuffix(type: PsiClassType): String =
"${type.rawType().canonicalText}.class.getClassLoader(), " +
"${type.rawType().canonicalText}.class"
-}
\ No newline at end of file
+}
+
+/**
+ * This class derives the type to be appended by inferring the expected array type
+ * for the method result.
+ */
+class ArrayReturnMigrator(
+ method: Method,
+ rejects: Set<String> = emptySet(),
+) : CallMigrator(method, rejects) {
+ override fun getBoundingClass(
+ context: JavaContext, call: UCallExpression, method: PsiMethod
+ ): PsiType? {
+ val type = getReceivingType(call.uastParent!!)
+ return (type as? PsiArrayType)?.componentType
+ }
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt
index c032fa2..0826e8e 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/Method.kt
@@ -35,4 +35,8 @@
val prefix = if (params.isEmpty()) "" else "${params.joinToString(", ", "<", ">")} "
return "$prefix$clazz.$name(${parameters.joinToString()})"
}
-}
\ No newline at end of file
+
+ val className: String by lazy {
+ clazz.split(".").last()
+ }
+}
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
index 89dbcae..f928263 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/parcel/SaferParcelChecker.kt
@@ -24,6 +24,7 @@
import org.jetbrains.uast.UCallExpression
import java.util.*
+@Suppress("UnstableApiUsage")
class SaferParcelChecker : Detector(), SourceCodeScanner {
override fun getApplicableMethodNames(): List<String> =
MIGRATORS
@@ -47,27 +48,18 @@
return "$prefix$name($parameters)"
}
- /** Taken from androidx-main:core/core/src/main/java/androidx/core/os/BuildCompat.java */
private fun isAtLeastT(context: Context): Boolean {
val project = if (context.isGlobalAnalysis()) context.mainProject else context.project
- return project.isAndroidProject
- && project.minSdkVersion.featureLevel >= 32
- && isAtLeastPreReleaseCodename("Tiramisu", project.minSdkVersion.codename)
- }
-
- /** Taken from androidx-main:core/core/src/main/java/androidx/core/os/BuildCompat.java */
- private fun isAtLeastPreReleaseCodename(min: String, actual: String): Boolean {
- if (actual == "REL") return false
- return actual.uppercase(Locale.ROOT) >= min.uppercase(Locale.ROOT)
+ return project.isAndroidProject && project.minSdkVersion.featureLevel >= 33
}
companion object {
@JvmField
val ISSUE_UNSAFE_API_USAGE: Issue = Issue.create(
id = "UnsafeParcelApi",
- briefDescription = "Use of unsafe Parcel API",
+ briefDescription = "Use of unsafe deserialization API",
explanation = """
- You are using a deprecated Parcel API that doesn't accept the expected class as\
+ You are using a deprecated deserialization API that doesn't accept the expected class as\
a parameter. This means that unexpected classes could be instantiated and\
unexpected code executed.
@@ -83,25 +75,52 @@
)
)
- private val METHOD_READ_SERIALIZABLE = Method("android.os.Parcel", "readSerializable", listOf())
- private val METHOD_READ_ARRAY_LIST = Method("android.os.Parcel", "readArrayList", listOf("java.lang.ClassLoader"))
- private val METHOD_READ_LIST = Method("android.os.Parcel", "readList", listOf("java.util.List", "java.lang.ClassLoader"))
- private val METHOD_READ_PARCELABLE = Method(listOf("T"), "android.os.Parcel", "readParcelable", listOf("java.lang.ClassLoader"))
- private val METHOD_READ_PARCELABLE_LIST = Method(listOf("T"), "android.os.Parcel", "readParcelableList", listOf("java.util.List<T>", "java.lang.ClassLoader"))
- private val METHOD_READ_SPARSE_ARRAY = Method(listOf("T"), "android.os.Parcel", "readSparseArray", listOf("java.lang.ClassLoader"))
+ // Parcel
+ private val PARCEL_METHOD_READ_SERIALIZABLE = Method("android.os.Parcel", "readSerializable", listOf())
+ private val PARCEL_METHOD_READ_ARRAY_LIST = Method("android.os.Parcel", "readArrayList", listOf("java.lang.ClassLoader"))
+ private val PARCEL_METHOD_READ_LIST = Method("android.os.Parcel", "readList", listOf("java.util.List", "java.lang.ClassLoader"))
+ private val PARCEL_METHOD_READ_PARCELABLE = Method(listOf("T"), "android.os.Parcel", "readParcelable", listOf("java.lang.ClassLoader"))
+ private val PARCEL_METHOD_READ_PARCELABLE_LIST = Method(listOf("T"), "android.os.Parcel", "readParcelableList", listOf("java.util.List<T>", "java.lang.ClassLoader"))
+ private val PARCEL_METHOD_READ_SPARSE_ARRAY = Method(listOf("T"), "android.os.Parcel", "readSparseArray", listOf("java.lang.ClassLoader"))
+ private val PARCEL_METHOD_READ_ARRAY = Method("android.os.Parcel", "readArray", listOf("java.lang.ClassLoader"))
+ private val PARCEL_METHOD_READ_PARCELABLE_ARRAY = Method("android.os.Parcel", "readParcelableArray", listOf("java.lang.ClassLoader"))
+
+ // Bundle
+ private val BUNDLE_METHOD_GET_SERIALIZABLE = Method("android.os.Bundle", "getSerializable", listOf("java.lang.String"))
+ private val BUNDLE_METHOD_GET_PARCELABLE = Method(listOf("T"), "android.os.Bundle", "getParcelable", listOf("java.lang.String"))
+ private val BUNDLE_METHOD_GET_PARCELABLE_ARRAY_LIST = Method(listOf("T"), "android.os.Bundle", "getParcelableArrayList", listOf("java.lang.String"))
+ private val BUNDLE_METHOD_GET_PARCELABLE_ARRAY = Method("android.os.Bundle", "getParcelableArray", listOf("java.lang.String"))
+ private val BUNDLE_METHOD_GET_SPARSE_PARCELABLE_ARRAY = Method(listOf("T"), "android.os.Bundle", "getSparseParcelableArray", listOf("java.lang.String"))
+
+ // Intent
+ private val INTENT_METHOD_GET_SERIALIZABLE_EXTRA = Method("android.content.Intent", "getSerializableExtra", listOf("java.lang.String"))
+ private val INTENT_METHOD_GET_PARCELABLE_EXTRA = Method(listOf("T"), "android.content.Intent", "getParcelableExtra", listOf("java.lang.String"))
+ private val INTENT_METHOD_GET_PARCELABLE_ARRAY_EXTRA = Method("android.content.Intent", "getParcelableArrayExtra", listOf("java.lang.String"))
+ private val INTENT_METHOD_GET_PARCELABLE_ARRAY_LIST_EXTRA = Method(listOf("T"), "android.content.Intent", "getParcelableArrayListExtra", listOf("java.lang.String"))
// TODO: Write migrators for methods below
- private val METHOD_READ_ARRAY = Method("android.os.Parcel", "readArray", listOf("java.lang.ClassLoader"))
- private val METHOD_READ_PARCELABLE_ARRAY = Method("android.os.Parcel", "readParcelableArray", listOf("java.lang.ClassLoader"))
- private val METHOD_READ_PARCELABLE_CREATOR = Method("android.os.Parcel", "readParcelableCreator", listOf("java.lang.ClassLoader"))
+ private val PARCEL_METHOD_READ_PARCELABLE_CREATOR = Method("android.os.Parcel", "readParcelableCreator", listOf("java.lang.ClassLoader"))
private val MIGRATORS = listOf(
- ReturnMigrator(METHOD_READ_PARCELABLE, setOf("android.os.Parcelable")),
- ContainerArgumentMigrator(METHOD_READ_LIST, 0, "java.util.List"),
- ContainerReturnMigrator(METHOD_READ_ARRAY_LIST, "java.util.Collection"),
- ContainerReturnMigrator(METHOD_READ_SPARSE_ARRAY, "android.util.SparseArray"),
- ContainerArgumentMigrator(METHOD_READ_PARCELABLE_LIST, 0, "java.util.List"),
- ReturnMigratorWithClassLoader(METHOD_READ_SERIALIZABLE),
+ ReturnMigrator(PARCEL_METHOD_READ_PARCELABLE, setOf("android.os.Parcelable")),
+ ContainerArgumentMigrator(PARCEL_METHOD_READ_LIST, 0, "java.util.List"),
+ ContainerReturnMigrator(PARCEL_METHOD_READ_ARRAY_LIST, "java.util.Collection"),
+ ContainerReturnMigrator(PARCEL_METHOD_READ_SPARSE_ARRAY, "android.util.SparseArray"),
+ ContainerArgumentMigrator(PARCEL_METHOD_READ_PARCELABLE_LIST, 0, "java.util.List"),
+ ReturnMigratorWithClassLoader(PARCEL_METHOD_READ_SERIALIZABLE),
+ ArrayReturnMigrator(PARCEL_METHOD_READ_ARRAY, setOf("java.lang.Object")),
+ ArrayReturnMigrator(PARCEL_METHOD_READ_PARCELABLE_ARRAY, setOf("android.os.Parcelable")),
+
+ ReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE, setOf("android.os.Parcelable")),
+ ContainerReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE_ARRAY_LIST, "java.util.Collection", setOf("android.os.Parcelable")),
+ ArrayReturnMigrator(BUNDLE_METHOD_GET_PARCELABLE_ARRAY, setOf("android.os.Parcelable")),
+ ContainerReturnMigrator(BUNDLE_METHOD_GET_SPARSE_PARCELABLE_ARRAY, "android.util.SparseArray", setOf("android.os.Parcelable")),
+ ReturnMigrator(BUNDLE_METHOD_GET_SERIALIZABLE, setOf("java.io.Serializable")),
+
+ ReturnMigrator(INTENT_METHOD_GET_PARCELABLE_EXTRA, setOf("android.os.Parcelable")),
+ ContainerReturnMigrator(INTENT_METHOD_GET_PARCELABLE_ARRAY_LIST_EXTRA, "java.util.Collection", setOf("android.os.Parcelable")),
+ ArrayReturnMigrator(INTENT_METHOD_GET_PARCELABLE_ARRAY_EXTRA, setOf("android.os.Parcelable")),
+ ReturnMigrator(INTENT_METHOD_GET_SERIALIZABLE_EXTRA, setOf("java.io.Serializable")),
)
}
-}
\ No newline at end of file
+}
diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
index 05c7850..e686695 100644
--- a/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
+++ b/tools/lint/checks/src/test/java/com/google/android/lint/parcel/SaferParcelCheckerTest.kt
@@ -27,20 +27,22 @@
override fun getDetector(): Detector = SaferParcelChecker()
override fun getIssues(): List<Issue> = listOf(
- SaferParcelChecker.ISSUE_UNSAFE_API_USAGE
+ SaferParcelChecker.ISSUE_UNSAFE_API_USAGE
)
override fun lint(): TestLintTask =
- super.lint()
- .allowMissingSdk(true)
- // We don't do partial analysis in the platform
- .skipTestModes(TestMode.PARTIAL)
+ super.lint()
+ .allowMissingSdk(true)
+ // We don't do partial analysis in the platform
+ .skipTestModes(TestMode.PARTIAL)
- fun testDetectUnsafeReadSerializable() {
+ /** Parcel Tests */
+
+ fun testParcelDetectUnsafeReadSerializable() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.os.Parcel;
import java.io.Serializable;
@@ -51,27 +53,27 @@
}
}
"""
- ).indented(),
- *includes
- )
- .expectIdenticalTestModeOutput(false)
- .run()
- .expect(
- """
+ ).indented(),
+ *includes
+ )
+ .expectIdenticalTestModeOutput(false)
+ .run()
+ .expect(
+ """
src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readSerializable() \
API usage [UnsafeParcelApi]
Serializable ans = p.readSerializable();
~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
""".addLineContinuation()
- )
+ )
}
- fun testDoesNotDetectSafeReadSerializable() {
+ fun testParcelDoesNotDetectSafeReadSerializable() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.os.Parcel;
import java.io.Serializable;
@@ -82,18 +84,18 @@
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect("No warnings.")
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
}
- fun testDetectUnsafeReadArrayList() {
+ fun testParcelDetectUnsafeReadArrayList() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.os.Parcel;
@@ -103,26 +105,26 @@
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect(
- """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
src/test/pkg/TestClass.java:6: Warning: Unsafe Parcel.readArrayList() API \
usage [UnsafeParcelApi]
ArrayList ans = p.readArrayList(null);
~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
""".addLineContinuation()
- )
+ )
}
- fun testDoesNotDetectSafeReadArrayList() {
+ fun testParcelDoesNotDetectSafeReadArrayList() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -133,18 +135,18 @@
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect("No warnings.")
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
}
- fun testDetectUnsafeReadList() {
+ fun testParcelDetectUnsafeReadList() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -157,26 +159,26 @@
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect(
- """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
src/test/pkg/TestClass.java:9: Warning: Unsafe Parcel.readList() API usage \
[UnsafeParcelApi]
p.readList(list, null);
~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
""".addLineContinuation()
- )
+ )
}
- fun testDoesNotDetectSafeReadList() {
+ fun testDParceloesNotDetectSafeReadList() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -189,18 +191,18 @@
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect("No warnings.")
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
}
- fun testDetectUnsafeReadParcelable() {
+ fun testParcelDetectUnsafeReadParcelable() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -211,26 +213,26 @@
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect(
- """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readParcelable() API \
usage [UnsafeParcelApi]
Intent ans = p.readParcelable(null);
~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
""".addLineContinuation()
- )
+ )
}
- fun testDoesNotDetectSafeReadParcelable() {
+ fun testParcelDoesNotDetectSafeReadParcelable() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -241,18 +243,18 @@
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect("No warnings.")
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
}
- fun testDetectUnsafeReadParcelableList() {
+ fun testParcelDetectUnsafeReadParcelableList() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -265,26 +267,26 @@
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect(
- """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
src/test/pkg/TestClass.java:9: Warning: Unsafe Parcel.readParcelableList() \
API usage [UnsafeParcelApi]
List<Intent> ans = p.readParcelableList(list, null);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
""".addLineContinuation()
- )
+ )
}
- fun testDoesNotDetectSafeReadParcelableList() {
+ fun testParcelDoesNotDetectSafeReadParcelableList() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -298,18 +300,18 @@
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect("No warnings.")
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
}
- fun testDetectUnsafeReadSparseArray() {
+ fun testParcelDetectUnsafeReadSparseArray() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -321,26 +323,26 @@
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect(
- """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
src/test/pkg/TestClass.java:8: Warning: Unsafe Parcel.readSparseArray() API\
usage [UnsafeParcelApi]
SparseArray<Intent> ans = p.readSparseArray(null);
~~~~~~~~~~~~~~~~~~~~~~~
0 errors, 1 warnings
""".addLineContinuation()
- )
+ )
}
- fun testDoesNotDetectSafeReadSparseArray() {
+ fun testParcelDoesNotDetectSafeReadSparseArray() {
lint()
- .files(
- java(
- """
+ .files(
+ java(
+ """
package test.pkg;
import android.content.Intent;
import android.os.Parcel;
@@ -353,21 +355,383 @@
}
}
"""
- ).indented(),
- *includes
- )
- .run()
- .expect("No warnings.")
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
}
+ fun testParcelDetectUnsafeReadSArray() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Parcel;
+
+ public class TestClass {
+ private TestClass(Parcel p) {
+ Intent[] ans = p.readArray(null);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readArray() API\
+ usage [UnsafeParcelApi]
+ Intent[] ans = p.readArray(null);
+ ~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testParcelDoesNotDetectSafeReadArray() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Parcel;
+
+ public class TestClass {
+ private TestClass(Parcel p) {
+ Intent[] ans = p.readArray(null, Intent.class);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
+ }
+
+ fun testParcelDetectUnsafeReadParcelableSArray() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Parcel;
+
+ public class TestClass {
+ private TestClass(Parcel p) {
+ Intent[] ans = p.readParcelableArray(null);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Unsafe Parcel.readParcelableArray() API\
+ usage [UnsafeParcelApi]
+ Intent[] ans = p.readParcelableArray(null);
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testParcelDoesNotDetectSafeReadParcelableArray() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Parcel;
+
+ public class TestClass {
+ private TestClass(Parcel p) {
+ Intent[] ans = p.readParcelableArray(null, Intent.class);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
+ }
+
+ /** Bundle Tests */
+
+ fun testBundleDetectUnsafeGetParcelable() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Bundle;
+
+ public class TestClass {
+ private TestClass(Bundle b) {
+ Intent ans = b.getParcelable("key");
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getParcelable() API usage [UnsafeParcelApi]
+ Intent ans = b.getParcelable("key");
+ ~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testBundleDoesNotDetectSafeGetParcelable() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Bundle;
+
+ public class TestClass {
+ private TestClass(Bundle b) {
+ Intent ans = b.getParcelable("key", Intent.class);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
+ }
+
+ fun testBundleDetectUnsafeGetParcelableArrayList() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Bundle;
+
+ public class TestClass {
+ private TestClass(Bundle b) {
+ ArrayList<Intent> ans = b.getParcelableArrayList("key");
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getParcelableArrayList() API usage [UnsafeParcelApi]
+ ArrayList<Intent> ans = b.getParcelableArrayList("key");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testBundleDoesNotDetectSafeGetParcelableArrayList() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Bundle;
+
+ public class TestClass {
+ private TestClass(Bundle b) {
+ ArrayList<Intent> ans = b.getParcelableArrayList("key", Intent.class);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
+ }
+
+ fun testBundleDetectUnsafeGetParcelableArray() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Bundle;
+
+ public class TestClass {
+ private TestClass(Bundle b) {
+ Intent[] ans = b.getParcelableArray("key");
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getParcelableArray() API usage [UnsafeParcelApi]
+ Intent[] ans = b.getParcelableArray("key");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testBundleDoesNotDetectSafeGetParcelableArray() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Bundle;
+
+ public class TestClass {
+ private TestClass(Bundle b) {
+ Intent[] ans = b.getParcelableArray("key", Intent.class);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
+ }
+
+ fun testBundleDetectUnsafeGetSparseParcelableArray() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Bundle;
+
+ public class TestClass {
+ private TestClass(Bundle b) {
+ SparseArray<Intent> ans = b.getSparseParcelableArray("key");
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:7: Warning: Unsafe Bundle.getSparseParcelableArray() API usage [UnsafeParcelApi]
+ SparseArray<Intent> ans = b.getSparseParcelableArray("key");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testBundleDoesNotDetectSafeGetSparseParcelableArray() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+ import android.os.Bundle;
+
+ public class TestClass {
+ private TestClass(Bundle b) {
+ SparseArray<Intent> ans = b.getSparseParcelableArray("key", Intent.class);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
+ }
+
+ /** Intent Tests */
+
+ fun testIntentDetectUnsafeGetParcelableExtra() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+
+ public class TestClass {
+ private TestClass(Intent i) {
+ Intent ans = i.getParcelableExtra("name");
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect(
+ """
+ src/test/pkg/TestClass.java:6: Warning: Unsafe Intent.getParcelableExtra() API usage [UnsafeParcelApi]
+ Intent ans = i.getParcelableExtra("name");
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ 0 errors, 1 warnings
+ """.addLineContinuation()
+ )
+ }
+
+ fun testIntentDoesNotDetectSafeGetParcelableExtra() {
+ lint()
+ .files(
+ java(
+ """
+ package test.pkg;
+ import android.content.Intent;
+
+ public class TestClass {
+ private TestClass(Intent i) {
+ Intent ans = i.getParcelableExtra("name", Intent.class);
+ }
+ }
+ """
+ ).indented(),
+ *includes
+ )
+ .run()
+ .expect("No warnings.")
+ }
+
+
/** Stubs for classes used for testing */
private val includes =
- arrayOf(
- manifest().minSdk("Tiramisu"),
- java(
- """
+ arrayOf(
+ manifest().minSdk("33"),
+ java(
+ """
package android.os;
import java.util.ArrayList;
import java.util.List;
@@ -375,7 +739,7 @@
import java.util.HashMap;
public final class Parcel {
- // Deprecateds
+ // Deprecated
public Object[] readArray(ClassLoader loader) { return null; }
public ArrayList readArrayList(ClassLoader loader) { return null; }
public HashMap readHashMap(ClassLoader loader) { return null; }
@@ -402,26 +766,57 @@
public <T> SparseArray<T> readSparseArray(ClassLoader loader, Class<? extends T> clazz) { return null; }
}
"""
- ).indented(),
- java(
+ ).indented(),
+ java(
+ """
+ package android.os;
+ import java.util.ArrayList;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.HashMap;
+
+ public final class Bundle {
+ // Deprecated
+ public <T extends Parcelable> T getParcelable(String key) { return null; }
+ public <T extends Parcelable> ArrayList<T> getParcelableArrayList(String key) { return null; }
+ public Parcelable[] getParcelableArray(String key) { return null; }
+ public <T extends Parcelable> SparseArray<T> getSparseParcelableArray(String key) { return null; }
+
+ // Replacements
+ public <T> T getParcelable(String key, Class<T> clazz) { return null; }
+ public <T> ArrayList<T> getParcelableArrayList(String key, Class<? extends T> clazz) { return null; }
+ public <T> T[] getParcelableArray(String key, Class<T> clazz) { return null; }
+ public <T> SparseArray<T> getSparseParcelableArray(String key, Class<? extends T> clazz) { return null; }
+
+ }
"""
+ ).indented(),
+ java(
+ """
package android.os;
public interface Parcelable {}
"""
- ).indented(),
- java(
- """
+ ).indented(),
+ java(
+ """
package android.content;
- public class Intent implements Parcelable, Cloneable {}
+ public class Intent implements Parcelable, Cloneable {
+ // Deprecated
+ public <T extends Parcelable> T getParcelableExtra(String name) { return null; }
+
+ // Replacements
+ public <T> T getParcelableExtra(String name, Class<T> clazz) { return null; }
+
+ }
"""
- ).indented(),
- java(
- """
+ ).indented(),
+ java(
+ """
package android.util;
public class SparseArray<E> implements Cloneable {}
"""
- ).indented(),
- )
+ ).indented(),
+ )
// Substitutes "backslash + new line" with an empty string to imitate line continuation
private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "")