Merge "Make WallpaperMS bind wallpaper component PendingIntent immutable." into rvc-dev
diff --git a/api/current.txt b/api/current.txt
index 41a74cf..952ccda 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12097,6 +12097,7 @@
field public static final String FEATURE_COMPANION_DEVICE_SETUP = "android.software.companion_device_setup";
field public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
field public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
+ field public static final String FEATURE_CONTROLS = "android.software.controls";
field public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
field public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded";
field public static final String FEATURE_ETHERNET = "android.hardware.ethernet";
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 1d9f20e..4db0f91 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -1256,7 +1256,8 @@
*/
message ChargingStateChanged {
// State of the battery, from frameworks/base/core/proto/android/os/enums.proto.
- optional android.os.BatteryStatusEnum state = 1;
+ optional android.os.BatteryStatusEnum state = 1
+ [(state_field_option).exclusive_state = true, (state_field_option).nested = false];
}
/**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4d718ef..1e9cddbb 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3049,6 +3049,16 @@
public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: The device supports a system interface for the user to select
+ * and bind device control services provided by applications.
+ *
+ * @see android.service.controls.ControlsProviderService
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CONTROLS = "android.software.controls";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
* the requisite hardware support to support reboot escrow of synthetic password for updates.
*
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index 321dc9e..958c7fb 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -92,10 +92,7 @@
}
}
- if (!result.mDefaultStorage.startLoading()) {
- // TODO(b/146080380): add incremental-specific error code
- throw new IOException("Failed to start loading data for Incremental installation.");
- }
+ result.startLoading();
return result;
}
@@ -144,6 +141,15 @@
}
/**
+ * Starts or re-starts loading of data.
+ */
+ public void startLoading() throws IOException {
+ if (!mDefaultStorage.startLoading()) {
+ throw new IOException("Failed to start loading data for Incremental installation.");
+ }
+ }
+
+ /**
* Resets the states and unbinds storage instances for an installation session.
* TODO(b/136132412): make sure unnecessary binds are removed but useful storages are kept
*/
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ae88ba5..e0bc764 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9936,6 +9936,11 @@
* @hide */
public static final String PACKAGE_VERIFIER_TIMEOUT = "verifier_timeout";
+ /** Timeout for app integrity verification.
+ * @hide */
+ public static final String APP_INTEGRITY_VERIFICATION_TIMEOUT =
+ "app_integrity_verification_timeout";
+
/** Default response code for package verification.
* @hide */
public static final String PACKAGE_VERIFIER_DEFAULT_RESPONSE = "verifier_default_response";
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index db6fe0f..bd811fc 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -134,6 +134,23 @@
// we need to preserve the old one until the new one has drawn.
SurfaceControl mDeferredDestroySurfaceControl;
SurfaceControl mBackgroundControl;
+
+ /**
+ * We use this lock in SOME cases when reading or writing SurfaceControl,
+ * but use the following model so that the RenderThread can run locklessly
+ * in the position up-date case.
+ *
+ * 1. UI Thread can read from mSurfaceControl (use in Transactions) without
+ * holding the lock.
+ * 2. UI Thread will hold the lock when writing to mSurfaceControl (calling release
+ * or remove).
+ * 3. Render thread will also hold the lock when writing to mSurfaceControl (e.g.
+ * calling release from positionLost).
+ * 3. RenderNode.PositionUpdateListener::positionChanged will only be called
+ * when the UI thread is paused (blocked on the Render thread).
+ * 4. positionChanged thus will not be required to hold the lock as the
+ * UI thread is blocked, and the other writer is the RT itself.
+ */
final Object mSurfaceControlLock = new Object();
final Rect mTmpRect = new Rect();
@@ -1297,15 +1314,19 @@
(viewRoot != null ? viewRoot.getBLASTSyncTransaction() : mRtTransaction) :
mRtTransaction;
- if (frameNumber > 0 && viewRoot != null && !useBLAST) {
- if (viewRoot.mSurface.isValid()) {
- mRtTransaction.deferTransactionUntil(mSurfaceControl,
- viewRoot.getRenderSurfaceControl(), frameNumber);
- }
- }
- t.hide(mSurfaceControl);
-
+ /**
+ * positionLost can be called while UI thread is un-paused so we
+ * need to hold the lock here.
+ */
synchronized (mSurfaceControlLock) {
+ if (frameNumber > 0 && viewRoot != null && !useBLAST) {
+ if (viewRoot.mSurface.isValid()) {
+ mRtTransaction.deferTransactionUntil(mSurfaceControl,
+ viewRoot.getRenderSurfaceControl(), frameNumber);
+ }
+ }
+ t.hide(mSurfaceControl);
+
if (mRtReleaseSurfaces) {
mRtReleaseSurfaces = false;
mRtTransaction.remove(mSurfaceControl);
diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java
index 4c72474..e4ac588 100644
--- a/core/java/android/view/inputmethod/InlineSuggestion.java
+++ b/core/java/android/view/inputmethod/InlineSuggestion.java
@@ -326,11 +326,13 @@
@MainThread
private void handleOnSurfacePackageReleased() {
- mSurfacePackage = null;
- try {
- mInlineContentProvider.onSurfacePackageReleased();
- } catch (RemoteException e) {
- Slog.w(TAG, "Error calling onSurfacePackageReleased(): " + e);
+ if (mSurfacePackage != null) {
+ try {
+ mInlineContentProvider.onSurfacePackageReleased();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error calling onSurfacePackageReleased(): " + e);
+ }
+ mSurfacePackage = null;
}
}
@@ -573,7 +575,7 @@
};
@DataClass.Generated(
- time = 1588308946517L,
+ time = 1589396017700L,
codegenVersion = "1.0.15",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java",
inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\nprivate @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineContentCallbackImplParceling.class) @android.annotation.Nullable android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl mInlineContentCallback\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>)\nprivate static boolean isValid(int,int,int)\nprivate synchronized android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl getInlineContentCallback(android.content.Context,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index f8522ed..f78ec7c 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -189,14 +189,29 @@
public void onCloseWindow(WebView window) {}
/**
- * Tell the client to display a javascript alert dialog. If the client
- * returns {@code true}, WebView will assume that the client will handle the
- * dialog. If the client returns {@code false}, it will continue execution.
+ * Notify the host application that the web page wants to display a
+ * JavaScript {@code alert()} dialog.
+ * <p>The default behavior if this method returns {@code false} or is not
+ * overridden is to show a dialog containing the alert message and suspend
+ * JavaScript execution until the dialog is dismissed.
+ * <p>To show a custom dialog, the app should return {@code true} from this
+ * method, in which case the default dialog will not be shown and JavaScript
+ * execution will be suspended. The app should call
+ * {@code JsResult.confirm()} when the custom dialog is dismissed such that
+ * JavaScript execution can be resumed.
+ * <p>To suppress the dialog and allow JavaScript execution to
+ * continue, call {@code JsResult.confirm()} immediately and then return
+ * {@code true}.
+ * <p>Note that if the {@link WebChromeClient} is {@code null}, the default
+ * dialog will be suppressed and Javascript execution will continue
+ * immediately.
+ *
* @param view The WebView that initiated the callback.
* @param url The url of the page requesting the dialog.
* @param message Message to be displayed in the window.
- * @param result A JsResult to confirm that the user hit enter.
- * @return boolean Whether the client will handle the alert dialog.
+ * @param result A JsResult to confirm that the user closed the window.
+ * @return boolean {@code true} if the request is handled or ignored.
+ * {@code false} if WebView needs to show the default dialog.
*/
public boolean onJsAlert(WebView view, String url, String message,
JsResult result) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 29914aa..226f5797 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8315,23 +8315,6 @@
return false;
}
- /**
- * Returns true if pressing TAB in this field advances focus instead
- * of inserting the character. Insert tabs only in multi-line editors.
- */
- private boolean shouldAdvanceFocusOnTab() {
- if (getKeyListener() != null && !mSingleLine && mEditor != null
- && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
- == EditorInfo.TYPE_CLASS_TEXT) {
- int multilineFlags = EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
- | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
- if ((mEditor.mInputType & multilineFlags) != 0) {
- return false;
- }
- }
- return true;
- }
-
private boolean isDirectionalNavigationKey(int keyCode) {
switch(keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
@@ -8400,9 +8383,8 @@
case KeyEvent.KEYCODE_TAB:
if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
- if (shouldAdvanceFocusOnTab()) {
- return KEY_EVENT_NOT_HANDLED;
- }
+ // Tab is used to move focus.
+ return KEY_EVENT_NOT_HANDLED;
}
break;
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 9c2df13..3084f2a 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -358,6 +358,7 @@
: isHttpSchemeAndViewAction(getTargetIntent());
mSupportsAlwaysUseOption = supportsAlwaysUseOption;
+ mWorkProfileUserHandle = fetchWorkProfileUserProfile();
// The last argument of createResolverListAdapter is whether to do special handling
// of the last used choice to highlight it in the list. We need to always
@@ -366,7 +367,6 @@
// to handle. We also turn it off when the work tab is shown to simplify the UX.
boolean filterLastUsed = mSupportsAlwaysUseOption && !isVoiceInteraction()
&& !shouldShowTabs();
- mWorkProfileUserHandle = fetchWorkProfileUserProfile();
mMultiProfilePagerAdapter = createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
if (configureContentView()) {
return;
@@ -1612,6 +1612,7 @@
}
private void setupProfileTabs() {
+ maybeHideDivider();
TabHost tabHost = findViewById(R.id.profile_tabhost);
tabHost.setup();
ViewPager viewPager = findViewById(R.id.profile_pager);
@@ -1660,6 +1661,17 @@
findViewById(R.id.resolver_tab_divider).setVisibility(View.VISIBLE);
}
+ private void maybeHideDivider() {
+ if (!isIntentPicker()) {
+ return;
+ }
+ final View divider = findViewById(R.id.divider);
+ if (divider == null) {
+ return;
+ }
+ divider.setVisibility(View.GONE);
+ }
+
/**
* Callback called when user changes the profile tab.
* <p>This method is intended to be overridden by subclasses.
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 49c9302..ad6c7e8 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -132,6 +132,7 @@
}
};
}
+
private static final String TAG = "ScreenshotHelper";
// Time until we give up on the screenshot & show an error instead.
@@ -146,8 +147,6 @@
mContext = context;
}
-
-
/**
* Request a screenshot be taken.
*
@@ -284,8 +283,8 @@
break;
case SCREENSHOT_MSG_PROCESS_COMPLETE:
synchronized (mScreenshotLock) {
- if (mScreenshotConnection == myConn) {
- mContext.unbindService(mScreenshotConnection);
+ if (myConn != null && mScreenshotConnection == myConn) {
+ mContext.unbindService(myConn);
mScreenshotConnection = null;
mScreenshotService = null;
}
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f3f3d47d..54d14f8 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1475,7 +1475,7 @@
<string name="permdesc_mediaLocation">Allows the app to read locations from your media collection.</string>
<!-- Title shown when the system-provided biometric dialog is shown, asking the user to authenticate. [CHAR LIMIT=40] -->
- <string name="biometric_dialog_default_title">Verify it\u2018s you</string>
+ <string name="biometric_dialog_default_title">Verify it\u2019s you</string>
<!-- Message shown when biometric hardware is not available [CHAR LIMIT=50] -->
<string name="biometric_error_hw_unavailable">Biometric hardware unavailable</string>
<!-- Message shown when biometric authentication was canceled by the user [CHAR LIMIT=50] -->
@@ -3595,31 +3595,37 @@
<!-- Notification body when new external media is detected [CHAR LIMIT=30] -->
<string name="ext_media_new_notification_title">New <xliff:g id="name" example="SD card">%s</xliff:g></string>
+ <!-- Automotive specific notification body when new external media is detected [CHAR LIMIT=30] -->
+ <string name="ext_media_new_notification_title" product="automotive"><xliff:g id="name" example="SD card">%s</xliff:g> isn\u2019t working</string>
<!-- Notification body when new external media is detected [CHAR LIMIT=NONE] -->
<string name="ext_media_new_notification_message">Tap to set up</string>
- <!-- Automotive specific notification body when new external media is detected. Empty because there is no fix action (b/151671685) [CHAR LIMIT=NONE] -->
- <string name="ext_media_new_notification_message" product="automotive"></string>
+ <!-- Automotive specific notification body when new external media is detected. [CHAR LIMIT=NONE] -->
+ <string name="ext_media_new_notification_message" product="automotive">You may need to reformat the device. Tap to eject.</string>
<!-- Notification body when external media is ready for use [CHAR LIMIT=NONE] -->
<string name="ext_media_ready_notification_message">For transferring photos and media</string>
<!-- Notification title when external media is unmountable (corrupt) [CHAR LIMIT=30] -->
<string name="ext_media_unmountable_notification_title">Issue with <xliff:g id="name" example="SD card">%s</xliff:g></string>
+ <!-- Automotive specific notification title when external media is unmountable (corrupt) [CHAR LIMIT=30] -->
+ <string name="ext_media_unmountable_notification_title" product="automotive"><xliff:g id="name" example="SD card">%s</xliff:g> isn\u2019t working</string>
<!-- Notification body when external media is unmountable (corrupt) [CHAR LIMIT=NONE] -->
<string name="ext_media_unmountable_notification_message">Tap to fix</string>
<!-- TV-specific notification body when external media is unmountable (corrupt) [CHAR LIMIT=NONE] -->
<string name="ext_media_unmountable_notification_message" product="tv"><xliff:g id="name" example="SD card">%s</xliff:g> is corrupt. Select to fix.</string>
- <!-- Automotive specific notification body when external media is unmountable (corrupt). Empty because there is no fix action (b/151671685) [CHAR LIMIT=NONE] -->
- <string name="ext_media_unmountable_notification_message" product="automotive"></string>
+ <!-- Automotive specific notification body when external media is unmountable (corrupt) [CHAR LIMIT=NONE] -->
+ <string name="ext_media_unmountable_notification_message" product="automotive">You may need to reformat the device. Tap to eject.</string>
<!-- Notification title when external media is unsupported [CHAR LIMIT=30] -->
<string name="ext_media_unsupported_notification_title">Unsupported <xliff:g id="name" example="SD card">%s</xliff:g></string>
+ <!-- Automotive specific notification title when external media is unsupported [CHAR LIMIT=30] -->
+ <string name="ext_media_unsupported_notification_title" product="automotive"><xliff:g id="name" example="SD card">%s</xliff:g> isn\u2019t working</string>
<!-- Notification body when external media is unsupported [CHAR LIMIT=NONE] -->
<string name="ext_media_unsupported_notification_message">This device doesn\u2019t support this <xliff:g id="name" example="SD card">%s</xliff:g>. Tap to set up in a supported format.</string>
<!-- TV-specific notification body when external media is unsupported [CHAR LIMIT=NONE] -->
<string name="ext_media_unsupported_notification_message" product="tv">This device doesn\u2019t support this <xliff:g id="name" example="SD card">%s</xliff:g>. Select to set up in a supported format.</string>
- <!-- Automotive specific notification body when external media is unsupported. No action is specified to fix (b/151671685) [CHAR LIMIT=NONE] -->
- <string name="ext_media_unsupported_notification_message" product="automotive">This device doesn\u2019t support this <xliff:g id="name" example="SD card">%s</xliff:g>.</string>
+ <!-- Automotive specific notification body when external media is unsupported [CHAR LIMIT=NONE] -->
+ <string name="ext_media_unsupported_notification_message" product="automotive">You may need to reformat the device</string>
<!-- Notification title when external media is unsafely removed [CHAR LIMIT=30] -->
<string name="ext_media_badremoval_notification_title"><xliff:g id="name" example="SD card">%s</xliff:g> unexpectedly removed</string>
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index ecc3b4f..5c16772 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -28,29 +28,23 @@
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
-import static org.hamcrest.Matchers.equalTo;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assume.assumeTrue;
+import static org.junit.Assert.assertTrue;
import android.content.Context;
-import android.graphics.Insets;
-import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.WindowInsets.Side;
import android.view.WindowInsets.Type;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-
/**
* Tests for {@link ViewRootImpl}
*
@@ -62,59 +56,18 @@
@RunWith(AndroidJUnit4.class)
public class ViewRootImplTest {
- private Context mContext;
- private ViewRootImplAccessor mViewRootImpl;
+ private ViewRootImpl mViewRootImpl;
@Before
public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ final Context context = getInstrumentation().getTargetContext();
- InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- mViewRootImpl = new ViewRootImplAccessor(
- new ViewRootImpl(mContext, mContext.getDisplayNoVerify()));
- });
- }
-
- @Test
- public void negativeInsets_areSetToZero() throws Exception {
- assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
-
- mViewRootImpl.getAttachInfo().getContentInsets().set(-10, -20, -30 , -40);
- mViewRootImpl.getAttachInfo().getStableInsets().set(-10, -20, -30 , -40);
- final WindowInsets insets = mViewRootImpl.getWindowInsets(true /* forceConstruct */);
-
- assertThat(insets.getSystemWindowInsets(), equalTo(Insets.NONE));
- assertThat(insets.getStableInsets(), equalTo(Insets.NONE));
- }
-
- @Test
- public void negativeInsets_areSetToZero_positiveAreLeftAsIs() throws Exception {
- assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
-
- mViewRootImpl.getAttachInfo().getContentInsets().set(-10, 20, -30 , 40);
- mViewRootImpl.getAttachInfo().getStableInsets().set(10, -20, 30 , -40);
- final WindowInsets insets = mViewRootImpl.getWindowInsets(true /* forceConstruct */);
-
- assertThat(insets.getSystemWindowInsets(), equalTo(Insets.of(0, 20, 0, 40)));
- assertThat(insets.getStableInsets(), equalTo(Insets.of(10, 0, 30, 0)));
- }
-
- @Test
- public void positiveInsets_areLeftAsIs() throws Exception {
- assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
-
- mViewRootImpl.getAttachInfo().getContentInsets().set(10, 20, 30 , 40);
- mViewRootImpl.getAttachInfo().getStableInsets().set(10, 20, 30 , 40);
- final WindowInsets insets = mViewRootImpl.getWindowInsets(true /* forceConstruct */);
-
- assertThat(insets.getSystemWindowInsets(), equalTo(Insets.of(10, 20, 30, 40)));
- assertThat(insets.getStableInsets(), equalTo(Insets.of(10, 20, 30, 40)));
+ getInstrumentation().runOnMainSync(() ->
+ mViewRootImpl = new ViewRootImpl(context, context.getDisplayNoVerify()));
}
@Test
public void adjustLayoutParamsForCompatibility_layoutFullscreen() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
attrs.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
@@ -125,8 +78,6 @@
@Test
public void adjustLayoutParamsForCompatibility_layoutInScreen() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
attrs.flags = FLAG_LAYOUT_IN_SCREEN;
ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
@@ -137,8 +88,6 @@
@Test
public void adjustLayoutParamsForCompatibility_layoutHideNavigation() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
attrs.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
@@ -149,28 +98,22 @@
@Test
public void adjustLayoutParamsForCompatibility_toast() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_TOAST);
ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
- assertEquals(true, attrs.isFitInsetsIgnoringVisibility());
+ assertTrue(attrs.isFitInsetsIgnoringVisibility());
}
@Test
public void adjustLayoutParamsForCompatibility_systemAlert() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_SYSTEM_ALERT);
ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
- assertEquals(true, attrs.isFitInsetsIgnoringVisibility());
+ assertTrue(attrs.isFitInsetsIgnoringVisibility());
}
@Test
public void adjustLayoutParamsForCompatibility_fitSystemBars() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
@@ -180,8 +123,6 @@
@Test
public void adjustLayoutParamsForCompatibility_noAdjustLayout() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
final int types = Type.all();
final int sides = Side.TOP | Side.LEFT;
@@ -201,11 +142,8 @@
@Test
public void adjustLayoutParamsForCompatibility_noAdjustAppearance() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
- final ViewRootImpl viewRoot = mViewRootImpl.get();
- final WindowInsetsController controller = viewRoot.getInsetsController();
- final WindowManager.LayoutParams attrs = viewRoot.mWindowAttributes;
+ final WindowInsetsController controller = mViewRootImpl.getInsetsController();
+ final WindowManager.LayoutParams attrs = mViewRootImpl.mWindowAttributes;
final int appearance = 0;
controller.setSystemBarsAppearance(appearance, 0xffffffff);
attrs.systemUiVisibility = SYSTEM_UI_FLAG_LOW_PROFILE
@@ -220,11 +158,8 @@
@Test
public void adjustLayoutParamsForCompatibility_noAdjustBehavior() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
- final ViewRootImpl viewRoot = mViewRootImpl.get();
- final WindowInsetsController controller = viewRoot.getInsetsController();
- final WindowManager.LayoutParams attrs = viewRoot.mWindowAttributes;
+ final WindowInsetsController controller = mViewRootImpl.getInsetsController();
+ final WindowManager.LayoutParams attrs = mViewRootImpl.mWindowAttributes;
final int behavior = BEHAVIOR_SHOW_BARS_BY_TOUCH;
controller.setSystemBarsBehavior(behavior);
attrs.systemUiVisibility = SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
@@ -234,59 +169,4 @@
// setSystemBarsBehavior.
assertEquals(behavior, controller.getSystemBarsBehavior());
}
-
- private static class ViewRootImplAccessor {
-
- private final ViewRootImpl mViewRootImpl;
-
- ViewRootImplAccessor(ViewRootImpl viewRootImpl) {
- mViewRootImpl = viewRootImpl;
- }
-
- public ViewRootImpl get() {
- return mViewRootImpl;
- }
-
- AttachInfoAccessor getAttachInfo() throws Exception {
- return new AttachInfoAccessor(
- getField(mViewRootImpl, ViewRootImpl.class.getDeclaredField("mAttachInfo")));
- }
-
- WindowInsets getWindowInsets(boolean forceConstruct) throws Exception {
- return (WindowInsets) invokeMethod(mViewRootImpl,
- ViewRootImpl.class.getDeclaredMethod("getWindowInsets", boolean.class),
- forceConstruct);
- }
-
- class AttachInfoAccessor {
-
- private final Class<?> mClass;
- private final Object mAttachInfo;
-
- AttachInfoAccessor(Object attachInfo) throws Exception {
- mAttachInfo = attachInfo;
- mClass = ViewRootImpl.class.getClassLoader().loadClass(
- "android.view.View$AttachInfo");
- }
-
- Rect getContentInsets() throws Exception {
- return (Rect) getField(mAttachInfo, mClass.getDeclaredField("mContentInsets"));
- }
-
- Rect getStableInsets() throws Exception {
- return (Rect) getField(mAttachInfo, mClass.getDeclaredField("mStableInsets"));
- }
- }
-
- private static Object getField(Object o, Field field) throws Exception {
- field.setAccessible(true);
- return field.get(o);
- }
-
- private static Object invokeMethod(Object o, Method method, Object... args)
- throws Exception {
- method.setAccessible(true);
- return method.invoke(o, args);
- }
- }
}
diff --git a/core/tests/overlaytests/host/Android.bp b/core/tests/overlaytests/host/Android.bp
index 2b38cca..a2fcef5 100644
--- a/core/tests/overlaytests/host/Android.bp
+++ b/core/tests/overlaytests/host/Android.bp
@@ -16,7 +16,7 @@
name: "OverlayHostTests",
srcs: ["src/**/*.java"],
libs: ["tradefed"],
- test_suites: ["device-tests"],
+ test_suites: ["general-tests"],
target_required: [
"OverlayHostTests_NonPlatformSignatureOverlay",
"OverlayHostTests_PlatformSignatureStaticOverlay",
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0389639..bdb6bcc 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -324,6 +324,7 @@
<permission name="android.permission.MANAGE_ROLLBACKS"/>
<permission name="android.permission.MANAGE_USB"/>
<permission name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
+ <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.MOUNT_FORMAT_FILESYSTEMS"/>
<permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java b/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java
index 2bdb85f..03b61e0 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java
@@ -52,6 +52,15 @@
new CarAudioManager.CarVolumeCallback() {
@Override
public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {
+ initVolumeDialogComponent();
+ }
+
+ @Override
+ public void onMasterMuteChanged(int zoneId, int flags) {
+ initVolumeDialogComponent();
+ }
+
+ private void initVolumeDialogComponent() {
if (mVolumeDialogComponent == null) {
mMainHandler.post(() -> {
mVolumeDialogComponent = mVolumeDialogComponentLazy.get();
@@ -60,11 +69,6 @@
mCarAudioManager.unregisterCarVolumeCallback(mVolumeChangeCallback);
}
}
-
- @Override
- public void onMasterMuteChanged(int zoneId, int flags) {
- // ignored
- }
};
private boolean mEnabled;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 1d06df0..008a433 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -475,5 +475,10 @@
public void onRequestFailed(int reason) {
dispatchOnRequestFailed(reason);
}
+
+ @Override
+ public void onSessionUpdated(RoutingSessionInfo sessionInfo) {
+ dispatchDataChanged();
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 887a49b..a860ff1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -25,6 +25,7 @@
import android.util.Log;
import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.bluetooth.A2dpProfile;
@@ -65,6 +66,7 @@
}
private final Collection<DeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
+ private final Object mMediaDevicesLock = new Object();
@VisibleForTesting
final MediaDeviceCallback mMediaDeviceCallback = new MediaDeviceCallback();
@@ -145,7 +147,14 @@
* @param connectDevice the MediaDevice
*/
public void connectDevice(MediaDevice connectDevice) {
- final MediaDevice device = getMediaDeviceById(mMediaDevices, connectDevice.getId());
+ MediaDevice device = null;
+ synchronized (mMediaDevicesLock) {
+ device = getMediaDeviceById(mMediaDevices, connectDevice.getId());
+ }
+ if (device == null) {
+ Log.w(TAG, "connectDevice() connectDevice not in the list!");
+ return;
+ }
if (device instanceof BluetoothMediaDevice) {
final CachedBluetoothDevice cachedDevice =
((BluetoothMediaDevice) device).getCachedDevice();
@@ -184,15 +193,18 @@
* Start scan connected MediaDevice
*/
public void startScan() {
- mMediaDevices.clear();
+ synchronized (mMediaDevicesLock) {
+ mMediaDevices.clear();
+ }
mInfoMediaManager.registerCallback(mMediaDeviceCallback);
mInfoMediaManager.startScan();
}
void dispatchDeviceListUpdate() {
- Collections.sort(mMediaDevices, COMPARATOR);
+ final List<MediaDevice> mediaDevices = new ArrayList<>(mMediaDevices);
+ Collections.sort(mediaDevices, COMPARATOR);
for (DeviceCallback callback : getCallbacks()) {
- callback.onDeviceListUpdate(new ArrayList<>(mMediaDevices));
+ callback.onDeviceListUpdate(mediaDevices);
}
}
@@ -241,9 +253,11 @@
* @return MediaDevice
*/
public MediaDevice getMediaDeviceById(String id) {
- for (MediaDevice mediaDevice : mMediaDevices) {
- if (TextUtils.equals(mediaDevice.getId(), id)) {
- return mediaDevice;
+ synchronized (mMediaDevicesLock) {
+ for (MediaDevice mediaDevice : mMediaDevices) {
+ if (TextUtils.equals(mediaDevice.getId(), id)) {
+ return mediaDevice;
+ }
}
}
Log.i(TAG, "Unable to find device " + id);
@@ -255,6 +269,7 @@
*
* @return MediaDevice
*/
+ @Nullable
public MediaDevice getCurrentConnectedDevice() {
return mCurrentConnectedDevice;
}
@@ -367,17 +382,19 @@
}
private MediaDevice updateCurrentConnectedDevice() {
- MediaDevice phoneMediaDevice = null;
- for (MediaDevice device : mMediaDevices) {
- if (device instanceof BluetoothMediaDevice) {
- if (isActiveDevice(((BluetoothMediaDevice) device).getCachedDevice())) {
+ synchronized (mMediaDevicesLock) {
+ for (MediaDevice device : mMediaDevices) {
+ if (device instanceof BluetoothMediaDevice) {
+ if (isActiveDevice(((BluetoothMediaDevice) device).getCachedDevice())) {
+ return device;
+ }
+ } else if (device instanceof PhoneMediaDevice) {
return device;
}
- } else if (device instanceof PhoneMediaDevice) {
- phoneMediaDevice = device;
}
}
- return mMediaDevices.contains(phoneMediaDevice) ? phoneMediaDevice : null;
+ Log.w(TAG, "updateCurrentConnectedDevice() can't found current connected device");
+ return null;
}
private boolean isActiveDevice(CachedBluetoothDevice device) {
@@ -392,17 +409,26 @@
class MediaDeviceCallback implements MediaManager.MediaDeviceCallback {
@Override
public void onDeviceAdded(MediaDevice device) {
- if (!mMediaDevices.contains(device)) {
- mMediaDevices.add(device);
+ boolean isAdded = false;
+ synchronized (mMediaDevicesLock) {
+ if (!mMediaDevices.contains(device)) {
+ mMediaDevices.add(device);
+ isAdded = true;
+ }
+ }
+
+ if (isAdded) {
dispatchDeviceListUpdate();
}
}
@Override
public void onDeviceListAdded(List<MediaDevice> devices) {
- mMediaDevices.clear();
- mMediaDevices.addAll(devices);
- mMediaDevices.addAll(buildDisconnectedBluetoothDevice());
+ synchronized (mMediaDevicesLock) {
+ mMediaDevices.clear();
+ mMediaDevices.addAll(devices);
+ mMediaDevices.addAll(buildDisconnectedBluetoothDevice());
+ }
final MediaDevice infoMediaDevice = mInfoMediaManager.getCurrentConnectedDevice();
mCurrentConnectedDevice = infoMediaDevice != null
@@ -469,30 +495,42 @@
@Override
public void onDeviceRemoved(MediaDevice device) {
- if (mMediaDevices.contains(device)) {
- mMediaDevices.remove(device);
+ boolean isRemoved = false;
+ synchronized (mMediaDevicesLock) {
+ if (mMediaDevices.contains(device)) {
+ mMediaDevices.remove(device);
+ isRemoved = true;
+ }
+ }
+ if (isRemoved) {
dispatchDeviceListUpdate();
}
}
@Override
public void onDeviceListRemoved(List<MediaDevice> devices) {
- mMediaDevices.removeAll(devices);
+ synchronized (mMediaDevicesLock) {
+ mMediaDevices.removeAll(devices);
+ }
dispatchDeviceListUpdate();
}
@Override
public void onConnectedDeviceChanged(String id) {
- MediaDevice connectDevice = getMediaDeviceById(mMediaDevices, id);
+ MediaDevice connectDevice = null;
+ synchronized (mMediaDevicesLock) {
+ connectDevice = getMediaDeviceById(mMediaDevices, id);
+ }
connectDevice = connectDevice != null
? connectDevice : updateCurrentConnectedDevice();
- if (connectDevice != null) {
- connectDevice.setState(MediaDeviceState.STATE_CONNECTED);
- }
mCurrentConnectedDevice = connectDevice;
- dispatchSelectedDeviceStateChanged(mCurrentConnectedDevice,
- MediaDeviceState.STATE_CONNECTED);
+ if (connectDevice != null) {
+ connectDevice.setState(MediaDeviceState.STATE_CONNECTED);
+
+ dispatchSelectedDeviceStateChanged(mCurrentConnectedDevice,
+ MediaDeviceState.STATE_CONNECTED);
+ }
}
@Override
@@ -502,9 +540,11 @@
@Override
public void onRequestFailed(int reason) {
- for (MediaDevice device : mMediaDevices) {
- if (device.getState() == MediaDeviceState.STATE_CONNECTING) {
- device.setState(MediaDeviceState.STATE_CONNECTING_FAILED);
+ synchronized (mMediaDevicesLock) {
+ for (MediaDevice device : mMediaDevices) {
+ if (device.getState() == MediaDeviceState.STATE_CONNECTING) {
+ device.setState(MediaDeviceState.STATE_CONNECTING_FAILED);
+ }
}
}
dispatchOnRequestFailed(reason);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 99c568a..734866f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -625,6 +625,15 @@
}
@Test
+ public void onSessionUpdated_shouldDispatchDataChanged() {
+ mInfoMediaManager.registerCallback(mCallback);
+
+ mInfoMediaManager.mMediaRouterCallback.onSessionUpdated(mock(RoutingSessionInfo.class));
+
+ verify(mCallback).onDeviceAttributesChanged();
+ }
+
+ @Test
public void addMediaDevice_verifyDeviceTypeCanCorrespondToMediaDevice() {
final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
final CachedBluetoothDeviceManager cachedBluetoothDeviceManager =
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 29c31ea..e537b76 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -588,8 +588,9 @@
Settings.Global.POWER_BUTTON_VERY_LONG_PRESS,
Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, // Temporary for R beta
Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER,
- Settings.Global.ADVANCED_BATTERY_USAGE_AMOUNT,
- Settings.Global.CACHED_APPS_FREEZER_ENABLED);
+ Settings.Global.CACHED_APPS_FREEZER_ENABLED,
+ Settings.Global.APP_INTEGRITY_VERIFICATION_TIMEOUT,
+ Settings.Global.ADVANCED_BATTERY_USAGE_AMOUNT);
private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
newHashSet(
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 96caf31..83d7218 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -233,6 +233,9 @@
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
<uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/>
+ <!-- Permission required for CTS test - BatterySaverTest -->
+ <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
+
<!-- Permission required for CTS test - UiModeManagerTest -->
<uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 61b1e30..c4ea2e9 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -259,6 +259,9 @@
<!-- Permission to register process observer -->
<uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER"/>
+ <!-- Restore settings (used by QS) even if they have been modified -->
+ <uses-permission android:name="android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE" />
+
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
diff --git a/packages/SystemUI/res/drawable/control_no_favorites_background.xml b/packages/SystemUI/res/drawable/control_no_favorites_background.xml
index 947c77b..d895dd0 100644
--- a/packages/SystemUI/res/drawable/control_no_favorites_background.xml
+++ b/packages/SystemUI/res/drawable/control_no_favorites_background.xml
@@ -16,7 +16,22 @@
* limitations under the License.
*/
-->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <stroke android:width="1dp" android:color="@*android:color/foreground_material_dark"/>
- <corners android:radius="@dimen/control_corner_radius" />
-</shape>
+<!-- Should be kept in sync with the wallet plugin, as both share a similar
+ design: packages/apps/QuickAccessWallet -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="#000000" />
+ <corners android:radius="@dimen/control_corner_radius" />
+ </shape>
+ </item>
+ <item>
+ <shape>
+ <stroke
+ android:width="1dp"
+ android:color="#4DFFFFFF" />
+ <corners android:radius="@dimen/control_corner_radius"/>
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/SystemUI/res/layout/bubble_overflow_view.xml b/packages/SystemUI/res/layout/bubble_overflow_view.xml
index 1ed1f07..1218fba 100644
--- a/packages/SystemUI/res/layout/bubble_overflow_view.xml
+++ b/packages/SystemUI/res/layout/bubble_overflow_view.xml
@@ -30,9 +30,8 @@
<TextView
android:id="@+id/bubble_view_name"
- android:fontFamily="@*android:string/config_bodyFontFamily"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"
- android:textColor="?android:attr/textColorSecondary"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="13sp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:maxLines="1"
diff --git a/packages/SystemUI/res/layout/controls_icon.xml b/packages/SystemUI/res/layout/controls_icon.xml
index cc46ced..12bc5f6 100644
--- a/packages/SystemUI/res/layout/controls_icon.xml
+++ b/packages/SystemUI/res/layout/controls_icon.xml
@@ -19,8 +19,8 @@
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="28dp"
- android:layout_height="28dp"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
android:scaleType="fitCenter"
- android:layout_marginLeft="2dp"
- android:layout_marginRight="2dp" />
+ android:layout_marginLeft="5dp"
+ android:layout_marginRight="5dp" />
diff --git a/packages/SystemUI/res/layout/controls_no_favorites.xml b/packages/SystemUI/res/layout/controls_no_favorites.xml
index 4128230..1b24ee9 100644
--- a/packages/SystemUI/res/layout/controls_no_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_no_favorites.xml
@@ -24,11 +24,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
- android:paddingTop="40dp"
- android:paddingBottom="40dp"
- android:layout_marginLeft="10dp"
- android:layout_marginRight="10dp"
- android:layout_marginTop="@dimen/controls_top_margin"
+ android:paddingVertical="@dimen/controls_setup_vertical_padding"
+ android:layout_marginLeft="@dimen/global_actions_side_margin"
+ android:layout_marginRight="@dimen/global_actions_side_margin"
+ android:layout_marginTop="@dimen/controls_setup_top_margin"
android:background="@drawable/control_no_favorites_background">
<LinearLayout
@@ -37,7 +36,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="horizontal"
- android:paddingBottom="8dp" />
+ android:layout_marginBottom="16dp" />
<TextView
style="@style/TextAppearance.ControlSetup.Title"
diff --git a/packages/SystemUI/res/layout/partial_conversation_info.xml b/packages/SystemUI/res/layout/partial_conversation_info.xml
index 2401dfb..a261114 100644
--- a/packages/SystemUI/res/layout/partial_conversation_info.xml
+++ b/packages/SystemUI/res/layout/partial_conversation_info.xml
@@ -144,25 +144,36 @@
android:clipToPadding="false"
android:orientation="vertical">
- <LinearLayout
+ <com.android.systemui.statusbar.notification.row.ButtonLinearLayout
+ android:id="@+id/settings_link"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:orientation="horizontal">
- <ImageView
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:contentDescription="@null"
- android:src="@drawable/ic_info"
- android:tint="?android:attr/textColorPrimary"
- android:layout_marginEnd="8dp"/>
- <TextView
- android:id="@+id/non_configurable_text"
+ android:padding="@dimen/notification_importance_button_padding"
+ android:clickable="true"
+ android:focusable="true"
+ android:background="@drawable/notification_guts_priority_button_bg"
+ android:orientation="vertical">
+
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/TextAppearance.NotificationImportanceChannelGroup" />
- </LinearLayout>
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal">
+ <ImageView
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:contentDescription="@null"
+ android:src="@drawable/ic_info"
+ android:tint="?android:attr/textColorPrimary"
+ android:layout_marginEnd="8dp"/>
+ <TextView
+ android:id="@+id/non_configurable_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/TextAppearance.NotificationImportanceChannelGroup" />
+ </LinearLayout>
+ </com.android.systemui.statusbar.notification.row.ButtonLinearLayout>
<RelativeLayout
android:id="@+id/bottom_buttons"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c2694aa..a2e11a7 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1281,6 +1281,10 @@
<dimen name="control_status_padding">3dp</dimen>
<fraction name="controls_toggle_bg_intensity">5%</fraction>
<fraction name="controls_dimmed_alpha">40%</fraction>
+ <dimen name="controls_setup_top_margin">16dp</dimen>
+ <dimen name="controls_setup_title">22sp</dimen>
+ <dimen name="controls_setup_subtitle">14sp</dimen>
+ <dimen name="controls_setup_vertical_padding">52dp</dimen>
<!-- Home Controls activity view detail panel-->
<dimen name="controls_activity_view_top_offset">100dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ec29622..8c10f61 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2698,6 +2698,20 @@
<item quantity="other"><xliff:g id="number" example="3">%s</xliff:g> controls added.</item>
</plurals>
+ <!-- Removed control in management screen [CHAR LIMIT=20] -->
+ <string name="controls_removed">Removed</string>
+
+ <!-- a11y state description for a control that is currently favorited [CHAR LIMIT=NONE] -->
+ <string name="accessibility_control_favorite">Favorited</string>
+ <!-- a11y state description for a control that is currently favorited with its position [CHAR LIMIT=NONE] -->
+ <string name="accessibility_control_favorite_position">Favorited, position <xliff:g id="number" example="1">%d</xliff:g></string>
+ <!-- a11y state description for a control that is currently not favorited [CHAR LIMIT=NONE] -->
+ <string name="accessibility_control_not_favorite">Unfavorited</string>
+ <!-- a11y action to favorite a control. It will read as "Double-tap to favorite" in screen readers [CHAR LIMIT=NONE] -->
+ <string name="accessibility_control_change_favorite">favorite</string>
+ <!-- a11y action to unfavorite a control. It will read as "Double-tap to unfavorite" in screen readers [CHAR LIMIT=NONE] -->
+ <string name="accessibility_control_change_unfavorite">unfavorite</string>
+
<!-- Controls management controls screen default title [CHAR LIMIT=30] -->
<string name="controls_favorite_default_title">Controls</string>
<!-- Controls management controls screen subtitle [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f0edd63..c26d1f4 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -765,11 +765,11 @@
</style>
<style name="TextAppearance.ControlSetup.Title">
- <item name="android:textSize">25sp</item>
+ <item name="android:textSize">@dimen/controls_setup_title</item>
</style>
<style name="TextAppearance.ControlSetup.Subtitle">
- <item name="android:textSize">16sp</item>
+ <item name="android:textSize">@dimen/controls_setup_subtitle</item>
</style>
<!-- The attributes used for title (textAppearanceLarge) and message (textAppearanceMedium)
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index 0106609..b1e1434 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -19,8 +19,6 @@
import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
import static android.telephony.PhoneStateListener.LISTEN_NONE;
-import static com.android.systemui.DejankUtils.whitelistIpcs;
-
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -47,6 +45,7 @@
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
@@ -61,9 +60,11 @@
private final boolean mIsEmergencyCallCapable;
private final Handler mMainHandler;
+ private final Handler mBgHandler;
private boolean mTelephonyCapable;
private boolean mShowMissingSim;
private boolean mShowAirplaneMode;
+ private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
@VisibleForTesting
protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private WifiManager mWifiManager;
@@ -78,12 +79,14 @@
new WakefulnessLifecycle.Observer() {
@Override
public void onFinishedWakingUp() {
- if (mCarrierTextCallback != null) mCarrierTextCallback.finishedWakingUp();
+ final CarrierTextCallback callback = mCarrierTextCallback;
+ if (callback != null) callback.finishedWakingUp();
}
@Override
public void onStartedGoingToSleep() {
- if (mCarrierTextCallback != null) mCarrierTextCallback.startedGoingToSleep();
+ final CarrierTextCallback callback = mCarrierTextCallback;
+ if (callback != null) callback.startedGoingToSleep();
}
};
@@ -131,7 +134,7 @@
@Override
public void onActiveDataSubscriptionIdChanged(int subId) {
mActiveMobileDataSubscription = subId;
- if (mKeyguardUpdateMonitor != null) {
+ if (mNetworkSupported.get() && mCarrierTextCallback != null) {
updateCarrierText();
}
}
@@ -173,6 +176,17 @@
mSimSlotsNumber = getTelephonyManager().getSupportedModemCount();
mSimErrorState = new boolean[mSimSlotsNumber];
mMainHandler = Dependency.get(Dependency.MAIN_HANDLER);
+ mBgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
+ mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+ mBgHandler.post(() -> {
+ boolean supported = ConnectivityManager.from(mContext).isNetworkSupported(
+ ConnectivityManager.TYPE_MOBILE);
+ if (supported && mNetworkSupported.compareAndSet(false, supported)) {
+ // This will set/remove the listeners appropriately. Note that it will never double
+ // add the listeners.
+ handleSetListening(mCarrierTextCallback);
+ }
+ });
}
private TelephonyManager getTelephonyManager() {
@@ -221,48 +235,51 @@
}
/**
- * Sets the listening status of this controller. If the callback is null, it is set to
- * not listening
+ * This may be called internally after retrieving the correct value of {@code mNetworkSupported}
+ * (assumed false to start). In that case, the following happens:
+ * <ul>
+ * <li> If there was a registered callback, and the network is supported, it will register
+ * listeners.
+ * <li> If there was not a registered callback, it will try to remove unregistered listeners
+ * which is a no-op
+ * </ul>
*
- * @param callback Callback to provide text updates
+ * This call will always be processed in a background thread.
*/
- public void setListening(CarrierTextCallback callback) {
+ private void handleSetListening(CarrierTextCallback callback) {
TelephonyManager telephonyManager = getTelephonyManager();
if (callback != null) {
mCarrierTextCallback = callback;
- // TODO(b/140034799)
- if (whitelistIpcs(() -> ConnectivityManager.from(mContext).isNetworkSupported(
- ConnectivityManager.TYPE_MOBILE))) {
- mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+ if (mNetworkSupported.get()) {
// Keyguard update monitor expects callbacks from main thread
- mMainHandler.post(() -> {
- if (mKeyguardUpdateMonitor != null) {
- mKeyguardUpdateMonitor.registerCallback(mCallback);
- }
- });
+ mMainHandler.post(() -> mKeyguardUpdateMonitor.registerCallback(mCallback));
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
telephonyManager.listen(mPhoneStateListener,
LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
} else {
// Don't listen and clear out the text when the device isn't a phone.
- mKeyguardUpdateMonitor = null;
- callback.updateCarrierInfo(new CarrierTextCallbackInfo("", null, false, null));
+ mMainHandler.post(() -> callback.updateCarrierInfo(
+ new CarrierTextCallbackInfo("", null, false, null)
+ ));
}
} else {
mCarrierTextCallback = null;
- if (mKeyguardUpdateMonitor != null) {
- // Keyguard update monitor expects callbacks from main thread
- mMainHandler.post(() -> {
- if (mKeyguardUpdateMonitor != null) {
- mKeyguardUpdateMonitor.removeCallback(mCallback);
- }
- });
- mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
- }
+ mMainHandler.post(() -> mKeyguardUpdateMonitor.removeCallback(mCallback));
+ mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
}
}
+ /**
+ * Sets the listening status of this controller. If the callback is null, it is set to
+ * not listening.
+ *
+ * @param callback Callback to provide text updates
+ */
+ public void setListening(CarrierTextCallback callback) {
+ mBgHandler.post(() -> handleSetListening(callback));
+ }
+
protected List<SubscriptionInfo> getSubscriptionInfo() {
return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
}
@@ -500,7 +517,7 @@
*/
private CarrierTextController.StatusMode getStatusForIccState(int simState) {
final boolean missingAndNotProvisioned =
- !Dependency.get(KeyguardUpdateMonitor.class).isDeviceProvisioned()
+ !mKeyguardUpdateMonitor.isDeviceProvisioned()
&& (simState == TelephonyManager.SIM_STATE_ABSENT
|| simState == TelephonyManager.SIM_STATE_PERM_DISABLED);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 9d1dfa7..1dd6409 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -34,7 +34,6 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.WindowInsets.Type;
import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
@@ -635,7 +634,6 @@
lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("BiometricPrompt");
lp.token = windowToken;
- lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~Type.statusBars());
return lp;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 6a5e94c..887f1a7 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -198,6 +198,11 @@
/** Last known orientation, used to detect orientation changes in {@link #onConfigChanged}. */
private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
+ /**
+ * Last known screen density, used to detect display size changes in {@link #onConfigChanged}.
+ */
+ private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED;
+
private boolean mInflateSynchronously;
// TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
@@ -705,9 +710,16 @@
@Override
public void onConfigChanged(Configuration newConfig) {
- if (mStackView != null && newConfig != null && newConfig.orientation != mOrientation) {
- mOrientation = newConfig.orientation;
- mStackView.onOrientationChanged(newConfig.orientation);
+ if (mStackView != null && newConfig != null) {
+ if (newConfig.orientation != mOrientation) {
+ mOrientation = newConfig.orientation;
+ mStackView.onOrientationChanged(newConfig.orientation);
+ }
+ if (newConfig.densityDpi != mDensityDpi) {
+ mDensityDpi = newConfig.densityDpi;
+ mBubbleIconFactory = new BubbleIconFactory(mContext);
+ mStackView.onDisplaySizeChanged();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
index e96bef3..f4eb580 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
@@ -58,12 +58,13 @@
public BubbleOverflow(Context context) {
mContext = context;
mInflater = LayoutInflater.from(context);
- mBitmapSize = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_bitmap_size);
- mIconBitmapSize = mContext.getResources().getDimensionPixelSize(
- R.dimen.bubble_overflow_icon_bitmap_size);
}
void setUpOverflow(ViewGroup parentViewGroup, BubbleStackView stackView) {
+ mBitmapSize = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_bitmap_size);
+ mIconBitmapSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.bubble_overflow_icon_bitmap_size);
+
mExpandedView = (BubbleExpandedView) mInflater.inflate(
R.layout.bubble_expanded_view, parentViewGroup /* root */,
false /* attachToRoot */);
@@ -74,6 +75,7 @@
}
void updateIcon(Context context, ViewGroup parentViewGroup) {
+ mContext = context;
mInflater = LayoutInflater.from(context);
mOverflowBtn = (BadgedImageView) mInflater.inflate(R.layout.bubble_overflow_button,
parentViewGroup /* root */,
@@ -87,7 +89,7 @@
ta.recycle();
TypedValue typedValue = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
+ mContext.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
int colorAccent = mContext.getColor(typedValue.resourceId);
mOverflowBtn.getDrawable().setTint(colorAccent);
mDotColor = colorAccent;
@@ -97,7 +99,7 @@
mBitmapSize - mIconBitmapSize /* inset */);
AdaptiveIconDrawable adaptiveIconDrawable = new AdaptiveIconDrawable(bg, fg);
- BubbleIconFactory iconFactory = new BubbleIconFactory(context);
+ BubbleIconFactory iconFactory = new BubbleIconFactory(mContext);
mIcon = iconFactory.createBadgedIconBitmap(adaptiveIconDrawable,
null /* user */,
true /* shrinkNonAdaptiveIcons */).icon;
@@ -106,7 +108,7 @@
null /* outBounds */, null /* path */, null /* outMaskShape */);
float radius = DEFAULT_PATH_SIZE / 2f;
mPath = PathParser.createPathFromPathData(
- context.getResources().getString(com.android.internal.R.string.config_icon_mask));
+ mContext.getResources().getString(com.android.internal.R.string.config_icon_mask));
Matrix matrix = new Matrix();
matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
radius /* pivot y */);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 0074249..08ec789 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -41,6 +41,7 @@
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.R;
import java.util.ArrayList;
@@ -214,6 +215,8 @@
@Override
public BubbleOverflowAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
+
+ // Set layout for overflow bubble view.
LinearLayout overflowView = (LinearLayout) LayoutInflater.from(parent.getContext())
.inflate(R.layout.bubble_overflow_view, parent, false);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
@@ -222,6 +225,18 @@
params.width = mWidth;
params.height = mHeight;
overflowView.setLayoutParams(params);
+
+ // Ensure name has enough contrast.
+ final TypedArray ta = mContext.obtainStyledAttributes(
+ new int[]{android.R.attr.colorBackgroundFloating, android.R.attr.textColorPrimary});
+ final int bgColor = ta.getColor(0, Color.WHITE);
+ int textColor = ta.getColor(1, Color.BLACK);
+ textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true);
+ ta.recycle();
+
+ TextView viewName = overflowView.findViewById(R.id.bubble_view_name);
+ viewName.setTextColor(textColor);
+
return new ViewHolder(overflowView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index cb6e736..88f5eb0 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -445,6 +445,10 @@
final boolean clickedBubbleIsCurrentlyExpandedBubble =
clickedBubble.getKey().equals(mExpandedBubble.getKey());
+ if (isExpanded()) {
+ mExpandedAnimationController.onGestureFinished();
+ }
+
if (isExpanded() && !clickedBubbleIsCurrentlyExpandedBubble) {
if (clickedBubble != mBubbleData.getSelectedBubble()) {
// Select the clicked bubble.
@@ -464,7 +468,6 @@
mBubbleData.setExpanded(!mBubbleData.isExpanded());
}
}
- mExpandedAnimationController.onGestureFinished();
}
};
@@ -787,8 +790,8 @@
mOrientationChangedListener =
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- mExpandedAnimationController.updateOrientation(mOrientation, mDisplaySize);
- mStackAnimationController.updateOrientation(mOrientation);
+ mExpandedAnimationController.updateResources(mOrientation, mDisplaySize);
+ mStackAnimationController.updateResources(mOrientation);
// Reposition & adjust the height for new orientation
if (mIsExpanded) {
@@ -1004,7 +1007,7 @@
mBubbleOverflow.setUpOverflow(mBubbleContainer, this);
} else {
mBubbleContainer.removeView(mBubbleOverflow.getBtn());
- mBubbleOverflow.updateIcon(mContext, this);
+ mBubbleOverflow.updateIcon(mContext,this);
overflowBtnIndex = mBubbleContainer.getChildCount();
}
mBubbleContainer.addView(mBubbleOverflow.getBtn(), overflowBtnIndex,
@@ -1051,6 +1054,28 @@
mShowingManage = false;
}
+ /** Respond to the display size change by recalculating view size and location. */
+ public void onDisplaySizeChanged() {
+ setUpOverflow();
+
+ WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
+ wm.getDefaultDisplay().getRealSize(mDisplaySize);
+ Resources res = getContext().getResources();
+ mStatusBarHeight = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+ mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+ mBubbleSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
+ for (Bubble b : mBubbleData.getBubbles()) {
+ if (b.getIconView() == null) {
+ Log.d(TAG, "Display size changed. Icon null: " + b);
+ continue;
+ }
+ b.getIconView().setLayoutParams(new LayoutParams(mBubbleSize, mBubbleSize));
+ }
+ mExpandedAnimationController.updateResources(mOrientation, mDisplaySize);
+ mStackAnimationController.updateResources(mOrientation);
+ }
+
@Override
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
@@ -1291,7 +1316,7 @@
Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
}
- private void updateOverflowBtnVisibility(boolean apply) {
+ private void updateOverflowBtnVisibility() {
if (!BubbleExperimentConfig.allowBubbleOverflow(mContext)) {
return;
}
@@ -1300,11 +1325,6 @@
Log.d(TAG, "Show overflow button.");
}
mBubbleOverflow.setBtnVisible(VISIBLE);
- if (apply) {
- mExpandedAnimationController.expandFromStack(() -> {
- updatePointerPosition();
- } /* after */);
- }
} else {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "Collapsed. Hide overflow button.");
@@ -1564,7 +1584,7 @@
Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
mExpandedBubble));
}
- updateOverflowBtnVisibility(/* apply */ false);
+ updateOverflowBtnVisibility();
mBubbleContainer.cancelAllAnimations();
mExpandedAnimationController.collapseBackToStack(
mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
@@ -1588,7 +1608,7 @@
beforeExpandedViewAnimation();
mBubbleContainer.setActiveController(mExpandedAnimationController);
- updateOverflowBtnVisibility(/* apply */ false);
+ updateOverflowBtnVisibility();
mExpandedAnimationController.expandFromStack(() -> {
updatePointerPosition();
afterExpandedViewAnimation();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index 35406c7..f57cf42 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -118,7 +118,7 @@
public ExpandedAnimationController(Point displaySize, int expandedViewPadding,
int orientation) {
- updateOrientation(orientation, displaySize);
+ updateResources(orientation, displaySize);
mExpandedViewPadding = expandedViewPadding;
}
@@ -168,7 +168,7 @@
* @param orientation Landscape or portrait.
* @param displaySize Updated display size.
*/
- public void updateOrientation(int orientation, Point displaySize) {
+ public void updateResources(int orientation, Point displaySize) {
mScreenOrientation = orientation;
mDisplaySize = displaySize;
if (mLayout != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index 5f3a2bd..2cfe1dd 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -809,7 +809,7 @@
* Update effective screen width based on current orientation.
* @param orientation Landscape or portrait.
*/
- public void updateOrientation(int orientation) {
+ public void updateResources(int orientation) {
if (mLayout != null) {
Resources res = mLayout.getContext().getResources();
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index 79dd9ed..4b283d6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -23,9 +23,14 @@
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.view.accessibility.AccessibilityNodeInfo
import android.widget.CheckBox
import android.widget.ImageView
+import android.widget.Switch
import android.widget.TextView
+import androidx.core.view.AccessibilityDelegateCompat
+import androidx.core.view.ViewCompat
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
@@ -72,7 +77,8 @@
elevation = this@ControlAdapter.elevation
background = parent.context.getDrawable(
R.drawable.control_background_ripple)
- }
+ },
+ model is FavoritesModel // Indicates that position information is needed
) { id, favorite ->
model?.changeFavoriteStatus(id, favorite)
}
@@ -175,8 +181,14 @@
*/
internal class ControlHolder(
view: View,
+ val withPosition: Boolean,
val favoriteCallback: ModelFavoriteChanger
) : Holder(view) {
+ private val favoriteStateDescription =
+ itemView.context.getString(R.string.accessibility_control_favorite)
+ private val notFavoriteStateDescription =
+ itemView.context.getString(R.string.accessibility_control_not_favorite)
+
private val icon: ImageView = itemView.requireViewById(R.id.icon)
private val title: TextView = itemView.requireViewById(R.id.title)
private val subtitle: TextView = itemView.requireViewById(R.id.subtitle)
@@ -185,15 +197,38 @@
visibility = View.VISIBLE
}
+ private val accessibilityDelegate = ControlHolderAccessibilityDelegate(this::stateDescription)
+
+ init {
+ ViewCompat.setAccessibilityDelegate(itemView, accessibilityDelegate)
+ }
+
+ // Determine the stateDescription based on favorite state and maybe position
+ private fun stateDescription(favorite: Boolean): CharSequence? {
+ if (!favorite) {
+ return notFavoriteStateDescription
+ } else if (!withPosition) {
+ return favoriteStateDescription
+ } else {
+ val position = layoutPosition + 1
+ return itemView.context.getString(
+ R.string.accessibility_control_favorite_position, position)
+ }
+ }
+
override fun bindData(wrapper: ElementWrapper) {
wrapper as ControlInterface
val renderInfo = getRenderInfo(wrapper.component, wrapper.deviceType)
title.text = wrapper.title
subtitle.text = wrapper.subtitle
- favorite.isChecked = wrapper.favorite
- removed.text = if (wrapper.removed) "Removed" else ""
+ updateFavorite(wrapper.favorite)
+ removed.text = if (wrapper.removed) {
+ itemView.context.getText(R.string.controls_removed)
+ } else {
+ ""
+ }
itemView.setOnClickListener {
- favorite.isChecked = !favorite.isChecked
+ updateFavorite(!favorite.isChecked)
favoriteCallback(wrapper.controlId, favorite.isChecked)
}
applyRenderInfo(renderInfo)
@@ -201,6 +236,8 @@
override fun updateFavorite(favorite: Boolean) {
this.favorite.isChecked = favorite
+ accessibilityDelegate.isFavorite = favorite
+ itemView.stateDescription = stateDescription(favorite)
}
private fun getRenderInfo(
@@ -219,6 +256,36 @@
}
}
+private class ControlHolderAccessibilityDelegate(
+ val stateRetriever: (Boolean) -> CharSequence?
+) : AccessibilityDelegateCompat() {
+
+ var isFavorite = false
+
+ override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+
+ // Change the text for the double-tap action
+ val clickActionString = if (isFavorite) {
+ host.context.getString(R.string.accessibility_control_change_unfavorite)
+ } else {
+ host.context.getString(R.string.accessibility_control_change_favorite)
+ }
+ val click = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ AccessibilityNodeInfo.ACTION_CLICK,
+ // “favorite/unfavorite”
+ clickActionString)
+ info.addAction(click)
+
+ // Determine the stateDescription based on the holder information
+ info.stateDescription = stateRetriever(isFavorite)
+ // Remove the information at the end indicating row and column.
+ info.setCollectionItemInfo(null)
+
+ info.className = Switch::class.java.name
+ }
+}
+
class MarginItemDecorator(
private val topMargin: Int,
private val sideMargins: Int
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index 4e9c550..3a4e82c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -191,7 +191,18 @@
recyclerView.apply {
this.adapter = adapter
- layoutManager = GridLayoutManager(recyclerView.context, 2).apply {
+ layoutManager = object : GridLayoutManager(recyclerView.context, 2) {
+
+ // This will remove from the announcement the row corresponding to the divider,
+ // as it's not something that should be announced.
+ override fun getRowCountForAccessibility(
+ recycler: RecyclerView.Recycler,
+ state: RecyclerView.State
+ ): Int {
+ val initial = super.getRowCountForAccessibility(recycler, state)
+ return if (initial > 0) initial - 1 else initial
+ }
+ }.apply {
spanSizeLookup = adapter.spanSizeLookup
}
addItemDecoration(itemDecorator)
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 3f37861..03f8e66 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -206,7 +206,8 @@
@VisibleForTesting
protected final ArrayList<Action> mOverflowItems = new ArrayList<>();
- private ActionsDialog mDialog;
+ @VisibleForTesting
+ protected ActionsDialog mDialog;
private Action mSilentModeAction;
private ToggleAction mAirplaneModeOn;
@@ -246,6 +247,9 @@
@UiEvent(doc = "The global actions / power menu surface became visible on the screen.")
GA_POWER_MENU_OPEN(337),
+ @UiEvent(doc = "The global actions / power menu surface was dismissed.")
+ GA_POWER_MENU_CLOSE(471),
+
@UiEvent(doc = "The global actions bugreport button was pressed.")
GA_BUGREPORT_PRESS(344),
@@ -360,10 +364,9 @@
@Override
public void onUnlockedChanged() {
if (mDialog != null) {
- boolean unlocked = keyguardStateController.isUnlocked()
- || keyguardStateController.canDismissLockScreen();
if (mDialog.mPanelController != null) {
- mDialog.mPanelController.onDeviceLockStateChanged(unlocked);
+ mDialog.mPanelController.onDeviceLockStateChanged(
+ !mKeyguardStateController.isUnlocked());
}
if (!mDialog.isShowingControls() && shouldShowControls()) {
mDialog.showControls(mControlsUiController);
@@ -1168,6 +1171,7 @@
if (mDialog == dialog) {
mDialog = null;
}
+ mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_CLOSE);
mWindowManagerFuncs.onGlobalActionsHidden();
mLifecycle.setCurrentState(Lifecycle.State.DESTROYED);
}
@@ -2378,9 +2382,7 @@
@VisibleForTesting
protected boolean shouldShowControls() {
- boolean isUnlocked = mKeyguardStateController.isUnlocked()
- || mKeyguardStateController.canDismissLockScreen();
- return (isUnlocked || mShowLockScreenCardsAndControls)
+ return (mKeyguardStateController.isUnlocked() || mShowLockScreenCardsAndControls)
&& mControlsUiController.getAvailable()
&& !mControlsServiceInfos.isEmpty()
&& mDeviceProvisioned;
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
index 9c92b0a..d72c369 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
@@ -42,10 +42,10 @@
val mediaCarousel: HorizontalScrollView
private val mediaContent: ViewGroup
private val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf()
- private val visualStabilityCallback = ::reorderAllPlayers
+ private val visualStabilityCallback : VisualStabilityManager.Callback
private var activeMediaIndex: Int = 0
+ private var needsReordering: Boolean = false
private var scrollIntoCurrentMedia: Int = 0
-
private var currentlyExpanded = true
set(value) {
if (field != value) {
@@ -75,6 +75,16 @@
mediaCarousel = inflateMediaCarousel()
mediaCarousel.setOnScrollChangeListener(scrollChangedListener)
mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
+ visualStabilityCallback = VisualStabilityManager.Callback {
+ if (needsReordering) {
+ needsReordering = false
+ reorderAllPlayers()
+ }
+ // Let's reset our scroll position
+ mediaCarousel.scrollX = 0
+ }
+ visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback,
+ true /* persistent */)
mediaManager.addListener(object : MediaDataManager.Listener {
override fun onMediaDataLoaded(key: String, data: MediaData) {
updateView(key, data)
@@ -169,7 +179,7 @@
mediaContent.removeView(existingPlayer.view?.player)
mediaContent.addView(existingPlayer.view?.player, 0)
} else {
- visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback)
+ needsReordering = true
}
}
existingPlayer.bind(data)
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index c8cf02a..10b04c0 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -302,13 +302,15 @@
}
private void showAutoSaverSuggestionNotification() {
+ final CharSequence message = mContext.getString(R.string.auto_saver_text);
final Notification.Builder nb =
new Notification.Builder(mContext, NotificationChannels.HINTS)
.setSmallIcon(R.drawable.ic_power_saver)
.setWhen(0)
.setShowWhen(false)
.setContentTitle(mContext.getString(R.string.auto_saver_title))
- .setContentText(mContext.getString(R.string.auto_saver_text));
+ .setStyle(new Notification.BigTextStyle().bigText(message))
+ .setContentText(message);
nb.setContentIntent(pendingBroadcast(ACTION_ENABLE_AUTO_SAVER));
nb.setDeleteIntent(pendingBroadcast(ACTION_DISMISS_AUTO_SAVER_SUGGESTION));
nb.addAction(0,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index b157f4b..aaff9ac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -152,15 +152,33 @@
}
@Override
+ public void fakeDragBy(float xOffset) {
+ try {
+ super.fakeDragBy(xOffset);
+ // Keep on drawing until the animation has finished.
+ postInvalidateOnAnimation();
+ } catch (NullPointerException e) {
+ Log.e(TAG, "FakeDragBy called before begin", e);
+ // If we were trying to fake drag, it means we just added a new tile to the last
+ // page, so animate there.
+ final int lastPageNumber = mPages.size() - 1;
+ post(() -> {
+ setCurrentItem(lastPageNumber, true);
+ if (mBounceAnimatorSet != null) {
+ mBounceAnimatorSet.start();
+ }
+ setOffscreenPageLimit(1);
+ });
+ }
+ }
+
+ @Override
public void computeScroll() {
if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
if (!isFakeDragging()) {
beginFakeDrag();
}
fakeDragBy(getScrollX() - mScroller.getCurrX());
- // Keep on drawing until the animation has finished.
- postInvalidateOnAnimation();
- return;
} else if (isFakeDragging()) {
endFakeDrag();
mBounceAnimatorSet.start();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 54a928d..4008918 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -339,13 +339,18 @@
changeTileSpecs(tileSpecs-> !tileSpecs.contains(spec) && tileSpecs.add(spec));
}
+ private void saveTilesToSettings(List<String> tileSpecs) {
+ Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING,
+ TextUtils.join(",", tileSpecs), null /* tag */,
+ false /* default */, mCurrentUser, true /* overrideable by restore */);
+ }
+
private void changeTileSpecs(Predicate<List<String>> changeFunction) {
final String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
- TILES_SETTING, ActivityManager.getCurrentUser());
+ TILES_SETTING, mCurrentUser);
final List<String> tileSpecs = loadTileSpecs(mContext, setting);
if (changeFunction.test(tileSpecs)) {
- Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING,
- TextUtils.join(",", tileSpecs), ActivityManager.getCurrentUser());
+ saveTilesToSettings(tileSpecs);
}
}
@@ -375,7 +380,7 @@
Intent intent = new Intent().setComponent(component);
TileLifecycleManager lifecycleManager = new TileLifecycleManager(new Handler(),
mContext, mServices, new Tile(), intent,
- new UserHandle(ActivityManager.getCurrentUser()),
+ new UserHandle(mCurrentUser),
mBroadcastDispatcher);
lifecycleManager.onStopListening();
lifecycleManager.onTileRemoved();
@@ -384,8 +389,7 @@
}
}
if (DEBUG) Log.d(TAG, "saveCurrentTiles " + newTiles);
- Secure.putStringForUser(getContext().getContentResolver(), QSTileHost.TILES_SETTING,
- TextUtils.join(",", newTiles), ActivityManager.getCurrentUser());
+ saveTilesToSettings(newTiles);
}
public QSTile createTile(String tileSpec) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index e7f2618..765f85a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -195,13 +195,15 @@
boolean wasChildInGroup = ent.isChildInGroup();
if (isChildInGroup && !wasChildInGroup) {
isChildInGroup = wasChildInGroup;
- mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager);
+ mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager,
+ false /* persistent */);
} else if (!isChildInGroup && wasChildInGroup) {
// We allow grouping changes if the group was collapsed
if (mGroupManager.isLogicalGroupExpanded(ent.getSbn())) {
isChildInGroup = wasChildInGroup;
parent = ent.getRow().getNotificationParent().getEntry();
- mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager);
+ mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager,
+ false /* persistent */);
}
}
}
@@ -286,7 +288,8 @@
if (mVisualStabilityManager.canReorderNotification(targetChild)) {
mListContainer.changeViewPosition(targetChild, i);
} else {
- mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager);
+ mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager,
+ false /* persistent */);
}
}
j++;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 02c98ae..88f148b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -121,14 +121,14 @@
}
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
- return canHandleMotionEvent() && startExpansion(event)
+ return maybeStartExpansion(event)
}
- private fun canHandleMotionEvent(): Boolean {
- return !wakeUpCoordinator.canShowPulsingHuns || qsExpanded || bouncerShowing
- }
-
- private fun startExpansion(event: MotionEvent): Boolean {
+ private fun maybeStartExpansion(event: MotionEvent): Boolean {
+ if (!wakeUpCoordinator.canShowPulsingHuns || qsExpanded ||
+ bouncerShowing) {
+ return false
+ }
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain()
}
@@ -177,14 +177,9 @@
}
override fun onTouchEvent(event: MotionEvent): Boolean {
- if (!canHandleMotionEvent()) {
- return false
+ if (!isExpanding) {
+ return maybeStartExpansion(event)
}
-
- if (!isExpanding || event.actionMasked == MotionEvent.ACTION_DOWN) {
- return startExpansion(event)
- }
-
velocityTracker!!.addMovement(event)
val y = event.y
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
index e2b01ff..d7b391f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
@@ -22,6 +22,7 @@
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
+import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_MEDIA_CONTROLS
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
@@ -52,12 +53,14 @@
fun getNotificationBuckets(): IntArray {
return when {
isFilteringEnabled() && isMediaControlsEnabled() ->
- intArrayOf(BUCKET_HEADS_UP, BUCKET_MEDIA_CONTROLS, BUCKET_PEOPLE, BUCKET_ALERTING,
- BUCKET_SILENT)
+ intArrayOf(BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, BUCKET_MEDIA_CONTROLS,
+ BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT)
!isFilteringEnabled() && isMediaControlsEnabled() ->
- intArrayOf(BUCKET_HEADS_UP, BUCKET_MEDIA_CONTROLS, BUCKET_ALERTING, BUCKET_SILENT)
+ intArrayOf(BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, BUCKET_MEDIA_CONTROLS,
+ BUCKET_ALERTING, BUCKET_SILENT)
isFilteringEnabled() && !isMediaControlsEnabled() ->
- intArrayOf(BUCKET_HEADS_UP, BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT)
+ intArrayOf(BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, BUCKET_PEOPLE,
+ BUCKET_ALERTING, BUCKET_SILENT)
NotificationUtils.useNewInterruptionModel(context) ->
intArrayOf(BUCKET_ALERTING, BUCKET_SILENT)
else ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
index 7ac5995..8341c02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
@@ -43,7 +43,9 @@
private static final long TEMPORARY_REORDERING_ALLOWED_DURATION = 1000;
private final ArrayList<Callback> mReorderingAllowedCallbacks = new ArrayList<>();
+ private final ArraySet<Callback> mPersistentReorderingCallbacks = new ArraySet<>();
private final ArrayList<Callback> mGroupChangesAllowedCallbacks = new ArrayList<>();
+ private final ArraySet<Callback> mPersistentGroupCallbacks = new ArraySet<>();
private final Handler mHandler;
private boolean mPanelExpanded;
@@ -85,8 +87,15 @@
/**
* Add a callback to invoke when reordering is allowed again.
+ *
+ * @param callback the callback to add
+ * @param persistent {@code true} if this callback should this callback be persisted, otherwise
+ * it will be removed after a single invocation
*/
- public void addReorderingAllowedCallback(Callback callback) {
+ public void addReorderingAllowedCallback(Callback callback, boolean persistent) {
+ if (persistent) {
+ mPersistentReorderingCallbacks.add(callback);
+ }
if (mReorderingAllowedCallbacks.contains(callback)) {
return;
}
@@ -95,8 +104,15 @@
/**
* Add a callback to invoke when group changes are allowed again.
+ *
+ * @param callback the callback to add
+ * @param persistent {@code true} if this callback should this callback be persisted, otherwise
+ * it will be removed after a single invocation
*/
- public void addGroupChangesAllowedCallback(Callback callback) {
+ public void addGroupChangesAllowedCallback(Callback callback, boolean persistent) {
+ if (persistent) {
+ mPersistentGroupCallbacks.add(callback);
+ }
if (mGroupChangesAllowedCallbacks.contains(callback)) {
return;
}
@@ -136,21 +152,26 @@
boolean changedToTrue = reorderingAllowed && !mReorderingAllowed;
mReorderingAllowed = reorderingAllowed;
if (changedToTrue) {
- notifyChangeAllowed(mReorderingAllowedCallbacks);
+ notifyChangeAllowed(mReorderingAllowedCallbacks, mPersistentReorderingCallbacks);
}
boolean groupChangesAllowed = (!mScreenOn || !mPanelExpanded) && !mPulsing;
changedToTrue = groupChangesAllowed && !mGroupChangedAllowed;
mGroupChangedAllowed = groupChangesAllowed;
if (changedToTrue) {
- notifyChangeAllowed(mGroupChangesAllowedCallbacks);
+ notifyChangeAllowed(mGroupChangesAllowedCallbacks, mPersistentGroupCallbacks);
}
}
- private void notifyChangeAllowed(ArrayList<Callback> callbacks) {
+ private void notifyChangeAllowed(ArrayList<Callback> callbacks,
+ ArraySet<Callback> persistentCallbacks) {
for (int i = 0; i < callbacks.size(); i++) {
- callbacks.get(i).onChangeAllowed();
+ Callback callback = callbacks.get(i);
+ callback.onChangeAllowed();
+ if (!persistentCallbacks.contains(callback)) {
+ callbacks.remove(callback);
+ i--;
+ }
}
- callbacks.clear();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index c78370e..9ac4229 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -29,6 +29,7 @@
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
+import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
@@ -162,6 +163,8 @@
val isMedia = isImportantMedia(entry)
val isSystemMax = entry.isSystemMax()
return when {
+ entry.sbn.notification.isForegroundService && entry.sbn.notification.isColorized ->
+ BUCKET_FOREGROUND_SERVICE
usePeopleFiltering && entry.getPeopleNotificationType() != TYPE_NON_PERSON ->
BUCKET_PEOPLE
isHeadsUp || isMedia || isSystemMax || entry.isHighPriority() ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
index cc5de65..66c07bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
@@ -135,10 +135,13 @@
}
private void bindActions() {
+ final OnClickListener settingsOnClickListener = getSettingsOnClickListener();
final View settingsButton = findViewById(R.id.info);
- settingsButton.setOnClickListener(getSettingsOnClickListener());
+ settingsButton.setOnClickListener(settingsOnClickListener);
settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
+ findViewById(R.id.settings_link).setOnClickListener(settingsOnClickListener);
+
TextView msg = findViewById(R.id.non_configurable_text);
msg.setText(getResources().getString(R.string.no_shortcut, mAppName));
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index c9b1318..99691b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -463,7 +463,8 @@
mAttachedChildren.add(i, desiredChild);
result = true;
} else {
- visualStabilityManager.addReorderingAllowedCallback(callback);
+ visualStabilityManager.addReorderingAllowedCallback(callback,
+ false /* persistent */);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index dcf30ca..b5ba3a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -29,12 +29,10 @@
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.ViewGroup;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.media.KeyguardMediaController;
-import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
@@ -329,8 +327,6 @@
boolean peopleNotifsPresent = false;
int currentMediaControlsIdx = -1;
- // Currently, just putting media controls in the front and incrementing the position based
- // on the number of heads-up notifs.
int mediaControlsTarget = usingMediaControls ? 0 : -1;
int currentIncomingHeaderIdx = -1;
int incomingHeaderTarget = -1;
@@ -408,6 +404,11 @@
mediaControlsTarget++;
}
break;
+ case BUCKET_FOREGROUND_SERVICE:
+ if (mediaControlsTarget != -1) {
+ mediaControlsTarget++;
+ }
+ break;
case BUCKET_PEOPLE:
mLogger.logPosition(i, "Conversation");
peopleNotifsPresent = true;
@@ -488,7 +489,8 @@
adjustHeaderVisibilityAndPosition(
peopleHeaderTarget, mPeopleHubView, currentPeopleHeaderIdx);
adjustViewPosition(mediaControlsTarget, mMediaControlsView, currentMediaControlsIdx);
- adjustViewPosition(incomingHeaderTarget, mIncomingHeader, currentIncomingHeaderIdx);
+ adjustHeaderVisibilityAndPosition(incomingHeaderTarget, mIncomingHeader,
+ currentIncomingHeaderIdx);
mLogger.logStr("Final order:");
@@ -508,45 +510,29 @@
private void adjustHeaderVisibilityAndPosition(
int targetPosition, StackScrollerDecorView header, int currentPosition) {
- if (targetPosition == -1) {
- if (currentPosition != -1) {
- mParent.removeView(header);
- }
- } else {
- if (currentPosition == -1) {
- // If the header is animating away, it will still have a parent, so detach it first
- // TODO: We should really cancel the active animations here. This will happen
- // automatically when the view's intro animation starts, but it's a fragile link.
- if (header.getTransientContainer() != null) {
- header.getTransientContainer().removeTransientView(header);
- header.setTransientContainer(null);
- }
- header.setContentVisible(true);
- mParent.addView(header, targetPosition);
- } else {
- mParent.changeViewPosition(header, targetPosition);
- }
+ adjustViewPosition(targetPosition, header, currentPosition);
+ if (targetPosition != -1 && currentPosition == -1) {
+ header.setContentVisible(true);
}
}
- private void adjustViewPosition(int targetPosition, ExpandableView header,
- int currentPosition) {
+ private void adjustViewPosition(int targetPosition, ExpandableView view, int currentPosition) {
if (targetPosition == -1) {
if (currentPosition != -1) {
- mParent.removeView(header);
+ mParent.removeView(view);
}
} else {
if (currentPosition == -1) {
// If the header is animating away, it will still have a parent, so detach it first
// TODO: We should really cancel the active animations here. This will happen
// automatically when the view's intro animation starts, but it's a fragile link.
- if (header.getTransientContainer() != null) {
- header.getTransientContainer().removeTransientView(header);
- header.setTransientContainer(null);
+ if (view.getTransientContainer() != null) {
+ view.getTransientContainer().removeTransientView(view);
+ view.setTransientContainer(null);
}
- mParent.addView(header, targetPosition);
+ mParent.addView(view, targetPosition);
} else {
- mParent.changeViewPosition(header, targetPosition);
+ mParent.changeViewPosition(view, targetPosition);
}
}
}
@@ -641,6 +627,11 @@
}
@VisibleForTesting
+ ExpandableView getIncomingHeaderView() {
+ return mIncomingHeader;
+ }
+
+ @VisibleForTesting
void setPeopleHubVisible(boolean visible) {
mPeopleHubVisible = visible;
}
@@ -685,6 +676,7 @@
@Retention(SOURCE)
@IntDef(prefix = { "BUCKET_" }, value = {
BUCKET_HEADS_UP,
+ BUCKET_FOREGROUND_SERVICE,
BUCKET_MEDIA_CONTROLS,
BUCKET_PEOPLE,
BUCKET_ALERTING,
@@ -692,8 +684,9 @@
})
public @interface PriorityBucket {}
public static final int BUCKET_HEADS_UP = 0;
- public static final int BUCKET_MEDIA_CONTROLS = 1;
- public static final int BUCKET_PEOPLE = 2;
- public static final int BUCKET_ALERTING = 3;
- public static final int BUCKET_SILENT = 4;
+ public static final int BUCKET_FOREGROUND_SERVICE = 1;
+ public static final int BUCKET_MEDIA_CONTROLS = 2;
+ public static final int BUCKET_PEOPLE = 3;
+ public static final int BUCKET_ALERTING = 4;
+ public static final int BUCKET_SILENT = 5;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 6b0df95..3dcf7ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -439,7 +439,8 @@
// time out anyway
&& !entry.showingPulsing()) {
mEntriesToRemoveWhenReorderingAllowed.add(entry);
- mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManagerPhone.this);
+ mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManagerPhone.this,
+ false /* persistent */);
} else if (mTrackingHeadsUp) {
mEntriesToRemoveAfterExpand.add(entry);
} else if (mIsAutoHeadsUp && mStatusBarState == StatusBarState.KEYGUARD) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
index 2e4a929d..926c1bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
@@ -321,8 +321,7 @@
|| state.mPanelVisible || state.mKeyguardFadingAway || state.mBouncerShowing
|| state.mHeadsUpShowing
|| state.mScrimsVisibility != ScrimController.TRANSPARENT)
- || state.mBackgroundBlurRadius > 0
- || state.mLaunchingActivity;
+ || state.mBackgroundBlurRadius > 0;
}
private void applyFitsSystemWindows(State state) {
@@ -486,11 +485,6 @@
apply(mCurrentState);
}
- void setLaunchingActivity(boolean launching) {
- mCurrentState.mLaunchingActivity = launching;
- apply(mCurrentState);
- }
-
public void setScrimsVisibility(int scrimsVisibility) {
mCurrentState.mScrimsVisibility = scrimsVisibility;
apply(mCurrentState);
@@ -651,7 +645,6 @@
boolean mForceCollapsed;
boolean mForceDozeBrightness;
boolean mForceUserActivity;
- boolean mLaunchingActivity;
boolean mBackdropShowing;
boolean mWallpaperSupportsAmbientMode;
boolean mNotTouchable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 0d25898..596a607 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -93,7 +93,6 @@
private PhoneStatusBarView mStatusBarView;
private PhoneStatusBarTransitions mBarTransitions;
private StatusBar mService;
- private NotificationShadeWindowController mNotificationShadeWindowController;
private DragDownHelper mDragDownHelper;
private boolean mDoubleTapEnabled;
private boolean mSingleTapEnabled;
@@ -431,14 +430,10 @@
public void setExpandAnimationPending(boolean pending) {
mExpandAnimationPending = pending;
- mNotificationShadeWindowController
- .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning);
}
public void setExpandAnimationRunning(boolean running) {
mExpandAnimationRunning = running;
- mNotificationShadeWindowController
- .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning);
}
public void cancelExpandHelper() {
@@ -461,9 +456,8 @@
}
}
- public void setService(StatusBar statusBar, NotificationShadeWindowController controller) {
+ public void setService(StatusBar statusBar) {
mService = statusBar;
- mNotificationShadeWindowController = controller;
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index dd54a3d..bbf83bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1001,7 +1001,7 @@
updateTheme();
inflateStatusBarWindow();
- mNotificationShadeWindowViewController.setService(this, mNotificationShadeWindowController);
+ mNotificationShadeWindowViewController.setService(this);
mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());
// TODO: Deal with the ugliness that comes from having some of the statusbar broken out
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index 442c7ea..b6e72226 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -369,16 +369,24 @@
R.string.ext_media_new_notification_message, disk.getDescription());
final PendingIntent initIntent = buildInitPendingIntent(vol);
- return buildNotificationBuilder(vol, title, text)
- .addAction(new Action(R.drawable.ic_settings_24dp,
- mContext.getString(R.string.ext_media_init_action), initIntent))
- .addAction(new Action(R.drawable.ic_eject_24dp,
- mContext.getString(R.string.ext_media_unmount_action),
- buildUnmountPendingIntent(vol)))
- .setContentIntent(initIntent)
- .setDeleteIntent(buildSnoozeIntent(vol.getFsUuid()))
- .build();
+ final PendingIntent unmountIntent = buildUnmountPendingIntent(vol);
+ if (isAutomotive()) {
+ return buildNotificationBuilder(vol, title, text)
+ .setContentIntent(unmountIntent)
+ .setDeleteIntent(buildSnoozeIntent(vol.getFsUuid()))
+ .build();
+ } else {
+ return buildNotificationBuilder(vol, title, text)
+ .addAction(new Action(R.drawable.ic_settings_24dp,
+ mContext.getString(R.string.ext_media_init_action), initIntent))
+ .addAction(new Action(R.drawable.ic_eject_24dp,
+ mContext.getString(R.string.ext_media_unmount_action),
+ unmountIntent))
+ .setContentIntent(initIntent)
+ .setDeleteIntent(buildSnoozeIntent(vol.getFsUuid()))
+ .build();
+ }
} else {
final CharSequence title = disk.getDescription();
final CharSequence text = mContext.getString(
@@ -427,9 +435,15 @@
R.string.ext_media_unmountable_notification_title, disk.getDescription());
final CharSequence text = mContext.getString(
R.string.ext_media_unmountable_notification_message, disk.getDescription());
+ PendingIntent action;
+ if (isAutomotive()) {
+ action = buildUnmountPendingIntent(vol);
+ } else {
+ action = buildInitPendingIntent(vol);
+ }
return buildNotificationBuilder(vol, title, text)
- .setContentIntent(buildInitPendingIntent(vol))
+ .setContentIntent(action)
.setCategory(Notification.CATEGORY_ERROR)
.build();
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index 870010a..aa4122f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -134,6 +134,7 @@
mDependency.injectMockDependency(WakefulnessLifecycle.class);
mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
new Handler(mTestableLooper.getLooper()));
+ mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor);
doAnswer(this::checkMainThread).when(mKeyguardUpdateMonitor)
@@ -150,6 +151,7 @@
// This should not start listening on any of the real dependencies but will test that
// callbacks in mKeyguardUpdateMonitor are done in the mTestableLooper thread
mCarrierTextController.setListening(mCarrierTextCallback);
+ mTestableLooper.processAllMessages();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 64acdcc..ee7733a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -156,13 +156,20 @@
}
@Test
- public void testShouldLogVisibility() {
+ public void testShouldLogShow() {
mGlobalActionsDialog.onShow(null);
mTestableLooper.processAllMessages();
verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_OPEN);
}
@Test
+ public void testShouldLogDismiss() {
+ mGlobalActionsDialog.onDismiss(mGlobalActionsDialog.mDialog);
+ mTestableLooper.processAllMessages();
+ verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_CLOSE);
+ }
+
+ @Test
public void testShouldLogBugreportPress() throws InterruptedException {
GlobalActionsDialog.BugReportAction bugReportAction =
mGlobalActionsDialog.makeBugReportActionForTesting();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
index 3d06c57..ea1b498 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.any;
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;
@@ -108,7 +109,7 @@
public void testCallBackCalledScreenOn() {
mVisualStabilityManager.setPanelExpanded(true);
mVisualStabilityManager.setScreenOn(true);
- mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+ mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
mVisualStabilityManager.setScreenOn(false);
verify(mCallback).onChangeAllowed();
}
@@ -117,7 +118,7 @@
public void testCallBackCalledPanelExpanded() {
mVisualStabilityManager.setPanelExpanded(true);
mVisualStabilityManager.setScreenOn(true);
- mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+ mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
mVisualStabilityManager.setPanelExpanded(false);
verify(mCallback).onChangeAllowed();
}
@@ -126,7 +127,7 @@
public void testCallBackExactlyOnce() {
mVisualStabilityManager.setPanelExpanded(true);
mVisualStabilityManager.setScreenOn(true);
- mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+ mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
mVisualStabilityManager.setScreenOn(false);
mVisualStabilityManager.setScreenOn(true);
mVisualStabilityManager.setScreenOn(false);
@@ -134,6 +135,17 @@
}
@Test
+ public void testCallBackCalledContinuouslyWhenRequested() {
+ mVisualStabilityManager.setPanelExpanded(true);
+ mVisualStabilityManager.setScreenOn(true);
+ mVisualStabilityManager.addReorderingAllowedCallback(mCallback, true /* persistent */);
+ mVisualStabilityManager.setScreenOn(false);
+ mVisualStabilityManager.setScreenOn(true);
+ mVisualStabilityManager.setScreenOn(false);
+ verify(mCallback, times(2)).onChangeAllowed();
+ }
+
+ @Test
public void testAddedCanReorder() {
mVisualStabilityManager.setPanelExpanded(true);
mVisualStabilityManager.setScreenOn(true);
@@ -188,7 +200,7 @@
@Test
public void testCallBackCalled_Pulsing() {
mVisualStabilityManager.setPulsing(true);
- mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+ mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
mVisualStabilityManager.setPulsing(false);
verify(mCallback).onChangeAllowed();
}
@@ -198,7 +210,7 @@
// GIVEN having the panel open (which would block reordering)
mVisualStabilityManager.setScreenOn(true);
mVisualStabilityManager.setPanelExpanded(true);
- mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+ mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
// WHEN we temprarily allow reordering
mVisualStabilityManager.temporarilyAllowReordering();
@@ -212,7 +224,7 @@
public void testTemporarilyAllowReorderingDoesntOverridePulsing() {
// GIVEN we are in a pulsing state
mVisualStabilityManager.setPulsing(true);
- mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+ mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
// WHEN we temprarily allow reordering
mVisualStabilityManager.temporarilyAllowReordering();
@@ -227,7 +239,7 @@
// GIVEN having the panel open (which would block reordering)
mVisualStabilityManager.setScreenOn(true);
mVisualStabilityManager.setPanelExpanded(true);
- mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+ mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false /* persistent */);
// WHEN we temprarily allow reordering and then wait until the window expires
mVisualStabilityManager.temporarilyAllowReordering();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
index 1bfebfb..f21b1a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
@@ -356,6 +356,30 @@
}
@Test
+ public void testBindNotification_SetsOnClickListenerForSettings_mainText() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mInfo.bindNotification(
+ mMockPackageManager,
+ mMockINotificationManager,
+ mChannelEditorDialogController,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mNotificationChannelSet,
+ mEntry,
+ (View v, NotificationChannel c, int appUid) -> {
+ assertEquals(mNotificationChannel, c);
+ latch.countDown();
+ },
+ true,
+ false);
+
+ final View settingsButton = mInfo.findViewById(R.id.settings_link);
+ settingsButton.performClick();
+ // Verify that listener was triggered.
+ assertEquals(0, latch.getCount());
+ }
+
+ @Test
public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() {
mInfo.bindNotification(
mMockPackageManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 0b86a78..546bce8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -19,15 +19,19 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -62,6 +66,9 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.ArrayList;
+import java.util.List;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -84,10 +91,23 @@
@Before
public void setUp() {
- when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(2);
- when(mNotificationRowComponent.getActivatableNotificationViewController()).thenReturn(
- mActivatableNotificationViewController
- );
+ when(mSectionsFeatureManager.getNumberOfBuckets()).thenAnswer(
+ invocation -> {
+ int count = 2;
+ if (mSectionsFeatureManager.isFilteringEnabled()) {
+ count = 5;
+ }
+ if (mSectionsFeatureManager.isMediaControlsEnabled()) {
+ if (!mSectionsFeatureManager.isFilteringEnabled()) {
+ count = 5;
+ } else {
+ count += 1;
+ }
+ }
+ return count;
+ });
+ when(mNotificationRowComponent.getActivatableNotificationViewController())
+ .thenReturn(mActivatableNotificationViewController);
mSectionsManager =
new NotificationSectionsManager(
mActivityStarterDelegate,
@@ -104,6 +124,7 @@
mSectionsManager.initialize(mNssl, LayoutInflater.from(mContext));
when(mNssl.indexOfChild(any(View.class))).thenReturn(-1);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
+
}
@Test(expected = IllegalStateException.class)
@@ -339,6 +360,58 @@
}
@Test
+ public void testPeopleFiltering_HunWhilePeopleVisible() {
+ enablePeopleFiltering();
+
+ setupMockStack(
+ ChildType.PEOPLE_HEADER,
+ ChildType.HEADS_UP,
+ ChildType.PERSON,
+ ChildType.ALERTING_HEADER,
+ ChildType.GENTLE_HEADER,
+ ChildType.GENTLE
+ );
+ mSectionsManager.updateSectionBoundaries();
+
+ verifyMockStack(
+ ChildType.INCOMING_HEADER,
+ ChildType.HEADS_UP,
+ ChildType.PEOPLE_HEADER,
+ ChildType.PERSON,
+ ChildType.GENTLE_HEADER,
+ ChildType.GENTLE
+ );
+ }
+
+ @Test
+ public void testPeopleFiltering_Fsn() {
+ enablePeopleFiltering();
+
+ setupMockStack(
+ ChildType.INCOMING_HEADER,
+ ChildType.HEADS_UP,
+ ChildType.PEOPLE_HEADER,
+ ChildType.FSN,
+ ChildType.PERSON,
+ ChildType.ALERTING,
+ ChildType.GENTLE
+ );
+ mSectionsManager.updateSectionBoundaries();
+
+ verifyMockStack(
+ ChildType.INCOMING_HEADER,
+ ChildType.HEADS_UP,
+ ChildType.FSN,
+ ChildType.PEOPLE_HEADER,
+ ChildType.PERSON,
+ ChildType.ALERTING_HEADER,
+ ChildType.ALERTING,
+ ChildType.GENTLE_HEADER,
+ ChildType.GENTLE
+ );
+ }
+
+ @Test
public void testMediaControls_AddWhenEnterKeyguard() {
enableMediaControls();
@@ -358,30 +431,28 @@
enableMediaControls();
// GIVEN a stack that doesn't include media controls but includes HEADS_UP
- setStackState(ChildType.HEADS_UP, ChildType.ALERTING, ChildType.GENTLE_HEADER,
+ setupMockStack(ChildType.HEADS_UP, ChildType.ALERTING, ChildType.GENTLE_HEADER,
ChildType.GENTLE);
// WHEN we go back to the keyguard
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
mSectionsManager.updateSectionBoundaries();
- // Then the media controls are added after HEADS_UP
- verify(mNssl).addView(mSectionsManager.getMediaControlsView(), 1);
+ verifyMockStack(ChildType.HEADS_UP, ChildType.MEDIA_CONTROLS, ChildType.ALERTING,
+ ChildType.GENTLE);
}
private void enablePeopleFiltering() {
when(mSectionsFeatureManager.isFilteringEnabled()).thenReturn(true);
- when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(4);
}
private void enableMediaControls() {
when(mSectionsFeatureManager.isMediaControlsEnabled()).thenReturn(true);
- when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(4);
}
private enum ChildType {
- MEDIA_CONTROLS, PEOPLE_HEADER, ALERTING_HEADER, GENTLE_HEADER, HEADS_UP, PERSON, ALERTING,
- GENTLE, OTHER
+ INCOMING_HEADER, MEDIA_CONTROLS, PEOPLE_HEADER, ALERTING_HEADER, GENTLE_HEADER, HEADS_UP,
+ FSN, PERSON, ALERTING, GENTLE, OTHER
}
private void setStackState(ChildType... children) {
@@ -389,6 +460,9 @@
for (int i = 0; i < children.length; i++) {
View child;
switch (children[i]) {
+ case INCOMING_HEADER:
+ child = mSectionsManager.getIncomingHeaderView();
+ break;
case MEDIA_CONTROLS:
child = mSectionsManager.getMediaControlsView();
break;
@@ -404,6 +478,9 @@
case HEADS_UP:
child = mockNotification(BUCKET_HEADS_UP);
break;
+ case FSN:
+ child = mockNotification(BUCKET_FOREGROUND_SERVICE);
+ break;
case PERSON:
child = mockNotification(BUCKET_PEOPLE);
break;
@@ -434,4 +511,127 @@
when(notifRow.getParent()).thenReturn(mNssl);
return notifRow;
}
+
+ private void verifyMockStack(ChildType... expected) {
+ final List<ChildType> actual = new ArrayList<>();
+ int childCount = mNssl.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = mNssl.getChildAt(i);
+ if (child == mSectionsManager.getIncomingHeaderView()) {
+ actual.add(ChildType.INCOMING_HEADER);
+ continue;
+ }
+ if (child == mSectionsManager.getMediaControlsView()) {
+ actual.add(ChildType.MEDIA_CONTROLS);
+ continue;
+ }
+ if (child == mSectionsManager.getPeopleHeaderView()) {
+ actual.add(ChildType.PEOPLE_HEADER);
+ continue;
+ }
+ if (child == mSectionsManager.getAlertingHeaderView()) {
+ actual.add(ChildType.ALERTING_HEADER);
+ continue;
+ }
+ if (child == mSectionsManager.getGentleHeaderView()) {
+ actual.add(ChildType.GENTLE_HEADER);
+ continue;
+ }
+ if (child instanceof ExpandableNotificationRow) {
+ switch (((ExpandableNotificationRow) child).getEntry().getBucket()) {
+ case BUCKET_HEADS_UP:
+ actual.add(ChildType.HEADS_UP);
+ break;
+ case BUCKET_FOREGROUND_SERVICE:
+ actual.add(ChildType.FSN);
+ break;
+ case BUCKET_PEOPLE:
+ actual.add(ChildType.PERSON);
+ break;
+ case BUCKET_ALERTING:
+ actual.add(ChildType.ALERTING);
+ break;
+ case BUCKET_SILENT:
+ actual.add(ChildType.GENTLE);
+ break;
+ default:
+ actual.add(ChildType.OTHER);
+ break;
+ }
+ continue;
+ }
+ actual.add(ChildType.OTHER);
+ }
+ assertThat(actual).containsExactly((Object[]) expected).inOrder();
+ }
+
+ private void setupMockStack(ChildType... childTypes) {
+ final List<View> children = new ArrayList<>();
+ when(mNssl.getChildCount()).thenAnswer(invocation -> children.size());
+ when(mNssl.getChildAt(anyInt()))
+ .thenAnswer(invocation -> children.get(invocation.getArgument(0)));
+ when(mNssl.indexOfChild(any()))
+ .thenAnswer(invocation -> children.indexOf(invocation.getArgument(0)));
+ doAnswer(invocation -> {
+ View child = invocation.getArgument(0);
+ int index = invocation.getArgument(1);
+ children.add(index, child);
+ return null;
+ }).when(mNssl).addView(any(), anyInt());
+ doAnswer(invocation -> {
+ View child = invocation.getArgument(0);
+ children.remove(child);
+ return null;
+ }).when(mNssl).removeView(any());
+ doAnswer(invocation -> {
+ View child = invocation.getArgument(0);
+ int newIndex = invocation.getArgument(1);
+ children.remove(child);
+ children.add(newIndex, child);
+ return null;
+ }).when(mNssl).changeViewPosition(any(), anyInt());
+ for (ChildType childType : childTypes) {
+ View child;
+ switch (childType) {
+ case INCOMING_HEADER:
+ child = mSectionsManager.getIncomingHeaderView();
+ break;
+ case MEDIA_CONTROLS:
+ child = mSectionsManager.getMediaControlsView();
+ break;
+ case PEOPLE_HEADER:
+ child = mSectionsManager.getPeopleHeaderView();
+ break;
+ case ALERTING_HEADER:
+ child = mSectionsManager.getAlertingHeaderView();
+ break;
+ case GENTLE_HEADER:
+ child = mSectionsManager.getGentleHeaderView();
+ break;
+ case HEADS_UP:
+ child = mockNotification(BUCKET_HEADS_UP);
+ break;
+ case FSN:
+ child = mockNotification(BUCKET_FOREGROUND_SERVICE);
+ break;
+ case PERSON:
+ child = mockNotification(BUCKET_PEOPLE);
+ break;
+ case ALERTING:
+ child = mockNotification(BUCKET_ALERTING);
+ break;
+ case GENTLE:
+ child = mockNotification(BUCKET_SILENT);
+ break;
+ case OTHER:
+ child = mock(View.class);
+ when(child.getVisibility()).thenReturn(View.VISIBLE);
+ when(child.getParent()).thenReturn(mNssl);
+ break;
+ default:
+ throw new RuntimeException("Unknown ChildType: " + childType);
+ }
+ children.add(child);
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index e04d25b..cc2d1c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -83,7 +83,6 @@
@Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
@Mock private NotificationShadeDepthController mNotificationShadeDepthController;
@Mock private SuperStatusBarViewFactory mStatusBarViewFactory;
- @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@Before
public void setUp() {
@@ -122,7 +121,7 @@
mNotificationPanelViewController,
mStatusBarViewFactory);
mController.setupExpandedStatusBar();
- mController.setService(mStatusBar, mNotificationShadeWindowController);
+ mController.setService(mStatusBar);
mController.setDragDownHelper(mDragDownHelper);
}
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index cbc5e14..1ee017b 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -51,6 +51,11 @@
cc_library {
name: "libtetherutilsjni",
sdk_version: "current",
+ apex_available: [
+ "//apex_available:platform", // Used by InProcessTethering
+ "com.android.tethering",
+ ],
+ min_sdk_version: "current",
srcs: [
"jni/android_net_util_TetheringUtils.cpp",
],
@@ -110,6 +115,7 @@
// InProcessTethering is a replacement for Tethering
overrides: ["Tethering"],
apex_available: ["com.android.tethering"],
+ min_sdk_version: "current",
}
// Updatable tethering packaged as an application
@@ -123,4 +129,5 @@
// The permission configuration *must* be included to ensure security of the device
required: ["NetworkPermissionConfig"],
apex_available: ["com.android.tethering"],
+ min_sdk_version: "current",
}
diff --git a/packages/Tethering/apex/Android.bp b/packages/Tethering/apex/Android.bp
index 20ccd2a..67097a7 100644
--- a/packages/Tethering/apex/Android.bp
+++ b/packages/Tethering/apex/Android.bp
@@ -17,7 +17,7 @@
apex {
name: "com.android.tethering",
updatable: true,
- min_sdk_version: "R",
+ min_sdk_version: "current",
java_libs: ["framework-tethering"],
apps: ["Tethering"],
manifest: "manifest.json",
diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp
index d029d2b..ae4bb3e 100644
--- a/packages/Tethering/common/TetheringLib/Android.bp
+++ b/packages/Tethering/common/TetheringLib/Android.bp
@@ -13,43 +13,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// AIDL interfaces between the core system and the tethering mainline module.
-aidl_interface {
- name: "tethering-aidl-interfaces",
- unstable: true,
- local_include_dir: "src",
- include_dirs: ["frameworks/base/core/java"], // For framework parcelables.
- srcs: [
- // @JavaOnlyStableParcelable aidl declarations must not be listed here, as this would cause
- // compilation to fail (b/148001843).
- "src/android/net/IIntResultListener.aidl",
- "src/android/net/ITetheringConnector.aidl",
- "src/android/net/ITetheringEventCallback.aidl",
- "src/android/net/TetheringCallbackStartedParcel.aidl",
- "src/android/net/TetheringConfigurationParcel.aidl",
- "src/android/net/TetheringRequestParcel.aidl",
- "src/android/net/TetherStatesParcel.aidl",
- ],
- backend: {
- ndk: {
- enabled: false,
- },
- cpp: {
- enabled: false,
- },
- },
-}
-
java_library {
name: "framework-tethering",
sdk_version: "module_current",
srcs: [
- "src/android/net/TetheredClient.java",
- "src/android/net/TetheringManager.java",
- "src/android/net/TetheringConstants.java",
- ],
- static_libs: [
- "tethering-aidl-interfaces-java",
+ ":framework-tethering-srcs",
],
jarjar_rules: "jarjar-rules.txt",
installable: true,
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml
index 9dda716..9269c6f 100644
--- a/packages/Tethering/res/values/config.xml
+++ b/packages/Tethering/res/values/config.xml
@@ -57,6 +57,12 @@
<item>"bt-pan"</item>
</string-array>
+ <!-- Use the BPF offload for tethering when the kernel has support. True by default.
+ If the device doesn't want to support tether BPF offload, this should be false.
+ Note that this setting could be overridden by device config.
+ -->
+ <bool translatable="false" name="config_tether_enable_bpf_offload">true</bool>
+
<!-- Use the old dnsmasq DHCP server for tethering instead of the framework implementation. -->
<bool translatable="false" name="config_tether_enable_legacy_dhcp_server">false</bool>
diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml
index 4c78a74..4e2bb1e 100644
--- a/packages/Tethering/res/values/overlayable.xml
+++ b/packages/Tethering/res/values/overlayable.xml
@@ -23,6 +23,11 @@
<item type="array" name="config_tether_wifi_p2p_regexs"/>
<item type="array" name="config_tether_bluetooth_regexs"/>
<item type="array" name="config_tether_dhcp_range"/>
+ <!-- Use the BPF offload for tethering when the kernel has support. True by default.
+ If the device doesn't want to support tether BPF offload, this should be false.
+ Note that this setting could be overridden by device config.
+ -->
+ <item type="bool" name="config_tether_enable_bpf_offload"/>
<item type="bool" name="config_tether_enable_legacy_dhcp_server"/>
<item type="integer" name="config_tether_offload_poll_interval"/>
<item type="array" name="config_tether_upstream_types"/>
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 83727bc..d993306 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -227,6 +227,7 @@
private final int mInterfaceType;
private final LinkProperties mLinkProperties;
private final boolean mUsingLegacyDhcp;
+ private final boolean mUsingBpfOffload;
private final Dependencies mDeps;
@@ -302,9 +303,12 @@
private final IpNeighborMonitor mIpNeighborMonitor;
+ // TODO: Add a dependency object to pass the data members or variables from the tethering
+ // object. It helps to reduce the arguments of the constructor.
public IpServer(
String ifaceName, Looper looper, int interfaceType, SharedLog log,
- INetd netd, Callback callback, boolean usingLegacyDhcp, Dependencies deps) {
+ INetd netd, Callback callback, boolean usingLegacyDhcp, boolean usingBpfOffload,
+ Dependencies deps) {
super(ifaceName, looper);
mLog = log.forSubComponent(ifaceName);
mNetd = netd;
@@ -314,6 +318,7 @@
mInterfaceType = interfaceType;
mLinkProperties = new LinkProperties();
mUsingLegacyDhcp = usingLegacyDhcp;
+ mUsingBpfOffload = usingBpfOffload;
mDeps = deps;
resetLinkProperties();
mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
@@ -321,7 +326,12 @@
mIpNeighborMonitor = mDeps.getIpNeighborMonitor(getHandler(), mLog,
new MyNeighborEventConsumer());
- if (!mIpNeighborMonitor.start()) {
+
+ // IP neighbor monitor monitors the neighbor events for adding/removing offload
+ // forwarding rules per client. If BPF offload is not supported, don't start listening
+ // for neighbor events. See updateIpv6ForwardingRules, addIpv6ForwardingRule,
+ // removeIpv6ForwardingRule.
+ if (mUsingBpfOffload && !mIpNeighborMonitor.start()) {
mLog.e("Failed to create IpNeighborMonitor on " + mIfaceName);
}
@@ -715,12 +725,12 @@
final String upstreamIface = v6only.getInterfaceName();
params = new RaParams();
- // We advertise an mtu lower by 16, which is the closest multiple of 8 >= 14,
- // the ethernet header size. This makes kernel ebpf tethering offload happy.
- // This hack should be reverted once we have the kernel fixed up.
+ // When BPF offload is enabled, we advertise an mtu lower by 16, which is the closest
+ // multiple of 8 >= 14, the ethernet header size. This makes kernel ebpf tethering
+ // offload happy. This hack should be reverted once we have the kernel fixed up.
// Note: this will automatically clamp to at least 1280 (ipv6 minimum mtu)
// see RouterAdvertisementDaemon.java putMtu()
- params.mtu = v6only.getMtu() - 16;
+ params.mtu = mUsingBpfOffload ? v6only.getMtu() - 16 : v6only.getMtu();
params.hasDefaultRoute = v6only.hasIpv6DefaultRoute();
if (params.hasDefaultRoute) params.hopLimit = getHopLimit(upstreamIface);
@@ -844,6 +854,11 @@
}
private void addIpv6ForwardingRule(Ipv6ForwardingRule rule) {
+ // Theoretically, we don't need this check because IP neighbor monitor doesn't start if BPF
+ // offload is disabled. Add this check just in case.
+ // TODO: Perhaps remove this protection check.
+ if (!mUsingBpfOffload) return;
+
try {
mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel());
mIpv6ForwardingRules.put(rule.address, rule);
@@ -853,6 +868,11 @@
}
private void removeIpv6ForwardingRule(Ipv6ForwardingRule rule, boolean removeFromMap) {
+ // Theoretically, we don't need this check because IP neighbor monitor doesn't start if BPF
+ // offload is disabled. Add this check just in case.
+ // TODO: Perhaps remove this protection check.
+ if (!mUsingBpfOffload) return;
+
try {
mNetd.tetherOffloadRuleRemove(rule.toTetherOffloadRuleParcel());
if (removeFromMap) {
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index 293f8ea..fe92204 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -66,6 +66,7 @@
private final Handler mHandler;
private final SharedLog mLog;
+ private final Dependencies mDeps;
private IOffloadControl mOffloadControl;
private TetheringOffloadCallback mTetheringOffloadCallback;
private ControlCallback mControlCallback;
@@ -126,8 +127,76 @@
}
public OffloadHardwareInterface(Handler h, SharedLog log) {
+ this(h, log, new Dependencies(log));
+ }
+
+ OffloadHardwareInterface(Handler h, SharedLog log, Dependencies deps) {
mHandler = h;
mLog = log.forSubComponent(TAG);
+ mDeps = deps;
+ }
+
+ /** Capture OffloadHardwareInterface dependencies, for injection. */
+ static class Dependencies {
+ private final SharedLog mLog;
+
+ Dependencies(SharedLog log) {
+ mLog = log;
+ }
+
+ public IOffloadConfig getOffloadConfig() {
+ try {
+ return IOffloadConfig.getService(true /*retry*/);
+ } catch (RemoteException | NoSuchElementException e) {
+ mLog.e("getIOffloadConfig error " + e);
+ return null;
+ }
+ }
+
+ public IOffloadControl getOffloadControl() {
+ try {
+ return IOffloadControl.getService(true /*retry*/);
+ } catch (RemoteException | NoSuchElementException e) {
+ mLog.e("tethering offload control not supported: " + e);
+ return null;
+ }
+ }
+
+ public NativeHandle createConntrackSocket(final int groups) {
+ final FileDescriptor fd;
+ try {
+ fd = NetlinkSocket.forProto(OsConstants.NETLINK_NETFILTER);
+ } catch (ErrnoException e) {
+ mLog.e("Unable to create conntrack socket " + e);
+ return null;
+ }
+
+ final SocketAddress sockAddr = SocketUtils.makeNetlinkSocketAddress(0, groups);
+ try {
+ Os.bind(fd, sockAddr);
+ } catch (ErrnoException | SocketException e) {
+ mLog.e("Unable to bind conntrack socket for groups " + groups + " error: " + e);
+ try {
+ SocketUtils.closeSocket(fd);
+ } catch (IOException ie) {
+ // Nothing we can do here
+ }
+ return null;
+ }
+ try {
+ Os.connect(fd, sockAddr);
+ } catch (ErrnoException | SocketException e) {
+ mLog.e("connect to kernel fail for groups " + groups + " error: " + e);
+ try {
+ SocketUtils.closeSocket(fd);
+ } catch (IOException ie) {
+ // Nothing we can do here
+ }
+ return null;
+ }
+
+ return new NativeHandle(fd, true);
+ }
}
/** Get default value indicating whether offload is supported. */
@@ -141,13 +210,7 @@
* share them with offload management process.
*/
public boolean initOffloadConfig() {
- IOffloadConfig offloadConfig;
- try {
- offloadConfig = IOffloadConfig.getService(true /*retry*/);
- } catch (RemoteException | NoSuchElementException e) {
- mLog.e("getIOffloadConfig error " + e);
- return false;
- }
+ final IOffloadConfig offloadConfig = mDeps.getOffloadConfig();
if (offloadConfig == null) {
mLog.e("Could not find IOffloadConfig service");
return false;
@@ -159,11 +222,11 @@
//
// h2 provides a file descriptor bound to the following netlink groups
// (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
- final NativeHandle h1 = createConntrackSocket(
+ final NativeHandle h1 = mDeps.createConntrackSocket(
NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
if (h1 == null) return false;
- final NativeHandle h2 = createConntrackSocket(
+ final NativeHandle h2 = mDeps.createConntrackSocket(
NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY);
if (h2 == null) {
closeFdInNativeHandle(h1);
@@ -198,53 +261,12 @@
}
}
- private NativeHandle createConntrackSocket(final int groups) {
- FileDescriptor fd;
- try {
- fd = NetlinkSocket.forProto(OsConstants.NETLINK_NETFILTER);
- } catch (ErrnoException e) {
- mLog.e("Unable to create conntrack socket " + e);
- return null;
- }
-
- final SocketAddress sockAddr = SocketUtils.makeNetlinkSocketAddress(0, groups);
- try {
- Os.bind(fd, sockAddr);
- } catch (ErrnoException | SocketException e) {
- mLog.e("Unable to bind conntrack socket for groups " + groups + " error: " + e);
- try {
- SocketUtils.closeSocket(fd);
- } catch (IOException ie) {
- // Nothing we can do here
- }
- return null;
- }
- try {
- Os.connect(fd, sockAddr);
- } catch (ErrnoException | SocketException e) {
- mLog.e("connect to kernel fail for groups " + groups + " error: " + e);
- try {
- SocketUtils.closeSocket(fd);
- } catch (IOException ie) {
- // Nothing we can do here
- }
- return null;
- }
-
- return new NativeHandle(fd, true);
- }
-
/** Initialize the tethering offload HAL. */
public boolean initOffloadControl(ControlCallback controlCb) {
mControlCallback = controlCb;
if (mOffloadControl == null) {
- try {
- mOffloadControl = IOffloadControl.getService(true /*retry*/);
- } catch (RemoteException | NoSuchElementException e) {
- mLog.e("tethering offload control not supported: " + e);
- return false;
- }
+ mOffloadControl = mDeps.getOffloadControl();
if (mOffloadControl == null) {
mLog.e("tethering IOffloadControl.getService() returned null");
return false;
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
index ae6119f2..2a5e620 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -2296,7 +2296,7 @@
final TetherState tetherState = new TetherState(
new IpServer(iface, mLooper, interfaceType, mLog, mNetd,
makeControlCallback(), mConfig.enableLegacyDhcpServer,
- mDeps.getIpServerDependencies()));
+ mConfig.enableBpfOffload, mDeps.getIpServerDependencies()));
mTetherStates.put(iface, tetherState);
tetherState.ipServer.start();
}
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 9d4e747..91a6e29 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -73,6 +73,12 @@
private static final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
/**
+ * Override enabling BPF offload configuration for tethering.
+ */
+ public static final String OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD =
+ "override_tether_enable_bpf_offload";
+
+ /**
* Use the old dnsmasq DHCP server for tethering instead of the framework implementation.
*/
public static final String TETHER_ENABLE_LEGACY_DHCP_SERVER =
@@ -95,6 +101,8 @@
public final String[] legacyDhcpRanges;
public final String[] defaultIPv4DNS;
public final boolean enableLegacyDhcpServer;
+ // TODO: Add to TetheringConfigurationParcel if required.
+ public final boolean enableBpfOffload;
public final String[] provisioningApp;
public final String provisioningAppNoUi;
@@ -124,11 +132,12 @@
isDunRequired = checkDunRequired(ctx);
chooseUpstreamAutomatically = getResourceBoolean(
- res, R.bool.config_tether_upstream_automatic);
+ res, R.bool.config_tether_upstream_automatic, false /** default value */);
preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired);
legacyDhcpRanges = getLegacyDhcpRanges(res);
defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
+ enableBpfOffload = getEnableBpfOffload(res);
enableLegacyDhcpServer = getEnableLegacyDhcpServer(res);
provisioningApp = getResourceStringArray(res, R.array.config_mobile_hotspot_provision_app);
@@ -208,6 +217,9 @@
pw.print("provisioningAppNoUi: ");
pw.println(provisioningAppNoUi);
+ pw.print("enableBpfOffload: ");
+ pw.println(enableBpfOffload);
+
pw.print("enableLegacyDhcpServer: ");
pw.println(enableLegacyDhcpServer);
}
@@ -228,6 +240,7 @@
toIntArray(preferredUpstreamIfaceTypes)));
sj.add(String.format("provisioningApp:%s", makeString(provisioningApp)));
sj.add(String.format("provisioningAppNoUi:%s", provisioningAppNoUi));
+ sj.add(String.format("enableBpfOffload:%s", enableBpfOffload));
sj.add(String.format("enableLegacyDhcpServer:%s", enableLegacyDhcpServer));
return String.format("TetheringConfiguration{%s}", sj.toString());
}
@@ -332,11 +345,11 @@
}
}
- private static boolean getResourceBoolean(Resources res, int resId) {
+ private static boolean getResourceBoolean(Resources res, int resId, boolean defaultValue) {
try {
return res.getBoolean(resId);
} catch (Resources.NotFoundException e404) {
- return false;
+ return defaultValue;
}
}
@@ -357,8 +370,29 @@
}
}
+ private boolean getEnableBpfOffload(final Resources res) {
+ // Get BPF offload config
+ // Priority 1: Device config
+ // Priority 2: Resource config
+ // Priority 3: Default value
+ final boolean resourceValue = getResourceBoolean(
+ res, R.bool.config_tether_enable_bpf_offload, true /** default value */);
+
+ // Due to the limitation of static mock for testing, using #getProperty directly instead
+ // of getDeviceConfigBoolean. getDeviceConfigBoolean is not invoked because it uses
+ // #getBoolean to get the boolean device config. The test can't know that the returned
+ // boolean value comes from device config or default value (because of null property
+ // string). Because the test would like to verify null property boolean string case,
+ // use DeviceConfig.getProperty here. See also the test case testBpfOffload{*} in
+ // TetheringConfigurationTest.java.
+ final String value = DeviceConfig.getProperty(
+ NAMESPACE_CONNECTIVITY, OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD);
+ return (value != null) ? Boolean.parseBoolean(value) : resourceValue;
+ }
+
private boolean getEnableLegacyDhcpServer(final Resources res) {
- return getResourceBoolean(res, R.bool.config_tether_enable_legacy_dhcp_server)
+ return getResourceBoolean(
+ res, R.bool.config_tether_enable_legacy_dhcp_server, false /** default value */)
|| getDeviceConfigBoolean(TETHER_ENABLE_LEGACY_DHCP_SERVER);
}
diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index f9be7b9..b9622da 100644
--- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -106,6 +106,7 @@
private static final String BLUETOOTH_IFACE_ADDR = "192.168.42.1";
private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24;
private static final int DHCP_LEASE_TIME_SECS = 3600;
+ private static final boolean DEFAULT_USING_BPF_OFFLOAD = true;
private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams(
IFACE_NAME, 42 /* index */, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */);
@@ -130,10 +131,11 @@
private NeighborEventConsumer mNeighborEventConsumer;
private void initStateMachine(int interfaceType) throws Exception {
- initStateMachine(interfaceType, false /* usingLegacyDhcp */);
+ initStateMachine(interfaceType, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD);
}
- private void initStateMachine(int interfaceType, boolean usingLegacyDhcp) throws Exception {
+ private void initStateMachine(int interfaceType, boolean usingLegacyDhcp,
+ boolean usingBpfOffload) throws Exception {
doAnswer(inv -> {
final IDhcpServerCallbacks cb = inv.getArgument(2);
new Thread(() -> {
@@ -165,7 +167,7 @@
mIpServer = new IpServer(
IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd,
- mCallback, usingLegacyDhcp, mDependencies);
+ mCallback, usingLegacyDhcp, usingBpfOffload, mDependencies);
mIpServer.start();
mNeighborEventConsumer = neighborCaptor.getValue();
@@ -179,12 +181,13 @@
private void initTetheredStateMachine(int interfaceType, String upstreamIface)
throws Exception {
- initTetheredStateMachine(interfaceType, upstreamIface, false);
+ initTetheredStateMachine(interfaceType, upstreamIface, false,
+ DEFAULT_USING_BPF_OFFLOAD);
}
private void initTetheredStateMachine(int interfaceType, String upstreamIface,
- boolean usingLegacyDhcp) throws Exception {
- initStateMachine(interfaceType, usingLegacyDhcp);
+ boolean usingLegacyDhcp, boolean usingBpfOffload) throws Exception {
+ initStateMachine(interfaceType, usingLegacyDhcp, usingBpfOffload);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
if (upstreamIface != null) {
LinkProperties lp = new LinkProperties();
@@ -204,7 +207,8 @@
when(mDependencies.getIpNeighborMonitor(any(), any(), any()))
.thenReturn(mIpNeighborMonitor);
mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
- mNetd, mCallback, false /* usingLegacyDhcp */, mDependencies);
+ mNetd, mCallback, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD,
+ mDependencies);
mIpServer.start();
mLooper.dispatchAll();
verify(mCallback).updateInterfaceState(
@@ -494,7 +498,8 @@
@Test
public void doesNotStartDhcpServerIfDisabled() throws Exception {
- initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */);
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */,
+ DEFAULT_USING_BPF_OFFLOAD);
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
verify(mDependencies, never()).makeDhcpServer(any(), any(), any());
@@ -577,7 +582,8 @@
@Test
public void addRemoveipv6ForwardingRules() throws Exception {
- initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */);
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
+ DEFAULT_USING_BPF_OFFLOAD);
final int myIfindex = TEST_IFACE_PARAMS.index;
final int notMyIfindex = myIfindex - 1;
@@ -678,6 +684,53 @@
reset(mNetd);
}
+ @Test
+ public void enableDisableUsingBpfOffload() throws Exception {
+ final int myIfindex = TEST_IFACE_PARAMS.index;
+ final InetAddress neigh = InetAddresses.parseNumericAddress("2001:db8::1");
+ final MacAddress macA = MacAddress.fromString("00:00:00:00:00:0a");
+ final MacAddress macNull = MacAddress.fromString("00:00:00:00:00:00");
+
+ reset(mNetd);
+
+ // Expect that rules can be only added/removed when the BPF offload config is enabled.
+ // Note that the usingBpfOffload false case is not a realistic test case. Because IP
+ // neighbor monitor doesn't start if BPF offload is disabled, there should have no
+ // neighbor event listening. This is used for testing the protection check just in case.
+ // TODO: Perhaps remove this test once we don't need this check anymore.
+ for (boolean usingBpfOffload : new boolean[]{true, false}) {
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
+ usingBpfOffload);
+
+ // A neighbor is added.
+ recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA);
+ if (usingBpfOffload) {
+ verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neigh, macA));
+ } else {
+ verify(mNetd, never()).tetherOffloadRuleAdd(any());
+ }
+ reset(mNetd);
+
+ // A neighbor is deleted.
+ recvDelNeigh(myIfindex, neigh, NUD_STALE, macA);
+ if (usingBpfOffload) {
+ verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neigh, macNull));
+ } else {
+ verify(mNetd, never()).tetherOffloadRuleRemove(any());
+ }
+ reset(mNetd);
+ }
+ }
+
+ @Test
+ public void doesNotStartIpNeighborMonitorIfBpfOffloadDisabled() throws Exception {
+ initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
+ false /* usingBpfOffload */);
+
+ // IP neighbor monitor doesn't start if BPF offload is disabled.
+ verify(mIpNeighborMonitor, never()).start();
+ }
+
private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception {
verify(mDependencies, times(1)).makeDhcpServer(eq(IFACE_NAME), any(), any());
verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).startWithCallbacks(
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
new file mode 100644
index 0000000..f8ff1cb
--- /dev/null
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
@@ -0,0 +1,215 @@
+/*
+ * 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.networkstack.tethering;
+
+import static android.net.util.TetheringUtils.uint16;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
+import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
+import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
+import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
+import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
+import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
+import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.NativeHandle;
+import android.os.test.TestLooper;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class OffloadHardwareInterfaceTest {
+ private static final String RMNET0 = "test_rmnet_data0";
+
+ private final TestLooper mTestLooper = new TestLooper();
+
+ private OffloadHardwareInterface mOffloadHw;
+ private ITetheringOffloadCallback mTetheringOffloadCallback;
+ private OffloadHardwareInterface.ControlCallback mControlCallback;
+
+ @Mock private IOffloadConfig mIOffloadConfig;
+ @Mock private IOffloadControl mIOffloadControl;
+ @Mock private NativeHandle mNativeHandle;
+
+ class MyDependencies extends OffloadHardwareInterface.Dependencies {
+ MyDependencies(SharedLog log) {
+ super(log);
+ }
+
+ @Override
+ public IOffloadConfig getOffloadConfig() {
+ return mIOffloadConfig;
+ }
+
+ @Override
+ public IOffloadControl getOffloadControl() {
+ return mIOffloadControl;
+ }
+
+ @Override
+ public NativeHandle createConntrackSocket(final int groups) {
+ return mNativeHandle;
+ }
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ final SharedLog log = new SharedLog("test");
+ mOffloadHw = new OffloadHardwareInterface(new Handler(mTestLooper.getLooper()), log,
+ new MyDependencies(log));
+ mControlCallback = spy(new OffloadHardwareInterface.ControlCallback());
+ }
+
+ private void startOffloadHardwareInterface() throws Exception {
+ mOffloadHw.initOffloadConfig();
+ mOffloadHw.initOffloadControl(mControlCallback);
+ final ArgumentCaptor<ITetheringOffloadCallback> mOffloadCallbackCaptor =
+ ArgumentCaptor.forClass(ITetheringOffloadCallback.class);
+ verify(mIOffloadControl).initOffload(mOffloadCallbackCaptor.capture(), any());
+ mTetheringOffloadCallback = mOffloadCallbackCaptor.getValue();
+ }
+
+ @Test
+ public void testGetForwardedStats() throws Exception {
+ startOffloadHardwareInterface();
+ final OffloadHardwareInterface.ForwardedStats stats = mOffloadHw.getForwardedStats(RMNET0);
+ verify(mIOffloadControl).getForwardedStats(eq(RMNET0), any());
+ assertNotNull(stats);
+ }
+
+ @Test
+ public void testSetLocalPrefixes() throws Exception {
+ startOffloadHardwareInterface();
+ final ArrayList<String> localPrefixes = new ArrayList<>();
+ localPrefixes.add("127.0.0.0/8");
+ localPrefixes.add("fe80::/64");
+ mOffloadHw.setLocalPrefixes(localPrefixes);
+ verify(mIOffloadControl).setLocalPrefixes(eq(localPrefixes), any());
+ }
+
+ @Test
+ public void testSetDataLimit() throws Exception {
+ startOffloadHardwareInterface();
+ final long limit = 12345;
+ mOffloadHw.setDataLimit(RMNET0, limit);
+ verify(mIOffloadControl).setDataLimit(eq(RMNET0), eq(limit), any());
+ }
+
+ @Test
+ public void testSetUpstreamParameters() throws Exception {
+ startOffloadHardwareInterface();
+ final String v4addr = "192.168.10.1";
+ final String v4gateway = "192.168.10.255";
+ final ArrayList<String> v6gws = new ArrayList<>(0);
+ v6gws.add("2001:db8::1");
+ mOffloadHw.setUpstreamParameters(RMNET0, v4addr, v4gateway, v6gws);
+ verify(mIOffloadControl).setUpstreamParameters(eq(RMNET0), eq(v4addr), eq(v4gateway),
+ eq(v6gws), any());
+
+ final ArgumentCaptor<ArrayList<String>> mArrayListCaptor =
+ ArgumentCaptor.forClass(ArrayList.class);
+ mOffloadHw.setUpstreamParameters(null, null, null, null);
+ verify(mIOffloadControl).setUpstreamParameters(eq(""), eq(""), eq(""),
+ mArrayListCaptor.capture(), any());
+ assertEquals(mArrayListCaptor.getValue().size(), 0);
+ }
+
+ @Test
+ public void testUpdateDownstreamPrefix() throws Exception {
+ startOffloadHardwareInterface();
+ final String ifName = "wlan1";
+ final String prefix = "192.168.43.0/24";
+ mOffloadHw.addDownstreamPrefix(ifName, prefix);
+ verify(mIOffloadControl).addDownstream(eq(ifName), eq(prefix), any());
+
+ mOffloadHw.removeDownstreamPrefix(ifName, prefix);
+ verify(mIOffloadControl).removeDownstream(eq(ifName), eq(prefix), any());
+ }
+
+ @Test
+ public void testTetheringOffloadCallback() throws Exception {
+ startOffloadHardwareInterface();
+
+ mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STARTED);
+ mTestLooper.dispatchAll();
+ verify(mControlCallback).onStarted();
+
+ mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR);
+ mTestLooper.dispatchAll();
+ verify(mControlCallback).onStoppedError();
+
+ mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED);
+ mTestLooper.dispatchAll();
+ verify(mControlCallback).onStoppedUnsupported();
+
+ mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE);
+ mTestLooper.dispatchAll();
+ verify(mControlCallback).onSupportAvailable();
+
+ mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED);
+ mTestLooper.dispatchAll();
+ verify(mControlCallback).onStoppedLimitReached();
+
+ final NatTimeoutUpdate tcpParams = buildNatTimeoutUpdate(NetworkProtocol.TCP);
+ mTetheringOffloadCallback.updateTimeout(tcpParams);
+ mTestLooper.dispatchAll();
+ verify(mControlCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_TCP),
+ eq(tcpParams.src.addr),
+ eq(uint16(tcpParams.src.port)),
+ eq(tcpParams.dst.addr),
+ eq(uint16(tcpParams.dst.port)));
+
+ final NatTimeoutUpdate udpParams = buildNatTimeoutUpdate(NetworkProtocol.UDP);
+ mTetheringOffloadCallback.updateTimeout(udpParams);
+ mTestLooper.dispatchAll();
+ verify(mControlCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_UDP),
+ eq(udpParams.src.addr),
+ eq(uint16(udpParams.src.port)),
+ eq(udpParams.dst.addr),
+ eq(uint16(udpParams.dst.port)));
+ }
+
+ private NatTimeoutUpdate buildNatTimeoutUpdate(final int proto) {
+ final NatTimeoutUpdate params = new NatTimeoutUpdate();
+ params.proto = proto;
+ params.src.addr = "192.168.43.200";
+ params.src.port = 100;
+ params.dst.addr = "172.50.46.169";
+ params.dst.port = 150;
+ return params;
+ }
+}
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index e8ba5b8..fbfa871 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -127,6 +127,8 @@
.thenReturn(new String[0]);
when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
false);
+ initializeBpfOffloadConfiguration(true, null /* unset */);
+
mHasTelephonyManager = true;
mMockContext = new MockContext(mContext);
mEnableLegacyDhcpServer = false;
@@ -278,6 +280,50 @@
assertFalse(upstreamIterator.hasNext());
}
+ private void initializeBpfOffloadConfiguration(
+ final boolean fromRes, final String fromDevConfig) {
+ when(mResources.getBoolean(R.bool.config_tether_enable_bpf_offload)).thenReturn(fromRes);
+ doReturn(fromDevConfig).when(
+ () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
+ eq(TetheringConfiguration.OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD)));
+ }
+
+ @Test
+ public void testBpfOffloadEnabledByResource() {
+ initializeBpfOffloadConfiguration(true, null /* unset */);
+ final TetheringConfiguration enableByRes =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertTrue(enableByRes.enableBpfOffload);
+ }
+
+ @Test
+ public void testBpfOffloadEnabledByDeviceConfigOverride() {
+ for (boolean res : new boolean[]{true, false}) {
+ initializeBpfOffloadConfiguration(res, "true");
+ final TetheringConfiguration enableByDevConOverride =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertTrue(enableByDevConOverride.enableBpfOffload);
+ }
+ }
+
+ @Test
+ public void testBpfOffloadDisabledByResource() {
+ initializeBpfOffloadConfiguration(false, null /* unset */);
+ final TetheringConfiguration disableByRes =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertFalse(disableByRes.enableBpfOffload);
+ }
+
+ @Test
+ public void testBpfOffloadDisabledByDeviceConfigOverride() {
+ for (boolean res : new boolean[]{true, false}) {
+ initializeBpfOffloadConfiguration(res, "false");
+ final TetheringConfiguration disableByDevConOverride =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertFalse(disableByDevConOverride.enableBpfOffload);
+ }
+ }
+
@Test
public void testNewDhcpServerDisabled() {
when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d914bda..930f124 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13597,7 +13597,9 @@
pw.print(" unmapped + ");
pw.print(stringifyKBSize(ionPool));
pw.println(" pools)");
- kernelUsed += ionUnmapped;
+ // Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being
+ // set on ION VMAs, therefore consider the entire ION heap as used kernel memory
+ kernelUsed += ionHeap;
}
final long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss)
- memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
@@ -14403,7 +14405,9 @@
memInfoBuilder.append(" ION: ");
memInfoBuilder.append(stringifyKBSize(ionHeap + ionPool));
memInfoBuilder.append("\n");
- kernelUsed += ionUnmapped;
+ // Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being
+ // set on ION VMAs, therefore consider the entire ION heap as used kernel memory
+ kernelUsed += ionHeap;
}
memInfoBuilder.append(" Used RAM: ");
memInfoBuilder.append(stringifyKBSize(
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 17baead..8068e37 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7369,10 +7369,32 @@
return false;
}
boolean suppress = false;
- if (resolvedStream != AudioSystem.STREAM_MUSIC && mController != null) {
+ // Intended behavior:
+ // 1/ if the stream is not the default UI stream, do not suppress (as it is not involved
+ // in bringing up the UI)
+ // 2/ if the resolved and default stream is MUSIC, and media is playing, do not suppress
+ // 3/ otherwise suppress the first adjustments that occur during the "long press
+ // timeout" interval. Note this is true regardless of whether this is a "real long
+ // press" (where the user keeps pressing on the volume button), or repeated single
+ // presses (here we don't know if we are in a real long press, or repeated fast
+ // button presses).
+ // Once the long press timeout occurs (mNextLongPress reset to 0), do not suppress.
+ // Example: for a default and resolved stream of MUSIC, this allows modifying rapidly
+ // the volume when media is playing (whether by long press or repeated individual
+ // presses), or to bring up the volume UI when media is not playing, in order to make
+ // another change (e.g. switch ringer modes) without changing media volume.
+ if (resolvedStream == DEFAULT_VOL_STREAM_NO_PLAYBACK && mController != null) {
+ // never suppress media vol adjustement during media playback
+ if (resolvedStream == AudioSystem.STREAM_MUSIC
+ && AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, mLongPressTimeout))
+ {
+ // media is playing, adjust the volume right away
+ return false;
+ }
+
final long now = SystemClock.uptimeMillis();
if ((flags & AudioManager.FLAG_SHOW_UI) != 0 && !mVisible) {
- // ui will become visible
+ // UI is not visible yet, adjustment is ignored
if (mNextLongPress < now) {
mNextLongPress = now + mLongPressTimeout;
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java b/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
index 75fd7dc..927fcfb 100644
--- a/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
+++ b/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
@@ -630,7 +630,7 @@
}
Notification.Builder builder = new Notification.Builder(mContext,
- SystemNotificationChannels.NETWORK_ALERTS)
+ SystemNotificationChannels.NETWORK_STATUS)
.setSmallIcon(R.drawable.stat_sys_gps_on)
.setCategory(Notification.CATEGORY_SYSTEM)
.setVisibility(Notification.VISIBILITY_SECRET)
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 5e865e7..d7bd794 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -584,7 +584,8 @@
mAllRouterRecords.put(binder, routerRecord);
userRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::notifyRoutesToRouter, userRecord.mHandler, router));
+ obtainMessage(UserHandler::notifyRoutesToRouter,
+ userRecord.mHandler, routerRecord));
}
private void unregisterRouter2Locked(@NonNull IMediaRouter2 router, boolean died) {
@@ -1775,16 +1776,36 @@
}
}
- private void notifyRoutesToRouter(@NonNull IMediaRouter2 router) {
+ private void notifyRoutesToRouter(@NonNull RouterRecord routerRecord) {
List<MediaRoute2Info> routes = new ArrayList<>();
+
+ MediaRoute2ProviderInfo systemProviderInfo = null;
for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
+ // TODO: Create MediaRoute2ProviderInfo#isSystemProvider()
+ if (TextUtils.equals(providerInfo.getUniqueId(), mSystemProvider.getUniqueId())) {
+ // Adding routes from system provider will be handled below, so skip it here.
+ systemProviderInfo = providerInfo;
+ continue;
+ }
routes.addAll(providerInfo.getRoutes());
}
+
+ if (routerRecord.mHasModifyAudioRoutingPermission) {
+ if (systemProviderInfo != null) {
+ routes.addAll(systemProviderInfo.getRoutes());
+ } else {
+ // This shouldn't happen.
+ Slog.w(TAG, "notifyRoutesToRouter: System route provider not found.");
+ }
+ } else {
+ routes.add(mSystemProvider.getDefaultRoute());
+ }
+
if (routes.size() == 0) {
return;
}
try {
- router.notifyRoutesAdded(routes);
+ routerRecord.mRouter.notifyRoutesAdded(routes);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify all routes. Router probably died.", ex);
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 7af0787..1741aa7 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -395,7 +395,6 @@
private boolean mDataLoaderFinished = false;
- // TODO(b/146080380): merge file list with Callback installation.
private IncrementalFileStorages mIncrementalFileStorages;
private static final FileFilter sAddedApkFilter = new FileFilter() {
@@ -2698,6 +2697,7 @@
/**
* Makes sure files are present in staging location.
+ * @return if the image is ready for installation
*/
@GuardedBy("mLock")
private boolean prepareDataLoaderLocked()
@@ -2709,6 +2709,17 @@
return true;
}
+ // Retrying commit.
+ if (mIncrementalFileStorages != null) {
+ try {
+ mIncrementalFileStorages.startLoading();
+ } catch (IOException e) {
+ throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE, e.getMessage(),
+ e.getCause());
+ }
+ return false;
+ }
+
final List<InstallationFileParcel> addedFiles = new ArrayList<>();
final List<String> removedFiles = new ArrayList<>();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 3ead72c..7959461 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -608,6 +608,12 @@
private static final long DEFAULT_VERIFICATION_TIMEOUT = 10 * 1000;
/**
+ * The default maximum time to wait for the integrity verification to return in
+ * milliseconds.
+ */
+ private static final long DEFAULT_INTEGRITY_VERIFICATION_TIMEOUT = 30 * 1000;
+
+ /**
* Timeout duration in milliseconds for enabling package rollback. If we fail to enable
* rollback within that period, the install will proceed without rollback enabled.
*
@@ -13838,6 +13844,19 @@
}
/**
+ * Get the integrity verification timeout.
+ *
+ * @return verification timeout in milliseconds
+ */
+ private long getIntegrityVerificationTimeout() {
+ long timeout = Global.getLong(mContext.getContentResolver(),
+ Global.APP_INTEGRITY_VERIFICATION_TIMEOUT, DEFAULT_INTEGRITY_VERIFICATION_TIMEOUT);
+ // The setting can be used to increase the timeout but not decrease it, since that is
+ // equivalent to disabling the integrity component.
+ return Math.max(timeout, DEFAULT_INTEGRITY_VERIFICATION_TIMEOUT);
+ }
+
+ /**
* Get the default verification agent response code.
*
* @return default verification response code
@@ -15032,8 +15051,7 @@
final Message msg =
mHandler.obtainMessage(CHECK_PENDING_INTEGRITY_VERIFICATION);
msg.arg1 = verificationId;
- // TODO: do we want to use the same timeout?
- mHandler.sendMessageDelayed(msg, getVerificationTimeout());
+ mHandler.sendMessageDelayed(msg, getIntegrityVerificationTimeout());
}
}, /* scheduler= */ null,
/* initialCode= */ 0,
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index bc94528..0dc4d13 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3172,7 +3172,7 @@
metadata = (streamingVersion == 0) ? Metadata.forDataOnlyStreaming(fileId)
: Metadata.forStreaming(fileId);
try {
- if (V4Signature.readFrom(signature) == null) {
+ if ((signature.length > 0) && (V4Signature.readFrom(signature) == null)) {
getErrPrintWriter().println("V4 signature is invalid in: " + arg);
return 1;
}
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index 0519b80..529c4f6 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -85,4 +85,25 @@
}
void dumpDebug(ProtoOutputStream proto);
+
+ /**
+ * Gets called when the animation is about to finish and gives the client the opportunity to
+ * defer finishing the animation, i.e. it keeps the leash around until the client calls
+ * endDeferFinishCallback.
+ * <p>
+ * This has the same effect as
+ * {@link com.android.server.wm.SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)}
+ * . The later will be evaluated first and has precedence over this method if it returns true,
+ * which means that if the {@link com.android.server.wm.SurfaceAnimator.Animatable} requests to
+ * defer its finish, this method won't be called so this adapter will never have access to the
+ * finish callback. On the other hand, if the
+ * {@link com.android.server.wm.SurfaceAnimator.Animatable}, doesn't request to defer, this
+ * {@link AnimationAdapter} is responsible for ending the animation.
+ *
+ * @param endDeferFinishCallback The callback to call when defer finishing should be ended.
+ * @return Whether the client would like to defer the animation finish.
+ */
+ default boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e31eaf7..6655e92 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -499,6 +499,8 @@
*/
ActivityRecord mFixedRotationLaunchingApp;
+ FixedRotationAnimationController mFixedRotationAnimationController;
+
final FixedRotationTransitionListener mFixedRotationTransitionListener =
new FixedRotationTransitionListener();
@@ -1528,6 +1530,11 @@
}
private void startFixedRotationTransform(WindowToken token, int rotation) {
+ if (mFixedRotationAnimationController == null) {
+ mFixedRotationAnimationController = new FixedRotationAnimationController(
+ this);
+ }
+ mFixedRotationAnimationController.hide(rotation);
mTmpConfiguration.unset();
final DisplayInfo info = computeScreenConfiguration(mTmpConfiguration, rotation);
final WmDisplayCutout cutout = calculateDisplayCutoutForRotation(rotation);
@@ -1549,6 +1556,13 @@
}
}
+ void finishFixedRotationAnimation() {
+ if (mFixedRotationAnimationController != null
+ && mFixedRotationAnimationController.show()) {
+ mFixedRotationAnimationController = null;
+ }
+ }
+
/**
* Update rotation of the display.
*
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index ebfe70c..c3f9061 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -560,6 +560,7 @@
}, true /* traverseTopToBottom */);
mSeamlessRotationCount = 0;
mRotatingSeamlessly = false;
+ mDisplayContent.finishFixedRotationAnimation();
}
private void prepareSeamlessRotation() {
@@ -646,6 +647,7 @@
"Performing post-rotate rotation after seamless rotation");
// Finish seamless rotation.
mRotatingSeamlessly = false;
+ mDisplayContent.finishFixedRotationAnimation();
updateRotationAndSendNewConfigIfChanged();
}
diff --git a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
new file mode 100644
index 0000000..7aca637
--- /dev/null
+++ b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
@@ -0,0 +1,197 @@
+/*
+ * 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.server.wm;
+
+import static com.android.server.wm.AnimationSpecProto.WINDOW;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
+import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
+
+import android.content.res.Configuration;
+import android.util.proto.ProtoOutputStream;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Transformation;
+
+import com.android.internal.R;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Controller to fade out and in system ui when applying a fixed rotation transform to a window
+ * token.
+ *
+ * The system bars will be fade out when the fixed rotation transform starts and will be fade in
+ * once all surfaces have been rotated.
+ */
+public class FixedRotationAnimationController {
+
+ private final WindowManagerService mWmService;
+ private boolean mShowRequested = true;
+ private int mTargetRotation = Configuration.ORIENTATION_UNDEFINED;
+ private final ArrayList<WindowState> mAnimatedWindowStates = new ArrayList<>(2);
+ private final Runnable[] mDeferredFinishCallbacks;
+
+ public FixedRotationAnimationController(DisplayContent displayContent) {
+ mWmService = displayContent.mWmService;
+ addAnimatedWindow(displayContent.getDisplayPolicy().getStatusBar());
+ addAnimatedWindow(displayContent.getDisplayPolicy().getNavigationBar());
+ mDeferredFinishCallbacks = new Runnable[mAnimatedWindowStates.size()];
+ }
+
+ private void addAnimatedWindow(WindowState windowState) {
+ if (windowState != null) {
+ mAnimatedWindowStates.add(windowState);
+ }
+ }
+
+ /**
+ * Show the previously hidden {@link WindowToken} if their surfaces have already been rotated.
+ *
+ * @return True if the show animation has been started, in which case the caller no longer needs
+ * this {@link FixedRotationAnimationController}.
+ */
+ boolean show() {
+ if (!mShowRequested && readyToShow()) {
+ mShowRequested = true;
+ for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) {
+ WindowState windowState = mAnimatedWindowStates.get(i);
+ fadeWindowToken(true, windowState.getParent(), i);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ void hide(int targetRotation) {
+ mTargetRotation = targetRotation;
+ if (mShowRequested) {
+ mShowRequested = false;
+ for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) {
+ WindowState windowState = mAnimatedWindowStates.get(i);
+ fadeWindowToken(false /* show */, windowState.getParent(), i);
+ }
+ }
+ }
+
+ void cancel() {
+ for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) {
+ WindowState windowState = mAnimatedWindowStates.get(i);
+ mShowRequested = true;
+ fadeWindowToken(true /* show */, windowState.getParent(), i);
+ }
+ }
+
+ private void fadeWindowToken(boolean show, WindowContainer<WindowToken> windowToken,
+ int index) {
+ Animation animation = AnimationUtils.loadAnimation(mWmService.mContext,
+ show ? R.anim.fade_in : R.anim.fade_out);
+ LocalAnimationAdapter.AnimationSpec windowAnimationSpec = createAnimationSpec(animation);
+
+ FixedRotationAnimationAdapter animationAdapter = new FixedRotationAnimationAdapter(
+ windowAnimationSpec, windowToken.getSurfaceAnimationRunner(), show, index);
+
+ // We deferred the end of the animation when hiding the token, so we need to end it now that
+ // it's shown again.
+ SurfaceAnimator.OnAnimationFinishedCallback finishedCallback = show ? (t, r) -> {
+ if (mDeferredFinishCallbacks[index] != null) {
+ mDeferredFinishCallbacks[index].run();
+ mDeferredFinishCallbacks[index] = null;
+ }
+ } : null;
+ windowToken.startAnimation(windowToken.getPendingTransaction(), animationAdapter,
+ mShowRequested, ANIMATION_TYPE_FIXED_TRANSFORM, finishedCallback);
+ }
+
+ /**
+ * Check if all the mAnimatedWindowState's surfaces have been rotated to the
+ * mTargetRotation.
+ */
+ private boolean readyToShow() {
+ for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) {
+ WindowState windowState = mAnimatedWindowStates.get(i);
+ if (windowState.getConfiguration().windowConfiguration.getRotation()
+ != mTargetRotation || windowState.mPendingSeamlessRotate != null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ private LocalAnimationAdapter.AnimationSpec createAnimationSpec(Animation animation) {
+ return new LocalAnimationAdapter.AnimationSpec() {
+
+ Transformation mTransformation = new Transformation();
+
+ @Override
+ public boolean getShowWallpaper() {
+ return true;
+ }
+
+ @Override
+ public long getDuration() {
+ return animation.getDuration();
+ }
+
+ @Override
+ public void apply(SurfaceControl.Transaction t, SurfaceControl leash,
+ long currentPlayTime) {
+ mTransformation.clear();
+ animation.getTransformation(currentPlayTime, mTransformation);
+ t.setAlpha(leash, mTransformation.getAlpha());
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.println(animation);
+ }
+
+ @Override
+ public void dumpDebugInner(ProtoOutputStream proto) {
+ final long token = proto.start(WINDOW);
+ proto.write(ANIMATION, animation.toString());
+ proto.end(token);
+ }
+ };
+ }
+
+ private class FixedRotationAnimationAdapter extends LocalAnimationAdapter {
+ private final boolean mShow;
+ private final int mIndex;
+
+ FixedRotationAnimationAdapter(AnimationSpec windowAnimationSpec,
+ SurfaceAnimationRunner surfaceAnimationRunner, boolean show, int index) {
+ super(windowAnimationSpec, surfaceAnimationRunner);
+ mShow = show;
+ mIndex = index;
+ }
+
+ @Override
+ public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
+ // We defer the end of the hide animation to ensure the tokens stay hidden until
+ // we show them again.
+ if (!mShow) {
+ mDeferredFinishCallbacks[mIndex] = endDeferFinishCallback;
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 18e32c0..0143eb1 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -109,7 +109,10 @@
animationFinishCallback.onAnimationFinished(type, anim);
}
};
- if (!mAnimatable.shouldDeferAnimationFinish(resetAndInvokeFinish)) {
+ // If both the Animatable and AnimationAdapter requests to be deferred, only the
+ // first one will be called.
+ if (!(mAnimatable.shouldDeferAnimationFinish(resetAndInvokeFinish)
+ || anim.shouldDeferAnimationFinish(resetAndInvokeFinish))) {
resetAndInvokeFinish.run();
}
}
@@ -486,6 +489,12 @@
static final int ANIMATION_TYPE_INSETS_CONTROL = 1 << 5;
/**
+ * Animation when a fixed rotation transform is applied to a window token.
+ * @hide
+ */
+ static final int ANIMATION_TYPE_FIXED_TRANSFORM = 1 << 6;
+
+ /**
* Bitmask to include all animation types. This is NOT an {@link AnimationType}
* @hide
*/
@@ -502,7 +511,8 @@
ANIMATION_TYPE_DIMMER,
ANIMATION_TYPE_RECENTS,
ANIMATION_TYPE_WINDOW_ANIMATION,
- ANIMATION_TYPE_INSETS_CONTROL
+ ANIMATION_TYPE_INSETS_CONTROL,
+ ANIMATION_TYPE_FIXED_TRANSFORM
})
@Retention(RetentionPolicy.SOURCE)
@interface AnimationType {}
@@ -592,6 +602,12 @@
* Gets called when the animation is about to finish and gives the client the opportunity to
* defer finishing the animation, i.e. it keeps the leash around until the client calls
* {@link #cancelAnimation}.
+ * <p>
+ * {@link AnimationAdapter} has a similar method which is called only if this method returns
+ * false. This mean that if both this {@link Animatable} and the {@link AnimationAdapter}
+ * request to be deferred, this method is the sole responsible to call
+ * endDeferFinishCallback. On the other hand, the animation finish might still be deferred
+ * if this method return false and the one from the {@link AnimationAdapter} returns true.
*
* @param endDeferFinishCallback The callback to call when defer finishing should be ended.
* @return Whether the client would like to defer the animation finish.
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index c4cb4b5..707a789 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -138,6 +138,13 @@
Slog.e(TAG, "Attempt to operate on detached container: " + wc);
continue;
}
+ // Make sure we add to the syncSet before performing
+ // operations so we don't end up splitting effects between the WM
+ // pending transaction and the BLASTSync transaction.
+ if (syncId >= 0) {
+ mBLASTSyncEngine.addToSyncSet(syncId, wc);
+ }
+
int containerEffect = applyWindowContainerChange(wc, entry.getValue());
effects |= containerEffect;
@@ -146,9 +153,6 @@
&& (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
haveConfigChanges.add(wc);
}
- if (syncId >= 0) {
- mBLASTSyncEngine.addToSyncSet(syncId, wc);
- }
}
// Hierarchy changes
final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 768f89e..8739bad4 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -642,6 +642,9 @@
final int originalRotation = getWindowConfiguration().getRotation();
onConfigurationChanged(parent.getConfiguration());
onCancelFixedRotationTransform(originalRotation);
+ if (mDisplayContent.mFixedRotationAnimationController != null) {
+ mDisplayContent.mFixedRotationAnimationController.cancel();
+ }
}
/**
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index 6cf8133..e904645 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -18,6 +18,7 @@
#define LOG_TAG "PackageManagerShellCommandDataLoader-jni"
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <android-base/no_destructor.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <core_jni_helpers.h>
@@ -65,6 +66,7 @@
static constexpr MagicType INCR = 0x52434e49; // BE INCR
static constexpr auto PollTimeoutMs = 5000;
+static constexpr auto TraceTagCheckInterval = 1s;
struct JniIds {
jclass packageManagerShellCommandDataLoader;
@@ -337,9 +339,47 @@
return env;
}
-class PackageManagerShellCommandDataLoaderDataLoader : public android::dataloader::DataLoader {
+class PMSCDataLoader;
+
+struct OnTraceChanged {
+ OnTraceChanged();
+ ~OnTraceChanged() {
+ mRunning = false;
+ mChecker.join();
+ }
+
+ void registerCallback(PMSCDataLoader* callback) {
+ std::unique_lock lock(mMutex);
+ mCallbacks.insert(callback);
+ }
+
+ void unregisterCallback(PMSCDataLoader* callback) {
+ std::unique_lock lock(mMutex);
+ mCallbacks.erase(callback);
+ }
+
+private:
+ std::mutex mMutex;
+ std::unordered_set<PMSCDataLoader*> mCallbacks;
+ std::atomic<bool> mRunning{true};
+ std::thread mChecker;
+};
+
+static OnTraceChanged& onTraceChanged() {
+ static android::base::NoDestructor<OnTraceChanged> instance;
+ return *instance;
+}
+
+class PMSCDataLoader : public android::dataloader::DataLoader {
public:
- PackageManagerShellCommandDataLoaderDataLoader(JavaVM* jvm) : mJvm(jvm) { CHECK(mJvm); }
+ PMSCDataLoader(JavaVM* jvm) : mJvm(jvm) { CHECK(mJvm); }
+ ~PMSCDataLoader() { onTraceChanged().unregisterCallback(this); }
+
+ void updateReadLogsState(const bool enabled) {
+ if (enabled != mReadLogsEnabled.exchange(enabled)) {
+ mIfs->setParams({.readLogsEnabled = enabled});
+ }
+ }
private:
// Lifecycle.
@@ -353,7 +393,8 @@
mArgs = params.arguments();
mIfs = ifs;
mStatusListener = statusListener;
- mIfs->setParams({.readLogsEnabled = true});
+ updateReadLogsState(atrace_is_tag_enabled(ATRACE_TAG));
+ onTraceChanged().registerCallback(this);
return true;
}
bool onStart() final { return true; }
@@ -365,6 +406,7 @@
}
}
void onDestroy() final {
+ onTraceChanged().unregisterCallback(this);
// Make sure the receiver thread stopped.
CHECK(!mReceiverThread.joinable());
}
@@ -757,10 +799,28 @@
android::base::unique_fd mEventFd;
std::thread mReceiverThread;
std::atomic<bool> mStopReceiving = false;
+ std::atomic<bool> mReadLogsEnabled = false;
/** Tracks which files have been requested */
std::unordered_set<FileIdx> mRequestedFiles;
};
+OnTraceChanged::OnTraceChanged() {
+ mChecker = std::thread([this]() {
+ bool oldTrace = atrace_is_tag_enabled(ATRACE_TAG);
+ while (mRunning) {
+ bool newTrace = atrace_is_tag_enabled(ATRACE_TAG);
+ if (oldTrace != newTrace) {
+ std::unique_lock lock(mMutex);
+ for (auto&& callback : mCallbacks) {
+ callback->updateReadLogsState(newTrace);
+ }
+ }
+ oldTrace = newTrace;
+ std::this_thread::sleep_for(TraceTagCheckInterval);
+ }
+ });
+}
+
BlockHeader readHeader(std::span<uint8_t>& data) {
BlockHeader header;
if (data.size() < sizeof(header)) {
@@ -794,7 +854,7 @@
[](auto jvm, const auto& params) -> android::dataloader::DataLoaderPtr {
if (params.type() == DATA_LOADER_TYPE_INCREMENTAL) {
// This DataLoader only supports incremental installations.
- return std::make_unique<PackageManagerShellCommandDataLoaderDataLoader>(jvm);
+ return std::make_unique<PMSCDataLoader>(jvm);
}
return {};
});
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index a9dc92f..78439db 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -748,7 +748,7 @@
return -EINVAL;
}
- LOG(INFO) << "Removing bind point " << target;
+ LOG(INFO) << "Removing bind point " << target << " for storage " << storage;
// Here we should only look up by the exact target, not by a subdirectory of any existing mount,
// otherwise there's a chance to unmount something completely unrelated
@@ -1807,6 +1807,8 @@
targetStatus = mTargetStatus;
}
+ LOG(DEBUG) << "fsmStep: " << mId << ": " << currentStatus << " -> " << targetStatus;
+
if (currentStatus == targetStatus) {
return true;
}
@@ -1868,8 +1870,8 @@
listener = mListener;
if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE) {
- // For unavailable, reset target status.
- setTargetStatusLocked(IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE);
+ // For unavailable, unbind from DataLoader to ensure proper re-commit.
+ setTargetStatusLocked(IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
}
}
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 325218d..2e4625c 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -677,10 +677,10 @@
mDataLoaderManager->bindToDataLoaderSuccess();
mDataLoaderManager->getDataLoaderSuccess();
EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2);
- EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+ EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(2);
EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
EXPECT_CALL(*mDataLoader, start(_)).Times(0);
- EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+ EXPECT_CALL(*mDataLoader, destroy(_)).Times(2);
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
EXPECT_CALL(*mLooper, addFd(MockIncFs::kPendingReadsFd, _, _, _, _)).Times(1);
EXPECT_CALL(*mLooper, removeFd(MockIncFs::kPendingReadsFd)).Times(1);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index 5c21853..9263d8a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -316,20 +316,16 @@
}
@Test
- @FlakyTest(bugId = 130392471)
public void testAddRemoveRace() {
// There was once a race condition between adding and removing starting windows
+ final ActivityRecord appToken = mAppWindow.mActivityRecord;
for (int i = 0; i < 1000; i++) {
- final ActivityRecord appToken = createIsolatedTestActivityRecord();
-
appToken.addStartingWindow(mPackageName,
android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
false, false);
appToken.removeStartingWindow();
waitUntilHandlersIdle();
assertNoStartingWindow(appToken);
-
- appToken.getParent().getParent().removeImmediately();
}
}
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 ac95a81..7b23bfb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -57,6 +57,7 @@
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.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
@@ -1059,6 +1060,8 @@
@Test
public void testApplyTopFixedRotationTransform() {
mWm.mIsFixedRotationTransformEnabled = true;
+ mDisplayContent.getDisplayPolicy().addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs);
+ mDisplayContent.getDisplayPolicy().addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
final Configuration config90 = new Configuration();
mDisplayContent.computeScreenConfiguration(config90, ROTATION_90);
@@ -1079,6 +1082,12 @@
ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */,
false /* forceUpdate */));
+ assertNotNull(mDisplayContent.mFixedRotationAnimationController);
+ assertTrue(mStatusBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
+ ANIMATION_TYPE_FIXED_TRANSFORM));
+ assertTrue(mNavBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
+ ANIMATION_TYPE_FIXED_TRANSFORM));
+
final Rect outFrame = new Rect();
final Rect outInsets = new Rect();
final Rect outStableInsets = new Rect();
@@ -1131,6 +1140,9 @@
assertFalse(app.hasFixedRotationTransform());
assertFalse(app2.hasFixedRotationTransform());
assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation);
+
+ mDisplayContent.finishFixedRotationAnimation();
+ assertNull(mDisplayContent.mFixedRotationAnimationController);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index d605ab2..27c4e9b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -27,9 +27,7 @@
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
-import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
-import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
@@ -40,7 +38,6 @@
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -50,7 +47,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
-import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;
import static org.testng.Assert.expectThrows;
@@ -64,18 +60,15 @@
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.InsetsState;
-import android.view.ViewRootImpl;
import android.view.WindowInsets.Side;
import android.view.WindowInsets.Type;
import android.view.WindowManager;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.utils.WmDisplayCutout;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -116,12 +109,6 @@
updateDisplayFrames();
}
- @After
- public void tearDown() {
- PolicyControl.setFilters("");
- mWindow.getDisplayContent().mInputMethodTarget = null;
- }
-
public void setRotation(int rotation) {
mRotation = rotation;
updateDisplayFrames();
@@ -210,8 +197,6 @@
@Test
public void layoutWindowLw_fitStatusBars() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
mWindow.mAttrs.setFitInsetsTypes(Type.statusBars());
addWindow(mWindow);
@@ -228,8 +213,6 @@
@Test
public void layoutWindowLw_fitNavigationBars() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
mWindow.mAttrs.setFitInsetsTypes(Type.navigationBars());
addWindow(mWindow);
@@ -246,8 +229,6 @@
@Test
public void layoutWindowLw_fitAllSides() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
mWindow.mAttrs.setFitInsetsSides(Side.all());
addWindow(mWindow);
@@ -264,8 +245,6 @@
@Test
public void layoutWindowLw_fitTopOnly() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
mWindow.mAttrs.setFitInsetsSides(Side.TOP);
addWindow(mWindow);
@@ -282,8 +261,6 @@
@Test
public void layoutWindowLw_fitInsetsIgnoringVisibility() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
final InsetsState state =
mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow);
state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
@@ -304,8 +281,6 @@
@Test
public void layoutWindowLw_fitInsetsNotIgnoringVisibility() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
final InsetsState state =
mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow);
state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
@@ -326,7 +301,6 @@
@Test
public void layoutWindowLw_fitDisplayCutout() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
addDisplayCutout();
mWindow.mAttrs.setFitInsetsTypes(Type.displayCutout());
@@ -683,31 +657,6 @@
}
@Test
- public void layoutWindowLw_withForwardInset_SoftInputAdjustResize() {
- assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_NONE);
-
- mWindow.mAttrs.flags =
- FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
- mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
- mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
- addWindow(mWindow);
-
- final int forwardedInsetBottom = 50;
- mDisplayPolicy.setForwardedInsets(Insets.of(0, 0, 0, forwardedInsetBottom));
- mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
- mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-
- assertInsetBy(mWindow.getParentFrame(), 0, 0, 0, 0);
- assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
- assertInsetByTopBottom(mWindow.getContentFrameLw(),
- STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT + forwardedInsetBottom);
- assertInsetByTopBottom(mWindow.getVisibleFrameLw(),
- STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT + forwardedInsetBottom);
- assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0);
- assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0);
- }
-
- @Test
public void layoutWindowLw_withForwardInset_SoftInputAdjustNothing() {
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
@@ -728,117 +677,6 @@
assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0);
}
- // TODO(b/118118435): remove after removing PolicyControl
- @FlakyTest(bugId = 129711077)
- @Test
- public void layoutWindowLw_withImmersive_SoftInputAdjustResize() {
- assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
-
- synchronized (mWm.mGlobalLock) {
- mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
- mWindow.mAttrs.flags = 0;
- mWindow.mAttrs.systemUiVisibility =
- SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_HIDE_NAVIGATION;
-
- addWindow(mWindow);
-
- mWindow.getDisplayContent().mInputMethodTarget = mWindow;
- mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
- mFrames.mContent.bottom = mFrames.mVoiceContent.bottom = INPUT_METHOD_WINDOW_TOP;
- mFrames.mCurrent.bottom = INPUT_METHOD_WINDOW_TOP;
- mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-
- int bottomInset = mFrames.mDisplayHeight - INPUT_METHOD_WINDOW_TOP;
- assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
- assertInsetByTopBottom(mWindow.getContentFrameLw(), 0, 0);
- assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, bottomInset);
- }
- }
-
- // TODO(b/118118435): remove after removing PolicyControl
- @FlakyTest(bugId = 129711077)
- @Test
- public void layoutWindowLw_withImmersive_SoftInputAdjustNothing() {
- assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
-
- synchronized (mWm.mGlobalLock) {
- mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_NOTHING;
- mWindow.mAttrs.flags = 0;
- mWindow.mAttrs.systemUiVisibility =
- SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_HIDE_NAVIGATION;
-
- addWindow(mWindow);
-
- mWindow.getDisplayContent().mInputMethodTarget = mWindow;
- mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
- mFrames.mContent.bottom = mFrames.mVoiceContent.bottom = INPUT_METHOD_WINDOW_TOP;
- mFrames.mCurrent.bottom = INPUT_METHOD_WINDOW_TOP;
- mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-
- assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
- assertInsetByTopBottom(mWindow.getContentFrameLw(), 0, 0);
- assertInsetByTopBottom(mWindow.getVisibleFrameLw(), 0, 0);
- }
- }
-
- // TODO(b/118118435): remove after removing PolicyControl
- @FlakyTest(bugId = 129711077)
- @Test
- public void layoutWindowLw_withForceImmersive_fullscreen() {
- assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
-
- synchronized (mWm.mGlobalLock) {
- mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
- mWindow.mAttrs.flags = 0;
- mWindow.mAttrs.systemUiVisibility = 0;
- PolicyControl.setFilters(PolicyControl.NAME_IMMERSIVE_FULL + "=*");
-
- addWindow(mWindow);
-
- mWindow.getDisplayContent().mInputMethodTarget = mWindow;
- mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
- mFrames.mContent.bottom = mFrames.mVoiceContent.bottom = INPUT_METHOD_WINDOW_TOP;
- mFrames.mCurrent.bottom = INPUT_METHOD_WINDOW_TOP;
- mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-
- int bottomInset = mFrames.mDisplayHeight - INPUT_METHOD_WINDOW_TOP;
- assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
- assertInsetByTopBottom(mWindow.getContentFrameLw(), 0, 0);
- assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, bottomInset);
- }
- }
-
- // TODO(b/118118435): remove after removing PolicyControl
- @FlakyTest(bugId = 129711077)
- @Test
- public void layoutWindowLw_withForceImmersive_nonFullscreen() {
- assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
-
- synchronized (mWm.mGlobalLock) {
- mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
- mWindow.mAttrs.flags = 0;
- mWindow.mAttrs.systemUiVisibility = 0;
- mWindow.mAttrs.width = DISPLAY_WIDTH / 2;
- mWindow.mAttrs.height = DISPLAY_HEIGHT / 2;
- PolicyControl.setFilters(PolicyControl.NAME_IMMERSIVE_FULL + "=*");
-
- addWindow(mWindow);
-
- mWindow.getDisplayContent().mInputMethodTarget = mWindow;
- mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
- mFrames.mContent.bottom = mFrames.mVoiceContent.bottom = INPUT_METHOD_WINDOW_TOP;
- mFrames.mCurrent.bottom = INPUT_METHOD_WINDOW_TOP;
- mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-
- int bottomInset = mFrames.mDisplayHeight - INPUT_METHOD_WINDOW_TOP;
- assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, bottomInset);
- assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, bottomInset);
- assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, bottomInset);
- }
- }
-
@Test
public void layoutHint_appWindow() {
mWindow.mAttrs.flags =
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 552c476..79ba175 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -33,6 +33,7 @@
import static org.mockito.ArgumentMatchers.eq;
import android.platform.test.annotations.Presubmit;
+import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Builder;
import android.view.SurfaceControl.Transaction;
@@ -52,6 +53,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.PrintWriter;
+
/**
* Test class for {@link SurfaceAnimatorTest}.
*
@@ -267,6 +270,27 @@
assertFalse(mDeferFinishAnimatable.mFinishedCallbackCalled);
}
+ @Test
+ public void testDeferFinishFromAdapter() {
+
+ DeferredFinishAdapter deferredFinishAdapter = new DeferredFinishAdapter();
+ // Start animation
+ mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, deferredFinishAdapter,
+ true /* hidden */,
+ ANIMATION_TYPE_APP_TRANSITION);
+ assertAnimating(mAnimatable);
+ deferredFinishAdapter.mFinishCallback.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION,
+ deferredFinishAdapter);
+
+ assertAnimating(mAnimatable);
+ assertFalse(mAnimatable.mFinishedCallbackCalled);
+ // Now end defer finishing.
+ deferredFinishAdapter.mEndDeferFinishCallback.run();
+ assertNotAnimating(mAnimatable);
+ assertTrue(mAnimatable.mFinishedCallbackCalled);
+ verify(mTransaction).remove(eq(deferredFinishAdapter.mAnimationLeash));
+ }
+
private OnAnimationFinishedCallback startDeferFinishAnimatable(AnimationAdapter anim) {
mDeferFinishAnimatable.mSurfaceAnimator.startAnimation(mTransaction, anim,
true /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
@@ -389,4 +413,51 @@
return true;
}
}
+
+ private static class DeferredFinishAdapter implements AnimationAdapter {
+
+ private Runnable mEndDeferFinishCallback;
+ private OnAnimationFinishedCallback mFinishCallback;
+ private SurfaceControl mAnimationLeash;
+
+ @Override
+ public boolean getShowWallpaper() {
+ return true;
+ }
+
+ @Override
+ public void startAnimation(SurfaceControl animationLeash, Transaction t, int type,
+ OnAnimationFinishedCallback finishCallback) {
+ mFinishCallback = finishCallback;
+ mAnimationLeash = animationLeash;
+ }
+
+ @Override
+ public void onAnimationCancelled(SurfaceControl animationLeash) {
+ }
+
+ @Override
+ public long getDurationHint() {
+ return 100;
+ }
+
+ @Override
+ public long getStatusBarTransitionsStartTime() {
+ return 100;
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ }
+
+ @Override
+ public void dumpDebug(ProtoOutputStream proto) {
+ }
+
+ @Override
+ public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
+ mEndDeferFinishCallback = endDeferFinishCallback;
+ return true;
+ }
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 65fb2c0..0346329 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -22,7 +22,6 @@
import static android.hardware.camera2.params.OutputConfiguration.ROTATION_90;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.Surface.ROTATION_0;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
@@ -75,17 +74,13 @@
import android.view.DisplayCutout;
import android.view.InsetsSource;
import android.view.SurfaceControl;
-import android.view.ViewRootImpl;
import android.view.WindowManager;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.server.wm.utils.WmDisplayCutout;
-import org.junit.AfterClass;
import org.junit.Before;
-import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -103,21 +98,6 @@
@Presubmit
@RunWith(WindowTestRunner.class)
public class WindowStateTests extends WindowTestsBase {
- private static int sPreviousNewInsetsMode;
-
- @BeforeClass
- public static void setUpOnce() {
- // TODO: Make use of SettingsSession when it becomes feasible for this.
- sPreviousNewInsetsMode = ViewRootImpl.sNewInsetsMode;
- // To let the insets provider control the insets visibility, the insets mode has to be
- // NEW_INSETS_MODE_FULL.
- ViewRootImpl.sNewInsetsMode = NEW_INSETS_MODE_FULL;
- }
-
- @AfterClass
- public static void tearDownOnce() {
- ViewRootImpl.sNewInsetsMode = sPreviousNewInsetsMode;
- }
@Before
public void setUp() {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index e85dfbe..4246aef 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -10415,7 +10415,7 @@
* <p>To recognize a carrier (including MVNO) as a first-class identity, Android assigns each
* carrier with a canonical integer a.k.a. carrier id. The carrier ID is an Android
* platform-wide identifier for a carrier. AOSP maintains carrier ID assignments in
- * <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a>
+ * <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb">here</a>
*
* <p>Apps which have carrier-specific configurations or business logic can use the carrier id
* as an Android platform-wide identifier for carriers.
@@ -10477,7 +10477,7 @@
*
* <p>For carriers without fine-grained specific carrier ids, return {@link #getSimCarrierId()}
* <p>Specific carrier ids are defined in the same way as carrier id
- * <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a>
+ * <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb">here</a>
* except each with a "parent" id linking to its top-level carrier id.
*
* @return Returns fine-grained carrier id of the current subscription.
@@ -10526,7 +10526,7 @@
* This is used for fallback when configurations/logic for exact carrier id
* {@link #getSimCarrierId()} are not found.
*
- * Android carrier id table <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a>
+ * Android carrier id table <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb">here</a>
* can be updated out-of-band, its possible a MVNO (Mobile Virtual Network Operator) carrier
* was not fully recognized and assigned to its MNO (Mobile Network Operator) carrier id
* by default. After carrier id table update, a new carrier id was assigned. If apps don't
@@ -10553,7 +10553,7 @@
* used for fallback when configurations/logic for exact carrier id {@link #getSimCarrierId()}
* are not found.
*
- * Android carrier id table <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a>
+ * Android carrier id table <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb">here</a>
* can be updated out-of-band, its possible a MVNO (Mobile Virtual Network Operator) carrier
* was not fully recognized and assigned to its MNO (Mobile Network Operator) carrier id
* by default. After carrier id table update, a new carrier id was assigned. If apps don't
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
index eec3cdbe..8c2de40 100644
--- a/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
@@ -52,7 +52,7 @@
doReturn(mock(IBinder::class.java)).`when`(it).getSystemService(Context.NETD_SERVICE)
}
- private class TestPermissionChecker : NetworkStackConnector.PermissionChecker() {
+ private class TestPermissionChecker : NetworkStackService.PermissionChecker() {
override fun enforceNetworkStackCallingPermission() = Unit
}
@@ -62,8 +62,8 @@
override fun sendNetworkConditionsBroadcast(context: Context, broadcast: Intent) = Unit
}
- private inner class TestNetworkStackConnector(context: Context) :
- NetworkStackConnector(context, TestPermissionChecker()) {
+ private inner class TestNetworkStackConnector(context: Context) : NetworkStackConnector(
+ context, TestPermissionChecker(), NetworkStackService.Dependencies()) {
private val network = Network(TEST_NETID)
private val privateDnsBypassNetwork = TestNetwork(TEST_NETID)
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index d299cdc..94771ac 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -131,10 +131,10 @@
public @interface WifiBand {}
/**
- * Max band value
+ * All bands
* @hide
*/
- public static final int WIFI_BAND_MAX = 0x10;
+ public static final int WIFI_BAND_ALL = (1 << WIFI_BAND_COUNT) - 1;
/** Minimum supported scanning period */
public static final int MIN_SCAN_PERIOD_MS = 1000;
@@ -168,6 +168,22 @@
}
/**
+ * Test if scan is a full scan. i.e. scanning all available bands.
+ * For backward compatibility, since some apps don't include 6GHz in their requests yet,
+ * lacking 6GHz band does not cause the result to be false.
+ *
+ * @param bandScanned bands that are fully scanned
+ * @param excludeDfs when true, DFS band is excluded from the check
+ * @return true if all bands are scanned, false otherwise
+ *
+ * @hide
+ */
+ public static boolean isFullBandScan(@WifiBand int bandScanned, boolean excludeDfs) {
+ return (bandScanned | WIFI_BAND_6_GHZ | (excludeDfs ? WIFI_BAND_5_GHZ_DFS_ONLY : 0))
+ == WIFI_BAND_ALL;
+ }
+
+ /**
* Returns a list of all the possible channels for the given band(s).
*
* @param band one of the WifiScanner#WIFI_BAND_* constants, e.g. {@link #WIFI_BAND_24_GHZ}
diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
index 4881200..b68616f 100644
--- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
@@ -613,4 +613,22 @@
verify(mExecutor, never()).execute(any());
verify(mScanListener, never()).onResults(mScanData);
}
+
+ /**
+ * Tests isFullBandScan() method with and without DFS check
+ */
+ @Test
+ public void testIsFullBandScan() throws Exception {
+ assertFalse(WifiScanner.isFullBandScan(WifiScanner.WIFI_BAND_24_GHZ, true));
+ assertFalse(WifiScanner.isFullBandScan(WifiScanner.WIFI_BAND_5_GHZ, true));
+ assertFalse(WifiScanner.isFullBandScan(WifiScanner.WIFI_BAND_6_GHZ, true));
+ assertFalse(WifiScanner.isFullBandScan(
+ WifiScanner.WIFI_BAND_6_GHZ | WifiScanner.WIFI_BAND_5_GHZ, true));
+ assertTrue(WifiScanner.isFullBandScan(
+ WifiScanner.WIFI_BAND_24_GHZ | WifiScanner.WIFI_BAND_5_GHZ, true));
+ assertFalse(WifiScanner.isFullBandScan(
+ WifiScanner.WIFI_BAND_24_GHZ | WifiScanner.WIFI_BAND_5_GHZ, false));
+ assertTrue(WifiScanner.isFullBandScan(WifiScanner.WIFI_BAND_BOTH_WITH_DFS, true));
+ assertTrue(WifiScanner.isFullBandScan(WifiScanner.WIFI_BAND_BOTH_WITH_DFS, false));
+ }
}