summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ApplicationStartInfo.java25
-rw-r--r--core/java/android/os/FileUtils.java4
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig10
-rw-r--r--core/proto/android/nfc/card_emulation.proto1
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.kt52
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java19
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt20
-rw-r--r--packages/SettingsLib/res/values/arrays.xml20
-rw-r--r--packages/SettingsLib/res/values/strings.xml3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt5
-rw-r--r--packages/SystemUI/res/values/config.xml3
-rw-r--r--packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt122
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt1569
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/HeadsUpStatusBarModel.kt (renamed from packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt)9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NewRemoteViews.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt81
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt572
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java28
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerConstants.java98
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java12
-rw-r--r--services/core/java/com/android/server/am/AppProfiler.java20
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java75
-rw-r--r--services/core/java/com/android/server/pm/PackageHandler.java13
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java43
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java12
-rw-r--r--services/core/java/com/android/server/wm/DeferredDisplayUpdater.java10
-rw-r--r--services/core/java/com/android/server/wm/EventLogTags.logtags4
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java3
-rw-r--r--services/core/java/com/android/server/wm/Transition.java13
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java1
-rw-r--r--services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java19
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java67
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteSessionStats.aidl19
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteSessionStats.java224
-rw-r--r--telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl8
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl10
-rw-r--r--tests/Input/src/com/android/test/input/AnrTest.kt9
63 files changed, 3115 insertions, 531 deletions
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index 3715c6e633dc..f77c50a271be 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -416,11 +416,34 @@ public final class ApplicationStartInfo implements Parcelable {
/**
* @see #getStartIntent
+ *
+ * <p class="note"> Note: This method will clone the provided intent and ensure that the cloned
+ * intent doesn't contain any large objects like bitmaps in its extras by stripping it in the
+ * least aggressive acceptable way for the individual intent.</p>
+ *
* @hide
*/
public void setIntent(Intent startIntent) {
if (startIntent != null) {
- mStartIntent = startIntent.maybeStripForHistory();
+ if (startIntent.canStripForHistory()) {
+ // If maybeStripForHistory will return a lightened version, do that.
+ mStartIntent = startIntent.maybeStripForHistory();
+ } else if (startIntent.getExtras() != null) {
+ // If maybeStripForHistory would not return a lightened version and extras is
+ // non-null then extras contains un-parcelled data. Use cloneFilter to strip data
+ // more aggressively.
+ mStartIntent = startIntent.cloneFilter();
+ } else {
+ // Finally, if maybeStripForHistory would not return a lightened version and extras
+ // is null then do a regular clone so we don't leak the intent.
+ mStartIntent = new Intent(startIntent);
+ }
+
+ // If the newly cloned intent has an original intent, clear that as we don't need it and
+ // can't guarantee it doesn't need to be stripped as well.
+ if (mStartIntent.getOriginalIntent() != null) {
+ mStartIntent.setOriginalIntent(null);
+ }
}
}
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 61b52c67dfe2..e6b1c07846f9 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -580,6 +580,8 @@ public final class FileUtils {
", copied:" + progress +
", read:" + (count - countToRead) +
", in pipe: " + countInPipe);
+ Os.close(pipes[0]);
+ Os.close(pipes[1]);
throw new ErrnoException("splice, pipe --> fdOut", EIO);
} else {
progress += t;
@@ -607,6 +609,8 @@ public final class FileUtils {
listener.onProgress(progressSnapshot);
});
}
+ Os.close(pipes[0]);
+ Os.close(pipes[1]);
return progress;
}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index d6f65f8c9d8b..b71468247e37 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -19,6 +19,16 @@ flag {
}
flag {
+ name: "blast_sync_notification_shade_on_display_switch"
+ namespace: "windowing_frontend"
+ description: "Make the buffer content of notification shade synchronize with display switch"
+ bug: "337154331"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "edge_to_edge_by_default"
namespace: "windowing_frontend"
description: "Make app go edge-to-edge by default when targeting SDK 35 or greater"
diff --git a/core/proto/android/nfc/card_emulation.proto b/core/proto/android/nfc/card_emulation.proto
index 9c3c6d704922..81da30dd8bbf 100644
--- a/core/proto/android/nfc/card_emulation.proto
+++ b/core/proto/android/nfc/card_emulation.proto
@@ -59,6 +59,7 @@ message PreferredServicesProto {
optional .android.content.ComponentNameProto foreground_requested = 5;
optional .android.content.ComponentNameProto settings_default = 6;
optional bool prefer_foreground = 7;
+ optional .android.content.ComponentNameProto wallet_role_holder_payment_service = 8;
}
// Debugging information for com.android.nfc.cardemulation.EnabledNfcFServices
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4dfe000659ff..c6d4f3ad771d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4840,7 +4840,7 @@
See android.credentials.CredentialManager
-->
<string name="config_defaultCredentialManagerHybridService" translatable="false"></string>
-
+
<!-- The component name, flattened to a string, for the system's credential manager
autofill service. This service allows interceding autofill requests and routing
them to credential manager.
@@ -6503,9 +6503,6 @@
<string-array name="config_sharedLibrariesLoadedAfterApp" translatable="false">
</string-array>
- <!-- the number of the max cached processes in the system. -->
- <integer name="config_customizedMaxCachedProcesses">32</integer>
-
<!-- Whether this device should support taking app snapshots on closure -->
<bool name="config_disableTaskSnapshots">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8880ab5385ab..14f9f2ff5386 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5061,8 +5061,6 @@
code and resources provided by applications. -->
<java-symbol type="array" name="config_sharedLibrariesLoadedAfterApp" />
- <java-symbol type="integer" name="config_customizedMaxCachedProcesses" />
-
<java-symbol type="color" name="overview_background"/>
<java-symbol type="bool" name="config_disableTaskSnapshots" />
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d9055fbba408..4a1da4daf1ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1457,8 +1457,9 @@ public class BubbleController implements ConfigurationChangeListener,
SynchronousScreenCaptureListener screenCaptureListener) {
try {
ScreenCapture.CaptureArgs args = null;
- if (mStackView != null) {
- ViewRootImpl viewRoot = mStackView.getViewRootImpl();
+ View viewToUse = mStackView != null ? mStackView : mLayerView;
+ if (viewToUse != null) {
+ ViewRootImpl viewRoot = viewToUse.getViewRootImpl();
if (viewRoot != null) {
SurfaceControl bubbleLayer = viewRoot.getSurfaceControl();
if (bubbleLayer != null) {
@@ -1550,6 +1551,12 @@ public class BubbleController implements ConfigurationChangeListener,
Log.w(TAG, "Tried to add a bubble to the stack but the stack is null");
}
};
+ } else if (mBubbleData.isExpanded() && mBubbleData.getSelectedBubble() != null) {
+ callback = b -> {
+ if (b.getKey().equals(mBubbleData.getSelectedBubbleKey())) {
+ mLayerView.showExpandedView(b);
+ }
+ };
}
for (int i = mBubbleData.getBubbles().size() - 1; i >= 0; i--) {
Bubble bubble = mBubbleData.getBubbles().get(i);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 874102c20925..32873d96e76c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -256,11 +256,15 @@ public class BubbleData {
}
/**
- * Returns a bubble bar update populated with the current list of active bubbles.
+ * Returns a bubble bar update populated with the current list of active bubbles, expanded,
+ * and selected state.
*/
public BubbleBarUpdate getInitialStateForBubbleBar() {
BubbleBarUpdate initialState = mStateChange.getInitialState();
initialState.bubbleBarLocation = mPositioner.getBubbleBarLocation();
+ initialState.expanded = mExpanded;
+ initialState.expandedChanged = mExpanded; // only matters if we're expanded
+ initialState.selectedBubbleKey = getSelectedBubbleKey();
return initialState;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 4e8afccee40f..c7ccd50af550 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -482,31 +482,38 @@ public class BubbleExpandedView extends LinearLayout {
mPointerWidth, mPointerHeight, true /* pointLeft */));
mRightPointer = new ShapeDrawable(TriangleShape.createHorizontal(
mPointerWidth, mPointerHeight, false /* pointLeft */));
- if (mPointerView != null) {
- updatePointerView();
- }
+ updatePointerViewIfExists();
+ updateManageButtonIfExists();
+ }
- if (mManageButton != null) {
- int visibility = mManageButton.getVisibility();
- removeView(mManageButton);
- ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(),
- com.android.internal.R.style.Theme_DeviceDefault_DayNight);
- mManageButton = (AlphaOptimizedButton) LayoutInflater.from(ctw).inflate(
- R.layout.bubble_manage_button, this /* parent */, false /* attach */);
- addView(mManageButton);
- mManageButton.setVisibility(visibility);
- post(() -> {
- int touchAreaHeight =
- getResources().getDimensionPixelSize(
- R.dimen.bubble_manage_button_touch_area_height);
- Rect r = new Rect();
- mManageButton.getHitRect(r);
- int extraTouchArea = (touchAreaHeight - r.height()) / 2;
- r.top -= extraTouchArea;
- r.bottom += extraTouchArea;
- setTouchDelegate(new TouchDelegate(r, mManageButton));
- });
+
+ /**
+ * Reinflate manage button if {@link #mManageButton} is initialized.
+ * Does nothing otherwise.
+ */
+ private void updateManageButtonIfExists() {
+ if (mManageButton == null) {
+ return;
}
+ int visibility = mManageButton.getVisibility();
+ removeView(mManageButton);
+ ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(),
+ com.android.internal.R.style.Theme_DeviceDefault_DayNight);
+ mManageButton = (AlphaOptimizedButton) LayoutInflater.from(ctw).inflate(
+ R.layout.bubble_manage_button, this /* parent */, false /* attach */);
+ addView(mManageButton);
+ mManageButton.setVisibility(visibility);
+ post(() -> {
+ int touchAreaHeight =
+ getResources().getDimensionPixelSize(
+ R.dimen.bubble_manage_button_touch_area_height);
+ Rect r = new Rect();
+ mManageButton.getHitRect(r);
+ int extraTouchArea = (touchAreaHeight - r.height()) / 2;
+ r.top -= extraTouchArea;
+ r.bottom += extraTouchArea;
+ setTouchDelegate(new TouchDelegate(r, mManageButton));
+ });
}
void updateFontSize() {
@@ -548,11 +555,18 @@ public class BubbleExpandedView extends LinearLayout {
if (mTaskView != null) {
mTaskView.setCornerRadius(mCornerRadius);
}
- updatePointerView();
+ updatePointerViewIfExists();
+ updateManageButtonIfExists();
}
- /** Updates the size and visuals of the pointer. **/
- private void updatePointerView() {
+ /**
+ * Updates the size and visuals of the pointer if {@link #mPointerView} is initialized.
+ * Does nothing otherwise.
+ */
+ private void updatePointerViewIfExists() {
+ if (mPointerView == null) {
+ return;
+ }
LayoutParams lp = (LayoutParams) mPointerView.getLayoutParams();
if (mCurrentPointer == mLeftPointer || mCurrentPointer == mRightPointer) {
lp.width = mPointerHeight;
@@ -1055,7 +1069,7 @@ public class BubbleExpandedView extends LinearLayout {
// Post because we need the width of the view
post(() -> {
mCurrentPointer = showVertically ? onLeft ? mLeftPointer : mRightPointer : mTopPointer;
- updatePointerView();
+ updatePointerViewIfExists();
if (showVertically) {
mPointerPos.y = bubbleCenter - (mPointerWidth / 2f);
if (!isRtl) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index b52b0d8dee74..5ee6f6bb0e1f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -200,9 +200,6 @@ public class PipTransition extends PipTransitionController {
animator.cancel();
}
mExitTransition = mTransitions.startTransition(type, out, this);
- if (mPipOrganizer.getOutPipWindowingMode() == WINDOWING_MODE_UNDEFINED) {
- mHomeTransitionObserver.notifyHomeVisibilityChanged(false /* isVisible */);
- }
}
@Override
@@ -659,6 +656,9 @@ public class PipTransition extends PipTransitionController {
startTransaction.remove(mPipOrganizer.mPipOverlay);
mPipOrganizer.clearContentOverlay();
}
+ if (mPipOrganizer.getOutPipWindowingMode() == WINDOWING_MODE_UNDEFINED) {
+ mHomeTransitionObserver.notifyHomeVisibilityChanged(false /* isVisible */);
+ }
if (pipChange == null) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: No window of exiting PIP is found. Can't play expand animation", TAG);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 5401186ccb88..a4ade1b676ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -516,8 +516,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private void createResizeVeilIfNeeded() {
if (mResizeVeil != null) return;
loadAppInfoIfNeeded();
- mResizeVeil = new ResizeVeil(mContext, mDisplayController, mResizeVeilBitmap, mTaskInfo,
- mTaskSurface, mSurfaceControlTransactionSupplier);
+ mResizeVeil = new ResizeVeil(mContext, mDisplayController, mResizeVeilBitmap,
+ mTaskSurface, mSurfaceControlTransactionSupplier, mTaskInfo);
}
/**
@@ -525,7 +525,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
public void showResizeVeil(Rect taskBounds) {
createResizeVeilIfNeeded();
- mResizeVeil.showVeil(mTaskSurface, taskBounds);
+ mResizeVeil.showVeil(mTaskSurface, taskBounds, mTaskInfo);
}
/**
@@ -533,7 +533,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
public void showResizeVeil(SurfaceControl.Transaction tx, Rect taskBounds) {
createResizeVeilIfNeeded();
- mResizeVeil.showVeil(tx, mTaskSurface, taskBounds, false /* fadeIn */);
+ mResizeVeil.showVeil(tx, mTaskSurface, taskBounds, mTaskInfo, false /* fadeIn */);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.kt
index 4f2d945e49f9..cd2dac806a7f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.kt
@@ -20,7 +20,6 @@ import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
-import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.PixelFormat
@@ -36,10 +35,15 @@ import android.view.WindowManager
import android.view.WindowlessWindowManager
import android.widget.ImageView
import android.window.TaskConstants
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.ui.graphics.toArgb
import com.android.wm.shell.R
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
import com.android.wm.shell.windowdecor.WindowDecoration.SurfaceControlViewHostFactory
+import com.android.wm.shell.windowdecor.common.DecorThemeUtil
+import com.android.wm.shell.windowdecor.common.Theme
import java.util.function.Supplier
/**
@@ -49,14 +53,18 @@ class ResizeVeil @JvmOverloads constructor(
private val context: Context,
private val displayController: DisplayController,
private val appIcon: Bitmap,
- private val taskInfo: RunningTaskInfo,
private var parentSurface: SurfaceControl,
private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>,
private val surfaceControlBuilderFactory: SurfaceControlBuilderFactory =
object : SurfaceControlBuilderFactory {},
private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory =
- object : SurfaceControlViewHostFactory {}
+ object : SurfaceControlViewHostFactory {},
+ taskInfo: RunningTaskInfo,
) {
+ private val decorThemeUtil = DecorThemeUtil(context)
+ private val lightColors = dynamicLightColorScheme(context)
+ private val darkColors = dynamicDarkColorScheme(context)
+
private val surfaceSession = SurfaceSession()
private lateinit var iconView: ImageView
private var iconSize = 0
@@ -86,21 +94,10 @@ class ResizeVeil @JvmOverloads constructor(
return
}
displayController.removeDisplayWindowListener(this)
- setupResizeVeil()
+ setupResizeVeil(taskInfo)
}
}
- private val backgroundColorId: Int
- get() {
- val configuration = context.resources.configuration
- return if (configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
- == Configuration.UI_MODE_NIGHT_YES) {
- R.color.desktop_mode_resize_veil_dark
- } else {
- R.color.desktop_mode_resize_veil_light
- }
- }
-
/**
* Whether the resize veil is ready to be shown.
*/
@@ -108,14 +105,14 @@ class ResizeVeil @JvmOverloads constructor(
get() = viewHost != null
init {
- setupResizeVeil()
+ setupResizeVeil(taskInfo)
}
/**
* Create the veil in its default invisible state.
*/
- private fun setupResizeVeil() {
- if (!obtainDisplayOrRegisterListener()) {
+ private fun setupResizeVeil(taskInfo: RunningTaskInfo) {
+ if (!obtainDisplayOrRegisterListener(taskInfo.displayId)) {
// Display may not be available yet, skip this until then.
return
}
@@ -162,8 +159,8 @@ class ResizeVeil @JvmOverloads constructor(
Trace.endSection()
}
- private fun obtainDisplayOrRegisterListener(): Boolean {
- display = displayController.getDisplay(taskInfo.displayId)
+ private fun obtainDisplayOrRegisterListener(displayId: Int): Boolean {
+ display = displayController.getDisplay(displayId)
if (display == null) {
displayController.addDisplayWindowListener(onDisplaysChangedListener)
return false
@@ -184,7 +181,8 @@ class ResizeVeil @JvmOverloads constructor(
t: SurfaceControl.Transaction,
parent: SurfaceControl,
taskBounds: Rect,
- fadeIn: Boolean
+ taskInfo: RunningTaskInfo,
+ fadeIn: Boolean,
) {
if (!isReady || isVisible) {
t.apply()
@@ -202,13 +200,15 @@ class ResizeVeil @JvmOverloads constructor(
parentSurface = parent
}
-
+ val backgroundColor = when (decorThemeUtil.getAppTheme(taskInfo)) {
+ Theme.LIGHT -> lightColors.surfaceContainer
+ Theme.DARK -> darkColors.surfaceContainer
+ }
t.show(veil)
.setLayer(veil, VEIL_CONTAINER_LAYER)
.setLayer(icon, VEIL_ICON_LAYER)
.setLayer(background, VEIL_BACKGROUND_LAYER)
- .setColor(background,
- Color.valueOf(context.getColor(backgroundColorId)).components)
+ .setColor(background, Color.valueOf(backgroundColor.toArgb()).components)
relayout(taskBounds, t)
if (fadeIn) {
cancelAnimation()
@@ -270,12 +270,12 @@ class ResizeVeil @JvmOverloads constructor(
/**
* Animate veil's alpha to 1, fading it in.
*/
- fun showVeil(parentSurface: SurfaceControl, taskBounds: Rect) {
+ fun showVeil(parentSurface: SurfaceControl, taskBounds: Rect, taskInfo: RunningTaskInfo) {
if (!isReady || isVisible) {
return
}
val t = surfaceControlTransactionSupplier.get()
- showVeil(t, parentSurface, taskBounds, true /* fadeIn */)
+ showVeil(t, parentSurface, taskBounds, taskInfo, true /* fadeIn */)
}
/**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 0f433770777e..f55c96cb1769 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -1203,6 +1203,25 @@ public class BubbleDataTest extends ShellTestCase {
assertThat(update.currentBubbleList.get(0).getKey()).isEqualTo(mEntryA2.getKey());
assertThat(update.currentBubbleList.get(1).getKey()).isEqualTo(mEntryA1.getKey());
assertThat(update.bubbleBarLocation).isEqualTo(BubbleBarLocation.LEFT);
+ assertThat(update.expandedChanged).isFalse();
+ assertThat(update.selectedBubbleKey).isEqualTo(mEntryA2.getKey());
+ }
+
+ @Test
+ public void test_getInitialStateForBubbleBar_includesExpandedState() {
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ mPositioner.setBubbleBarLocation(BubbleBarLocation.LEFT);
+ mBubbleData.setExpanded(true);
+
+ BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar();
+ assertThat(update.currentBubbleList).hasSize(2);
+ assertThat(update.currentBubbleList.get(0).getKey()).isEqualTo(mEntryA2.getKey());
+ assertThat(update.currentBubbleList.get(1).getKey()).isEqualTo(mEntryA1.getKey());
+ assertThat(update.bubbleBarLocation).isEqualTo(BubbleBarLocation.LEFT);
+ assertThat(update.expandedChanged).isTrue();
+ assertThat(update.expanded).isTrue();
+ assertThat(update.selectedBubbleKey).isEqualTo(mEntryA2.getKey());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt
index 5da57c50e6c1..a07be79579eb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt
@@ -150,7 +150,7 @@ class ResizeVeilTest : ShellTestCase() {
fun showVeil() {
val veil = createResizeVeil()
- veil.showVeil(mockTransaction, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+ veil.showVeil(mockTransaction, mock(), Rect(0, 0, 100, 100), taskInfo, false /* fadeIn */)
verify(mockTransaction).show(mockResizeVeilSurface)
verify(mockTransaction).show(mockBackgroundSurface)
@@ -162,7 +162,7 @@ class ResizeVeilTest : ShellTestCase() {
fun showVeil_displayUnavailable_doesNotShow() {
val veil = createResizeVeil(withDisplayAvailable = false)
- veil.showVeil(mockTransaction, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+ veil.showVeil(mockTransaction, mock(), Rect(0, 0, 100, 100), taskInfo, false /* fadeIn */)
verify(mockTransaction, never()).show(mockResizeVeilSurface)
verify(mockTransaction, never()).show(mockBackgroundSurface)
@@ -174,8 +174,8 @@ class ResizeVeilTest : ShellTestCase() {
fun showVeil_alreadyVisible_doesNotShowAgain() {
val veil = createResizeVeil()
- veil.showVeil(mockTransaction, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
- veil.showVeil(mockTransaction, mock(), Rect(0, 0, 100, 100), false /* fadeIn */)
+ veil.showVeil(mockTransaction, mock(), Rect(0, 0, 100, 100), taskInfo, false /* fadeIn */)
+ veil.showVeil(mockTransaction, mock(), Rect(0, 0, 100, 100), taskInfo, false /* fadeIn */)
verify(mockTransaction, times(1)).show(mockResizeVeilSurface)
verify(mockTransaction, times(1)).show(mockBackgroundSurface)
@@ -188,7 +188,13 @@ class ResizeVeilTest : ShellTestCase() {
val veil = createResizeVeil(parent = mock())
val newParent = mock<SurfaceControl>()
- veil.showVeil(mockTransaction, newParent, Rect(0, 0, 100, 100), false /* fadeIn */)
+ veil.showVeil(
+ mockTransaction,
+ newParent,
+ Rect(0, 0, 100, 100),
+ taskInfo,
+ false /* fadeIn */
+ )
verify(mockTransaction).reparent(mockResizeVeilSurface, newParent)
}
@@ -212,11 +218,11 @@ class ResizeVeilTest : ShellTestCase() {
context,
mockDisplayController,
mockAppIcon,
- taskInfo,
parent,
{ mockTransaction },
mockSurfaceControlBuilderFactory,
- mockSurfaceControlViewHostFactory
+ mockSurfaceControlViewHostFactory,
+ taskInfo
)
}
}
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 5a4d3ce5661b..38ad560d1bf0 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -494,26 +494,6 @@
<item>show_deuteranomaly</item>
</string-array>
- <!-- Titles for app process limit preference. [CHAR LIMIT=35] -->
- <string-array name="app_process_limit_entries">
- <item>Standard limit</item>
- <item>No background processes</item>
- <item>At most 1 process</item>
- <item>At most 2 processes</item>
- <item>At most 3 processes</item>
- <item>At most 4 processes</item>
- </string-array>
-
- <!-- Values for app process limit preference. -->
- <string-array name="app_process_limit_values" translatable="false" >
- <item>-1</item>
- <item>0</item>
- <item>1</item>
- <item>2</item>
- <item>3</item>
- <item>4</item>
- </string-array>
-
<!-- USB configuration names for Developer Settings.
This can be overridden by devices with additional USB configurations. -->
<string-array name="usb_configuration_titles">
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 363045ec1d83..927aa6930ec3 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -975,9 +975,6 @@
<string name="immediately_destroy_activities_summary">Destroy every activity as soon as
the user leaves it</string>
- <!-- UI debug setting: limit number of running background processes [CHAR LIMIT=25] -->
- <string name="app_process_limit_title">Background process limit</string>
-
<!-- UI debug setting: show all ANRs? [CHAR LIMIT=25] -->
<string name="show_all_anrs">Show background ANRs</string>
<!-- UI debug setting: show all ANRs summary [CHAR LIMIT=100] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
index 8204569ce2f8..20b949f4a30f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt
@@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
@@ -161,6 +162,7 @@ class AudioRepositoryImpl(
},
volumeSettingChanges(audioStream),
)
+ .conflate()
.map { getCurrentAudioStream(audioStream) }
.onStart { emit(getCurrentAudioStream(audioStream)) }
.flowOn(backgroundCoroutineContext)
@@ -184,10 +186,11 @@ class AudioRepositoryImpl(
}
}
- override suspend fun setVolume(audioStream: AudioStream, volume: Int) =
+ override suspend fun setVolume(audioStream: AudioStream, volume: Int) {
withContext(backgroundCoroutineContext) {
audioManager.setStreamVolume(audioStream.value, volume, 0)
}
+ }
override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean {
return withContext(backgroundCoroutineContext) {
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index fb883640c9a9..f8762f0a98d5 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -37,6 +37,9 @@
<item>400</item>
</integer-array>
+ <!-- Whether to use deadzone with nav bar -->
+ <bool name="config_useDeadZone">true</bool>
+
<!-- decay duration (from size_max -> size), in ms -->
<integer name="navigation_bar_deadzone_hold">333</integer>
<integer name="navigation_bar_deadzone_decay">333</integer>
diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
index e284bc7752cf..92108e9e7103 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
@@ -36,7 +36,6 @@ import androidx.annotation.Nullable;
import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.Utils;
import com.android.systemui.CoreStartable;
-import com.android.systemui.Flags;
import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.complication.dagger.DreamHomeControlsComplicationComponent;
import com.android.systemui.controls.ControlsServiceInfo;
@@ -137,9 +136,7 @@ public class DreamHomeControlsComplication implements Complication {
private void updateHomeControlsComplication() {
mControlsComponent.getControlsListingController().ifPresent(c -> {
- final boolean replacedWithOpenHub =
- Flags.glanceableHubShortcutButton() && mReplacedByOpenHub;
- if (isHomeControlsAvailable(c.getCurrentServices()) && !replacedWithOpenHub) {
+ if (isHomeControlsAvailable(c.getCurrentServices())) {
mDreamOverlayStateController.addComplication(mComplication);
} else {
mDreamOverlayStateController.removeComplication(mComplication);
diff --git a/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java b/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java
index a679bfb15476..05df2bb4aa59 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java
@@ -28,7 +28,6 @@ import android.widget.ImageView;
import com.android.settingslib.Utils;
import com.android.systemui.CoreStartable;
-import com.android.systemui.Flags;
import com.android.systemui.communal.domain.interactor.CommunalInteractor;
import com.android.systemui.communal.shared.model.CommunalScenes;
import com.android.systemui.complication.dagger.OpenHubComplicationComponent;
@@ -111,11 +110,11 @@ public class OpenHubComplication implements Complication {
private void updateOpenHubComplication() {
// TODO(b/339667383): don't show the complication if glanceable hub is disabled
- if (Flags.glanceableHubShortcutButton()) {
- mDreamOverlayStateController.addComplication(mComplication);
- } else {
- mDreamOverlayStateController.removeComplication(mComplication);
- }
+// if (Flags.glanceableHubShortcutButton()) {
+// mDreamOverlayStateController.addComplication(mComplication);
+// } else {
+// mDreamOverlayStateController.removeComplication(mComplication);
+// }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
index 326830600635..8177fdec86e6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/DeadZone.java
@@ -61,6 +61,7 @@ public class DeadZone {
}
};
+ private final boolean mUseDeadZone;
private final NavigationBarController mNavBarController;
private final NavigationBarView mNavigationBarView;
@@ -86,9 +87,12 @@ public class DeadZone {
@Inject
public DeadZone(NavigationBarView view) {
+ mUseDeadZone = view.getResources().getBoolean(R.bool.config_useDeadZone);
+
mNavigationBarView = view;
mNavBarController = Dependency.get(NavigationBarController.class);
mDisplayId = view.getContext().getDisplayId();
+
onConfigurationChanged(HORIZONTAL);
}
@@ -108,12 +112,20 @@ public class DeadZone {
}
public void setFlashOnTouchCapture(boolean dbg) {
+ if (!mUseDeadZone) {
+ return;
+ }
+
mShouldFlash = dbg;
mFlashFrac = 0f;
mNavigationBarView.postInvalidate();
}
public void onConfigurationChanged(int rotation) {
+ if (!mUseDeadZone) {
+ return;
+ }
+
mDisplayRotation = rotation;
final Resources res = mNavigationBarView.getResources();
@@ -134,6 +146,10 @@ public class DeadZone {
// I made you a touch event...
public boolean onTouchEvent(MotionEvent event) {
+ if (!mUseDeadZone) {
+ return false;
+ }
+
if (DEBUG) {
Slog.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction()));
}
@@ -187,17 +203,17 @@ public class DeadZone {
if (mShouldFlash) mNavigationBarView.postInvalidate();
}
- public void setFlash(float f) {
+ private void setFlash(float f) {
mFlashFrac = f;
mNavigationBarView.postInvalidate();
}
- public float getFlash() {
+ private float getFlash() {
return mFlashFrac;
}
public void onDraw(Canvas can) {
- if (!mShouldFlash || mFlashFrac <= 0f) {
+ if (!mUseDeadZone || !mShouldFlash || mFlashFrac <= 0f) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index e6801022ad0e..70f3b847ce07 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -52,7 +52,6 @@ import com.android.systemui.screenrecord.RecordingService
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.traceur.TraceUtils.PresetTraceType
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -131,15 +130,11 @@ constructor(
}
}
- private fun startIssueRecordingService(screenRecord: Boolean, traceType: PresetTraceType) =
+ private fun startIssueRecordingService() =
PendingIntent.getForegroundService(
userContextProvider.userContext,
RecordingService.REQUEST_CODE,
- IssueRecordingService.getStartIntent(
- userContextProvider.userContext,
- screenRecord,
- traceType
- ),
+ IssueRecordingService.getStartIntent(userContextProvider.userContext),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
.send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
@@ -157,7 +152,7 @@ constructor(
val dialog: AlertDialog =
delegateFactory
.create {
- startIssueRecordingService(it.screenRecord, it.traceType)
+ startIssueRecordingService()
dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
panelInteractor.collapsePanels()
}
@@ -169,8 +164,7 @@ constructor(
if (expandable != null && !keyguardStateController.isShowing) {
expandable
.dialogTransitionController(DialogCuj(CUJ_SHADE_DIALOG_OPEN, TILE_SPEC))
- ?.let { dialogTransitionAnimator.show(dialog, it) }
- ?: dialog.show()
+ ?.let { dialogTransitionAnimator.show(dialog, it) } ?: dialog.show()
} else {
dialog.show()
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index 5ede64a4fc26..4a4c73ba9c81 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -35,8 +35,6 @@ import com.android.systemui.screenrecord.RecordingService
import com.android.systemui.screenrecord.RecordingServiceStrings
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
-import com.android.traceur.MessageConstants.INTENT_EXTRA_TRACE_TYPE
-import com.android.traceur.TraceUtils.PresetTraceType
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -76,15 +74,10 @@ constructor(
when (intent?.action) {
ACTION_START -> {
bgExecutor.execute {
- traceurMessageSender.startTracing(
- intent.getSerializableExtra(
- INTENT_EXTRA_TRACE_TYPE,
- PresetTraceType::class.java
- )
- )
+ traceurMessageSender.startTracing(issueRecordingState.traceType)
}
issueRecordingState.isRecording = true
- if (!intent.getBooleanExtra(EXTRA_SCREEN_RECORD, false)) {
+ if (!issueRecordingState.recordScreen) {
// If we don't want to record the screen, the ACTION_SHOW_START_NOTIF action
// will circumvent the RecordingService's screen recording start code.
return super.onStartCommand(Intent(ACTION_SHOW_START_NOTIF), flags, startId)
@@ -107,7 +100,7 @@ constructor(
)
val screenRecording = intent.getParcelableExtra(EXTRA_PATH, Uri::class.java)
- if (issueRecordingState.takeBugReport) {
+ if (issueRecordingState.takeBugreport) {
iActivityManager.requestBugReportWithExtraAttachment(screenRecording)
} else {
traceurMessageSender.shareTraces(applicationContext, screenRecording)
@@ -130,7 +123,6 @@ constructor(
companion object {
private const val TAG = "IssueRecordingService"
private const val CHANNEL_ID = "issue_record"
- private const val EXTRA_SCREEN_RECORD = "extra_screenRecord"
/**
* Get an intent to stop the issue recording service.
@@ -148,35 +140,36 @@ constructor(
*
* @param context Context from the requesting activity
*/
- fun getStartIntent(
- context: Context,
- screenRecord: Boolean,
- traceType: PresetTraceType,
- ): Intent =
- Intent(context, IssueRecordingService::class.java)
- .setAction(ACTION_START)
- .putExtra(EXTRA_SCREEN_RECORD, screenRecord)
- .putExtra(INTENT_EXTRA_TRACE_TYPE, traceType)
+ fun getStartIntent(context: Context): Intent =
+ Intent(context, IssueRecordingService::class.java).setAction(ACTION_START)
}
}
private class IrsStrings(private val res: Resources) : RecordingServiceStrings(res) {
override val title
get() = res.getString(R.string.issuerecord_title)
+
override val notificationChannelDescription
get() = res.getString(R.string.issuerecord_channel_description)
+
override val startErrorResId
get() = R.string.issuerecord_start_error
+
override val startError
get() = res.getString(R.string.issuerecord_start_error)
+
override val saveErrorResId
get() = R.string.issuerecord_save_error
+
override val saveError
get() = res.getString(R.string.issuerecord_save_error)
+
override val ongoingRecording
get() = res.getString(R.string.issuerecord_ongoing_screen_only)
+
override val backgroundProcessingLabel
get() = res.getString(R.string.issuerecord_background_processing_label)
+
override val saveTitle
get() = res.getString(R.string.issuerecord_save_title)
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
index 12ed06d75ce3..4ea334522ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingState.kt
@@ -16,16 +16,51 @@
package com.android.systemui.recordissue
+import android.content.Context
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.tiles.RecordIssueTile
+import com.android.systemui.res.R
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.traceur.TraceUtils.PresetTraceType
import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject
@SysUISingleton
-class IssueRecordingState @Inject constructor() {
+class IssueRecordingState
+@Inject
+constructor(
+ userTracker: UserTracker,
+ userFileManager: UserFileManager,
+) {
- private val listeners = CopyOnWriteArrayList<Runnable>()
+ private val prefs =
+ userFileManager.getSharedPreferences(
+ RecordIssueTile.TILE_SPEC,
+ Context.MODE_PRIVATE,
+ userTracker.userId
+ )
+
+ var takeBugreport
+ get() = prefs.getBoolean(KEY_TAKE_BUG_REPORT, false)
+ set(value) = prefs.edit().putBoolean(KEY_TAKE_BUG_REPORT, value).apply()
+
+ var recordScreen
+ get() = prefs.getBoolean(KEY_RECORD_SCREEN, false)
+ set(value) = prefs.edit().putBoolean(KEY_RECORD_SCREEN, value).apply()
+
+ var hasUserApprovedScreenRecording
+ get() = prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)
+ private set(value) = prefs.edit().putBoolean(HAS_APPROVED_SCREEN_RECORDING, value).apply()
+
+ var issueTypeRes
+ get() = prefs.getInt(KEY_ISSUE_TYPE_RES, ISSUE_TYPE_NOT_SET)
+ set(value) = prefs.edit().putInt(KEY_ISSUE_TYPE_RES, value).apply()
+
+ val traceType: PresetTraceType
+ get() = ALL_ISSUE_TYPES[issueTypeRes] ?: PresetTraceType.UNSET
- var takeBugReport: Boolean = false
+ private val listeners = CopyOnWriteArrayList<Runnable>()
var isRecording = false
set(value) {
@@ -33,6 +68,10 @@ class IssueRecordingState @Inject constructor() {
listeners.forEach(Runnable::run)
}
+ fun markUserApprovalForScreenRecording() {
+ hasUserApprovedScreenRecording = true
+ }
+
fun addListener(listener: Runnable) {
listeners.add(listener)
}
@@ -40,4 +79,20 @@ class IssueRecordingState @Inject constructor() {
fun removeListener(listener: Runnable) {
listeners.remove(listener)
}
+
+ companion object {
+ private const val KEY_TAKE_BUG_REPORT = "key_takeBugReport"
+ private const val HAS_APPROVED_SCREEN_RECORDING = "HasApprovedScreenRecord"
+ private const val KEY_RECORD_SCREEN = "key_recordScreen"
+ const val KEY_ISSUE_TYPE_RES = "key_issueTypeRes"
+ const val ISSUE_TYPE_NOT_SET = -1
+
+ val ALL_ISSUE_TYPES: Map<Int, PresetTraceType> =
+ hashMapOf(
+ Pair(R.string.performance, PresetTraceType.PERFORMANCE),
+ Pair(R.string.user_interface, PresetTraceType.UI),
+ Pair(R.string.battery, PresetTraceType.BATTERY),
+ Pair(R.string.thermal, PresetTraceType.THERMAL)
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index 84a063a36740..bbf4e51b35e9 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -17,7 +17,7 @@
package com.android.systemui.recordissue
import android.annotation.SuppressLint
-import android.app.AlertDialog
+import android.app.AlertDialog.BUTTON_POSITIVE
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
@@ -41,18 +41,16 @@ import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.SessionCreationSource
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate
-import com.android.systemui.qs.tiles.RecordIssueTile
+import com.android.systemui.recordissue.IssueRecordingState.Companion.ALL_ISSUE_TYPES
+import com.android.systemui.recordissue.IssueRecordingState.Companion.ISSUE_TYPE_NOT_SET
+import com.android.systemui.recordissue.IssueRecordingState.Companion.KEY_ISSUE_TYPE_RES
import com.android.systemui.res.R
-import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.traceur.MessageConstants.INTENT_EXTRA_TRACE_TYPE
-import com.android.traceur.TraceUtils.PresetTraceType
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.concurrent.Executor
-import java.util.function.Consumer
class RecordIssueDialogDelegate
@AssistedInject
@@ -64,31 +62,20 @@ constructor(
@Main private val mainExecutor: Executor,
private val devicePolicyResolver: dagger.Lazy<ScreenCaptureDevicePolicyResolver>,
private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
- private val userFileManager: UserFileManager,
private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate,
- private val issueRecordingState: IssueRecordingState,
+ private val state: IssueRecordingState,
private val traceurMessageSender: TraceurMessageSender,
- @Assisted private val onStarted: Consumer<IssueRecordingConfig>,
+ @Assisted private val onStarted: Runnable,
) : SystemUIDialog.Delegate {
- private val issueTypeOptions: Map<Int, PresetTraceType> =
- hashMapOf(
- Pair(R.string.performance, PresetTraceType.PERFORMANCE),
- Pair(R.string.user_interface, PresetTraceType.UI),
- Pair(R.string.battery, PresetTraceType.BATTERY),
- Pair(R.string.thermal, PresetTraceType.THERMAL)
- )
- private var selectedIssueType: PresetTraceType? = null
-
/** To inject dependencies and allow for easier testing */
@AssistedFactory
interface Factory {
/** Create a dialog object */
- fun create(onStarted: Consumer<IssueRecordingConfig>): RecordIssueDialogDelegate
+ fun create(onStarted: Runnable): RecordIssueDialogDelegate
}
@SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
- @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var bugReportSwitch: Switch
private lateinit var issueTypeButton: Button
@MainThread
@@ -97,21 +84,8 @@ constructor(
setView(LayoutInflater.from(context).inflate(R.layout.record_issue_dialog, null))
setTitle(context.getString(R.string.qs_record_issue_label))
setIcon(R.drawable.qs_record_issue_icon_off)
- setNegativeButton(R.string.cancel) { _, _ -> dismiss() }
- setPositiveButton(
- R.string.qs_record_issue_start,
- { _, _ ->
- issueRecordingState.takeBugReport = bugReportSwitch.isChecked
- onStarted.accept(
- IssueRecordingConfig(
- screenRecordSwitch.isChecked,
- selectedIssueType ?: PresetTraceType.UNSET
- )
- )
- dismiss()
- },
- false
- )
+ setNegativeButton(R.string.cancel) { _, _ -> }
+ setPositiveButton(R.string.qs_record_issue_start) { _, _ -> onStarted.run() }
}
bgExecutor.execute { traceurMessageSender.bindToTraceur(dialog.context) }
}
@@ -121,22 +95,39 @@ constructor(
@MainThread
override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
dialog.apply {
- window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
- window?.setGravity(Gravity.CENTER)
+ window?.apply {
+ addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+ setGravity(Gravity.CENTER)
+ }
- screenRecordSwitch = requireViewById(R.id.screenrecord_switch)
- screenRecordSwitch.setOnCheckedChangeListener { _, isEnabled ->
- if (isEnabled) {
- bgExecutor.execute { onScreenRecordSwitchClicked() }
+ screenRecordSwitch =
+ requireViewById<Switch>(R.id.screenrecord_switch).apply {
+ isChecked = state.recordScreen
+ setOnCheckedChangeListener { _, isChecked ->
+ state.recordScreen = isChecked
+ if (isChecked) {
+ bgExecutor.execute { onScreenRecordSwitchClicked() }
+ }
+ }
}
+
+ requireViewById<Switch>(R.id.bugreport_switch).apply {
+ isChecked = state.takeBugreport
+ setOnCheckedChangeListener { _, isChecked -> state.takeBugreport = isChecked }
}
- bugReportSwitch = requireViewById(R.id.bugreport_switch)
- val startButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
- issueTypeButton = requireViewById(R.id.issue_type_button)
- issueTypeButton.setOnClickListener {
- onIssueTypeClicked(context) { startButton.isEnabled = true }
- }
- startButton.isEnabled = false
+
+ issueTypeButton =
+ requireViewById<Button>(R.id.issue_type_button).apply {
+ val startButton = dialog.getButton(BUTTON_POSITIVE)
+ if (state.issueTypeRes != ISSUE_TYPE_NOT_SET) {
+ setText(state.issueTypeRes)
+ } else {
+ startButton.isEnabled = false
+ }
+ setOnClickListener {
+ onIssueTypeClicked(context) { startButton.isEnabled = true }
+ }
+ }
}
}
@@ -160,19 +151,14 @@ constructor(
SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
)
- if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) {
- val prefs =
- userFileManager.getSharedPreferences(
- RecordIssueTile.TILE_SPEC,
- Context.MODE_PRIVATE,
- userTracker.userId
- )
- if (!prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)) {
- mainExecutor.execute {
- ScreenCapturePermissionDialogDelegate(factory, prefs).createDialog().apply {
- setOnCancelListener { screenRecordSwitch.isChecked = false }
- show()
- }
+ if (
+ flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING) &&
+ !state.hasUserApprovedScreenRecording
+ ) {
+ mainExecutor.execute {
+ ScreenCapturePermissionDialogDelegate(factory, state).createDialog().apply {
+ setOnCancelListener { screenRecordSwitch.isChecked = false }
+ show()
}
}
}
@@ -182,23 +168,21 @@ constructor(
private fun onIssueTypeClicked(context: Context, onIssueTypeSelected: Runnable) {
val popupMenu = PopupMenu(context, issueTypeButton)
- issueTypeOptions.keys.forEach {
+ ALL_ISSUE_TYPES.keys.forEach {
popupMenu.menu.add(it).apply {
setIcon(R.drawable.arrow_pointing_down)
- if (issueTypeOptions[it] != selectedIssueType) {
+ if (it != state.issueTypeRes) {
iconTintList = ColorStateList.valueOf(Color.TRANSPARENT)
}
- intent = Intent().putExtra(INTENT_EXTRA_TRACE_TYPE, issueTypeOptions[it])
+ intent = Intent().putExtra(KEY_ISSUE_TYPE_RES, it)
}
}
popupMenu.apply {
setOnMenuItemClickListener {
issueTypeButton.text = it.title
- selectedIssueType =
- it.intent?.getSerializableExtra(
- INTENT_EXTRA_TRACE_TYPE,
- PresetTraceType::class.java
- )
+ state.issueTypeRes =
+ it.intent?.getIntExtra(KEY_ISSUE_TYPE_RES, ISSUE_TYPE_NOT_SET)
+ ?: ISSUE_TYPE_NOT_SET
onIssueTypeSelected.run()
true
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt
index de6d3f698285..b029d077b6d1 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt
@@ -16,18 +16,15 @@
package com.android.systemui.recordissue
-import android.content.SharedPreferences
import android.os.Bundle
import android.view.Gravity
import android.view.WindowManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.SystemUIDialog
-const val HAS_APPROVED_SCREEN_RECORDING = "HasApprovedScreenRecord"
-
class ScreenCapturePermissionDialogDelegate(
private val dialogFactory: SystemUIDialog.Factory,
- private val sharedPreferences: SharedPreferences,
+ private val state: IssueRecordingState,
) : SystemUIDialog.Delegate {
override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
@@ -37,7 +34,7 @@ class ScreenCapturePermissionDialogDelegate(
setMessage(R.string.screenrecord_permission_dialog_warning_entire_screen)
setNegativeButton(R.string.slice_permission_deny) { _, _ -> cancel() }
setPositiveButton(R.string.slice_permission_allow) { _, _ ->
- sharedPreferences.edit().putBoolean(HAS_APPROVED_SCREEN_RECORDING, true).apply()
+ state.markUserApprovalForScreenRecording()
dismiss()
}
window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
index 189336cf69bc..51744aa47c1a 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt
@@ -93,7 +93,7 @@ class TraceurMessageSender @Inject constructor(@Background private val backgroun
}
@WorkerThread
- fun startTracing(traceType: PresetTraceType?) {
+ fun startTracing(traceType: PresetTraceType) {
val data =
Bundle().apply { putSerializable(MessageConstants.INTENT_EXTRA_TRACE_TYPE, traceType) }
notifyTraceur(MessageConstants.START_WHAT, data)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
index 80aa0efb0a19..4f27b9e4dbf0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
@@ -79,7 +79,10 @@ class PolicyRequestProcessor(
}
/** Produce a new [ScreenshotData] using [CaptureParameters] */
- suspend fun modify(original: ScreenshotData, updates: CaptureParameters): ScreenshotData {
+ private suspend fun modify(
+ original: ScreenshotData,
+ updates: CaptureParameters,
+ ): ScreenshotData {
// Update and apply bitmap capture depending on the parameters.
val updated =
when (val type = updates.type) {
@@ -117,7 +120,7 @@ class PolicyRequestProcessor(
return replaceWithScreenshot(
original = original,
componentName = topMainRootTask?.topActivity ?: defaultComponent,
- owner = topMainRootTask?.userId?.let { UserHandle.of(it) } ?: defaultOwner,
+ owner = defaultOwner,
displayId = original.displayId
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index b397906fc06f..1adfef061235 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -68,6 +68,9 @@ import com.android.systemui.statusbar.notification.icon.IconPack;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
+import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarModel;
+import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel;
+import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
import com.android.systemui.statusbar.notification.stack.PriorityBucket;
import com.android.systemui.util.ListenerSet;
@@ -951,6 +954,7 @@ public final class NotificationEntry extends ListEntry {
* heads up.
*/
public void setHeadsUpStatusBarText(CharSequence headsUpStatusBarText) {
+ NotificationRowContentBinderRefactor.assertInLegacyMode();
this.mHeadsUpStatusBarText.setValue(headsUpStatusBarText);
}
@@ -964,6 +968,7 @@ public final class NotificationEntry extends ListEntry {
* heads up, and its content is sensitive right now.
*/
public void setHeadsUpStatusBarTextPublic(CharSequence headsUpStatusBarTextPublic) {
+ NotificationRowContentBinderRefactor.assertInLegacyMode();
this.mHeadsUpStatusBarTextPublic.setValue(headsUpStatusBarTextPublic);
}
@@ -1036,6 +1041,14 @@ public final class NotificationEntry extends ListEntry {
== Notification.VISIBILITY_PRIVATE;
}
+ /** Set the content generated by the notification inflater. */
+ public void setContentModel(NotificationContentModel contentModel) {
+ if (NotificationRowContentBinderRefactor.isUnexpectedlyInLegacyMode()) return;
+ HeadsUpStatusBarModel headsUpStatusBarModel = contentModel.getHeadsUpStatusBarModel();
+ this.mHeadsUpStatusBarText.setValue(headsUpStatusBarModel.getPrivateText());
+ this.mHeadsUpStatusBarTextPublic.setValue(headsUpStatusBarModel.getPublicText());
+ }
+
/** Information about a suggestion that is being edited. */
public static class EditedSuggestionInfo {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 6ba26d99419e..af5117e0b561 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -56,6 +56,7 @@ import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
+import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineConversationViewBinder;
import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder;
import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel;
@@ -105,6 +106,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
HeadsUpStyleProvider headsUpStyleProvider,
NotificationRowContentBinderLogger logger) {
+ NotificationRowContentBinderRefactor.assertInLegacyMode();
mRemoteViewCache = remoteViewCache;
mRemoteInputManager = remoteInputManager;
mConversationProcessor = conversationProcessor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
index c1302a0d3e57..fe0ee5235808 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -147,11 +147,6 @@ public interface NotificationRowContentBinder {
* Use increased height when binding heads up views.
*/
public boolean usesIncreasedHeadsUpHeight;
-
- /**
- * Is group summary notification
- */
- public boolean mIsGroupSummary;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
new file mode 100644
index 000000000000..e70414084598
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -0,0 +1,1569 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar.notification.row
+
+import android.annotation.SuppressLint
+import android.app.Notification
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.os.AsyncTask
+import android.os.Build
+import android.os.CancellationSignal
+import android.os.Trace
+import android.os.UserHandle
+import android.service.notification.StatusBarNotification
+import android.util.Log
+import android.view.NotificationHeaderView
+import android.view.View
+import android.view.ViewGroup
+import android.widget.RemoteViews
+import android.widget.RemoteViews.InteractionHandler
+import android.widget.RemoteViews.OnViewAppliedListener
+import com.android.app.tracing.TraceUtils
+import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.widget.ImageMessageConsumer
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.NotifInflation
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.InflationTask
+import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
+import com.android.systemui.statusbar.notification.InflationException
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_SINGLE_LINE
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_GROUP_SUMMARY_HEADER
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
+import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation
+import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation
+import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarModel
+import com.android.systemui.statusbar.notification.row.shared.NewRemoteViews
+import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel
+import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineConversationViewBinder
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.SingleLineViewBinder
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper
+import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
+import com.android.systemui.statusbar.policy.InflatedSmartReplyState
+import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder
+import com.android.systemui.statusbar.policy.SmartReplyStateInflater
+import com.android.systemui.util.Assert
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+import javax.inject.Inject
+
+/**
+ * [NotificationRowContentBinderImpl] binds content to a [ExpandableNotificationRow] by
+ * asynchronously building the content's [RemoteViews] and applying it to the row.
+ */
+@SysUISingleton
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+class NotificationRowContentBinderImpl
+@Inject
+constructor(
+ private val remoteViewCache: NotifRemoteViewCache,
+ private val remoteInputManager: NotificationRemoteInputManager,
+ private val conversationProcessor: ConversationNotificationProcessor,
+ @NotifInflation private val inflationExecutor: Executor,
+ private val smartReplyStateInflater: SmartReplyStateInflater,
+ private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
+ private val headsUpStyleProvider: HeadsUpStyleProvider,
+ private val logger: NotificationRowContentBinderLogger
+) : NotificationRowContentBinder {
+
+ init {
+ /* check if */ NotificationRowContentBinderRefactor.isUnexpectedlyInLegacyMode()
+ }
+
+ private var inflateSynchronously = false
+
+ override fun bindContent(
+ entry: NotificationEntry,
+ row: ExpandableNotificationRow,
+ @InflationFlag contentToBind: Int,
+ bindParams: BindParams,
+ forceInflate: Boolean,
+ callback: InflationCallback?
+ ) {
+ if (row.isRemoved) {
+ // We don't want to reinflate anything for removed notifications. Otherwise views might
+ // be readded to the stack, leading to leaks. This may happen with low-priority groups
+ // where the removal of already removed children can lead to a reinflation.
+ logger.logNotBindingRowWasRemoved(entry)
+ return
+ }
+ logger.logBinding(entry, contentToBind)
+ val sbn: StatusBarNotification = entry.sbn
+
+ // To check if the notification has inline image and preload inline image if necessary.
+ row.imageResolver.preloadImages(sbn.notification)
+ if (forceInflate) {
+ remoteViewCache.clearCache(entry)
+ }
+
+ // Cancel any pending frees on any view we're trying to bind since we should be bound after.
+ cancelContentViewFrees(row, contentToBind)
+ val task =
+ AsyncInflationTask(
+ inflationExecutor,
+ inflateSynchronously,
+ /* reInflateFlags = */ contentToBind,
+ remoteViewCache,
+ entry,
+ conversationProcessor,
+ row,
+ bindParams.isMinimized,
+ bindParams.usesIncreasedHeight,
+ bindParams.usesIncreasedHeadsUpHeight,
+ callback,
+ remoteInputManager.remoteViewsOnClickHandler,
+ /* isMediaFlagEnabled = */ smartReplyStateInflater,
+ notifLayoutInflaterFactoryProvider,
+ headsUpStyleProvider,
+ logger
+ )
+ if (inflateSynchronously) {
+ task.onPostExecute(task.doInBackground())
+ } else {
+ task.executeOnExecutor(inflationExecutor)
+ }
+ }
+
+ @VisibleForTesting
+ fun inflateNotificationViews(
+ entry: NotificationEntry,
+ row: ExpandableNotificationRow,
+ bindParams: BindParams,
+ inflateSynchronously: Boolean,
+ @InflationFlag reInflateFlags: Int,
+ builder: Notification.Builder,
+ packageContext: Context,
+ smartRepliesInflater: SmartReplyStateInflater
+ ): InflationProgress {
+ val systemUIContext = row.context
+ val result =
+ beginInflationAsync(
+ reInflateFlags = reInflateFlags,
+ entry = entry,
+ builder = builder,
+ isMinimized = bindParams.isMinimized,
+ usesIncreasedHeight = bindParams.usesIncreasedHeight,
+ usesIncreasedHeadsUpHeight = bindParams.usesIncreasedHeadsUpHeight,
+ systemUIContext = systemUIContext,
+ packageContext = packageContext,
+ row = row,
+ notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
+ headsUpStyleProvider = headsUpStyleProvider,
+ conversationProcessor = conversationProcessor,
+ logger = logger,
+ )
+ inflateSmartReplyViews(
+ result,
+ reInflateFlags,
+ entry,
+ systemUIContext,
+ packageContext,
+ row.existingSmartReplyState,
+ smartRepliesInflater,
+ logger,
+ )
+ if (AsyncHybridViewInflation.isEnabled) {
+ result.inflatedSingleLineView =
+ result.contentModel.singleLineViewModel?.let { viewModel ->
+ SingleLineViewInflater.inflateSingleLineViewHolder(
+ viewModel.isConversation(),
+ reInflateFlags,
+ entry,
+ systemUIContext,
+ logger,
+ )
+ }
+ }
+ apply(
+ inflationExecutor,
+ inflateSynchronously,
+ bindParams.isMinimized,
+ result,
+ reInflateFlags,
+ remoteViewCache,
+ entry,
+ row,
+ remoteInputManager.remoteViewsOnClickHandler,
+ /* callback= */ null,
+ logger
+ )
+ return result
+ }
+
+ override fun cancelBind(entry: NotificationEntry, row: ExpandableNotificationRow): Boolean {
+ val abortedTask: Boolean = entry.abortTask()
+ if (abortedTask) {
+ logger.logCancelBindAbortedTask(entry)
+ }
+ return abortedTask
+ }
+
+ @SuppressLint("WrongConstant")
+ override fun unbindContent(
+ entry: NotificationEntry,
+ row: ExpandableNotificationRow,
+ @InflationFlag contentToUnbind: Int
+ ) {
+ logger.logUnbinding(entry, contentToUnbind)
+ var curFlag = 1
+ var contentLeftToUnbind = contentToUnbind
+ while (contentLeftToUnbind != 0) {
+ if (contentLeftToUnbind and curFlag != 0) {
+ freeNotificationView(entry, row, curFlag)
+ }
+ contentLeftToUnbind = contentLeftToUnbind and curFlag.inv()
+ curFlag = curFlag shl 1
+ }
+ }
+
+ /**
+ * Frees the content view associated with the inflation flag as soon as the view is not showing.
+ *
+ * @param inflateFlag the flag corresponding to the content view which should be freed
+ */
+ private fun freeNotificationView(
+ entry: NotificationEntry,
+ row: ExpandableNotificationRow,
+ @InflationFlag inflateFlag: Int
+ ) {
+ when (inflateFlag) {
+ FLAG_CONTENT_VIEW_CONTRACTED ->
+ row.privateLayout.performWhenContentInactive(
+ NotificationContentView.VISIBLE_TYPE_CONTRACTED
+ ) {
+ row.privateLayout.setContractedChild(null)
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)
+ }
+ FLAG_CONTENT_VIEW_EXPANDED ->
+ row.privateLayout.performWhenContentInactive(
+ NotificationContentView.VISIBLE_TYPE_EXPANDED
+ ) {
+ row.privateLayout.setExpandedChild(null)
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
+ }
+ FLAG_CONTENT_VIEW_HEADS_UP ->
+ row.privateLayout.performWhenContentInactive(
+ NotificationContentView.VISIBLE_TYPE_HEADSUP
+ ) {
+ row.privateLayout.setHeadsUpChild(null)
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
+ row.privateLayout.setHeadsUpInflatedSmartReplies(null)
+ }
+ FLAG_CONTENT_VIEW_PUBLIC ->
+ row.publicLayout.performWhenContentInactive(
+ NotificationContentView.VISIBLE_TYPE_CONTRACTED
+ ) {
+ row.publicLayout.setContractedChild(null)
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)
+ }
+ FLAG_CONTENT_VIEW_SINGLE_LINE -> {
+ if (AsyncHybridViewInflation.isEnabled) {
+ row.privateLayout.performWhenContentInactive(
+ NotificationContentView.VISIBLE_TYPE_SINGLELINE
+ ) {
+ row.privateLayout.setSingleLineView(null)
+ }
+ }
+ }
+ else -> {}
+ }
+ }
+
+ /**
+ * Cancel any pending content view frees from [.freeNotificationView] for the provided content
+ * views.
+ *
+ * @param row top level notification row containing the content views
+ * @param contentViews content views to cancel pending frees on
+ */
+ private fun cancelContentViewFrees(
+ row: ExpandableNotificationRow,
+ @InflationFlag contentViews: Int
+ ) {
+ if (contentViews and FLAG_CONTENT_VIEW_CONTRACTED != 0) {
+ row.privateLayout.removeContentInactiveRunnable(
+ NotificationContentView.VISIBLE_TYPE_CONTRACTED
+ )
+ }
+ if (contentViews and FLAG_CONTENT_VIEW_EXPANDED != 0) {
+ row.privateLayout.removeContentInactiveRunnable(
+ NotificationContentView.VISIBLE_TYPE_EXPANDED
+ )
+ }
+ if (contentViews and FLAG_CONTENT_VIEW_HEADS_UP != 0) {
+ row.privateLayout.removeContentInactiveRunnable(
+ NotificationContentView.VISIBLE_TYPE_HEADSUP
+ )
+ }
+ if (contentViews and FLAG_CONTENT_VIEW_PUBLIC != 0) {
+ row.publicLayout.removeContentInactiveRunnable(
+ NotificationContentView.VISIBLE_TYPE_CONTRACTED
+ )
+ }
+ if (
+ AsyncHybridViewInflation.isEnabled &&
+ contentViews and FLAG_CONTENT_VIEW_SINGLE_LINE != 0
+ ) {
+ row.privateLayout.removeContentInactiveRunnable(
+ NotificationContentView.VISIBLE_TYPE_SINGLELINE
+ )
+ }
+ }
+
+ /**
+ * Sets whether to perform inflation on the same thread as the caller. This method should only
+ * be used in tests, not in production.
+ */
+ @VisibleForTesting
+ override fun setInflateSynchronously(inflateSynchronously: Boolean) {
+ this.inflateSynchronously = inflateSynchronously
+ }
+
+ class AsyncInflationTask(
+ private val inflationExecutor: Executor,
+ private val inflateSynchronously: Boolean,
+ @get:InflationFlag @get:VisibleForTesting @InflationFlag val reInflateFlags: Int,
+ private val remoteViewCache: NotifRemoteViewCache,
+ private val entry: NotificationEntry,
+ private val conversationProcessor: ConversationNotificationProcessor,
+ private val row: ExpandableNotificationRow,
+ private val isMinimized: Boolean,
+ private val usesIncreasedHeight: Boolean,
+ private val usesIncreasedHeadsUpHeight: Boolean,
+ private val callback: InflationCallback?,
+ private val remoteViewClickHandler: InteractionHandler?,
+ private val smartRepliesInflater: SmartReplyStateInflater,
+ private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
+ private val headsUpStyleProvider: HeadsUpStyleProvider,
+ private val logger: NotificationRowContentBinderLogger
+ ) : AsyncTask<Void, Void, Result<InflationProgress>>(), InflationCallback, InflationTask {
+ private val context: Context
+ get() = row.context
+
+ private var cancellationSignal: CancellationSignal? = null
+
+ init {
+ entry.setInflationTask(this)
+ }
+
+ private fun updateApplicationInfo(sbn: StatusBarNotification) {
+ val packageName: String = sbn.packageName
+ val userId: Int = UserHandle.getUserId(sbn.uid)
+ val appInfo: ApplicationInfo
+ try {
+ // This method has an internal cache, so we don't need to add our own caching here.
+ appInfo =
+ context.packageManager.getApplicationInfoAsUser(
+ packageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES,
+ userId
+ )
+ } catch (e: PackageManager.NameNotFoundException) {
+ return
+ }
+ Notification.addFieldsFromContext(appInfo, sbn.notification)
+ }
+
+ override fun onPreExecute() {
+ Trace.beginAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this))
+ }
+
+ public override fun doInBackground(vararg params: Void): Result<InflationProgress> {
+ return TraceUtils.trace(
+ "NotificationContentInflater.AsyncInflationTask#doInBackground"
+ ) {
+ try {
+ return@trace Result.success(doInBackgroundInternal())
+ } catch (e: Exception) {
+ logger.logAsyncTaskException(entry, "inflating", e)
+ return@trace Result.failure(e)
+ }
+ }
+ }
+
+ private fun doInBackgroundInternal(): InflationProgress {
+ val sbn: StatusBarNotification = entry.sbn
+ // Ensure the ApplicationInfo is updated before a builder is recovered.
+ updateApplicationInfo(sbn)
+ val recoveredBuilder = Notification.Builder.recoverBuilder(context, sbn.notification)
+ var packageContext: Context = sbn.getPackageContext(context)
+ if (recoveredBuilder.usesTemplate()) {
+ // For all of our templates, we want it to be RTL
+ packageContext = RtlEnabledContext(packageContext)
+ }
+ val inflationProgress =
+ beginInflationAsync(
+ reInflateFlags = reInflateFlags,
+ entry = entry,
+ builder = recoveredBuilder,
+ isMinimized = isMinimized,
+ usesIncreasedHeight = usesIncreasedHeight,
+ usesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight,
+ systemUIContext = context,
+ packageContext = packageContext,
+ row = row,
+ notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
+ headsUpStyleProvider = headsUpStyleProvider,
+ conversationProcessor = conversationProcessor,
+ logger = logger
+ )
+ logger.logAsyncTaskProgress(
+ entry,
+ "getting existing smart reply state (on wrong thread!)"
+ )
+ val previousSmartReplyState: InflatedSmartReplyState? = row.existingSmartReplyState
+ logger.logAsyncTaskProgress(entry, "inflating smart reply views")
+ inflateSmartReplyViews(
+ /* result = */ inflationProgress,
+ reInflateFlags,
+ entry,
+ context,
+ packageContext,
+ previousSmartReplyState,
+ smartRepliesInflater,
+ logger,
+ )
+ if (AsyncHybridViewInflation.isEnabled) {
+ logger.logAsyncTaskProgress(entry, "inflating single line view")
+ inflationProgress.inflatedSingleLineView =
+ inflationProgress.contentModel.singleLineViewModel?.let {
+ SingleLineViewInflater.inflateSingleLineViewHolder(
+ it.isConversation(),
+ reInflateFlags,
+ entry,
+ context,
+ logger
+ )
+ }
+ }
+ logger.logAsyncTaskProgress(entry, "getting row image resolver (on wrong thread!)")
+ val imageResolver = row.imageResolver
+ // wait for image resolver to finish preloading
+ logger.logAsyncTaskProgress(entry, "waiting for preloaded images")
+ imageResolver.waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS)
+ return inflationProgress
+ }
+
+ public override fun onPostExecute(result: Result<InflationProgress>) {
+ Trace.endAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this))
+ result
+ .onSuccess { progress ->
+ // Logged in detail in apply.
+ cancellationSignal =
+ apply(
+ inflationExecutor,
+ inflateSynchronously,
+ isMinimized,
+ progress,
+ reInflateFlags,
+ remoteViewCache,
+ entry,
+ row,
+ remoteViewClickHandler,
+ this /* callback */,
+ logger
+ )
+ }
+ .onFailure { error -> handleError(error as Exception) }
+ }
+
+ override fun onCancelled(result: Result<InflationProgress>) {
+ Trace.endAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this))
+ }
+
+ private fun handleError(e: Exception) {
+ entry.onInflationTaskFinished()
+ val sbn: StatusBarNotification = entry.sbn
+ val ident: String = (sbn.packageName + "/0x" + Integer.toHexString(sbn.id))
+ Log.e(TAG, "couldn't inflate view for notification $ident", e)
+ callback?.handleInflationException(
+ row.entry,
+ InflationException("Couldn't inflate contentViews$e")
+ )
+
+ // Cancel any image loading tasks, not useful any more
+ row.imageResolver.cancelRunningTasks()
+ }
+
+ override fun abort() {
+ logger.logAsyncTaskProgress(entry, "cancelling inflate")
+ cancel(/* mayInterruptIfRunning= */ true)
+ if (cancellationSignal != null) {
+ logger.logAsyncTaskProgress(entry, "cancelling apply")
+ cancellationSignal!!.cancel()
+ }
+ logger.logAsyncTaskProgress(entry, "aborted")
+ }
+
+ override fun handleInflationException(entry: NotificationEntry, e: Exception) {
+ handleError(e)
+ }
+
+ override fun onAsyncInflationFinished(entry: NotificationEntry) {
+ this.entry.onInflationTaskFinished()
+ row.onNotificationUpdated()
+ callback?.onAsyncInflationFinished(this.entry)
+
+ // Notify the resolver that the inflation task has finished,
+ // try to purge unnecessary cached entries.
+ row.imageResolver.purgeCache()
+
+ // Cancel any image loading tasks that have not completed at this point
+ row.imageResolver.cancelRunningTasks()
+ }
+
+ class RtlEnabledContext(packageContext: Context) : ContextWrapper(packageContext) {
+ override fun getApplicationInfo(): ApplicationInfo {
+ val applicationInfo = ApplicationInfo(super.getApplicationInfo())
+ applicationInfo.flags = applicationInfo.flags or ApplicationInfo.FLAG_SUPPORTS_RTL
+ return applicationInfo
+ }
+ }
+
+ companion object {
+ private const val IMG_PRELOAD_TIMEOUT_MS = 1000L
+ }
+ }
+
+ @VisibleForTesting
+ class InflationProgress(
+ @VisibleForTesting val packageContext: Context,
+ val remoteViews: NewRemoteViews,
+ val contentModel: NotificationContentModel,
+ ) {
+
+ var inflatedContentView: View? = null
+ var inflatedHeadsUpView: View? = null
+ var inflatedExpandedView: View? = null
+ var inflatedPublicView: View? = null
+ var inflatedGroupHeaderView: NotificationHeaderView? = null
+ var inflatedMinimizedGroupHeaderView: NotificationHeaderView? = null
+ var inflatedSmartReplyState: InflatedSmartReplyState? = null
+ var expandedInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
+ var headsUpInflatedSmartReplies: InflatedSmartReplyViewHolder? = null
+
+ // Inflated SingleLineView that lacks the UI State
+ var inflatedSingleLineView: HybridNotificationView? = null
+ }
+
+ @VisibleForTesting
+ abstract class ApplyCallback {
+ abstract fun setResultView(v: View)
+
+ abstract val remoteView: RemoteViews
+ }
+
+ companion object {
+ const val TAG = "NotifContentInflater"
+
+ private fun inflateSmartReplyViews(
+ result: InflationProgress,
+ @InflationFlag reInflateFlags: Int,
+ entry: NotificationEntry,
+ context: Context,
+ packageContext: Context,
+ previousSmartReplyState: InflatedSmartReplyState?,
+ inflater: SmartReplyStateInflater,
+ logger: NotificationRowContentBinderLogger
+ ) {
+ val inflateContracted =
+ (reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0 &&
+ result.remoteViews.contracted != null)
+ val inflateExpanded =
+ (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0 &&
+ result.remoteViews.expanded != null)
+ val inflateHeadsUp =
+ (reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0 &&
+ result.remoteViews.headsUp != null)
+ if (inflateContracted || inflateExpanded || inflateHeadsUp) {
+ logger.logAsyncTaskProgress(entry, "inflating contracted smart reply state")
+ result.inflatedSmartReplyState = inflater.inflateSmartReplyState(entry)
+ }
+ if (inflateExpanded) {
+ logger.logAsyncTaskProgress(entry, "inflating expanded smart reply state")
+ result.expandedInflatedSmartReplies =
+ inflater.inflateSmartReplyViewHolder(
+ context,
+ packageContext,
+ entry,
+ previousSmartReplyState,
+ result.inflatedSmartReplyState!!
+ )
+ }
+ if (inflateHeadsUp) {
+ logger.logAsyncTaskProgress(entry, "inflating heads up smart reply state")
+ result.headsUpInflatedSmartReplies =
+ inflater.inflateSmartReplyViewHolder(
+ context,
+ packageContext,
+ entry,
+ previousSmartReplyState,
+ result.inflatedSmartReplyState!!
+ )
+ }
+ }
+
+ private fun beginInflationAsync(
+ @InflationFlag reInflateFlags: Int,
+ entry: NotificationEntry,
+ builder: Notification.Builder,
+ isMinimized: Boolean,
+ usesIncreasedHeight: Boolean,
+ usesIncreasedHeadsUpHeight: Boolean,
+ systemUIContext: Context,
+ packageContext: Context,
+ row: ExpandableNotificationRow,
+ notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
+ headsUpStyleProvider: HeadsUpStyleProvider,
+ conversationProcessor: ConversationNotificationProcessor,
+ logger: NotificationRowContentBinderLogger
+ ): InflationProgress {
+ // process conversations and extract the messaging style
+ val messagingStyle =
+ if (entry.ranking.isConversation) {
+ conversationProcessor.processNotification(entry, builder, logger)
+ } else null
+
+ val remoteViews =
+ createRemoteViews(
+ reInflateFlags = reInflateFlags,
+ builder = builder,
+ isMinimized = isMinimized,
+ usesIncreasedHeight = usesIncreasedHeight,
+ usesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight,
+ row = row,
+ notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
+ headsUpStyleProvider = headsUpStyleProvider,
+ logger = logger,
+ )
+
+ val singleLineViewModel =
+ if (
+ AsyncHybridViewInflation.isEnabled &&
+ reInflateFlags and FLAG_CONTENT_VIEW_SINGLE_LINE != 0
+ ) {
+ logger.logAsyncTaskProgress(entry, "inflating single line view model")
+ SingleLineViewInflater.inflateSingleLineViewModel(
+ notification = entry.sbn.notification,
+ messagingStyle = messagingStyle,
+ builder = builder,
+ systemUiContext = systemUIContext,
+ )
+ } else null
+
+ val headsUpStatusBarModel =
+ HeadsUpStatusBarModel(
+ privateText = builder.getHeadsUpStatusBarText(/* publicMode= */ false),
+ publicText = builder.getHeadsUpStatusBarText(/* publicMode= */ true),
+ )
+
+ val contentModel =
+ NotificationContentModel(
+ headsUpStatusBarModel = headsUpStatusBarModel,
+ singleLineViewModel = singleLineViewModel,
+ )
+
+ return InflationProgress(
+ packageContext = packageContext,
+ remoteViews = remoteViews,
+ contentModel = contentModel,
+ )
+ }
+
+ private fun createRemoteViews(
+ @InflationFlag reInflateFlags: Int,
+ builder: Notification.Builder,
+ isMinimized: Boolean,
+ usesIncreasedHeight: Boolean,
+ usesIncreasedHeadsUpHeight: Boolean,
+ row: ExpandableNotificationRow,
+ notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
+ headsUpStyleProvider: HeadsUpStyleProvider,
+ logger: NotificationRowContentBinderLogger
+ ): NewRemoteViews {
+ return TraceUtils.trace("NotificationContentInflater.createRemoteViews") {
+ val entryForLogging: NotificationEntry = row.entry
+ val contracted =
+ if (reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0) {
+ logger.logAsyncTaskProgress(
+ entryForLogging,
+ "creating contracted remote view"
+ )
+ createContentView(builder, isMinimized, usesIncreasedHeight)
+ } else null
+ val expanded =
+ if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) {
+ logger.logAsyncTaskProgress(
+ entryForLogging,
+ "creating expanded remote view"
+ )
+ createExpandedView(builder, isMinimized)
+ } else null
+ val headsUp =
+ if (reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0) {
+ logger.logAsyncTaskProgress(
+ entryForLogging,
+ "creating heads up remote view"
+ )
+ val isHeadsUpCompact = headsUpStyleProvider.shouldApplyCompactStyle()
+ if (isHeadsUpCompact) {
+ builder.createCompactHeadsUpContentView()
+ } else {
+ builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight)
+ }
+ } else null
+ val public =
+ if (reInflateFlags and FLAG_CONTENT_VIEW_PUBLIC != 0) {
+ logger.logAsyncTaskProgress(entryForLogging, "creating public remote view")
+ builder.makePublicContentView(isMinimized)
+ } else null
+ val normalGroupHeader =
+ if (
+ AsyncGroupHeaderViewInflation.isEnabled &&
+ reInflateFlags and FLAG_GROUP_SUMMARY_HEADER != 0
+ ) {
+ logger.logAsyncTaskProgress(
+ entryForLogging,
+ "creating group summary remote view"
+ )
+ builder.makeNotificationGroupHeader()
+ } else null
+ val minimizedGroupHeader =
+ if (
+ AsyncGroupHeaderViewInflation.isEnabled &&
+ reInflateFlags and FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER != 0
+ ) {
+ logger.logAsyncTaskProgress(
+ entryForLogging,
+ "creating low-priority group summary remote view"
+ )
+ builder.makeLowPriorityContentView(true /* useRegularSubtext */)
+ } else null
+ NewRemoteViews(
+ contracted = contracted,
+ headsUp = headsUp,
+ expanded = expanded,
+ public = public,
+ normalGroupHeader = normalGroupHeader,
+ minimizedGroupHeader = minimizedGroupHeader
+ )
+ .withLayoutInflaterFactory(row, notifLayoutInflaterFactoryProvider)
+ }
+ }
+
+ private fun NewRemoteViews.withLayoutInflaterFactory(
+ row: ExpandableNotificationRow,
+ provider: NotifLayoutInflaterFactory.Provider
+ ): NewRemoteViews {
+ contracted?.let {
+ it.layoutInflaterFactory = provider.provide(row, FLAG_CONTENT_VIEW_CONTRACTED)
+ }
+ expanded?.let {
+ it.layoutInflaterFactory = provider.provide(row, FLAG_CONTENT_VIEW_EXPANDED)
+ }
+ headsUp?.let {
+ it.layoutInflaterFactory = provider.provide(row, FLAG_CONTENT_VIEW_HEADS_UP)
+ }
+ public?.let {
+ it.layoutInflaterFactory = provider.provide(row, FLAG_CONTENT_VIEW_PUBLIC)
+ }
+ return this
+ }
+
+ private fun apply(
+ inflationExecutor: Executor,
+ inflateSynchronously: Boolean,
+ isMinimized: Boolean,
+ result: InflationProgress,
+ @InflationFlag reInflateFlags: Int,
+ remoteViewCache: NotifRemoteViewCache,
+ entry: NotificationEntry,
+ row: ExpandableNotificationRow,
+ remoteViewClickHandler: InteractionHandler?,
+ callback: InflationCallback?,
+ logger: NotificationRowContentBinderLogger
+ ): CancellationSignal {
+ Trace.beginAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row))
+ val privateLayout = row.privateLayout
+ val publicLayout = row.publicLayout
+ val runningInflations = HashMap<Int, CancellationSignal>()
+ var flag = FLAG_CONTENT_VIEW_CONTRACTED
+ if (reInflateFlags and flag != 0) {
+ val isNewView =
+ !canReapplyRemoteView(
+ newView = result.remoteViews.contracted,
+ oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)
+ )
+ val applyCallback: ApplyCallback =
+ object : ApplyCallback() {
+ override fun setResultView(v: View) {
+ logger.logAsyncTaskProgress(entry, "contracted view applied")
+ result.inflatedContentView = v
+ }
+
+ override val remoteView: RemoteViews
+ get() = result.remoteViews.contracted!!
+ }
+ logger.logAsyncTaskProgress(entry, "applying contracted view")
+ applyRemoteView(
+ inflationExecutor = inflationExecutor,
+ inflateSynchronously = inflateSynchronously,
+ isMinimized = isMinimized,
+ result = result,
+ reInflateFlags = reInflateFlags,
+ inflationId = flag,
+ remoteViewCache = remoteViewCache,
+ entry = entry,
+ row = row,
+ isNewView = isNewView,
+ remoteViewClickHandler = remoteViewClickHandler,
+ callback = callback,
+ parentLayout = privateLayout,
+ existingView = privateLayout.contractedChild,
+ existingWrapper =
+ privateLayout.getVisibleWrapper(
+ NotificationContentView.VISIBLE_TYPE_CONTRACTED
+ ),
+ runningInflations = runningInflations,
+ applyCallback = applyCallback,
+ logger = logger
+ )
+ }
+ flag = FLAG_CONTENT_VIEW_EXPANDED
+ if (reInflateFlags and flag != 0) {
+ if (result.remoteViews.expanded != null) {
+ val isNewView =
+ !canReapplyRemoteView(
+ newView = result.remoteViews.expanded,
+ oldView =
+ remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
+ )
+ val applyCallback: ApplyCallback =
+ object : ApplyCallback() {
+ override fun setResultView(v: View) {
+ logger.logAsyncTaskProgress(entry, "expanded view applied")
+ result.inflatedExpandedView = v
+ }
+
+ override val remoteView: RemoteViews
+ get() = result.remoteViews.expanded
+ }
+ logger.logAsyncTaskProgress(entry, "applying expanded view")
+ applyRemoteView(
+ inflationExecutor = inflationExecutor,
+ inflateSynchronously = inflateSynchronously,
+ isMinimized = isMinimized,
+ result = result,
+ reInflateFlags = reInflateFlags,
+ inflationId = flag,
+ remoteViewCache = remoteViewCache,
+ entry = entry,
+ row = row,
+ isNewView = isNewView,
+ remoteViewClickHandler = remoteViewClickHandler,
+ callback = callback,
+ parentLayout = privateLayout,
+ existingView = privateLayout.expandedChild,
+ existingWrapper =
+ privateLayout.getVisibleWrapper(
+ NotificationContentView.VISIBLE_TYPE_EXPANDED
+ ),
+ runningInflations = runningInflations,
+ applyCallback = applyCallback,
+ logger = logger
+ )
+ }
+ }
+ flag = FLAG_CONTENT_VIEW_HEADS_UP
+ if (reInflateFlags and flag != 0) {
+ if (result.remoteViews.headsUp != null) {
+ val isNewView =
+ !canReapplyRemoteView(
+ newView = result.remoteViews.headsUp,
+ oldView =
+ remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
+ )
+ val applyCallback: ApplyCallback =
+ object : ApplyCallback() {
+ override fun setResultView(v: View) {
+ logger.logAsyncTaskProgress(entry, "heads up view applied")
+ result.inflatedHeadsUpView = v
+ }
+
+ override val remoteView: RemoteViews
+ get() = result.remoteViews.headsUp
+ }
+ logger.logAsyncTaskProgress(entry, "applying heads up view")
+ applyRemoteView(
+ inflationExecutor = inflationExecutor,
+ inflateSynchronously = inflateSynchronously,
+ isMinimized = isMinimized,
+ result = result,
+ reInflateFlags = reInflateFlags,
+ inflationId = flag,
+ remoteViewCache = remoteViewCache,
+ entry = entry,
+ row = row,
+ isNewView = isNewView,
+ remoteViewClickHandler = remoteViewClickHandler,
+ callback = callback,
+ parentLayout = privateLayout,
+ existingView = privateLayout.headsUpChild,
+ existingWrapper =
+ privateLayout.getVisibleWrapper(
+ NotificationContentView.VISIBLE_TYPE_HEADSUP
+ ),
+ runningInflations = runningInflations,
+ applyCallback = applyCallback,
+ logger = logger
+ )
+ }
+ }
+ flag = FLAG_CONTENT_VIEW_PUBLIC
+ if (reInflateFlags and flag != 0) {
+ val isNewView =
+ !canReapplyRemoteView(
+ newView = result.remoteViews.public,
+ oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)
+ )
+ val applyCallback: ApplyCallback =
+ object : ApplyCallback() {
+ override fun setResultView(v: View) {
+ logger.logAsyncTaskProgress(entry, "public view applied")
+ result.inflatedPublicView = v
+ }
+
+ override val remoteView: RemoteViews
+ get() = result.remoteViews.public!!
+ }
+ logger.logAsyncTaskProgress(entry, "applying public view")
+ applyRemoteView(
+ inflationExecutor = inflationExecutor,
+ inflateSynchronously = inflateSynchronously,
+ isMinimized = isMinimized,
+ result = result,
+ reInflateFlags = reInflateFlags,
+ inflationId = flag,
+ remoteViewCache = remoteViewCache,
+ entry = entry,
+ row = row,
+ isNewView = isNewView,
+ remoteViewClickHandler = remoteViewClickHandler,
+ callback = callback,
+ parentLayout = publicLayout,
+ existingView = publicLayout.contractedChild,
+ existingWrapper =
+ publicLayout.getVisibleWrapper(
+ NotificationContentView.VISIBLE_TYPE_CONTRACTED
+ ),
+ runningInflations = runningInflations,
+ applyCallback = applyCallback,
+ logger = logger
+ )
+ }
+ if (AsyncGroupHeaderViewInflation.isEnabled) {
+ val childrenContainer: NotificationChildrenContainer =
+ row.getChildrenContainerNonNull()
+ if (reInflateFlags and FLAG_GROUP_SUMMARY_HEADER != 0) {
+ val isNewView =
+ !canReapplyRemoteView(
+ newView = result.remoteViews.normalGroupHeader,
+ oldView =
+ remoteViewCache.getCachedView(entry, FLAG_GROUP_SUMMARY_HEADER)
+ )
+ val applyCallback: ApplyCallback =
+ object : ApplyCallback() {
+ override fun setResultView(v: View) {
+ logger.logAsyncTaskProgress(entry, "group header view applied")
+ result.inflatedGroupHeaderView = v as NotificationHeaderView?
+ }
+
+ override val remoteView: RemoteViews
+ get() = result.remoteViews.normalGroupHeader!!
+ }
+ logger.logAsyncTaskProgress(entry, "applying group header view")
+ applyRemoteView(
+ inflationExecutor = inflationExecutor,
+ inflateSynchronously = inflateSynchronously,
+ isMinimized = isMinimized,
+ result = result,
+ reInflateFlags = reInflateFlags,
+ inflationId = FLAG_GROUP_SUMMARY_HEADER,
+ remoteViewCache = remoteViewCache,
+ entry = entry,
+ row = row,
+ isNewView = isNewView,
+ remoteViewClickHandler = remoteViewClickHandler,
+ callback = callback,
+ parentLayout = childrenContainer,
+ existingView = childrenContainer.groupHeader,
+ existingWrapper = childrenContainer.notificationHeaderWrapper,
+ runningInflations = runningInflations,
+ applyCallback = applyCallback,
+ logger = logger
+ )
+ }
+ if (reInflateFlags and FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER != 0) {
+ val isNewView =
+ !canReapplyRemoteView(
+ newView = result.remoteViews.minimizedGroupHeader,
+ oldView =
+ remoteViewCache.getCachedView(
+ entry,
+ FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER
+ )
+ )
+ val applyCallback: ApplyCallback =
+ object : ApplyCallback() {
+ override fun setResultView(v: View) {
+ logger.logAsyncTaskProgress(
+ entry,
+ "low-priority group header view applied"
+ )
+ result.inflatedMinimizedGroupHeaderView =
+ v as NotificationHeaderView?
+ }
+
+ override val remoteView: RemoteViews
+ get() = result.remoteViews.minimizedGroupHeader!!
+ }
+ logger.logAsyncTaskProgress(entry, "applying low priority group header view")
+ applyRemoteView(
+ inflationExecutor = inflationExecutor,
+ inflateSynchronously = inflateSynchronously,
+ isMinimized = isMinimized,
+ result = result,
+ reInflateFlags = reInflateFlags,
+ inflationId = FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
+ remoteViewCache = remoteViewCache,
+ entry = entry,
+ row = row,
+ isNewView = isNewView,
+ remoteViewClickHandler = remoteViewClickHandler,
+ callback = callback,
+ parentLayout = childrenContainer,
+ existingView = childrenContainer.minimizedNotificationHeader,
+ existingWrapper = childrenContainer.minimizedGroupHeaderWrapper,
+ runningInflations = runningInflations,
+ applyCallback = applyCallback,
+ logger = logger
+ )
+ }
+ }
+
+ // Let's try to finish, maybe nobody is even inflating anything
+ finishIfDone(
+ result,
+ isMinimized,
+ reInflateFlags,
+ remoteViewCache,
+ runningInflations,
+ callback,
+ entry,
+ row,
+ logger
+ )
+ val cancellationSignal = CancellationSignal()
+ cancellationSignal.setOnCancelListener {
+ logger.logAsyncTaskProgress(entry, "apply cancelled")
+ Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row))
+ runningInflations.values.forEach(
+ Consumer { obj: CancellationSignal -> obj.cancel() }
+ )
+ }
+ return cancellationSignal
+ }
+
+ @VisibleForTesting
+ fun applyRemoteView(
+ inflationExecutor: Executor?,
+ inflateSynchronously: Boolean,
+ isMinimized: Boolean,
+ result: InflationProgress,
+ @InflationFlag reInflateFlags: Int,
+ @InflationFlag inflationId: Int,
+ remoteViewCache: NotifRemoteViewCache,
+ entry: NotificationEntry,
+ row: ExpandableNotificationRow,
+ isNewView: Boolean,
+ remoteViewClickHandler: InteractionHandler?,
+ callback: InflationCallback?,
+ parentLayout: ViewGroup?,
+ existingView: View?,
+ existingWrapper: NotificationViewWrapper?,
+ runningInflations: HashMap<Int, CancellationSignal>,
+ applyCallback: ApplyCallback,
+ logger: NotificationRowContentBinderLogger
+ ) {
+ val newContentView: RemoteViews = applyCallback.remoteView
+ if (inflateSynchronously) {
+ try {
+ if (isNewView) {
+ val v: View =
+ newContentView.apply(
+ result.packageContext,
+ parentLayout,
+ remoteViewClickHandler
+ )
+ validateView(v, entry, row.resources)
+ applyCallback.setResultView(v)
+ } else {
+ requireNotNull(existingView)
+ requireNotNull(existingWrapper)
+ newContentView.reapply(
+ result.packageContext,
+ existingView,
+ remoteViewClickHandler
+ )
+ validateView(existingView, entry, row.resources)
+ existingWrapper.onReinflated()
+ }
+ } catch (e: Exception) {
+ handleInflationError(
+ runningInflations,
+ e,
+ row.entry,
+ callback,
+ logger,
+ "applying view synchronously"
+ )
+ // Add a running inflation to make sure we don't trigger callbacks.
+ // Safe to do because only happens in tests.
+ runningInflations[inflationId] = CancellationSignal()
+ }
+ return
+ }
+ val listener: OnViewAppliedListener =
+ object : OnViewAppliedListener {
+ override fun onViewInflated(v: View) {
+ if (v is ImageMessageConsumer) {
+ (v as ImageMessageConsumer).setImageResolver(row.imageResolver)
+ }
+ }
+
+ override fun onViewApplied(v: View) {
+ val invalidReason = isValidView(v, entry, row.resources)
+ if (invalidReason != null) {
+ handleInflationError(
+ runningInflations,
+ InflationException(invalidReason),
+ row.entry,
+ callback,
+ logger,
+ "applied invalid view"
+ )
+ runningInflations.remove(inflationId)
+ return
+ }
+ if (isNewView) {
+ applyCallback.setResultView(v)
+ } else {
+ existingWrapper?.onReinflated()
+ }
+ runningInflations.remove(inflationId)
+ finishIfDone(
+ result,
+ isMinimized,
+ reInflateFlags,
+ remoteViewCache,
+ runningInflations,
+ callback,
+ entry,
+ row,
+ logger
+ )
+ }
+
+ override fun onError(e: Exception) {
+ // Uh oh the async inflation failed. Due to some bugs (see b/38190555), this
+ // could
+ // actually also be a system issue, so let's try on the UI thread again to
+ // be safe.
+ try {
+ val newView =
+ if (isNewView) {
+ newContentView.apply(
+ result.packageContext,
+ parentLayout,
+ remoteViewClickHandler
+ )
+ } else {
+ newContentView.reapply(
+ result.packageContext,
+ existingView,
+ remoteViewClickHandler
+ )
+ existingView!!
+ }
+ Log.wtf(
+ TAG,
+ "Async Inflation failed but normal inflation finished normally.",
+ e
+ )
+ onViewApplied(newView)
+ } catch (anotherException: Exception) {
+ runningInflations.remove(inflationId)
+ handleInflationError(
+ runningInflations,
+ e,
+ row.entry,
+ callback,
+ logger,
+ "applying view"
+ )
+ }
+ }
+ }
+ val cancellationSignal: CancellationSignal =
+ if (isNewView) {
+ newContentView.applyAsync(
+ result.packageContext,
+ parentLayout,
+ inflationExecutor,
+ listener,
+ remoteViewClickHandler
+ )
+ } else {
+ newContentView.reapplyAsync(
+ result.packageContext,
+ existingView,
+ inflationExecutor,
+ listener,
+ remoteViewClickHandler
+ )
+ }
+ runningInflations[inflationId] = cancellationSignal
+ }
+
+ /**
+ * Checks if the given View is a valid notification View.
+ *
+ * @return null == valid, non-null == invalid, String represents reason for rejection.
+ */
+ @VisibleForTesting
+ fun isValidView(view: View, entry: NotificationEntry, resources: Resources): String? {
+ return if (!satisfiesMinHeightRequirement(view, entry, resources)) {
+ "inflated notification does not meet minimum height requirement"
+ } else null
+ }
+
+ private fun satisfiesMinHeightRequirement(
+ view: View,
+ entry: NotificationEntry,
+ resources: Resources
+ ): Boolean {
+ return if (!requiresHeightCheck(entry)) {
+ true
+ } else
+ TraceUtils.trace("NotificationContentInflater#satisfiesMinHeightRequirement") {
+ val heightSpec =
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ val referenceWidth =
+ resources.getDimensionPixelSize(
+ R.dimen.notification_validation_reference_width
+ )
+ val widthSpec =
+ View.MeasureSpec.makeMeasureSpec(referenceWidth, View.MeasureSpec.EXACTLY)
+ view.measure(widthSpec, heightSpec)
+ val minHeight =
+ resources.getDimensionPixelSize(
+ R.dimen.notification_validation_minimum_allowed_height
+ )
+ view.measuredHeight >= minHeight
+ }
+ }
+
+ /**
+ * Notifications with undecorated custom views need to satisfy a minimum height to avoid
+ * visual issues.
+ */
+ private fun requiresHeightCheck(entry: NotificationEntry): Boolean {
+ // Undecorated custom views are disallowed from S onwards
+ if (entry.targetSdk >= Build.VERSION_CODES.S) {
+ return false
+ }
+ // No need to check if the app isn't using any custom views
+ val notification: Notification = entry.sbn.notification
+ @Suppress("DEPRECATION")
+ return !(notification.contentView == null &&
+ notification.bigContentView == null &&
+ notification.headsUpContentView == null)
+ }
+
+ @Throws(InflationException::class)
+ private fun validateView(view: View, entry: NotificationEntry, resources: Resources) {
+ val invalidReason = isValidView(view, entry, resources)
+ if (invalidReason != null) {
+ throw InflationException(invalidReason)
+ }
+ }
+
+ private fun handleInflationError(
+ runningInflations: HashMap<Int, CancellationSignal>,
+ e: Exception,
+ notification: NotificationEntry,
+ callback: InflationCallback?,
+ logger: NotificationRowContentBinderLogger,
+ logContext: String
+ ) {
+ Assert.isMainThread()
+ logger.logAsyncTaskException(notification, logContext, e)
+ runningInflations.values.forEach(Consumer { obj: CancellationSignal -> obj.cancel() })
+ callback?.handleInflationException(notification, e)
+ }
+
+ /**
+ * Finish the inflation of the views
+ *
+ * @return true if the inflation was finished
+ */
+ private fun finishIfDone(
+ result: InflationProgress,
+ isMinimized: Boolean,
+ @InflationFlag reInflateFlags: Int,
+ remoteViewCache: NotifRemoteViewCache,
+ runningInflations: HashMap<Int, CancellationSignal>,
+ endListener: InflationCallback?,
+ entry: NotificationEntry,
+ row: ExpandableNotificationRow,
+ logger: NotificationRowContentBinderLogger
+ ): Boolean {
+ Assert.isMainThread()
+ if (runningInflations.isNotEmpty()) {
+ return false
+ }
+ val privateLayout = row.privateLayout
+ val publicLayout = row.publicLayout
+ logger.logAsyncTaskProgress(entry, "finishing")
+ if (reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0) {
+ if (result.inflatedContentView != null) {
+ // New view case
+ privateLayout.setContractedChild(result.inflatedContentView)
+ remoteViewCache.putCachedView(
+ entry,
+ FLAG_CONTENT_VIEW_CONTRACTED,
+ result.remoteViews.contracted
+ )
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) {
+ // Reinflation case. Only update if it's still cached (i.e. view has not been
+ // freed while inflating).
+ remoteViewCache.putCachedView(
+ entry,
+ FLAG_CONTENT_VIEW_CONTRACTED,
+ result.remoteViews.contracted
+ )
+ }
+ }
+ if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) {
+ if (result.inflatedExpandedView != null) {
+ privateLayout.setExpandedChild(result.inflatedExpandedView)
+ remoteViewCache.putCachedView(
+ entry,
+ FLAG_CONTENT_VIEW_EXPANDED,
+ result.remoteViews.expanded
+ )
+ } else if (result.remoteViews.expanded == null) {
+ privateLayout.setExpandedChild(null)
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) {
+ remoteViewCache.putCachedView(
+ entry,
+ FLAG_CONTENT_VIEW_EXPANDED,
+ result.remoteViews.expanded
+ )
+ }
+ if (result.remoteViews.expanded != null) {
+ privateLayout.setExpandedInflatedSmartReplies(
+ result.expandedInflatedSmartReplies
+ )
+ } else {
+ privateLayout.setExpandedInflatedSmartReplies(null)
+ }
+ row.setExpandable(result.remoteViews.expanded != null)
+ }
+ if (reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0) {
+ if (result.inflatedHeadsUpView != null) {
+ privateLayout.setHeadsUpChild(result.inflatedHeadsUpView)
+ remoteViewCache.putCachedView(
+ entry,
+ FLAG_CONTENT_VIEW_HEADS_UP,
+ result.remoteViews.headsUp
+ )
+ } else if (result.remoteViews.headsUp == null) {
+ privateLayout.setHeadsUpChild(null)
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) {
+ remoteViewCache.putCachedView(
+ entry,
+ FLAG_CONTENT_VIEW_HEADS_UP,
+ result.remoteViews.headsUp
+ )
+ }
+ if (result.remoteViews.headsUp != null) {
+ privateLayout.setHeadsUpInflatedSmartReplies(result.headsUpInflatedSmartReplies)
+ } else {
+ privateLayout.setHeadsUpInflatedSmartReplies(null)
+ }
+ }
+ if (
+ AsyncHybridViewInflation.isEnabled &&
+ reInflateFlags and FLAG_CONTENT_VIEW_SINGLE_LINE != 0
+ ) {
+ val singleLineView = result.inflatedSingleLineView
+ val viewModel = result.contentModel.singleLineViewModel
+ if (singleLineView != null && viewModel != null) {
+ if (viewModel.isConversation()) {
+ SingleLineConversationViewBinder.bind(viewModel, singleLineView)
+ } else {
+ SingleLineViewBinder.bind(viewModel, singleLineView)
+ }
+ privateLayout.setSingleLineView(result.inflatedSingleLineView)
+ }
+ }
+ result.inflatedSmartReplyState?.let { privateLayout.setInflatedSmartReplyState(it) }
+ if (reInflateFlags and FLAG_CONTENT_VIEW_PUBLIC != 0) {
+ if (result.inflatedPublicView != null) {
+ publicLayout.setContractedChild(result.inflatedPublicView)
+ remoteViewCache.putCachedView(
+ entry,
+ FLAG_CONTENT_VIEW_PUBLIC,
+ result.remoteViews.public
+ )
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) {
+ remoteViewCache.putCachedView(
+ entry,
+ FLAG_CONTENT_VIEW_PUBLIC,
+ result.remoteViews.public
+ )
+ }
+ }
+ if (AsyncGroupHeaderViewInflation.isEnabled) {
+ if (reInflateFlags and FLAG_GROUP_SUMMARY_HEADER != 0) {
+ if (result.inflatedGroupHeaderView != null) {
+ // We need to set if the row is minimized before setting the group header to
+ // make sure the setting of header view works correctly
+ row.setIsMinimized(isMinimized)
+ row.setGroupHeader(/* headerView= */ result.inflatedGroupHeaderView)
+ remoteViewCache.putCachedView(
+ entry,
+ FLAG_GROUP_SUMMARY_HEADER,
+ result.remoteViews.normalGroupHeader
+ )
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_GROUP_SUMMARY_HEADER)) {
+ // Re-inflation case. Only update if it's still cached (i.e. view has not
+ // been freed while inflating).
+ remoteViewCache.putCachedView(
+ entry,
+ FLAG_GROUP_SUMMARY_HEADER,
+ result.remoteViews.normalGroupHeader
+ )
+ }
+ }
+ if (reInflateFlags and FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER != 0) {
+ if (result.inflatedMinimizedGroupHeaderView != null) {
+ // We need to set if the row is minimized before setting the group header to
+ // make sure the setting of header view works correctly
+ row.setIsMinimized(isMinimized)
+ row.setMinimizedGroupHeader(
+ /* headerView= */ result.inflatedMinimizedGroupHeaderView
+ )
+ remoteViewCache.putCachedView(
+ entry,
+ FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
+ result.remoteViews.minimizedGroupHeader
+ )
+ } else if (
+ remoteViewCache.hasCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)
+ ) {
+ // Re-inflation case. Only update if it's still cached (i.e. view has not
+ // been freed while inflating).
+ remoteViewCache.putCachedView(
+ entry,
+ FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
+ result.remoteViews.normalGroupHeader
+ )
+ }
+ }
+ }
+ entry.setContentModel(result.contentModel)
+ Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row))
+ endListener?.onAsyncInflationFinished(entry)
+ return true
+ }
+
+ private fun createExpandedView(
+ builder: Notification.Builder,
+ isMinimized: Boolean
+ ): RemoteViews? {
+ @Suppress("DEPRECATION")
+ val bigContentView: RemoteViews? = builder.createBigContentView()
+ if (bigContentView != null) {
+ return bigContentView
+ }
+ if (isMinimized) {
+ @Suppress("DEPRECATION") val contentView: RemoteViews = builder.createContentView()
+ Notification.Builder.makeHeaderExpanded(contentView)
+ return contentView
+ }
+ return null
+ }
+
+ private fun createContentView(
+ builder: Notification.Builder,
+ isMinimized: Boolean,
+ useLarge: Boolean
+ ): RemoteViews {
+ return if (isMinimized) {
+ builder.makeLowPriorityContentView(false /* useRegularSubtext */)
+ } else builder.createContentView(useLarge)
+ }
+
+ /**
+ * @param newView The new view that will be applied
+ * @param oldView The old view that was applied to the existing view before
+ * @return `true` if the RemoteViews are the same and the view can be reused to reapply.
+ */
+ @VisibleForTesting
+ fun canReapplyRemoteView(newView: RemoteViews?, oldView: RemoteViews?): Boolean {
+ return newView == null && oldView == null ||
+ newView != null &&
+ oldView != null &&
+ oldView.getPackage() != null &&
+ newView.getPackage() != null &&
+ newView.getPackage() == oldView.getPackage() &&
+ newView.layoutId == oldView.layoutId &&
+ !oldView.hasFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED)
+ }
+
+ private const val ASYNC_TASK_TRACE_METHOD =
+ "NotificationRowContentBinderImpl.AsyncInflationTask"
+ private const val APPLY_TRACE_METHOD = "NotificationRowContentBinderImpl#apply"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
index 17c20268bc1c..84f2f6670839 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -17,9 +17,13 @@
package com.android.systemui.statusbar.notification.row;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
import dagger.Binds;
import dagger.Module;
+import dagger.Provides;
+
+import javax.inject.Provider;
/**
* Dagger Module containing notification row and view inflation implementations.
@@ -30,10 +34,18 @@ public abstract class NotificationRowModule {
/**
* Provides notification row content binder instance.
*/
- @Binds
+ @Provides
@SysUISingleton
- public abstract NotificationRowContentBinder provideNotificationRowContentBinder(
- NotificationContentInflater contentBinderImpl);
+ public static NotificationRowContentBinder provideNotificationRowContentBinder(
+ Provider<NotificationContentInflater> legacyImpl,
+ Provider<NotificationRowContentBinderImpl> refactoredImpl
+ ) {
+ if (NotificationRowContentBinderRefactor.isEnabled()) {
+ return refactoredImpl.get();
+ } else {
+ return legacyImpl.get();
+ }
+ }
/**
* Provides notification remote view cache instance.
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/HeadsUpStatusBarModel.kt
index 23dbc26d40d3..e43ce76d6482 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/HeadsUpStatusBarModel.kt
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.recordissue
+package com.android.systemui.statusbar.notification.row.shared
-import com.android.traceur.TraceUtils.PresetTraceType
-
-data class IssueRecordingConfig(val screenRecord: Boolean, val traceType: PresetTraceType)
+class HeadsUpStatusBarModel(
+ val privateText: CharSequence,
+ val publicText: CharSequence,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NewRemoteViews.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NewRemoteViews.kt
new file mode 100644
index 000000000000..63bba86464b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NewRemoteViews.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.shared
+
+import android.widget.RemoteViews
+
+class NewRemoteViews(
+ val contracted: RemoteViews? = null,
+ val headsUp: RemoteViews? = null,
+ val expanded: RemoteViews? = null,
+ val public: RemoteViews? = null,
+ val normalGroupHeader: RemoteViews? = null,
+ val minimizedGroupHeader: RemoteViews? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt
new file mode 100644
index 000000000000..b2421bc72d00
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationContentModel.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.shared
+
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.SingleLineViewModel
+
+data class NotificationContentModel(
+ val headsUpStatusBarModel: HeadsUpStatusBarModel,
+ val singleLineViewModel: SingleLineViewModel? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 850162e65aa6..c18573ed1545 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -32,9 +32,13 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -49,6 +53,7 @@ constructor(
private val uiEventLogger: UiEventLogger,
) : SliderViewModel {
+ private val volumeChanges = MutableStateFlow<Int?>(null)
private val streamsAffectedByRing =
setOf(
AudioManager.STREAM_RING,
@@ -104,12 +109,17 @@ constructor(
}
.stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
+ init {
+ volumeChanges
+ .filterNotNull()
+ .onEach { audioVolumeInteractor.setVolume(audioStream, it) }
+ .launchIn(coroutineScope)
+ }
+
override fun onValueChanged(state: SliderState, newValue: Float) {
val audioViewModel = state as? State
audioViewModel ?: return
- coroutineScope.launch {
- audioVolumeInteractor.setVolume(audioStream, newValue.roundToInt())
- }
+ volumeChanges.tryEmit(newValue.roundToInt())
}
override fun onValueChangeFinished() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index df59e572bd3b..73548baad377 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -72,13 +72,13 @@ class RecordIssueTileTest : SysuiTestCase() {
@Mock private lateinit var dialogLauncherAnimator: DialogTransitionAnimator
@Mock private lateinit var panelInteractor: PanelInteractor
@Mock private lateinit var userContextProvider: UserContextProvider
+ @Mock private lateinit var issueRecordingState: IssueRecordingState
@Mock private lateinit var traceurMessageSender: TraceurMessageSender
@Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory
@Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate
@Mock private lateinit var dialog: SystemUIDialog
private lateinit var testableLooper: TestableLooper
- private val issueRecordingState = IssueRecordingState()
private lateinit var tile: RecordIssueTile
@Before
@@ -114,7 +114,7 @@ class RecordIssueTileTest : SysuiTestCase() {
@Test
fun qsTileUi_shouldLookCorrect_whenInactive() {
- issueRecordingState.isRecording = false
+ whenever(issueRecordingState.isRecording).thenReturn(false)
val testState = tile.newTileState()
tile.handleUpdateState(testState, null)
@@ -126,7 +126,7 @@ class RecordIssueTileTest : SysuiTestCase() {
@Test
fun qsTileUi_shouldLookCorrect_whenRecording() {
- issueRecordingState.isRecording = true
+ whenever(issueRecordingState.isRecording).thenReturn(true)
val testState = tile.newTileState()
tile.handleUpdateState(testState, null)
@@ -137,7 +137,7 @@ class RecordIssueTileTest : SysuiTestCase() {
@Test
fun inActiveQsTile_switchesToActive_whenClicked() {
- issueRecordingState.isRecording = false
+ whenever(issueRecordingState.isRecording).thenReturn(false)
val testState = tile.newTileState()
tile.handleUpdateState(testState, null)
@@ -147,7 +147,7 @@ class RecordIssueTileTest : SysuiTestCase() {
@Test
fun activeQsTile_switchesToInActive_whenClicked() {
- issueRecordingState.isRecording = true
+ whenever(issueRecordingState.isRecording).thenReturn(true)
val testState = tile.newTileState()
tile.handleUpdateState(testState, null)
@@ -157,7 +157,7 @@ class RecordIssueTileTest : SysuiTestCase() {
@Test
fun showPrompt_shouldUseKeyguardDismissUtil_ToShowDialog() {
- issueRecordingState.isRecording = false
+ whenever(issueRecordingState.isRecording).thenReturn(false)
tile.handleClick(null)
testableLooper.processAllMessages()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index ca606505af68..503c52f89b0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.recordissue
import android.app.Dialog
-import android.content.Context
import android.content.SharedPreferences
import android.os.UserHandle
import android.testing.TestableLooper
@@ -35,9 +34,8 @@ import com.android.systemui.mediaprojection.SessionCreationSource
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate
import com.android.systemui.model.SysUiState
-import com.android.systemui.qs.tiles.RecordIssueTile
+import com.android.systemui.recordissue.IssueRecordingState.Companion.ISSUE_TYPE_NOT_SET
import com.android.systemui.res.R
-import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -72,7 +70,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() {
@Mock private lateinit var dprLazy: dagger.Lazy<ScreenCaptureDevicePolicyResolver>
@Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger
@Mock private lateinit var userTracker: UserTracker
- @Mock private lateinit var userFileManager: UserFileManager
+ @Mock private lateinit var state: IssueRecordingState
@Mock private lateinit var sharedPreferences: SharedPreferences
@Mock
private lateinit var screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate
@@ -90,7 +88,6 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() {
private lateinit var dialog: SystemUIDialog
private lateinit var factory: SystemUIDialog.Factory
private lateinit var latch: CountDownLatch
- private var issueRecordingState = IssueRecordingState()
@Before
fun setup() {
@@ -99,14 +96,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() {
whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState)
whenever(screenCaptureDisabledDialogDelegate.createSysUIDialog())
.thenReturn(screenCaptureDisabledDialog)
- whenever(
- userFileManager.getSharedPreferences(
- eq(RecordIssueTile.TILE_SPEC),
- eq(Context.MODE_PRIVATE),
- anyInt()
- )
- )
- .thenReturn(sharedPreferences)
+ whenever(state.issueTypeRes).thenReturn(ISSUE_TYPE_NOT_SET)
factory =
spy(
@@ -129,9 +119,8 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() {
mainExecutor,
dprLazy,
mediaProjectionMetricsLogger,
- userFileManager,
screenCaptureDisabledDialogDelegate,
- issueRecordingState,
+ state,
traceurMessageSender
) {
latch.countDown()
@@ -190,8 +179,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() {
whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
.thenReturn(false)
whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
- whenever(sharedPreferences.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false))
- .thenReturn(false)
+ whenever(state.hasUserApprovedScreenRecording).thenReturn(false)
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
screenRecordSwitch.isChecked = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
new file mode 100644
index 000000000000..4945ace3b88c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.screenshot.policy
+
+import android.content.ComponentName
+import android.graphics.Insets
+import android.graphics.Rect
+import android.os.UserHandle
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
+import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN
+import com.android.systemui.screenshot.ImageCapture
+import com.android.systemui.screenshot.ScreenshotData
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.TaskSpec
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.singleFullScreen
+import com.android.systemui.screenshot.data.repository.DisplayContentRepository
+import com.android.systemui.screenshot.policy.TestUserIds.PERSONAL
+import com.android.systemui.screenshot.policy.TestUserIds.WORK
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+
+class PolicyRequestProcessorTest {
+
+ val imageCapture = object : ImageCapture {
+ override fun captureDisplay(displayId: Int, crop: Rect?) = null
+ override suspend fun captureTask(taskId: Int) = null
+ }
+
+ /** Tests behavior when no policies are applied */
+ @Test
+ fun testProcess_defaultOwner_whenNoPolicyApplied() {
+ val fullScreenWork = DisplayContentRepository {
+ singleFullScreen(TaskSpec(taskId = 1001, name = FILES, userId = WORK))
+ }
+
+ val request =
+ ScreenshotData(TAKE_SCREENSHOT_FULLSCREEN,
+ SCREENSHOT_KEY_CHORD,
+ null,
+ topComponent = null,
+ screenBounds = Rect(0, 0, 1, 1),
+ taskId = -1,
+ insets = Insets.NONE,
+ bitmap = null,
+ displayId = DEFAULT_DISPLAY)
+
+ /* Create a policy request processor with no capture policies */
+ val requestProcessor =
+ PolicyRequestProcessor(Dispatchers.Unconfined,
+ imageCapture,
+ policies = emptyList(),
+ defaultOwner = UserHandle.of(PERSONAL),
+ defaultComponent = ComponentName("default", "Component"),
+ displayTasks = fullScreenWork)
+
+ val result = runBlocking { requestProcessor.process(request) }
+
+ assertWithMessage(
+ "With no policy, the screenshot should be assigned to the default user"
+ ).that(result.userHandle).isEqualTo(UserHandle.of(PERSONAL))
+
+ assertWithMessage("The topComponent of the screenshot").that(result.topComponent)
+ .isEqualTo(ComponentName.unflattenFromString(FILES))
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 0d3ab865669e..b278f1a48b3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -41,6 +41,7 @@ import android.os.AsyncTask;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
+import android.platform.test.annotations.DisableFlags;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.util.TypedValue;
@@ -60,6 +61,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
import com.android.systemui.statusbar.policy.SmartReplyStateInflater;
@@ -81,6 +83,7 @@ import java.util.concurrent.TimeUnit;
@SmallTest
@RunWith(AndroidJUnit4.class)
@RunWithLooper(setAsMainLooper = true)
+@DisableFlags(NotificationRowContentBinderRefactor.FLAG_NAME)
public class NotificationContentInflaterTest extends SysuiTestCase {
private NotificationContentInflater mNotificationInflater;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
new file mode 100644
index 000000000000..e6cba1c39c85
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -0,0 +1,572 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar.notification.row
+
+import android.app.Notification
+import android.content.Context
+import android.os.AsyncTask
+import android.os.Build
+import android.os.CancellationSignal
+import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper.RunWithLooper
+import android.util.TypedValue
+import android.view.View
+import android.view.ViewGroup
+import android.widget.RemoteViews
+import android.widget.TextView
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
+import com.android.systemui.statusbar.notification.row.shared.HeadsUpStatusBarModel
+import com.android.systemui.statusbar.notification.row.shared.NewRemoteViews
+import com.android.systemui.statusbar.notification.row.shared.NotificationContentModel
+import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor
+import com.android.systemui.statusbar.policy.InflatedSmartReplyState
+import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder
+import com.android.systemui.statusbar.policy.SmartReplyStateInflater
+import com.android.systemui.util.concurrency.mockExecutorHandler
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+@EnableFlags(NotificationRowContentBinderRefactor.FLAG_NAME)
+class NotificationRowContentBinderImplTest : SysuiTestCase() {
+ private lateinit var mNotificationInflater: NotificationRowContentBinderImpl
+ private lateinit var mBuilder: Notification.Builder
+ private lateinit var mRow: ExpandableNotificationRow
+ private lateinit var mHelper: NotificationTestHelper
+
+ private var mCache: NotifRemoteViewCache = mock()
+ private var mConversationNotificationProcessor: ConversationNotificationProcessor = mock()
+ private var mInflatedSmartReplyState: InflatedSmartReplyState = mock()
+ private var mInflatedSmartReplies: InflatedSmartReplyViewHolder = mock()
+ private var mNotifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider = mock()
+ private var mHeadsUpStyleProvider: HeadsUpStyleProvider = mock()
+ private var mNotifLayoutInflaterFactory: NotifLayoutInflaterFactory = mock()
+ private val mSmartReplyStateInflater: SmartReplyStateInflater =
+ object : SmartReplyStateInflater {
+ override fun inflateSmartReplyViewHolder(
+ sysuiContext: Context,
+ notifPackageContext: Context,
+ entry: NotificationEntry,
+ existingSmartReplyState: InflatedSmartReplyState?,
+ newSmartReplyState: InflatedSmartReplyState
+ ): InflatedSmartReplyViewHolder {
+ return mInflatedSmartReplies
+ }
+
+ override fun inflateSmartReplyState(entry: NotificationEntry): InflatedSmartReplyState {
+ return mInflatedSmartReplyState
+ }
+ }
+
+ @Before
+ fun setUp() {
+ allowTestableLooperAsMainThread()
+ mBuilder =
+ Notification.Builder(mContext, "no-id")
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentTitle("Title")
+ .setContentText("Text")
+ .setStyle(Notification.BigTextStyle().bigText("big text"))
+ mHelper = NotificationTestHelper(mContext, mDependency)
+ val row = mHelper.createRow(mBuilder.build())
+ mRow = spy(row)
+ whenever(mNotifLayoutInflaterFactoryProvider.provide(any(), any()))
+ .thenReturn(mNotifLayoutInflaterFactory)
+ mNotificationInflater =
+ NotificationRowContentBinderImpl(
+ mCache,
+ mock(),
+ mConversationNotificationProcessor,
+ mock(),
+ mSmartReplyStateInflater,
+ mNotifLayoutInflaterFactoryProvider,
+ mHeadsUpStyleProvider,
+ mock()
+ )
+ }
+
+ @Test
+ fun testIncreasedHeadsUpBeingUsed() {
+ val params = BindParams()
+ params.usesIncreasedHeadsUpHeight = true
+ val builder = spy(mBuilder)
+ mNotificationInflater.inflateNotificationViews(
+ mRow.entry,
+ mRow,
+ params,
+ true /* inflateSynchronously */,
+ NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL,
+ builder,
+ mContext,
+ mSmartReplyStateInflater
+ )
+ verify(builder).createHeadsUpContentView(true)
+ }
+
+ @Test
+ fun testIncreasedHeightBeingUsed() {
+ val params = BindParams()
+ params.usesIncreasedHeight = true
+ val builder = spy(mBuilder)
+ mNotificationInflater.inflateNotificationViews(
+ mRow.entry,
+ mRow,
+ params,
+ true /* inflateSynchronously */,
+ NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL,
+ builder,
+ mContext,
+ mSmartReplyStateInflater
+ )
+ verify(builder).createContentView(true)
+ }
+
+ @Test
+ fun testInflationCallsUpdated() {
+ inflateAndWait(
+ mNotificationInflater,
+ NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL,
+ mRow
+ )
+ verify(mRow).onNotificationUpdated()
+ }
+
+ @Test
+ fun testInflationOnlyInflatesSetFlags() {
+ inflateAndWait(
+ mNotificationInflater,
+ NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP,
+ mRow
+ )
+ Assert.assertNotNull(mRow.privateLayout.headsUpChild)
+ verify(mRow).onNotificationUpdated()
+ }
+
+ @Test
+ fun testInflationThrowsErrorDoesntCallUpdated() {
+ mRow.privateLayout.removeAllViews()
+ mRow.entry.sbn.notification.contentView =
+ RemoteViews(mContext.packageName, R.layout.status_bar)
+ inflateAndWait(
+ true /* expectingException */,
+ mNotificationInflater,
+ NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL,
+ mRow
+ )
+ Assert.assertTrue(mRow.privateLayout.childCount == 0)
+ verify(mRow, times(0)).onNotificationUpdated()
+ }
+
+ @Test
+ fun testAsyncTaskRemoved() {
+ mRow.entry.abortTask()
+ inflateAndWait(
+ mNotificationInflater,
+ NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL,
+ mRow
+ )
+ verify(mRow).onNotificationUpdated()
+ }
+
+ @Test
+ fun testRemovedNotInflated() {
+ mRow.setRemoved()
+ mNotificationInflater.setInflateSynchronously(true)
+ mNotificationInflater.bindContent(
+ mRow.entry,
+ mRow,
+ NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL,
+ BindParams(),
+ false /* forceInflate */,
+ null /* callback */
+ )
+ Assert.assertNull(mRow.entry.runningTask)
+ }
+
+ @Test
+ @Ignore("b/345418902")
+ fun testInflationIsRetriedIfAsyncFails() {
+ val headsUpStatusBarModel = HeadsUpStatusBarModel("private", "public")
+ val result =
+ NotificationRowContentBinderImpl.InflationProgress(
+ packageContext = mContext,
+ remoteViews = NewRemoteViews(),
+ contentModel = NotificationContentModel(headsUpStatusBarModel)
+ )
+ val countDownLatch = CountDownLatch(1)
+ NotificationRowContentBinderImpl.applyRemoteView(
+ AsyncTask.SERIAL_EXECUTOR,
+ inflateSynchronously = false,
+ isMinimized = false,
+ result = result,
+ reInflateFlags = NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED,
+ inflationId = 0,
+ remoteViewCache = mock(),
+ entry = mRow.entry,
+ row = mRow,
+ isNewView = true, /* isNewView */
+ remoteViewClickHandler = { _, _, _ -> true },
+ callback =
+ object : InflationCallback {
+ override fun handleInflationException(entry: NotificationEntry, e: Exception) {
+ countDownLatch.countDown()
+ throw RuntimeException("No Exception expected")
+ }
+
+ override fun onAsyncInflationFinished(entry: NotificationEntry) {
+ countDownLatch.countDown()
+ }
+ },
+ parentLayout = mRow.privateLayout,
+ existingView = null,
+ existingWrapper = null,
+ runningInflations = HashMap(),
+ applyCallback =
+ object : NotificationRowContentBinderImpl.ApplyCallback() {
+ override fun setResultView(v: View) {}
+
+ override val remoteView: RemoteViews
+ get() =
+ AsyncFailRemoteView(
+ mContext.packageName,
+ com.android.systemui.tests.R.layout.custom_view_dark
+ )
+ },
+ logger = mock(),
+ )
+ Assert.assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS))
+ }
+
+ @Test
+ fun doesntReapplyDisallowedRemoteView() {
+ mBuilder.setStyle(Notification.MediaStyle())
+ val mediaView = mBuilder.createContentView()
+ mBuilder.setStyle(Notification.DecoratedCustomViewStyle())
+ mBuilder.setCustomContentView(
+ RemoteViews(context.packageName, com.android.systemui.tests.R.layout.custom_view_dark)
+ )
+ val decoratedMediaView = mBuilder.createContentView()
+ Assert.assertFalse(
+ "The decorated media style doesn't allow a view to be reapplied!",
+ NotificationRowContentBinderImpl.canReapplyRemoteView(mediaView, decoratedMediaView)
+ )
+ }
+
+ @Test
+ @Ignore("b/345418902")
+ fun testUsesSameViewWhenCachedPossibleToReuse() {
+ // GIVEN a cached view.
+ val contractedRemoteView = mBuilder.createContentView()
+ whenever(
+ mCache.hasCachedView(
+ mRow.entry,
+ NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
+ )
+ )
+ .thenReturn(true)
+ whenever(
+ mCache.getCachedView(
+ mRow.entry,
+ NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
+ )
+ )
+ .thenReturn(contractedRemoteView)
+
+ // GIVEN existing bound view with same layout id.
+ val view = contractedRemoteView.apply(mContext, null /* parent */)
+ mRow.privateLayout.setContractedChild(view)
+
+ // WHEN inflater inflates
+ inflateAndWait(
+ mNotificationInflater,
+ NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED,
+ mRow
+ )
+
+ // THEN the view should be re-used
+ Assert.assertEquals(
+ "Binder inflated a new view even though the old one was cached and usable.",
+ view,
+ mRow.privateLayout.contractedChild
+ )
+ }
+
+ @Test
+ fun testInflatesNewViewWhenCachedNotPossibleToReuse() {
+ // GIVEN a cached remote view.
+ val contractedRemoteView = mBuilder.createHeadsUpContentView()
+ whenever(
+ mCache.hasCachedView(
+ mRow.entry,
+ NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
+ )
+ )
+ .thenReturn(true)
+ whenever(
+ mCache.getCachedView(
+ mRow.entry,
+ NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
+ )
+ )
+ .thenReturn(contractedRemoteView)
+
+ // GIVEN existing bound view with different layout id.
+ val view: View = TextView(mContext)
+ mRow.privateLayout.setContractedChild(view)
+
+ // WHEN inflater inflates
+ inflateAndWait(
+ mNotificationInflater,
+ NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED,
+ mRow
+ )
+
+ // THEN the view should be a new view
+ Assert.assertNotEquals(
+ "Binder (somehow) used the same view when inflating.",
+ view,
+ mRow.privateLayout.contractedChild
+ )
+ }
+
+ @Test
+ fun testInflationCachesCreatedRemoteView() {
+ // WHEN inflater inflates
+ inflateAndWait(
+ mNotificationInflater,
+ NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED,
+ mRow
+ )
+
+ // THEN inflater informs cache of the new remote view
+ verify(mCache)
+ .putCachedView(
+ eq(mRow.entry),
+ eq(NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED),
+ any()
+ )
+ }
+
+ @Test
+ fun testUnbindRemovesCachedRemoteView() {
+ // WHEN inflated unbinds content
+ mNotificationInflater.unbindContent(
+ mRow.entry,
+ mRow,
+ NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP
+ )
+
+ // THEN inflated informs cache to remove remote view
+ verify(mCache)
+ .removeCachedView(
+ eq(mRow.entry),
+ eq(NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP)
+ )
+ }
+
+ @Test
+ fun testNotificationViewHeightTooSmallFailsValidation() {
+ val validationError =
+ getValidationError(
+ measuredHeightDp = 5f,
+ targetSdk = Build.VERSION_CODES.R,
+ contentView = mock(),
+ )
+ Assert.assertNotNull(validationError)
+ }
+
+ @Test
+ fun testNotificationViewHeightPassesForNewerSDK() {
+ val validationError =
+ getValidationError(
+ measuredHeightDp = 5f,
+ targetSdk = Build.VERSION_CODES.S,
+ contentView = mock(),
+ )
+ Assert.assertNull(validationError)
+ }
+
+ @Test
+ fun testNotificationViewHeightPassesForTemplatedViews() {
+ val validationError =
+ getValidationError(
+ measuredHeightDp = 5f,
+ targetSdk = Build.VERSION_CODES.R,
+ contentView = null,
+ )
+ Assert.assertNull(validationError)
+ }
+
+ @Test
+ fun testNotificationViewPassesValidation() {
+ val validationError =
+ getValidationError(
+ measuredHeightDp = 20f,
+ targetSdk = Build.VERSION_CODES.R,
+ contentView = mock(),
+ )
+ Assert.assertNull(validationError)
+ }
+
+ private fun getValidationError(
+ measuredHeightDp: Float,
+ targetSdk: Int,
+ contentView: RemoteViews?
+ ): String? {
+ val view: View = mock()
+ whenever(view.measuredHeight)
+ .thenReturn(
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_SP,
+ measuredHeightDp,
+ mContext.resources.displayMetrics
+ )
+ .toInt()
+ )
+ mRow.entry.targetSdk = targetSdk
+ mRow.entry.sbn.notification.contentView = contentView
+ return NotificationRowContentBinderImpl.isValidView(view, mRow.entry, mContext.resources)
+ }
+
+ @Test
+ fun testInvalidNotificationDoesNotInvokeCallback() {
+ mRow.privateLayout.removeAllViews()
+ mRow.entry.sbn.notification.contentView =
+ RemoteViews(
+ mContext.packageName,
+ com.android.systemui.tests.R.layout.invalid_notification_height
+ )
+ inflateAndWait(
+ true,
+ mNotificationInflater,
+ NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL,
+ mRow
+ )
+ Assert.assertEquals(0, mRow.privateLayout.childCount.toLong())
+ verify(mRow, times(0)).onNotificationUpdated()
+ }
+
+ private class ExceptionHolder {
+ var mException: Exception? = null
+
+ fun setException(exception: Exception?) {
+ mException = exception
+ }
+ }
+
+ private class AsyncFailRemoteView(packageName: String?, layoutId: Int) :
+ RemoteViews(packageName, layoutId) {
+ var mHandler = mockExecutorHandler { p0 -> p0.run() }
+
+ override fun apply(context: Context, parent: ViewGroup): View {
+ return super.apply(context, parent)
+ }
+
+ override fun applyAsync(
+ context: Context,
+ parent: ViewGroup,
+ executor: Executor,
+ listener: OnViewAppliedListener,
+ handler: InteractionHandler?
+ ): CancellationSignal {
+ mHandler.post { listener.onError(RuntimeException("Failed to inflate async")) }
+ return CancellationSignal()
+ }
+
+ override fun applyAsync(
+ context: Context,
+ parent: ViewGroup,
+ executor: Executor,
+ listener: OnViewAppliedListener
+ ): CancellationSignal {
+ return applyAsync(context, parent, executor, listener, null)
+ }
+ }
+
+ companion object {
+ private fun inflateAndWait(
+ inflater: NotificationRowContentBinderImpl,
+ @InflationFlag contentToInflate: Int,
+ row: ExpandableNotificationRow
+ ) {
+ inflateAndWait(false /* expectingException */, inflater, contentToInflate, row)
+ }
+
+ private fun inflateAndWait(
+ expectingException: Boolean,
+ inflater: NotificationRowContentBinderImpl,
+ @InflationFlag contentToInflate: Int,
+ row: ExpandableNotificationRow,
+ ) {
+ val countDownLatch = CountDownLatch(1)
+ val exceptionHolder = ExceptionHolder()
+ inflater.setInflateSynchronously(true)
+ val callback: InflationCallback =
+ object : InflationCallback {
+ override fun handleInflationException(entry: NotificationEntry, e: Exception) {
+ if (!expectingException) {
+ exceptionHolder.setException(e)
+ }
+ countDownLatch.countDown()
+ }
+
+ override fun onAsyncInflationFinished(entry: NotificationEntry) {
+ if (expectingException) {
+ exceptionHolder.setException(
+ RuntimeException(
+ "Inflation finished even though there should be an error"
+ )
+ )
+ }
+ countDownLatch.countDown()
+ }
+ }
+ inflater.bindContent(
+ row.entry,
+ row,
+ contentToInflate,
+ BindParams(),
+ false /* forceInflate */,
+ callback /* callback */
+ )
+ Assert.assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS))
+ exceptionHolder.mException?.let { throw it }
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 65941adf280e..21d586b1b5fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -84,6 +84,7 @@ import com.android.systemui.statusbar.notification.people.PeopleNotificationIden
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -192,16 +193,27 @@ public class NotificationTestHelper {
mBgCoroutineContext,
mMainCoroutineContext);
- NotificationRowContentBinder contentBinder = new NotificationContentInflater(
- mock(NotifRemoteViewCache.class),
- mock(NotificationRemoteInputManager.class),
- mock(ConversationNotificationProcessor.class),
- mock(MediaFeatureFlag.class),
- mock(Executor.class),
- new MockSmartReplyInflater(),
- mock(NotifLayoutInflaterFactory.Provider.class),
- mock(HeadsUpStyleProvider.class),
- mock(NotificationRowContentBinderLogger.class));
+ NotificationRowContentBinder contentBinder =
+ NotificationRowContentBinderRefactor.isEnabled()
+ ? new NotificationRowContentBinderImpl(
+ mock(NotifRemoteViewCache.class),
+ mock(NotificationRemoteInputManager.class),
+ mock(ConversationNotificationProcessor.class),
+ mock(Executor.class),
+ new MockSmartReplyInflater(),
+ mock(NotifLayoutInflaterFactory.Provider.class),
+ mock(HeadsUpStyleProvider.class),
+ mock(NotificationRowContentBinderLogger.class))
+ : new NotificationContentInflater(
+ mock(NotifRemoteViewCache.class),
+ mock(NotificationRemoteInputManager.class),
+ mock(ConversationNotificationProcessor.class),
+ mock(MediaFeatureFlag.class),
+ mock(Executor.class),
+ new MockSmartReplyInflater(),
+ mock(NotifLayoutInflaterFactory.Provider.class),
+ mock(HeadsUpStyleProvider.class),
+ mock(NotificationRowContentBinderLogger.class));
contentBinder.setInflateSynchronously(true);
mBindStage = new RowContentBindStage(contentBinder,
mock(NotifInflationErrorManager.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 0f89dcc42c82..fabb9b770a11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -169,6 +169,7 @@ import com.android.wm.shell.bubbles.BubbleViewInfoTask;
import com.android.wm.shell.bubbles.BubbleViewProvider;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.bubbles.StackEducationView;
+import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import com.android.wm.shell.bubbles.properties.BubbleProperties;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -2109,6 +2110,33 @@ public class BubblesTest extends SysuiTestCase {
}
@Test
+ public void registerBubbleBarListener_switchToBarWhileExpanded() {
+ mBubbleProperties.mIsBubbleBarEnabled = true;
+ mPositioner.setIsLargeScreen(true);
+
+ mEntryListener.onEntryAdded(mRow);
+ mBubbleController.updateBubble(mBubbleEntry);
+ BubbleStackView stackView = mBubbleController.getStackView();
+ spyOn(stackView);
+
+ mBubbleData.setExpanded(true);
+
+ assertStackMode();
+ assertThat(mBubbleData.isExpanded()).isTrue();
+ assertThat(stackView.isExpanded()).isTrue();
+
+ FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+ mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+ BubbleBarLayerView layerView = mBubbleController.getLayerView();
+ spyOn(layerView);
+
+ assertBarMode();
+ assertThat(mBubbleData.isExpanded()).isTrue();
+ assertThat(layerView.isExpanded()).isTrue();
+ }
+
+ @Test
public void switchBetweenBarAndStack_noBubbles_shouldBeIgnored() {
mBubbleProperties.mIsBubbleBarEnabled = false;
mPositioner.setIsLargeScreen(true);
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 26aa0535d43e..95c0e0ee5272 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -169,7 +169,6 @@ final class ActivityManagerConstants extends ContentObserver {
*/
static final String KEY_ENABLE_NEW_OOMADJ = "enable_new_oom_adj";
- private static final int DEFAULT_MAX_CACHED_PROCESSES = 1024;
private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true;
private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000;
@@ -294,12 +293,7 @@ final class ActivityManagerConstants extends ContentObserver {
private static final long DEFAULT_SERVICE_BACKGROUND_TIMEOUT = DEFAULT_SERVICE_TIMEOUT * 10;
/**
- * Maximum number of cached processes.
- */
- private static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
-
- /**
- * Maximum number of cached processes.
+ * Maximum number of phantom processes.
*/
private static final String KEY_MAX_PHANTOM_PROCESSES = "max_phantom_processes";
@@ -446,9 +440,6 @@ final class ActivityManagerConstants extends ContentObserver {
volatile int mProcStateDebugSetProcStateDelay = 0;
volatile int mProcStateDebugSetUidStateDelay = 0;
- // Maximum number of cached processes we will allow.
- public int MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
-
// This is the amount of time we allow an app to settle after it goes into the background,
// before we start restricting what it can do.
public long BACKGROUND_SETTLE_TIME = DEFAULT_BACKGROUND_SETTLE_TIME;
@@ -857,24 +848,6 @@ final class ActivityManagerConstants extends ContentObserver {
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
- private int mOverrideMaxCachedProcesses = -1;
- private final int mCustomizedMaxCachedProcesses;
-
- // The maximum number of cached processes we will keep around before killing them.
- // NOTE: this constant is *only* a control to not let us go too crazy with
- // keeping around processes on devices with large amounts of RAM. For devices that
- // are tighter on RAM, the out of memory killer is responsible for killing background
- // processes as RAM is needed, and we should *never* be relying on this limit to
- // kill them. Also note that this limit only applies to cached background processes;
- // we have no limit on the number of service, visible, foreground, or other such
- // processes and the number of those processes does not count against the cached
- // process limit. This will be initialized in the constructor.
- public int CUR_MAX_CACHED_PROCESSES;
-
- // The maximum number of empty app processes we will let sit around. This will be
- // initialized in the constructor.
- public int CUR_MAX_EMPTY_PROCESSES;
-
/** @see #mNoKillCachedProcessesUntilBootCompleted */
private static final String KEY_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED =
"no_kill_cached_processes_until_boot_completed";
@@ -906,15 +879,6 @@ final class ActivityManagerConstants extends ContentObserver {
volatile long mNoKillCachedProcessesPostBootCompletedDurationMillis =
DEFAULT_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS;
- // The number of empty apps at which we don't consider it necessary to do
- // memory trimming.
- public int CUR_TRIM_EMPTY_PROCESSES = computeEmptyProcessLimit(MAX_CACHED_PROCESSES) / 2;
-
- // The number of cached at which we don't consider it necessary to do
- // memory trimming.
- public int CUR_TRIM_CACHED_PROCESSES =
- (MAX_CACHED_PROCESSES - computeEmptyProcessLimit(MAX_CACHED_PROCESSES)) / 3;
-
/** @see #mNoKillCachedProcessesUntilBootCompleted */
private static final String KEY_MAX_EMPTY_TIME_MILLIS =
"max_empty_time_millis";
@@ -1165,9 +1129,6 @@ final class ActivityManagerConstants extends ContentObserver {
return;
}
switch (name) {
- case KEY_MAX_CACHED_PROCESSES:
- updateMaxCachedProcesses();
- break;
case KEY_DEFAULT_BACKGROUND_ACTIVITY_STARTS_ENABLED:
updateBackgroundActivityStarts();
break;
@@ -1417,16 +1378,7 @@ final class ActivityManagerConstants extends ContentObserver {
context.getResources().getStringArray(
com.android.internal.R.array.config_keep_warming_services))
.map(ComponentName::unflattenFromString).collect(Collectors.toSet()));
- mCustomizedMaxCachedProcesses = context.getResources().getInteger(
- com.android.internal.R.integer.config_customizedMaxCachedProcesses);
- CUR_MAX_CACHED_PROCESSES = mCustomizedMaxCachedProcesses;
- CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES);
-
- final int rawMaxEmptyProcesses = computeEmptyProcessLimit(
- Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES));
- CUR_TRIM_EMPTY_PROCESSES = rawMaxEmptyProcesses / 2;
- CUR_TRIM_CACHED_PROCESSES = (Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES)
- - rawMaxEmptyProcesses) / 3;
+
loadNativeBootDeviceConfigConstants();
mDefaultDisableAppProfilerPssProfiling = context.getResources().getBoolean(
R.bool.config_am_disablePssProfiling);
@@ -1481,19 +1433,6 @@ final class ActivityManagerConstants extends ContentObserver {
DEFAULT_ENABLE_NEW_OOM_ADJ);
}
- public void setOverrideMaxCachedProcesses(int value) {
- mOverrideMaxCachedProcesses = value;
- updateMaxCachedProcesses();
- }
-
- public int getOverrideMaxCachedProcesses() {
- return mOverrideMaxCachedProcesses;
- }
-
- public static int computeEmptyProcessLimit(int totalProcessLimit) {
- return totalProcessLimit/2;
- }
-
@Override
public void onChange(boolean selfChange, Uri uri) {
if (uri == null) return;
@@ -1994,29 +1933,6 @@ final class ActivityManagerConstants extends ContentObserver {
mSystemServerAutomaticHeapDumpPackageName);
}
- private void updateMaxCachedProcesses() {
- String maxCachedProcessesFlag = DeviceConfig.getProperty(
- DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MAX_CACHED_PROCESSES);
- try {
- CUR_MAX_CACHED_PROCESSES = mOverrideMaxCachedProcesses < 0
- ? (TextUtils.isEmpty(maxCachedProcessesFlag)
- ? mCustomizedMaxCachedProcesses : Integer.parseInt(maxCachedProcessesFlag))
- : mOverrideMaxCachedProcesses;
- } catch (NumberFormatException e) {
- // Bad flag value from Phenotype, revert to default.
- Slog.e(TAG,
- "Unable to parse flag for max_cached_processes: " + maxCachedProcessesFlag, e);
- CUR_MAX_CACHED_PROCESSES = mCustomizedMaxCachedProcesses;
- }
- CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES);
-
- final int rawMaxEmptyProcesses = computeEmptyProcessLimit(
- Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES));
- CUR_TRIM_EMPTY_PROCESSES = rawMaxEmptyProcesses / 2;
- CUR_TRIM_CACHED_PROCESSES = (Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES)
- - rawMaxEmptyProcesses) / 3;
- }
-
private void updateProactiveKillsEnabled() {
PROACTIVE_KILLS_ENABLED = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -2275,8 +2191,6 @@ final class ActivityManagerConstants extends ContentObserver {
pw.println("ACTIVITY MANAGER SETTINGS (dumpsys activity settings) "
+ Settings.Global.ACTIVITY_MANAGER_CONSTANTS + ":");
- pw.print(" "); pw.print(KEY_MAX_CACHED_PROCESSES); pw.print("=");
- pw.println(MAX_CACHED_PROCESSES);
pw.print(" "); pw.print(KEY_BACKGROUND_SETTLE_TIME); pw.print("=");
pw.println(BACKGROUND_SETTLE_TIME);
pw.print(" "); pw.print(KEY_FGSERVICE_MIN_SHOWN_TIME); pw.print("=");
@@ -2477,14 +2391,6 @@ final class ActivityManagerConstants extends ContentObserver {
pw.print("="); pw.println(MAX_PREVIOUS_TIME);
pw.println();
- if (mOverrideMaxCachedProcesses >= 0) {
- pw.print(" mOverrideMaxCachedProcesses="); pw.println(mOverrideMaxCachedProcesses);
- }
- pw.print(" mCustomizedMaxCachedProcesses="); pw.println(mCustomizedMaxCachedProcesses);
- pw.print(" CUR_MAX_CACHED_PROCESSES="); pw.println(CUR_MAX_CACHED_PROCESSES);
- pw.print(" CUR_MAX_EMPTY_PROCESSES="); pw.println(CUR_MAX_EMPTY_PROCESSES);
- pw.print(" CUR_TRIM_EMPTY_PROCESSES="); pw.println(CUR_TRIM_EMPTY_PROCESSES);
- pw.print(" CUR_TRIM_CACHED_PROCESSES="); pw.println(CUR_TRIM_CACHED_PROCESSES);
pw.print(" OOMADJ_UPDATE_QUICK="); pw.println(OOMADJ_UPDATE_QUICK);
pw.print(" ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION=");
pw.println(mEnableWaitForFinishAttachApplication);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2e08f555bc46..e0ec17124318 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5846,19 +5846,13 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void setProcessLimit(int max) {
- enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT,
- "setProcessLimit()");
- synchronized (this) {
- mConstants.setOverrideMaxCachedProcesses(max);
- trimApplicationsLocked(true, OOM_ADJ_REASON_PROCESS_END);
- }
+ // Process limits are deprecated since b/253908413
}
@Override
public int getProcessLimit() {
- synchronized (this) {
- return mConstants.getOverrideMaxCachedProcesses();
- }
+ // Process limits are deprecated since b/253908413
+ return Integer.MAX_VALUE;
}
void importanceTokenDied(ImportanceToken token) {
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index dda48adbf732..3ed60fcae5e4 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -507,8 +507,7 @@ public class AppProfiler {
final int lruSize = mService.mProcessList.getLruSizeLOSP();
if (mCachedAppFrozenDurations == null
|| mCachedAppFrozenDurations.length < lruSize) {
- mCachedAppFrozenDurations = new long[Math.max(
- lruSize, mService.mConstants.CUR_MAX_CACHED_PROCESSES)];
+ mCachedAppFrozenDurations = new long[lruSize];
}
mService.mProcessList.forEachLruProcessesLOSP(true, app -> {
if (app.mOptRecord.isFrozen()) {
@@ -1370,18 +1369,13 @@ public class AppProfiler {
// are managing to keep around is less than half the maximum we desire;
// if we are keeping a good number around, we'll let them use whatever
// memory they want.
- if (numCached <= mService.mConstants.CUR_TRIM_CACHED_PROCESSES
- && numEmpty <= mService.mConstants.CUR_TRIM_EMPTY_PROCESSES) {
- final int numCachedAndEmpty = numCached + numEmpty;
- if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {
- memFactor = ADJ_MEM_FACTOR_CRITICAL;
- } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {
- memFactor = ADJ_MEM_FACTOR_LOW;
- } else {
- memFactor = ADJ_MEM_FACTOR_MODERATE;
- }
+ final int numCachedAndEmpty = numCached + numEmpty;
+ if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {
+ memFactor = ADJ_MEM_FACTOR_CRITICAL;
+ } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {
+ memFactor = ADJ_MEM_FACTOR_LOW;
} else {
- memFactor = ADJ_MEM_FACTOR_NORMAL;
+ memFactor = ADJ_MEM_FACTOR_MODERATE;
}
}
// We always allow the memory level to go up (better). We only allow it to go
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index ab34dd4477fd..105e201add52 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -470,7 +470,7 @@ public class OomAdjuster {
return true;
});
mTmpUidRecords = new ActiveUids(service, false);
- mTmpQueue = new ArrayDeque<ProcessRecord>(mConstants.CUR_MAX_CACHED_PROCESSES << 1);
+ mTmpQueue = new ArrayDeque<ProcessRecord>();
mNumSlots = ((CACHED_APP_MAX_ADJ - CACHED_APP_MIN_ADJ + 1) >> 1)
/ CACHED_APP_IMPORTANCE_LEVELS;
}
@@ -1079,23 +1079,11 @@ public class OomAdjuster {
int curEmptyAdj = CACHED_APP_MIN_ADJ + CACHED_APP_IMPORTANCE_LEVELS;
int nextEmptyAdj = curEmptyAdj + (CACHED_APP_IMPORTANCE_LEVELS * 2);
- final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
- final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES
- - emptyProcessLimit;
// Let's determine how many processes we have running vs.
// how many slots we have for background processes; we may want
// to put multiple processes in a slot of there are enough of
// them.
int numEmptyProcs = numLru - mNumNonCachedProcs - mNumCachedHiddenProcs;
- if (numEmptyProcs > cachedProcessLimit) {
- // If there are more empty processes than our limit on cached
- // processes, then use the cached process limit for the factor.
- // This ensures that the really old empty processes get pushed
- // down to the bottom, so if we are running low on memory we will
- // have a better chance at keeping around more cached processes
- // instead of a gazillion empty processes.
- numEmptyProcs = cachedProcessLimit;
- }
int cachedFactor = (mNumCachedHiddenProcs > 0
? (mNumCachedHiddenProcs + mNumSlots - 1) : 1)
/ mNumSlots;
@@ -1217,17 +1205,6 @@ public class OomAdjuster {
ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
final int numLru = lruList.size();
- final boolean doKillExcessiveProcesses = shouldKillExcessiveProcesses(now);
- if (!doKillExcessiveProcesses) {
- if (mNextNoKillDebugMessageTime < now) {
- Slog.d(TAG, "Not killing cached processes"); // STOPSHIP Remove it b/222365734
- mNextNoKillDebugMessageTime = now + 5000; // Every 5 seconds
- }
- }
- final int emptyProcessLimit = doKillExcessiveProcesses
- ? mConstants.CUR_MAX_EMPTY_PROCESSES : Integer.MAX_VALUE;
- final int cachedProcessLimit = doKillExcessiveProcesses
- ? (mConstants.CUR_MAX_CACHED_PROCESSES - emptyProcessLimit) : Integer.MAX_VALUE;
int lastCachedGroup = 0;
int lastCachedGroupUid = 0;
int numCached = 0;
@@ -1279,36 +1256,14 @@ public class OomAdjuster {
} else {
lastCachedGroupUid = lastCachedGroup = 0;
}
- if ((numCached - numCachedExtraGroup) > cachedProcessLimit) {
- app.killLocked("cached #" + numCached,
- "too many cached",
- ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_TOO_MANY_CACHED,
- true);
- } else if (proactiveKillsEnabled) {
+ if (proactiveKillsEnabled) {
lruCachedApp = app;
}
break;
case PROCESS_STATE_CACHED_EMPTY:
- if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES
- && app.getLastActivityTime() < oldTime) {
- app.killLocked("empty for " + ((now
- - app.getLastActivityTime()) / 1000) + "s",
- "empty for too long",
- ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_TRIM_EMPTY,
- true);
- } else {
- numEmpty++;
- if (numEmpty > emptyProcessLimit) {
- app.killLocked("empty #" + numEmpty,
- "too many empty",
- ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY,
- true);
- } else if (proactiveKillsEnabled) {
- lruCachedApp = app;
- }
+ numEmpty++;
+ if (proactiveKillsEnabled) {
+ lruCachedApp = app;
}
break;
default:
@@ -1349,7 +1304,6 @@ public class OomAdjuster {
}
if (proactiveKillsEnabled // Proactive kills enabled?
- && doKillExcessiveProcesses // Should kill excessive processes?
&& freeSwapPercent < lowSwapThresholdPercent // Swap below threshold?
&& lruCachedApp != null // If no cached app, let LMKD decide
// If swap is non-decreasing, give reclaim a chance to catch up
@@ -1544,25 +1498,6 @@ public class OomAdjuster {
}
}
- /**
- * Return true if we should kill excessive cached/empty processes.
- */
- private boolean shouldKillExcessiveProcesses(long nowUptime) {
- final long lastUserUnlockingUptime = mService.mUserController.getLastUserUnlockingUptime();
-
- if (lastUserUnlockingUptime == 0) {
- // No users have been unlocked.
- return !mConstants.mNoKillCachedProcessesUntilBootCompleted;
- }
- final long noKillCachedProcessesPostBootCompletedDurationMillis =
- mConstants.mNoKillCachedProcessesPostBootCompletedDurationMillis;
- if ((lastUserUnlockingUptime + noKillCachedProcessesPostBootCompletedDurationMillis)
- > nowUptime) {
- return false;
- }
- return true;
- }
-
protected final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback =
new ComputeOomAdjWindowCallback();
diff --git a/services/core/java/com/android/server/pm/PackageHandler.java b/services/core/java/com/android/server/pm/PackageHandler.java
index 68f6ca1c019f..0a0882d80cc1 100644
--- a/services/core/java/com/android/server/pm/PackageHandler.java
+++ b/services/core/java/com/android/server/pm/PackageHandler.java
@@ -123,19 +123,10 @@ final class PackageHandler extends Handler {
}
} break;
case WRITE_SETTINGS: {
- if (!mPm.tryWriteSettings(/*sync=*/false)) {
- // Failed to write.
- this.removeMessages(WRITE_SETTINGS);
- mPm.scheduleWriteSettings();
- }
+ mPm.writeSettings(/*sync=*/false);
} break;
case WRITE_PACKAGE_LIST: {
- int userId = msg.arg1;
- if (!mPm.tryWritePackageList(userId)) {
- // Failed to write.
- this.removeMessages(WRITE_PACKAGE_LIST);
- mPm.scheduleWritePackageList(userId);
- }
+ mPm.writePackageList(msg.arg1);
} break;
case CHECK_PENDING_VERIFICATION: {
final int verificationId = msg.arg1;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2b0e62c0272e..ca84d68cd9bb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -485,9 +485,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
*/
static final long WATCHDOG_TIMEOUT = 1000*60*10; // ten minutes
- // How long to wait for Lock in async writeSettings and writePackageList.
- private static final long WRITE_LOCK_TIMEOUT_MS = 1000 * 10; // 10 seconds
-
/**
* Default IncFs timeouts. Maximum values in IncFs is 1hr.
*
@@ -1576,7 +1573,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
}
- void scheduleWritePackageList(int userId) {
+ void scheduleWritePackageListLocked(int userId) {
invalidatePackageInfoCache();
if (!mHandler.hasMessages(WRITE_PACKAGE_LIST)) {
Message msg = mHandler.obtainMessage(WRITE_PACKAGE_LIST);
@@ -1628,42 +1625,22 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mSettings.writePackageRestrictions(dirtyUsers);
}
- private boolean tryUnderLock(boolean sync, long timeoutMs, Runnable runnable) {
- try {
- PackageManagerTracedLock.RawLock lock = mLock.getRawLock();
- if (sync) {
- lock.lock();
- } else if (!lock.tryLock(timeoutMs, TimeUnit.MILLISECONDS)) {
- return false;
- }
- try {
- runnable.run();
- return true;
- } finally {
- lock.unlock();
- }
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to obtain mLock", e);
- }
- return false;
- }
-
- boolean tryWriteSettings(boolean sync) {
- return tryUnderLock(sync, WRITE_LOCK_TIMEOUT_MS, () -> {
+ void writeSettings(boolean sync) {
+ synchronized (mLock) {
mHandler.removeMessages(WRITE_SETTINGS);
mBackgroundHandler.removeMessages(WRITE_DIRTY_PACKAGE_RESTRICTIONS);
writeSettingsLPrTEMP(sync);
synchronized (mDirtyUsers) {
mDirtyUsers.clear();
}
- });
+ }
}
- boolean tryWritePackageList(int userId) {
- return tryUnderLock(/*sync=*/false, WRITE_LOCK_TIMEOUT_MS, () -> {
+ void writePackageList(int userId) {
+ synchronized (mLock) {
mHandler.removeMessages(WRITE_PACKAGE_LIST);
mSettings.writePackageListLPr(userId);
- });
+ }
}
private static final Handler.Callback BACKGROUND_HANDLER_CALLBACK = new Handler.Callback() {
@@ -3068,9 +3045,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
if (mHandler.hasMessages(WRITE_SETTINGS)
|| mBackgroundHandler.hasMessages(WRITE_DIRTY_PACKAGE_RESTRICTIONS)
|| mHandler.hasMessages(WRITE_PACKAGE_LIST)) {
- while (!tryWriteSettings(/*sync=*/true)) {
- Slog.wtf(TAG, "Failed to write settings on shutdown");
- }
+ writeSettings(/*sync=*/true);
}
}
}
@@ -4432,7 +4407,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
synchronized (mLock) {
scheduleWritePackageRestrictions(userId);
- scheduleWritePackageList(userId);
+ scheduleWritePackageListLocked(userId);
mAppsFilter.onUserCreated(snapshotComputer(), userId);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index a9192c4c6139..d7a696f47b7e 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1365,18 +1365,6 @@ class ActivityStarter {
request.outActivity[0] = mLastStartActivityRecord;
}
- // Reset the ActivityRecord#mCurrentLaunchCanTurnScreenOn state of activity started
- // before this one if it is no longer the top-most focusable activity.
- // Doing so in case the state is not yet consumed during rapid activity launch.
- if (previousStart != null && !previousStart.finishing && previousStart.isAttached()
- && previousStart.currentLaunchCanTurnScreenOn()) {
- final ActivityRecord topFocusable = previousStart.getDisplayContent().getActivity(
- ar -> ar.isFocusable() && !ar.finishing);
- if (previousStart != topFocusable) {
- previousStart.setCurrentLaunchCanTurnScreenOn(false);
- }
- }
-
return mLastStartActivityResult;
}
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 125eb2a3a810..be44629a1fcf 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -194,6 +194,16 @@ public class DeferredDisplayUpdater implements DisplayUpdater {
final Rect startBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth,
mDisplayContent.mInitialDisplayHeight);
final int fromRotation = mDisplayContent.getRotation();
+ if (Flags.blastSyncNotificationShadeOnDisplaySwitch() && physicalDisplayUpdated) {
+ final WindowState notificationShade =
+ mDisplayContent.getDisplayPolicy().getNotificationShade();
+ if (notificationShade != null && notificationShade.isVisible()
+ && mDisplayContent.mAtmService.mKeyguardController.isKeyguardOrAodShowing(
+ mDisplayContent.mDisplayId)) {
+ Slog.i(TAG, notificationShade + " uses blast for display switch");
+ notificationShade.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST;
+ }
+ }
mDisplayContent.mAtmService.deferWindowLayout();
try {
diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index d957591ab7f9..cc2249de010c 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -59,6 +59,10 @@ option java_package com.android.server.wm
31002 wm_task_moved (TaskId|1|5),(Root Task ID|1|5),(Display Id|1|5),(ToTop|1),(Index|1)
# Task removed with source explanation.
31003 wm_task_removed (TaskId|1|5),(Root Task ID|1|5),(Display Id|1|5),(Reason|3)
+# Embedded TaskFragment created
+31004 wm_tf_created (Token|1|5),(TaskId|1|5)
+# Embedded TaskFragment removed
+31005 wm_tf_removed (Token|1|5),(TaskId|1|5)
# Set the requested orientation of an activity.
31006 wm_set_requested_orientation (Orientation|1|5),(Component Name|3)
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 61022cc971e2..b8b746a3de7f 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2999,6 +2999,9 @@ class TaskFragment extends WindowContainer<WindowContainer> {
@Override
void removeImmediately() {
+ if (asTask() == null) {
+ EventLogTags.writeWmTfRemoved(System.identityHashCode(this), getTaskId());
+ }
mIsRemovalRequested = false;
resetAdjacentTaskFragment();
cleanUpEmbeddedTaskFragment();
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 7b1c66132c7e..4aa3e3644daa 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1374,13 +1374,22 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// processed all the participants first (in particular, we want to trigger pip-enter first)
for (int i = 0; i < mParticipants.size(); ++i) {
final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
+ if (ar == null) continue;
+
// If the activity was just inserted to an invisible task, it will keep INITIALIZING
// state. Then no need to notify the callback to avoid clearing some states
// unexpectedly, e.g. launch-task-behind.
- if (ar != null && (ar.isVisibleRequested()
- || !ar.isState(ActivityRecord.State.INITIALIZING))) {
+ if (ar.isVisibleRequested() || !ar.isState(ActivityRecord.State.INITIALIZING)) {
mController.dispatchLegacyAppTransitionFinished(ar);
}
+
+ // Reset the ActivityRecord#mCurrentLaunchCanTurnScreenOn state if it is not the top
+ // running activity. Doing so in case the state is not yet consumed during rapid
+ // activity launch.
+ if (ar.currentLaunchCanTurnScreenOn() && ar.getDisplayContent() != null
+ && ar.getDisplayContent().topRunningActivity() != ar) {
+ ar.setCurrentLaunchCanTurnScreenOn(false);
+ }
}
// Update the input-sink (touch-blocking) state now that the animation is finished.
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index cf7a6c9d65de..72109d34ec8a 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -2388,6 +2388,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
position = POSITION_TOP;
}
ownerTask.addChild(taskFragment, position);
+ EventLogTags.writeWmTfCreated(System.identityHashCode(taskFragment), ownerTask.mTaskId);
taskFragment.setWindowingMode(creationParams.getWindowingMode());
if (!creationParams.getInitialRelativeBounds().isEmpty()) {
// The surface operations for the task fragment should sync with the transition.
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index c0259135132b..cd70ed23a824 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -323,16 +323,14 @@ public final class ProfcollectForwardingService extends SystemService {
"dex2oat_trace_freq", 25);
int randomNum = ThreadLocalRandom.current().nextInt(100);
if (randomNum < traceFrequency) {
- BackgroundThread.get().getThreadHandler().post(() -> {
+ // Dex2oat could take a while before it starts. Add a short delay before start tracing.
+ BackgroundThread.get().getThreadHandler().postDelayed(() -> {
try {
- // Dex2oat could take a while before it starts. Add a short delay before start
- // tracing.
- Thread.sleep(1000);
mIProfcollect.trace_once("dex2oat");
- } catch (RemoteException | InterruptedException e) {
+ } catch (RemoteException e) {
Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
}
- });
+ }, 1000);
}
}
@@ -394,15 +392,14 @@ public final class ProfcollectForwardingService extends SystemService {
if (randomNum >= traceFrequency) {
return;
}
- BackgroundThread.get().getThreadHandler().post(() -> {
+ // Wait for 1s before starting tracing.
+ BackgroundThread.get().getThreadHandler().postDelayed(() -> {
try {
- // Wait for a short time before starting tracing.
- Thread.sleep(1000);
mIProfcollect.trace_once("camera");
- } catch (RemoteException | InterruptedException e) {
+ } catch (RemoteException e) {
Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
}
- });
+ }, 1000);
}
}, null);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 47f53f372d33..2a359cd56d1b 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -193,6 +193,14 @@ public final class SatelliteManager {
/**
* Bundle key to get the response from
+ * {@link #requestSessionStats(Executor, OutcomeReceiver)}.
+ * @hide
+ */
+
+ public static final String KEY_SESSION_STATS = "session_stats";
+
+ /**
+ * Bundle key to get the response from
* {@link #requestIsProvisioned(Executor, OutcomeReceiver)}.
* @hide
*/
@@ -2493,6 +2501,65 @@ public final class SatelliteManager {
}
}
+ /**
+ * Request to get the {@link SatelliteSessionStats} of the satellite service.
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback object to which the result will be delivered.
+ * If the request is successful, {@link OutcomeReceiver#onResult(Object)}
+ * will return the {@link SatelliteSessionStats} of the satellite service.
+ * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
+ * will return a {@link SatelliteException} with the {@link SatelliteResult}.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @hide
+ */
+ @RequiresPermission(allOf = {Manifest.permission.PACKAGE_USAGE_STATS,
+ Manifest.permission.MODIFY_PHONE_STATE})
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public void requestSessionStats(@NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<SatelliteSessionStats, SatelliteException> callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ResultReceiver receiver = new ResultReceiver(null) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SATELLITE_RESULT_SUCCESS) {
+ if (resultData.containsKey(KEY_SESSION_STATS)) {
+ SatelliteSessionStats stats =
+ resultData.getParcelable(KEY_SESSION_STATS,
+ SatelliteSessionStats.class);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onResult(stats)));
+ } else {
+ loge("KEY_SESSION_STATS does not exist.");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(
+ SATELLITE_RESULT_REQUEST_FAILED))));
+ }
+ } else {
+ executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+ callback.onError(new SatelliteException(resultCode))));
+ }
+ }
+ };
+ telephony.requestSatelliteSessionStats(mSubId, receiver);
+ } else {
+ loge("requestSessionStats() invalid telephony");
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ } catch (RemoteException ex) {
+ loge("requestSessionStats() RemoteException: " + ex);
+ executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
+ new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
+ }
+ }
+
@Nullable
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
diff --git a/telephony/java/android/telephony/satellite/SatelliteSessionStats.aidl b/telephony/java/android/telephony/satellite/SatelliteSessionStats.aidl
new file mode 100644
index 000000000000..417512504969
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteSessionStats.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package android.telephony.satellite;
+
+ parcelable SatelliteSessionStats; \ No newline at end of file
diff --git a/telephony/java/android/telephony/satellite/SatelliteSessionStats.java b/telephony/java/android/telephony/satellite/SatelliteSessionStats.java
new file mode 100644
index 000000000000..aabb058691d8
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteSessionStats.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * SatelliteSessionStats is used to represent the usage stats of the satellite service.
+ * @hide
+ */
+public class SatelliteSessionStats implements Parcelable {
+ private int mCountOfSuccessfulUserMessages;
+ private int mCountOfUnsuccessfulUserMessages;
+ private int mCountOfTimedOutUserMessagesWaitingForConnection;
+ private int mCountOfTimedOutUserMessagesWaitingForAck;
+ private int mCountOfUserMessagesInQueueToBeSent;
+
+ /**
+ * SatelliteSessionStats constructor
+ * @param builder Builder to create SatelliteSessionStats object/
+ */
+ public SatelliteSessionStats(@NonNull Builder builder) {
+ mCountOfSuccessfulUserMessages = builder.mCountOfSuccessfulUserMessages;
+ mCountOfUnsuccessfulUserMessages = builder.mCountOfUnsuccessfulUserMessages;
+ mCountOfTimedOutUserMessagesWaitingForConnection =
+ builder.mCountOfTimedOutUserMessagesWaitingForConnection;
+ mCountOfTimedOutUserMessagesWaitingForAck =
+ builder.mCountOfTimedOutUserMessagesWaitingForAck;
+ mCountOfUserMessagesInQueueToBeSent = builder.mCountOfUserMessagesInQueueToBeSent;
+ }
+
+ private SatelliteSessionStats(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mCountOfSuccessfulUserMessages);
+ out.writeInt(mCountOfUnsuccessfulUserMessages);
+ out.writeInt(mCountOfTimedOutUserMessagesWaitingForConnection);
+ out.writeInt(mCountOfTimedOutUserMessagesWaitingForAck);
+ out.writeInt(mCountOfUserMessagesInQueueToBeSent);
+ }
+
+ @NonNull
+ public static final Creator<SatelliteSessionStats> CREATOR = new Parcelable.Creator<>() {
+
+ @Override
+ public SatelliteSessionStats createFromParcel(Parcel in) {
+ return new SatelliteSessionStats(in);
+ }
+
+ @Override
+ public SatelliteSessionStats[] newArray(int size) {
+ return new SatelliteSessionStats[size];
+ }
+ };
+
+ @Override
+ @NonNull
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("countOfSuccessfulUserMessages:");
+ sb.append(mCountOfSuccessfulUserMessages);
+ sb.append(",");
+
+ sb.append("countOfUnsuccessfulUserMessages:");
+ sb.append(mCountOfUnsuccessfulUserMessages);
+ sb.append(",");
+
+ sb.append("countOfTimedOutUserMessagesWaitingForConnection:");
+ sb.append(mCountOfTimedOutUserMessagesWaitingForConnection);
+ sb.append(",");
+
+ sb.append("countOfTimedOutUserMessagesWaitingForAck:");
+ sb.append(mCountOfTimedOutUserMessagesWaitingForAck);
+ sb.append(",");
+
+ sb.append("countOfUserMessagesInQueueToBeSent:");
+ sb.append(mCountOfUserMessagesInQueueToBeSent);
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SatelliteSessionStats that = (SatelliteSessionStats) o;
+ return mCountOfSuccessfulUserMessages == that.mCountOfSuccessfulUserMessages
+ && mCountOfUnsuccessfulUserMessages == that.mCountOfUnsuccessfulUserMessages
+ && mCountOfTimedOutUserMessagesWaitingForConnection
+ == that.mCountOfTimedOutUserMessagesWaitingForConnection
+ && mCountOfTimedOutUserMessagesWaitingForAck
+ == that.mCountOfTimedOutUserMessagesWaitingForAck
+ && mCountOfUserMessagesInQueueToBeSent
+ == that.mCountOfUserMessagesInQueueToBeSent;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mCountOfSuccessfulUserMessages, mCountOfUnsuccessfulUserMessages,
+ mCountOfTimedOutUserMessagesWaitingForConnection,
+ mCountOfTimedOutUserMessagesWaitingForAck,
+ mCountOfUserMessagesInQueueToBeSent);
+ }
+
+ public int getCountOfSuccessfulUserMessages() {
+ return mCountOfSuccessfulUserMessages;
+ }
+
+ public int getCountOfUnsuccessfulUserMessages() {
+ return mCountOfUnsuccessfulUserMessages;
+ }
+
+ public int getCountOfTimedOutUserMessagesWaitingForConnection() {
+ return mCountOfTimedOutUserMessagesWaitingForConnection;
+ }
+
+ public int getCountOfTimedOutUserMessagesWaitingForAck() {
+ return mCountOfTimedOutUserMessagesWaitingForAck;
+ }
+
+ public int getCountOfUserMessagesInQueueToBeSent() {
+ return mCountOfUserMessagesInQueueToBeSent;
+ }
+
+ private void readFromParcel(Parcel in) {
+ mCountOfSuccessfulUserMessages = in.readInt();
+ mCountOfUnsuccessfulUserMessages = in.readInt();
+ mCountOfTimedOutUserMessagesWaitingForConnection = in.readInt();
+ mCountOfTimedOutUserMessagesWaitingForAck = in.readInt();
+ mCountOfUserMessagesInQueueToBeSent = in.readInt();
+ }
+
+ /**
+ * A builder class to create {@link SatelliteSessionStats} data object.
+ */
+ public static final class Builder {
+ private int mCountOfSuccessfulUserMessages;
+ private int mCountOfUnsuccessfulUserMessages;
+ private int mCountOfTimedOutUserMessagesWaitingForConnection;
+ private int mCountOfTimedOutUserMessagesWaitingForAck;
+ private int mCountOfUserMessagesInQueueToBeSent;
+
+ /**
+ * Sets countOfSuccessfulUserMessages value of {@link SatelliteSessionStats}
+ * and then returns the Builder class.
+ */
+ @NonNull
+ public Builder setCountOfSuccessfulUserMessages(int count) {
+ mCountOfSuccessfulUserMessages = count;
+ return this;
+ }
+
+ /**
+ * Sets countOfUnsuccessfulUserMessages value of {@link SatelliteSessionStats}
+ * and then returns the Builder class.
+ */
+ @NonNull
+ public Builder setCountOfUnsuccessfulUserMessages(int count) {
+ mCountOfUnsuccessfulUserMessages = count;
+ return this;
+ }
+
+ /**
+ * Sets countOfTimedOutUserMessagesWaitingForConnection value of
+ * {@link SatelliteSessionStats} and then returns the Builder class.
+ */
+ @NonNull
+ public Builder setCountOfTimedOutUserMessagesWaitingForConnection(int count) {
+ mCountOfTimedOutUserMessagesWaitingForConnection = count;
+ return this;
+ }
+
+ /**
+ * Sets countOfTimedOutUserMessagesWaitingForAck value of {@link SatelliteSessionStats}
+ * and then returns the Builder class.
+ */
+ @NonNull
+ public Builder setCountOfTimedOutUserMessagesWaitingForAck(int count) {
+ mCountOfTimedOutUserMessagesWaitingForAck = count;
+ return this;
+ }
+
+ /**
+ * Sets countOfUserMessagesInQueueToBeSent value of {@link SatelliteSessionStats}
+ * and then returns the Builder class.
+ */
+ @NonNull
+ public Builder setCountOfUserMessagesInQueueToBeSent(int count) {
+ mCountOfUserMessagesInQueueToBeSent = count;
+ return this;
+ }
+
+ /** Returns SatelliteSessionStats object. */
+ @NonNull
+ public SatelliteSessionStats build() {
+ return new SatelliteSessionStats(this);
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
index 5b9dfc67c68c..b4eb15fde632 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
@@ -81,4 +81,12 @@ oneway interface ISatelliteListener {
* @param supported True means satellite service is supported and false means it is not.
*/
void onSatelliteSupportedStateChanged(in boolean supported);
+
+ /**
+ * Indicates that the satellite registration failed with following failure code
+ *
+ * @param causeCode the primary failure cause code of the procedure.
+ * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9
+ */
+ void onRegistrationFailure(in int causeCode);
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 9af73eae48a6..9b01b71fc596 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3390,4 +3390,14 @@ interface ITelephony {
* @return {@code true} if the setting is successful, {@code false} otherwise.
*/
boolean setIsSatelliteCommunicationAllowedForCurrentLocationCache(in String state);
+
+ /**
+ * Request to get the session stats of the satellite service.
+ *
+ * @param subId The subId of the subscription to get the session stats for.
+ * @param receiver Result receiver to get the error code of the request and the requested
+ * session stats of the satellite service.
+ * @hide
+ */
+ void requestSatelliteSessionStats(int subId, in ResultReceiver receiver);
}
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 8d3a5ea0be29..8d1fc508ffe7 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -37,6 +37,7 @@ import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
+import com.android.cts.input.DebugInputRule
import com.android.cts.input.UinputTouchScreen
import java.util.concurrent.TimeUnit
@@ -46,6 +47,7 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -74,6 +76,9 @@ class AnrTest {
private val DISPATCHING_TIMEOUT = (UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
Build.HW_TIMEOUT_MULTIPLIER)
+ @get:Rule
+ val debugInputRule = DebugInputRule()
+
@Before
fun setUp() {
val contentResolver = instrumentation.targetContext.contentResolver
@@ -89,12 +94,14 @@ class AnrTest {
}
@Test
+ @DebugInputRule.DebugInput(bug = 339924248)
fun testGestureMonitorAnr_Close() {
triggerAnr()
clickCloseAppOnAnrDialog()
}
@Test
+ @DebugInputRule.DebugInput(bug = 339924248)
fun testGestureMonitorAnr_Wait() {
triggerAnr()
clickWaitOnAnrDialog()
@@ -110,7 +117,7 @@ class AnrTest {
val closeAppButton: UiObject2? =
uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
if (closeAppButton == null) {
- fail("Could not find anr dialog")
+ fail("Could not find anr dialog/close button")
return
}
closeAppButton.click()