diff options
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() |