diff options
55 files changed, 2647 insertions, 1086 deletions
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index bd81fb9b891d..80f39bfbdc21 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -798,6 +798,12 @@ public class Handler { /** * Remove any pending posts of messages with code 'what' that are in the * message queue. + * + * Note that `Message#what` is 0 unless otherwise set. + * When calling `postMessage(Runnable)` or `postAtTime(Runnable, long)`, + * the `Runnable` is internally wrapped with a `Message` whose `what` is 0. + * Calling `removeMessages(0)` will remove all messages without a `what`, + * including posted `Runnable`s. */ public final void removeMessages(int what) { mQueue.removeMessages(this, what, null); diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java index a1db9be0b693..702fdc2bbaa6 100644 --- a/core/java/android/os/Message.java +++ b/core/java/android/os/Message.java @@ -41,6 +41,9 @@ public final class Message implements Parcelable { * what this message is about. Each {@link Handler} has its own name-space * for message codes, so you do not need to worry about yours conflicting * with other handlers. + * + * If not specified, this value is 0. + * Use values other than 0 to indicate custom message codes. */ public int what; diff --git a/core/java/android/util/Half.java b/core/java/android/util/Half.java index fe536a6e4e68..22583acb75ce 100644 --- a/core/java/android/util/Half.java +++ b/core/java/android/util/Half.java @@ -19,6 +19,7 @@ package android.util; import android.annotation.HalfFloat; import android.annotation.NonNull; import android.annotation.Nullable; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; import libcore.util.FP16; @@ -92,6 +93,7 @@ import libcore.util.FP16; * <p>This table shows that numbers higher than 1024 lose all fractional precision.</p> */ @SuppressWarnings("SimplifiableIfStatement") +@RavenwoodKeepWholeClass public final class Half extends Number implements Comparable<Half> { /** * The number of bits used to represent a half-precision float value. diff --git a/core/java/com/android/internal/statusbar/StatusBarIcon.java b/core/java/com/android/internal/statusbar/StatusBarIcon.java index f8a143693c83..1938cdb0ba84 100644 --- a/core/java/com/android/internal/statusbar/StatusBarIcon.java +++ b/core/java/com/android/internal/statusbar/StatusBarIcon.java @@ -51,6 +51,19 @@ public class StatusBarIcon implements Parcelable { ResourceIcon } + public enum Shape { + /** + * Icon view should use WRAP_CONTENT -- so that the horizontal space occupied depends on the + * icon's shape (skinny/fat icons take less/more). Most icons will want to use this option + * for a nicer-looking overall spacing in the status bar, as long as the icon is "known" + * (i.e. not coming from a 3P package). + */ + WRAP_CONTENT, + + /** Icon should always be displayed in a space as wide as the status bar is tall. */ + FIXED_SPACE, + } + public UserHandle user; public String pkg; public Icon icon; @@ -59,6 +72,7 @@ public class StatusBarIcon implements Parcelable { public int number; public CharSequence contentDescription; public Type type; + public Shape shape; /** * Optional {@link Drawable} corresponding to {@link #icon}. This field is not parcelable, so @@ -68,7 +82,7 @@ public class StatusBarIcon implements Parcelable { @Nullable public Drawable preloadedIcon; public StatusBarIcon(UserHandle user, String resPackage, Icon icon, int iconLevel, int number, - CharSequence contentDescription, Type type) { + CharSequence contentDescription, Type type, Shape shape) { if (icon.getType() == Icon.TYPE_RESOURCE && TextUtils.isEmpty(icon.getResPackage())) { // This is an odd situation where someone's managed to hand us an icon without a @@ -83,6 +97,13 @@ public class StatusBarIcon implements Parcelable { this.number = number; this.contentDescription = contentDescription; this.type = type; + this.shape = shape; + } + + public StatusBarIcon(UserHandle user, String resPackage, Icon icon, int iconLevel, int number, + CharSequence contentDescription, Type type) { + this(user, resPackage, icon, iconLevel, number, contentDescription, type, + Shape.WRAP_CONTENT); } public StatusBarIcon(String iconPackage, UserHandle user, @@ -107,7 +128,7 @@ public class StatusBarIcon implements Parcelable { @Override public StatusBarIcon clone() { StatusBarIcon that = new StatusBarIcon(this.user, this.pkg, this.icon, - this.iconLevel, this.number, this.contentDescription, this.type); + this.iconLevel, this.number, this.contentDescription, this.type, this.shape); that.visible = this.visible; that.preloadedIcon = this.preloadedIcon; return that; @@ -129,6 +150,7 @@ public class StatusBarIcon implements Parcelable { this.number = in.readInt(); this.contentDescription = in.readCharSequence(); this.type = Type.valueOf(in.readString()); + this.shape = Shape.valueOf(in.readString()); } public void writeToParcel(Parcel out, int flags) { @@ -140,6 +162,7 @@ public class StatusBarIcon implements Parcelable { out.writeInt(this.number); out.writeCharSequence(this.contentDescription); out.writeString(this.type.name()); + out.writeString(this.shape.name()); } public int describeContents() { diff --git a/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java b/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java index b183ecb50591..149e132a0df4 100644 --- a/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java +++ b/core/tests/coretests/src/com/android/internal/statusbar/StatusBarIconTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Icon; import android.os.Parcel; import android.os.UserHandle; @@ -69,22 +70,22 @@ public class StatusBarIconTest { assertThat(copy.preloadedIcon).isEqualTo(original.preloadedIcon); } - private static StatusBarIcon newStatusBarIcon() { final UserHandle dummyUserHandle = UserHandle.of(100); final String dummyIconPackageName = "com.android.internal.statusbar.test"; - final int dummyIconId = 123; + final Icon dummyIcon = Icon.createWithResource(dummyIconPackageName, 123); final int dummyIconLevel = 1; final int dummyIconNumber = 2; final CharSequence dummyIconContentDescription = "dummyIcon"; return new StatusBarIcon( - dummyIconPackageName, dummyUserHandle, - dummyIconId, + dummyIconPackageName, + dummyIcon, dummyIconLevel, dummyIconNumber, dummyIconContentDescription, - StatusBarIcon.Type.SystemIcon); + StatusBarIcon.Type.SystemIcon, + StatusBarIcon.Shape.FIXED_SPACE); } private static void assertSerializableFieldsEqual(StatusBarIcon copy, StatusBarIcon original) { @@ -96,6 +97,7 @@ public class StatusBarIconTest { assertThat(copy.number).isEqualTo(original.number); assertThat(copy.contentDescription).isEqualTo(original.contentDescription); assertThat(copy.type).isEqualTo(original.type); + assertThat(copy.shape).isEqualTo(original.shape); } private static StatusBarIcon parcelAndUnparcel(StatusBarIcon original) { diff --git a/graphics/java/android/graphics/Matrix44.java b/graphics/java/android/graphics/Matrix44.java index a99e20101c3b..683f6149a203 100644 --- a/graphics/java/android/graphics/Matrix44.java +++ b/graphics/java/android/graphics/Matrix44.java @@ -19,6 +19,7 @@ package android.graphics; import android.annotation.FlaggedApi; import android.annotation.IntRange; import android.annotation.NonNull; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; import com.android.graphics.hwui.flags.Flags; @@ -30,6 +31,7 @@ import java.util.Arrays; * in row-major order. The values and operations are treated as column vectors. */ @FlaggedApi(Flags.FLAG_MATRIX_44) +@RavenwoodKeepWholeClass public class Matrix44 { final float[] mBackingArray; /** diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java index 618e6dcc4433..c7b89412cc47 100644 --- a/graphics/java/android/graphics/Outline.java +++ b/graphics/java/android/graphics/Outline.java @@ -21,6 +21,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.drawable.Drawable; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -35,6 +36,7 @@ import java.lang.annotation.RetentionPolicy; * @see android.view.View#setOutlineProvider(android.view.ViewOutlineProvider) * @see Drawable#getOutline(Outline) */ +@RavenwoodKeepWholeClass public final class Outline { private static final float RADIUS_UNDEFINED = Float.NEGATIVE_INFINITY; diff --git a/graphics/java/android/graphics/ParcelableColorSpace.java b/graphics/java/android/graphics/ParcelableColorSpace.java index 748d66cb5f6c..76c1715475ae 100644 --- a/graphics/java/android/graphics/ParcelableColorSpace.java +++ b/graphics/java/android/graphics/ParcelableColorSpace.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; /** * A {@link Parcelable} wrapper for a {@link ColorSpace}. In order to enable parceling, the @@ -27,6 +28,7 @@ import android.os.Parcelable; * {@link ColorSpace.Rgb} instance that has an ICC parametric transfer function as returned by * {@link ColorSpace.Rgb#getTransferParameters()}. */ +@RavenwoodKeepWholeClass public final class ParcelableColorSpace implements Parcelable { private final ColorSpace mColorSpace; diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java index 3ec5b9cc7dae..a872e03db3b5 100644 --- a/graphics/java/android/graphics/PixelFormat.java +++ b/graphics/java/android/graphics/PixelFormat.java @@ -17,10 +17,12 @@ package android.graphics; import android.annotation.IntDef; +import android.ravenwood.annotation.RavenwoodKeepWholeClass; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +@RavenwoodKeepWholeClass public class PixelFormat { /** @hide */ @IntDef({UNKNOWN, TRANSLUCENT, TRANSPARENT, OPAQUE}) diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java index fe0f98af1186..43c6c5050850 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenIconLoader.java @@ -31,7 +31,6 @@ import android.util.LruCache; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; import com.google.common.util.concurrent.FluentFuture; import com.google.common.util.concurrent.ListenableFuture; @@ -53,25 +52,22 @@ public class ZenIconLoader { private final LruCache<ZenIcon.Key, Drawable> mCache; private final ListeningExecutorService mBackgroundExecutor; + /** Obtains the singleton {@link ZenIconLoader}. */ public static ZenIconLoader getInstance() { if (sInstance == null) { - sInstance = new ZenIconLoader(); + sInstance = new ZenIconLoader(Executors.newFixedThreadPool(4)); } return sInstance; } - /** Replaces the singleton instance of {@link ZenIconLoader} by the provided one. */ - @VisibleForTesting(otherwise = VisibleForTesting.NONE) - public static void setInstance(@Nullable ZenIconLoader instance) { - sInstance = instance; - } - - private ZenIconLoader() { - this(Executors.newFixedThreadPool(4)); - } - - @VisibleForTesting - public ZenIconLoader(ExecutorService backgroundExecutor) { + /** + * Constructs a ZenIconLoader with the specified {@code backgroundExecutor}. + * + * <p>ZenIconLoader <em>should be a singleton</em>, so this should only be used to instantiate + * and provide the singleton instance in a module. If the app doesn't support dependency + * injection, use {@link #getInstance} instead. + */ + public ZenIconLoader(@NonNull ExecutorService backgroundExecutor) { mCache = new LruCache<>(50); mBackgroundExecutor = MoreExecutors.listeningDecorator(backgroundExecutor); diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt index 7707a600c691..fe9105ed195e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt @@ -29,6 +29,7 @@ import com.android.internal.util.LatencyTracker import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockscreenCredential import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor +import com.android.systemui.Flags as AconfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FakeFeatureFlags @@ -56,7 +57,6 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -import com.android.systemui.Flags as AconfigFlags @SmallTest @RunWith(AndroidJUnit4::class) @@ -66,8 +66,7 @@ import com.android.systemui.Flags as AconfigFlags class KeyguardPasswordViewControllerTest : SysuiTestCase() { @Mock private lateinit var keyguardPasswordView: KeyguardPasswordView @Mock private lateinit var passwordEntry: EditText - private var passwordEntryLayoutParams = - ViewGroup.LayoutParams(/* width = */ 0, /* height = */ 0) + private var passwordEntryLayoutParams = ViewGroup.LayoutParams(/* width= */ 0, /* height= */ 0) @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock lateinit var securityMode: KeyguardSecurityModel.SecurityMode @Mock lateinit var lockPatternUtils: LockPatternUtils @@ -106,6 +105,8 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { whenever(keyguardPasswordView.findViewById<ImageView>(R.id.switch_ime_button)) .thenReturn(mock(ImageView::class.java)) `when`(keyguardPasswordView.resources).thenReturn(context.resources) + // TODO(b/362362385): No need to mock keyguardPasswordView.context once this bug is fixed. + `when`(keyguardPasswordView.context).thenReturn(context) whenever(passwordEntry.layoutParams).thenReturn(passwordEntryLayoutParams) val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository()) val fakeFeatureFlags = FakeFeatureFlags() @@ -187,9 +188,11 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { verify(passwordEntry).setOnKeyListener(keyListenerArgumentCaptor.capture()) val eventHandled = - keyListenerArgumentCaptor.value.onKey(keyguardPasswordView, + keyListenerArgumentCaptor.value.onKey( + keyguardPasswordView, KeyEvent.KEYCODE_SPACE, - KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE)) + KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE) + ) assertFalse("Unlock attempted.", eventHandled) } @@ -204,9 +207,11 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { verify(passwordEntry).setOnKeyListener(keyListenerArgumentCaptor.capture()) val eventHandled = - keyListenerArgumentCaptor.value.onKey(keyguardPasswordView, + keyListenerArgumentCaptor.value.onKey( + keyguardPasswordView, KeyEvent.KEYCODE_ENTER, - KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER)) + KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER) + ) assertTrue("Unlock not attempted.", eventHandled) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt index 5925819f27a7..5fd3a242e195 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt @@ -24,9 +24,10 @@ import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.settingslib.notification.modes.ZenIconLoader +import com.android.internal.R import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.asIcon +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope @@ -36,7 +37,6 @@ import com.android.systemui.statusbar.policy.data.repository.fakeZenModeReposito import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat -import com.google.common.util.concurrent.MoreExecutors import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toCollection @@ -60,10 +60,10 @@ class ModesTileDataInteractorTest : SysuiTestCase() { @Before fun setUp() { context.orCreateTestableResources.apply { - addOverride(com.android.internal.R.drawable.ic_zen_mode_type_bedtime, BEDTIME_DRAWABLE) - addOverride(com.android.internal.R.drawable.ic_zen_mode_type_driving, DRIVING_DRAWABLE) + addOverride(MODES_DRAWABLE_ID, MODES_DRAWABLE) + addOverride(R.drawable.ic_zen_mode_type_bedtime, BEDTIME_DRAWABLE) + addOverride(R.drawable.ic_zen_mode_type_driving, DRIVING_DRAWABLE) } - ZenIconLoader.setInstance(ZenIconLoader(MoreExecutors.newDirectExecutorService())) } @EnableFlags(Flags.FLAG_MODES_UI) @@ -128,28 +128,34 @@ class ModesTileDataInteractorTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS) - fun changesIconWhenActiveModesChange() = + fun tileData_iconsFlagEnabled_changesIconWhenActiveModesChange() = testScope.runTest { - val dataList: List<ModesTileModel> by - collectValues( + val tileData by + collectLastValue( underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)) ) + + // Tile starts with the generic Modes icon. runCurrent() - assertThat(dataList.map { it.icon }).containsExactly(null).inOrder() + assertThat(tileData?.icon).isEqualTo(MODES_ICON) + assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID) - // Add an inactive mode: state hasn't changed, so this shouldn't cause another emission + // Add an inactive mode -> Still modes icon zenModeRepository.addMode(id = "Mode", active = false) runCurrent() - assertThat(dataList.map { it.icon }).containsExactly(null).inOrder() + assertThat(tileData?.icon).isEqualTo(MODES_ICON) + assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID) - // Add an active mode: icon should be the mode icon + // Add an active mode: icon should be the mode icon. No iconResId, because we don't + // really know that it's a system icon. zenModeRepository.addMode( id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME, active = true ) runCurrent() - assertThat(dataList.map { it.icon }).containsExactly(null, BEDTIME_ICON).inOrder() + assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON) + assertThat(tileData?.iconResId).isNull() // Add another, less-prioritized mode: icon should remain the first mode icon zenModeRepository.addMode( @@ -158,29 +164,58 @@ class ModesTileDataInteractorTest : SysuiTestCase() { active = true ) runCurrent() - assertThat(dataList.map { it.icon }) - .containsExactly(null, BEDTIME_ICON, BEDTIME_ICON) - .inOrder() + assertThat(tileData?.icon).isEqualTo(BEDTIME_ICON) + assertThat(tileData?.iconResId).isNull() - // Deactivate more important mode: icon should be the less important, still active mode. + // Deactivate more important mode: icon should be the less important, still active mode zenModeRepository.deactivateMode("Bedtime") runCurrent() - assertThat(dataList.map { it.icon }) - .containsExactly(null, BEDTIME_ICON, BEDTIME_ICON, DRIVING_ICON) - .inOrder() + assertThat(tileData?.icon).isEqualTo(DRIVING_ICON) + assertThat(tileData?.iconResId).isNull() - // Deactivate remaining mode: no icon + // Deactivate remaining mode: back to the default modes icon zenModeRepository.deactivateMode("Driving") runCurrent() - assertThat(dataList.map { it.icon }) - .containsExactly(null, BEDTIME_ICON, BEDTIME_ICON, DRIVING_ICON, null) - .inOrder() + assertThat(tileData?.icon).isEqualTo(MODES_ICON) + assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID) + } + + @Test + @EnableFlags(Flags.FLAG_MODES_UI) + @DisableFlags(Flags.FLAG_MODES_UI_ICONS) + fun tileData_iconsFlagDisabled_hasPriorityModesIcon() = + testScope.runTest { + val tileData by + collectLastValue( + underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)) + ) + + runCurrent() + assertThat(tileData?.icon).isEqualTo(MODES_ICON) + assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID) + + // Activate a Mode -> Icon doesn't change. + zenModeRepository.addMode(id = "Mode", active = true) + runCurrent() + assertThat(tileData?.icon).isEqualTo(MODES_ICON) + assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID) + + zenModeRepository.deactivateMode(id = "Mode") + runCurrent() + assertThat(tileData?.icon).isEqualTo(MODES_ICON) + assertThat(tileData?.iconResId).isEqualTo(MODES_DRAWABLE_ID) } private companion object { val TEST_USER = UserHandle.of(1)!! + + val MODES_DRAWABLE_ID = com.android.systemui.res.R.drawable.qs_dnd_icon_off + + val MODES_DRAWABLE = TestStubDrawable("modes_icon") val BEDTIME_DRAWABLE = TestStubDrawable("bedtime") val DRIVING_DRAWABLE = TestStubDrawable("driving") + + val MODES_ICON = MODES_DRAWABLE.asIcon() val BEDTIME_ICON = BEDTIME_DRAWABLE.asIcon() val DRIVING_ICON = DRIVING_DRAWABLE.asIcon() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt index 4b7564981855..cd5812710292 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractorTest.kt @@ -16,12 +16,14 @@ package com.android.systemui.qs.tiles.impl.modes.domain.interactor +import android.graphics.drawable.TestStubDrawable import android.platform.test.annotations.EnableFlags import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Expandable +import com.android.systemui.common.shared.model.asIcon import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx @@ -54,10 +56,7 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() { fun handleClick_active() = runTest { val expandable = mock<Expandable>() underTest.handleInput( - QSTileInputTestKtx.click( - data = ModesTileModel(true, listOf("DND")), - expandable = expandable - ) + QSTileInputTestKtx.click(data = modelOf(true, listOf("DND")), expandable = expandable) ) verify(mockDialogDelegate).showDialog(eq(expandable)) @@ -67,10 +66,7 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() { fun handleClick_inactive() = runTest { val expandable = mock<Expandable>() underTest.handleInput( - QSTileInputTestKtx.click( - data = ModesTileModel(false, emptyList()), - expandable = expandable - ) + QSTileInputTestKtx.click(data = modelOf(false, emptyList()), expandable = expandable) ) verify(mockDialogDelegate).showDialog(eq(expandable)) @@ -78,7 +74,7 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() { @Test fun handleLongClick_active() = runTest { - underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(true, listOf("DND")))) + underTest.handleInput(QSTileInputTestKtx.longClick(modelOf(true, listOf("DND")))) QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS) @@ -87,10 +83,14 @@ class ModesTileUserActionInteractorTest : SysuiTestCase() { @Test fun handleLongClick_inactive() = runTest { - underTest.handleInput(QSTileInputTestKtx.longClick(ModesTileModel(false, emptyList()))) + underTest.handleInput(QSTileInputTestKtx.longClick(modelOf(false, emptyList()))) QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { assertThat(it.intent.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS) } } + + private fun modelOf(isActivated: Boolean, activeModeNames: List<String>): ModesTileModel { + return ModesTileModel(isActivated, activeModeNames, TestStubDrawable("icon").asIcon(), 123) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt index a41f15d8ff82..f7bdcb8086ef 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt @@ -18,11 +18,12 @@ package com.android.systemui.qs.tiles.impl.modes.ui import android.app.Flags import android.graphics.drawable.TestStubDrawable +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.asIcon import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder import com.android.systemui.qs.tiles.viewmodel.QSTileState @@ -58,47 +59,88 @@ class ModesTileMapperTest : SysuiTestCase() { @Test fun inactiveState() { - val model = ModesTileModel(isActivated = false, activeModes = emptyList()) + val icon = TestStubDrawable("res123").asIcon() + val model = + ModesTileModel( + isActivated = false, + activeModes = emptyList(), + icon = icon, + ) val state = underTest.map(config, model) assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.INACTIVE) - assertThat(state.iconRes).isEqualTo(R.drawable.qs_dnd_icon_off) + assertThat(state.icon()).isEqualTo(icon) assertThat(state.secondaryLabel).isEqualTo("No active modes") } @Test fun activeState_oneMode() { - val model = ModesTileModel(isActivated = true, activeModes = listOf("DND")) + val icon = TestStubDrawable("res123").asIcon() + val model = + ModesTileModel( + isActivated = true, + activeModes = listOf("DND"), + icon = icon, + ) val state = underTest.map(config, model) assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE) - assertThat(state.iconRes).isEqualTo(R.drawable.qs_dnd_icon_on) + assertThat(state.icon()).isEqualTo(icon) assertThat(state.secondaryLabel).isEqualTo("DND is active") } @Test fun activeState_multipleModes() { + val icon = TestStubDrawable("res123").asIcon() val model = - ModesTileModel(isActivated = true, activeModes = listOf("Mode 1", "Mode 2", "Mode 3")) + ModesTileModel( + isActivated = true, + activeModes = listOf("Mode 1", "Mode 2", "Mode 3"), + icon = icon, + ) val state = underTest.map(config, model) assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE) - assertThat(state.iconRes).isEqualTo(R.drawable.qs_dnd_icon_on) + assertThat(state.icon()).isEqualTo(icon) assertThat(state.secondaryLabel).isEqualTo("3 modes are active") } @Test @EnableFlags(Flags.FLAG_MODES_UI_ICONS) - fun activeState_withIcon() { - val icon = Icon.Resource(1234, contentDescription = null) - val model = ModesTileModel(isActivated = true, activeModes = listOf("DND"), icon = icon) + fun state_withEnabledFlag_noIconResId() { + val icon = TestStubDrawable("res123").asIcon() + val model = + ModesTileModel( + isActivated = false, + activeModes = emptyList(), + icon = icon, + iconResId = 123 // Should not be populated, but is ignored even if present + ) val state = underTest.map(config, model) + assertThat(state.icon()).isEqualTo(icon) assertThat(state.iconRes).isNull() + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI_ICONS) + fun state_withDisabledFlag_includesIconResId() { + val icon = TestStubDrawable("res123").asIcon() + val model = + ModesTileModel( + isActivated = false, + activeModes = emptyList(), + icon = icon, + iconResId = 123 + ) + + val state = underTest.map(config, model) + assertThat(state.icon()).isEqualTo(icon) + assertThat(state.iconRes).isEqualTo(123) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt index 20d3a7b6b7b5..639d34d5e74d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt @@ -22,8 +22,10 @@ import android.provider.Settings import android.provider.Settings.Secure.ZEN_DURATION import android.provider.Settings.Secure.ZEN_DURATION_FOREVER import android.provider.Settings.Secure.ZEN_DURATION_PROMPT +import android.service.notification.SystemZenRules import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.internal.R import com.android.settingslib.notification.data.repository.updateNotificationPolicy import com.android.settingslib.notification.modes.TestModeBuilder import com.android.systemui.SysuiTestCase @@ -220,30 +222,86 @@ class ZenModeInteractorTest : SysuiTestCase() { } @Test - fun mainActiveMode_returnsMainActiveMode() = + fun activeModes_computesMainActiveMode() = testScope.runTest { - val mainActiveMode by collectLastValue(underTest.mainActiveMode) + val activeModes by collectLastValue(underTest.activeModes) zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME) zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER) runCurrent() + assertThat(activeModes?.modeNames).hasSize(0) + assertThat(activeModes?.mainMode).isNull() + + zenModeRepository.activateMode("Other") + runCurrent() + assertThat(activeModes?.modeNames).containsExactly("Mode Other") + assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Other") + + zenModeRepository.activateMode("Bedtime") + runCurrent() + assertThat(activeModes?.modeNames) + .containsExactly("Mode Bedtime", "Mode Other") + .inOrder() + assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Bedtime") + + zenModeRepository.deactivateMode("Other") + runCurrent() + assertThat(activeModes?.modeNames).containsExactly("Mode Bedtime") + assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Bedtime") + + zenModeRepository.deactivateMode("Bedtime") + runCurrent() + assertThat(activeModes?.modeNames).hasSize(0) + assertThat(activeModes?.mainMode).isNull() + } + + @Test + fun mainActiveMode_flows() = + testScope.runTest { + val mainActiveMode by collectLastValue(underTest.mainActiveMode) + + zenModeRepository.addModes( + listOf( + TestModeBuilder() + .setId("Bedtime") + .setName("Mode Bedtime") + .setType(AutomaticZenRule.TYPE_BEDTIME) + .setActive(false) + .setPackage(mContext.packageName) + .setIconResId(R.drawable.ic_zen_mode_type_bedtime) + .build(), + TestModeBuilder() + .setId("Other") + .setName("Mode Other") + .setType(AutomaticZenRule.TYPE_OTHER) + .setActive(false) + .setPackage(SystemZenRules.PACKAGE_ANDROID) + .setIconResId(R.drawable.ic_zen_mode_type_other) + .build(), + ) + ) + + runCurrent() assertThat(mainActiveMode).isNull() zenModeRepository.activateMode("Other") runCurrent() - assertThat(mainActiveMode).isNotNull() - assertThat(mainActiveMode!!.id).isEqualTo("Other") + assertThat(mainActiveMode?.name).isEqualTo("Mode Other") + assertThat(mainActiveMode?.icon?.key?.resId) + .isEqualTo(R.drawable.ic_zen_mode_type_other) zenModeRepository.activateMode("Bedtime") runCurrent() - assertThat(mainActiveMode).isNotNull() - assertThat(mainActiveMode!!.id).isEqualTo("Bedtime") + assertThat(mainActiveMode?.name).isEqualTo("Mode Bedtime") + assertThat(mainActiveMode?.icon?.key?.resId) + .isEqualTo(R.drawable.ic_zen_mode_type_bedtime) zenModeRepository.deactivateMode("Other") runCurrent() - assertThat(mainActiveMode).isNotNull() - assertThat(mainActiveMode!!.id).isEqualTo("Bedtime") + assertThat(mainActiveMode?.name).isEqualTo("Mode Bedtime") + assertThat(mainActiveMode?.icon?.key?.resId) + .isEqualTo(R.drawable.ic_zen_mode_type_bedtime) zenModeRepository.deactivateMode("Bedtime") runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt index 33f379d020b7..d2bc54e09944 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt @@ -23,7 +23,6 @@ import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.notification.modes.TestModeBuilder -import com.android.settingslib.notification.modes.ZenIconLoader import com.android.settingslib.notification.modes.ZenMode import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -35,12 +34,10 @@ import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogEventLogger import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat -import com.google.common.util.concurrent.MoreExecutors import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.clearInvocations @@ -67,11 +64,6 @@ class ModesDialogViewModelTest : SysuiTestCase() { mockDialogEventLogger ) - @Before - fun setUp() { - ZenIconLoader.setInstance(ZenIconLoader(MoreExecutors.newDirectExecutorService())) - } - @Test fun tiles_filtersOutUserDisabledModes() = testScope.runTest { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index 490ad5c4136d..3ad73bc17704 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -19,9 +19,12 @@ package com.android.keyguard; import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; +import android.content.Context; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.UserHandle; +import android.os.UserManager; import android.text.Editable; import android.text.InputType; import android.text.TextUtils; @@ -170,8 +173,33 @@ public class KeyguardPasswordViewController mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener); mPasswordEntry.setOnKeyListener(mKeyListener); mPasswordEntry.addTextChangedListener(mTextWatcher); + // Poke the wakelock any time the text is selected or modified - mPasswordEntry.setOnClickListener(v -> mKeyguardSecurityCallback.userActivity()); + // TODO(b/362362385): Revert to the previous onClickListener implementation once this bug is + // fixed. + mPasswordEntry.setOnClickListener(new View.OnClickListener() { + + private final boolean mAutomotiveAndVisibleBackgroundUsers = + isAutomotiveAndVisibleBackgroundUsers(); + + @Override + public void onClick(View v) { + if (mAutomotiveAndVisibleBackgroundUsers) { + mInputMethodManager.restartInput(v); + } + mKeyguardSecurityCallback.userActivity(); + } + + private boolean isAutomotiveAndVisibleBackgroundUsers() { + final Context context = getContext(); + return context.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_AUTOMOTIVE) + && UserManager.isVisibleBackgroundUsersEnabled() + && context.getResources().getBoolean( + android.R.bool.config_perDisplayFocusEnabled); + } + }); + mSwitchImeButton.setOnClickListener(v -> { mKeyguardSecurityCallback.userActivity(); // Leave the screen on a bit longer // Do not show auxiliary subtypes in password lock screen. diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt index 3cdb57318e8d..aef5f1f422d1 100644 --- a/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/Icon.kt @@ -38,5 +38,5 @@ sealed class Icon { } /** Creates [Icon.Loaded] for a given drawable with an optional [contentDescription]. */ -fun Drawable.asIcon(contentDescription: ContentDescription? = null): Icon = +fun Drawable.asIcon(contentDescription: ContentDescription? = null): Icon.Loaded = Icon.Loaded(this, contentDescription) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt index 3f18fc2066eb..664951d199a7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt @@ -20,10 +20,12 @@ import android.app.Flags import android.content.Context import android.os.UserHandle import com.android.app.tracing.coroutines.flow.map +import com.android.systemui.common.shared.model.asIcon import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -52,18 +54,32 @@ constructor( */ fun tileData() = zenModeInteractor.activeModes - .map { modes -> - ModesTileModel( - isActivated = modes.isNotEmpty(), - icon = - if (Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons()) - zenModeInteractor.getActiveModeIcon(modes) - else null, - activeModes = modes.map { it.name } - ) + .map { activeModes -> + val modesIconResId = R.drawable.qs_dnd_icon_off + + if (usesModeIcons()) { + val mainModeDrawable = activeModes.mainMode?.icon?.drawable + val iconResId = if (mainModeDrawable == null) modesIconResId else null + + ModesTileModel( + isActivated = activeModes.isAnyActive(), + icon = (mainModeDrawable ?: context.getDrawable(modesIconResId)!!).asIcon(), + iconResId = iconResId, + activeModes = activeModes.modeNames + ) + } else { + ModesTileModel( + isActivated = activeModes.isAnyActive(), + icon = context.getDrawable(modesIconResId)!!.asIcon(), + iconResId = modesIconResId, + activeModes = activeModes.modeNames + ) + } } .flowOn(bgDispatcher) .distinctUntilChanged() override fun availability(user: UserHandle): Flow<Boolean> = flowOf(Flags.modesUi()) + + private fun usesModeIcons() = Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt index 904ff3aaad26..db4812342050 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt @@ -21,5 +21,12 @@ import com.android.systemui.common.shared.model.Icon data class ModesTileModel( val isActivated: Boolean, val activeModes: List<String>, - val icon: Icon? = null + val icon: Icon.Loaded, + + /** + * Resource id corresponding to [icon]. Will only be present if it's know to correspond to a + * resource with a known id in SystemUI (such as resources from `android.R`, + * `com.android.internal.R`, or `com.android.systemui.res` itself). + */ + val iconResId: Int? = null ) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt index 83c3335ebffb..7f571b135fc8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt @@ -16,11 +16,9 @@ package com.android.systemui.qs.tiles.impl.modes.ui -import android.app.Flags import android.content.res.Resources import android.icu.text.MessageFormat import android.widget.Button -import com.android.systemui.common.shared.model.asIcon import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel @@ -38,15 +36,10 @@ constructor( ) : QSTileDataToStateMapper<ModesTileModel> { override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState = QSTileState.build(resources, theme, config.uiConfig) { - if (Flags.modesApi() && Flags.modesUi() && Flags.modesUiIcons() && data.icon != null) { - icon = { data.icon } - } else { - val iconRes = - if (data.isActivated) R.drawable.qs_dnd_icon_on else R.drawable.qs_dnd_icon_off - val icon = resources.getDrawable(iconRes, theme).asIcon() - this.iconRes = iconRes - this.icon = { icon } + if (!android.app.Flags.modesUiIcons()) { + iconRes = data.iconResId } + icon = { data.icon } activationState = if (data.isActivated) { QSTileState.ActivationState.ACTIVE diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 6eadd2627399..2b44c2f9ea7f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -58,6 +58,7 @@ import androidx.core.graphics.ColorUtils; import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.statusbar.StatusBarIcon.Shape; import com.android.internal.util.ContrastColorUtil; import com.android.systemui.Flags; import com.android.systemui.res.R; @@ -211,16 +212,19 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi /** Should always be preceded by {@link #reloadDimens()} */ @VisibleForTesting public void maybeUpdateIconScaleDimens() { - // We do not resize and scale system icons (on the right), only notification icons (on the - // left). - if (isNotification()) { - updateIconScaleForNotifications(); + // We scale notification icons (on the left) plus icons on the right that explicitly + // want FIXED_SPACE. + boolean useNonSystemIconScaling = isNotification() + || (usesModeIcons() && mIcon != null && mIcon.shape == Shape.FIXED_SPACE); + + if (useNonSystemIconScaling) { + updateIconScaleForNonSystemIcons(); } else { updateIconScaleForSystemIcons(); } } - private void updateIconScaleForNotifications() { + private void updateIconScaleForNonSystemIcons() { float iconScale; // we need to scale the image size to be same as the original size // (fit mOriginalStatusBarIconSize), then we can scale it with mScaleToFitNewIconSize @@ -411,7 +415,9 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi if (!levelEquals) { setImageLevel(icon.iconLevel); } - + if (usesModeIcons()) { + setScaleType(icon.shape == Shape.FIXED_SPACE ? ScaleType.FIT_CENTER : ScaleType.CENTER); + } if (!visibilityEquals) { setVisibility(icon.visible && !mBlocked ? VISIBLE : GONE); } @@ -501,7 +507,12 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi @Nullable private Drawable loadDrawable(Context context, StatusBarIcon statusBarIcon) { if (usesModeIcons() && statusBarIcon.preloadedIcon != null) { - return statusBarIcon.preloadedIcon.mutate(); + Drawable.ConstantState cached = statusBarIcon.preloadedIcon.getConstantState(); + if (cached != null) { + return cached.newDrawable(mContext.getResources()).mutate(); + } else { + return statusBarIcon.preloadedIcon.mutate(); + } } else { int userId = statusBarIcon.user.getIdentifier(); if (userId == UserHandle.USER_ALL) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 05bd1a7676ae..ba39c3bb4124 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -44,7 +44,7 @@ import android.view.View; import androidx.lifecycle.Observer; -import com.android.settingslib.notification.modes.ZenMode; +import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.Flags; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.DisplayId; @@ -80,6 +80,7 @@ import com.android.systemui.statusbar.policy.SensorPrivacyController; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor; +import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo; import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.time.DateFormatUtil; @@ -363,7 +364,7 @@ public class PhoneStatusBarPolicy // Note that we're not fully replacing ZenModeController with ZenModeInteractor, so // we listen for the extra event here but still add the ZMC callback. mJavaAdapter.alwaysCollectFlow(mZenModeInteractor.getMainActiveMode(), - this::onActiveModeChanged); + this::onMainActiveModeChanged); } mZenController.addCallback(mZenControllerCallback); if (!Flags.statusBarScreenSharingChips()) { @@ -395,20 +396,23 @@ public class PhoneStatusBarPolicy () -> mResources.getString(R.string.accessibility_managed_profile)); } - private void onActiveModeChanged(@Nullable ZenMode mode) { + private void onMainActiveModeChanged(@Nullable ZenModeInfo mainActiveMode) { if (!usesModeIcons()) { - Log.wtf(TAG, "onActiveModeChanged shouldn't be called if MODES_UI_ICONS is disabled"); + Log.wtf(TAG, "onMainActiveModeChanged shouldn't run if MODES_UI_ICONS is disabled"); return; } - boolean visible = mode != null; - if (visible) { - // TODO: b/360399800 - Get the resource id, package, and cached drawable from the mode; - // this is a shortcut for testing. - String resPackage = mode.getIconKey().resPackage(); - int iconResId = mode.getIconKey().resId(); - mIconController.setResourceIcon(mSlotZen, resPackage, iconResId, - /* preloadedIcon= */ null, mode.getName()); + boolean visible = mainActiveMode != null; + if (visible) { + // Shape=FIXED_SPACE because mode icons can be from 3P packages and may not be square; + // we don't want to allow apps to set incredibly wide icons and take up too much space + // in the status bar. + mIconController.setResourceIcon(mSlotZen, + mainActiveMode.getIcon().key().resPackage(), + mainActiveMode.getIcon().key().resId(), + mainActiveMode.getIcon().drawable(), + mainActiveMode.getName(), + StatusBarIcon.Shape.FIXED_SPACE); } if (visible != mZenVisible) { mIconController.setIconVisibility(mSlotZen, visible); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java index 8871dae3c620..6c303303c8f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/DarkIconManager.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.phone.ui; -import android.view.ViewGroup; import android.widget.LinearLayout; import com.android.internal.statusbar.StatusBarIcon; @@ -64,9 +63,8 @@ public class DarkIconManager extends IconManager { } @Override - protected LinearLayout.LayoutParams onCreateLayoutParams() { - LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize); + protected LinearLayout.LayoutParams onCreateLayoutParams(StatusBarIcon.Shape shape) { + LinearLayout.LayoutParams lp = super.onCreateLayoutParams(shape); lp.setMargins(mIconHorizontalMargin, 0, mIconHorizontalMargin, 0); return lp; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java index 5ad737684ca1..91ead614ffa4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java @@ -20,6 +20,7 @@ import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_BIND import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON; import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW; import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW; +import static com.android.systemui.statusbar.phone.ui.StatusBarIconControllerImpl.usesModeIcons; import android.annotation.Nullable; import android.content.Context; @@ -27,9 +28,8 @@ import android.os.Bundle; import android.view.ViewGroup; import android.widget.LinearLayout; -import androidx.annotation.VisibleForTesting; - import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.statusbar.StatusBarIcon.Shape; import com.android.systemui.demomode.DemoModeCommandReceiver; import com.android.systemui.statusbar.BaseStatusBarFrameLayout; import com.android.systemui.statusbar.StatusBarIconView; @@ -155,12 +155,11 @@ public class IconManager implements DemoModeCommandReceiver { }; } - @VisibleForTesting protected StatusBarIconView addIcon(int index, String slot, boolean blocked, StatusBarIcon icon) { StatusBarIconView view = onCreateStatusBarIconView(slot, blocked); view.set(icon); - mGroup.addView(view, index, onCreateLayoutParams()); + mGroup.addView(view, index, onCreateLayoutParams(icon.shape)); return view; } @@ -174,7 +173,7 @@ public class IconManager implements DemoModeCommandReceiver { int index) { mBindableIcons.put(holder.getSlot(), holder); ModernStatusBarView view = holder.getInitializer().createAndBind(mContext); - mGroup.addView(view, index, onCreateLayoutParams()); + mGroup.addView(view, index, onCreateLayoutParams(Shape.WRAP_CONTENT)); if (mIsInDemoMode) { mDemoStatusIcons.addBindableIcon(holder); } @@ -183,7 +182,7 @@ public class IconManager implements DemoModeCommandReceiver { protected StatusIconDisplayable addNewWifiIcon(int index, String slot) { ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot); - mGroup.addView(view, index, onCreateLayoutParams()); + mGroup.addView(view, index, onCreateLayoutParams(Shape.WRAP_CONTENT)); if (mIsInDemoMode) { mDemoStatusIcons.addModernWifiView(mWifiViewModel); @@ -199,7 +198,7 @@ public class IconManager implements DemoModeCommandReceiver { int subId ) { BaseStatusBarFrameLayout view = onCreateModernStatusBarMobileView(slot, subId); - mGroup.addView(view, index, onCreateLayoutParams()); + mGroup.addView(view, index, onCreateLayoutParams(Shape.WRAP_CONTENT)); if (mIsInDemoMode) { Context mobileContext = mMobileContextProvider @@ -233,8 +232,12 @@ public class IconManager implements DemoModeCommandReceiver { ); } - protected LinearLayout.LayoutParams onCreateLayoutParams() { - return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize); + protected LinearLayout.LayoutParams onCreateLayoutParams(Shape shape) { + int width = usesModeIcons() && shape == StatusBarIcon.Shape.FIXED_SPACE + ? mIconSize + : ViewGroup.LayoutParams.WRAP_CONTENT; + + return new LinearLayout.LayoutParams(width, mIconSize); } protected void destroy() { @@ -256,6 +259,13 @@ public class IconManager implements DemoModeCommandReceiver { /** Called once an icon has been set. */ public void onSetIcon(int viewIndex, StatusBarIcon icon) { StatusBarIconView view = (StatusBarIconView) mGroup.getChildAt(viewIndex); + if (usesModeIcons()) { + ViewGroup.LayoutParams current = view.getLayoutParams(); + ViewGroup.LayoutParams desired = onCreateLayoutParams(icon.shape); + if (desired.width != current.width || desired.height != current.height) { + view.setLayoutParams(desired); + } + } view.set(icon); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java index ee528e915079..0459b9749e0a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconController.java @@ -70,7 +70,8 @@ public interface StatusBarIconController { * @param preloadedIcon optional drawable corresponding to {@code iconResId}, if known */ void setResourceIcon(String slot, @Nullable String resPackage, @DrawableRes int iconResId, - @Nullable Drawable preloadedIcon, CharSequence contentDescription); + @Nullable Drawable preloadedIcon, CharSequence contentDescription, + StatusBarIcon.Shape shape); /** * Sets up a wifi icon using the new data pipeline. No effect if the wifi icon has already been diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java index ad3a9e350c4b..9b6d32bd179d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java @@ -234,13 +234,14 @@ public class StatusBarIconControllerImpl implements Tunable, Icon.createWithResource(mContext, resourceId), /* preloadedIcon= */ null, contentDescription, - StatusBarIcon.Type.SystemIcon); + StatusBarIcon.Type.SystemIcon, + StatusBarIcon.Shape.WRAP_CONTENT); } @Override public void setResourceIcon(String slot, @Nullable String resPackage, @DrawableRes int iconResId, @Nullable Drawable preloadedIcon, - CharSequence contentDescription) { + CharSequence contentDescription, StatusBarIcon.Shape shape) { if (!usesModeIcons()) { Log.wtf("TAG", "StatusBarIconController.setResourceIcon() should not be called without " @@ -260,12 +261,13 @@ public class StatusBarIconControllerImpl implements Tunable, icon, preloadedIcon, contentDescription, - StatusBarIcon.Type.ResourceIcon); + StatusBarIcon.Type.ResourceIcon, + shape); } private void setResourceIconInternal(String slot, Icon resourceIcon, @Nullable Drawable preloadedIcon, CharSequence contentDescription, - StatusBarIcon.Type type) { + StatusBarIcon.Type type, StatusBarIcon.Shape shape) { checkArgument(resourceIcon.getType() == Icon.TYPE_RESOURCE, "Expected Icon of TYPE_RESOURCE, but got " + resourceIcon.getType()); String resPackage = resourceIcon.getResPackage(); @@ -277,7 +279,7 @@ public class StatusBarIconControllerImpl implements Tunable, if (holder == null) { StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, resPackage, resourceIcon, /* iconLevel= */ 0, /* number=*/ 0, - contentDescription, type); + contentDescription, type, shape); icon.preloadedIcon = preloadedIcon; holder = StatusBarIconHolder.fromIcon(icon); setIcon(slot, holder); @@ -286,6 +288,7 @@ public class StatusBarIconControllerImpl implements Tunable, holder.getIcon().icon = resourceIcon; holder.getIcon().contentDescription = contentDescription; holder.getIcon().type = type; + holder.getIcon().shape = shape; holder.getIcon().preloadedIcon = preloadedIcon; handleSet(slot, holder); } @@ -578,7 +581,7 @@ public class StatusBarIconControllerImpl implements Tunable, } } - private static boolean usesModeIcons() { + static boolean usesModeIcons() { return android.app.Flags.modesApi() && android.app.Flags.modesUi() && android.app.Flags.modesUiIcons(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java index 71bcdfcba049..6cebcbd2731a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java @@ -22,8 +22,10 @@ import android.os.UserManager; import com.android.internal.R; import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager; +import com.android.settingslib.notification.modes.ZenIconLoader; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.log.LogBuffer; import com.android.systemui.log.LogBufferFactory; import com.android.systemui.settings.UserTracker; @@ -79,6 +81,7 @@ import dagger.Module; import dagger.Provides; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import javax.inject.Named; @@ -236,4 +239,12 @@ public interface StatusBarPolicyModule { static LogBuffer provideCastControllerLog(LogBufferFactory factory) { return factory.create("CastControllerLog", 50); } + + /** Provides a {@link ZenIconLoader} that fetches icons in a background thread. */ + @Provides + @SysUISingleton + static ZenIconLoader provideZenIconLoader( + @UiBackground ExecutorService backgroundExecutorService) { + return new ZenIconLoader(backgroundExecutorService); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt index a67b47a9a0c9..93c631f65df7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt @@ -23,16 +23,20 @@ import android.provider.Settings.Secure.ZEN_DURATION_PROMPT import android.util.Log import androidx.concurrent.futures.await import com.android.settingslib.notification.data.repository.ZenModeRepository +import com.android.settingslib.notification.modes.ZenIcon import com.android.settingslib.notification.modes.ZenIconLoader import com.android.settingslib.notification.modes.ZenMode -import com.android.systemui.common.shared.model.Icon -import com.android.systemui.common.shared.model.asIcon +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository +import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes +import com.android.systemui.statusbar.policy.domain.model.ZenModeInfo import java.time.Duration import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map /** @@ -45,9 +49,9 @@ constructor( private val context: Context, private val zenModeRepository: ZenModeRepository, private val notificationSettingsRepository: NotificationSettingsRepository, + @Background private val bgDispatcher: CoroutineDispatcher, + private val iconLoader: ZenIconLoader, ) { - private val iconLoader: ZenIconLoader = ZenIconLoader.getInstance() - val isZenModeEnabled: Flow<Boolean> = zenModeRepository.globalZenMode .map { @@ -76,34 +80,27 @@ constructor( val modes: Flow<List<ZenMode>> = zenModeRepository.modes - val activeModes: Flow<List<ZenMode>> = - modes.map { modes -> modes.filter { mode -> mode.isActive } }.distinctUntilChanged() - - /** Flow returning the most prioritized of the active modes, if any. */ - val mainActiveMode: Flow<ZenMode?> = - activeModes.map { modes -> getMainActiveMode(modes) }.distinctUntilChanged() - - /** - * Given the list of modes (which may include zero or more currently active modes), returns the - * most prioritized of the active modes, if any. - */ - private fun getMainActiveMode(modes: List<ZenMode>): ZenMode? { - return modes.sortedWith(ZenMode.PRIORITIZING_COMPARATOR).firstOrNull { it.isActive } - } + /** Flow returning the currently active mode(s), if any. */ + val activeModes: Flow<ActiveZenModes> = + modes + .map { modes -> + val activeModesList = + modes + .filter { mode -> mode.isActive } + .sortedWith(ZenMode.PRIORITIZING_COMPARATOR) + val mainActiveMode = + activeModesList.firstOrNull()?.let { ZenModeInfo(it.name, getModeIcon(it)) } + + ActiveZenModes(activeModesList.map { m -> m.name }, mainActiveMode) + } + .flowOn(bgDispatcher) + .distinctUntilChanged() - suspend fun getModeIcon(mode: ZenMode): Icon { - return iconLoader.getIcon(context, mode).await().drawable().asIcon() - } + val mainActiveMode: Flow<ZenModeInfo?> = + activeModes.map { a -> a.mainMode }.distinctUntilChanged() - /** - * Given the list of modes (which may include zero or more currently active modes), returns an - * icon representing the active mode, if any (or, if multiple modes are active, to the most - * prioritized one). This icon is suitable for use in the status bar or lockscreen (uses the - * standard DND icon for implicit modes, instead of the launcher icon of the associated - * package). - */ - suspend fun getActiveModeIcon(modes: List<ZenMode>): Icon? { - return getMainActiveMode(modes)?.let { m -> getModeIcon(m) } + suspend fun getModeIcon(mode: ZenMode): ZenIcon { + return iconLoader.getIcon(context, mode).await() } fun activateMode(zenMode: ZenMode) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ActiveZenModes.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ActiveZenModes.kt new file mode 100644 index 000000000000..569e517d6ed5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ActiveZenModes.kt @@ -0,0 +1,30 @@ +/* + * 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.policy.domain.model + +import com.android.settingslib.notification.modes.ZenMode + +/** + * Represents the list of [ZenMode] instances that are currently active. + * + * @property modeNames Names of all the active modes, sorted by their priority. + * @property mainMode The most prioritized active mode, if any modes active. Guaranteed to be + * non-null if [modeNames] is not empty. + */ +data class ActiveZenModes(val modeNames: List<String>, val mainMode: ZenModeInfo?) { + fun isAnyActive(): Boolean = modeNames.isNotEmpty() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ZenModeInfo.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ZenModeInfo.kt new file mode 100644 index 000000000000..5004f4c21371 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/model/ZenModeInfo.kt @@ -0,0 +1,23 @@ +/* + * 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.policy.domain.model + +import com.android.settingslib.notification.modes.ZenIcon +import com.android.settingslib.notification.modes.ZenMode + +/** Name and icon of a [ZenMode] */ +data class ZenModeInfo(val name: String, val icon: ZenIcon) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt index be90bec03e52..841071347c08 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt @@ -23,6 +23,7 @@ import android.provider.Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID import com.android.settingslib.notification.modes.EnableZenModeDialog import com.android.settingslib.notification.modes.ZenMode +import com.android.systemui.common.shared.model.asIcon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.qs.tiles.dialog.QSZenModeDialogMetricsLogger @@ -88,7 +89,7 @@ constructor( modesList.map { mode -> ModeTileViewModel( id = mode.id, - icon = zenModeInteractor.getModeIcon(mode), + icon = zenModeInteractor.getModeIcon(mode).drawable().asIcon(), text = mode.name, subtext = getTileSubtext(mode), enabled = mode.isActive, diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java index ecf1165566dc..70774f13fe6b 100644 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/GlobalConcurrencyModule.java @@ -28,6 +28,7 @@ import dagger.Module; import dagger.Provides; import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.inject.Singleton; @@ -81,6 +82,18 @@ public abstract class GlobalConcurrencyModule { @Singleton @UiBackground public static Executor provideUiBackgroundExecutor() { + return provideUiBackgroundExecutorService(); + } + + /** + * Provide an ExecutorService specifically for running UI operations on a separate thread. + * + * <p>Keep submitted runnables short and to the point, just as with any other UI code. + */ + @Provides + @Singleton + @UiBackground + public static ExecutorService provideUiBackgroundExecutorService() { return Executors.newSingleThreadExecutor(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt index 19735e29834e..8435b1cb71dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ModesTileTest.kt @@ -25,9 +25,10 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.settingslib.notification.data.repository.FakeZenModeRepository import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost @@ -41,16 +42,15 @@ import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig import com.android.systemui.res.R -import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository -import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor +import com.android.systemui.statusbar.policy.data.repository.zenModeRepository +import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.After @@ -59,7 +59,6 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations -import org.mockito.kotlin.mock import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @@ -68,6 +67,9 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) class ModesTileTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val testDispatcher = kosmos.testDispatcher @Mock private lateinit var qsHost: QSHost @@ -85,17 +87,10 @@ class ModesTileTest : SysuiTestCase() { @Mock private lateinit var dialogDelegate: ModesDialogDelegate - private val testDispatcher = UnconfinedTestDispatcher() - private val testScope = TestScope(testDispatcher) - private val inputHandler = FakeQSTileIntentUserInputHandler() - private val zenModeRepository = FakeZenModeRepository() + private val zenModeRepository = kosmos.zenModeRepository private val tileDataInteractor = - ModesTileDataInteractor( - context, - ZenModeInteractor(context, zenModeRepository, mock<NotificationSettingsRepository>()), - testDispatcher - ) + ModesTileDataInteractor(context, kosmos.zenModeInteractor, testDispatcher) private val mapper = ModesTileMapper( context.orCreateTestableResources diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt index 76dc65cbc915..2ed34735db1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt @@ -34,6 +34,7 @@ import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.internal.statusbar.StatusBarIcon import com.android.settingslib.notification.modes.TestModeBuilder import com.android.systemui.Flags import com.android.systemui.SysuiTestCase @@ -41,6 +42,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.State +import com.android.systemui.kosmos.testScope import com.android.systemui.privacy.PrivacyItemController import com.android.systemui.privacy.logging.PrivacyLogger import com.android.systemui.screenrecord.RecordingController @@ -71,9 +73,7 @@ import com.android.systemui.util.time.DateFormatUtil import com.android.systemui.util.time.FakeSystemClock import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -145,7 +145,7 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { private lateinit var alarmCallbackCaptor: ArgumentCaptor<NextAlarmController.NextAlarmChangeCallback> - private val testScope = TestScope(UnconfinedTestDispatcher()) + private val testScope = kosmos.testScope private val fakeConnectedDisplayStateProvider = FakeConnectedDisplayStateProvider() private val zenModeController = FakeZenModeController() @@ -249,7 +249,7 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { statusBarPolicy.init() clearInvocations(iconController) - fakeConnectedDisplayStateProvider.emit(State.CONNECTED) + fakeConnectedDisplayStateProvider.setState(State.CONNECTED) runCurrent() verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true) @@ -261,7 +261,8 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { statusBarPolicy.init() clearInvocations(iconController) - fakeConnectedDisplayStateProvider.emit(State.DISCONNECTED) + fakeConnectedDisplayStateProvider.setState(State.DISCONNECTED) + runCurrent() verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, false) } @@ -272,9 +273,12 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { statusBarPolicy.init() clearInvocations(iconController) - fakeConnectedDisplayStateProvider.emit(State.CONNECTED) - fakeConnectedDisplayStateProvider.emit(State.DISCONNECTED) - fakeConnectedDisplayStateProvider.emit(State.CONNECTED) + fakeConnectedDisplayStateProvider.setState(State.CONNECTED) + runCurrent() + fakeConnectedDisplayStateProvider.setState(State.DISCONNECTED) + runCurrent() + fakeConnectedDisplayStateProvider.setState(State.CONNECTED) + runCurrent() inOrder(iconController).apply { verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true) @@ -289,7 +293,8 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { statusBarPolicy.init() clearInvocations(iconController) - fakeConnectedDisplayStateProvider.emit(State.CONNECTED_SECURE) + fakeConnectedDisplayStateProvider.setState(State.CONNECTED_SECURE) + runCurrent() verify(iconController).setIconVisibility(CONNECTED_DISPLAY_SLOT, true) } @@ -390,7 +395,7 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_UI_ICONS) + @EnableFlags(android.app.Flags.FLAG_MODES_UI, android.app.Flags.FLAG_MODES_UI_ICONS) fun zenModeInteractorActiveModeChanged_showsModeIcon() = testScope.runTest { statusBarPolicy.init() @@ -403,8 +408,8 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { .setName("Bedtime Mode") .setType(AutomaticZenRule.TYPE_BEDTIME) .setActive(true) - .setPackage("some.package") - .setIconResId(123) + .setPackage(mContext.packageName) + .setIconResId(android.R.drawable.ic_lock_lock) .build(), TestModeBuilder() .setId("other") @@ -412,7 +417,7 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { .setType(AutomaticZenRule.TYPE_OTHER) .setActive(true) .setPackage(SystemZenRules.PACKAGE_ANDROID) - .setIconResId(456) + .setIconResId(android.R.drawable.ic_media_play) .build(), ) ) @@ -422,17 +427,25 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { verify(iconController) .setResourceIcon( eq(ZEN_SLOT), - eq("some.package"), - eq(123), - eq(null), - eq("Bedtime Mode") + eq(mContext.packageName), + eq(android.R.drawable.ic_lock_lock), + any(), // non-null + eq("Bedtime Mode"), + eq(StatusBarIcon.Shape.FIXED_SPACE) ) zenModeRepository.deactivateMode("bedtime") runCurrent() verify(iconController) - .setResourceIcon(eq(ZEN_SLOT), eq(null), eq(456), eq(null), eq("Other Mode")) + .setResourceIcon( + eq(ZEN_SLOT), + eq(null), + eq(android.R.drawable.ic_media_play), + any(), // non-null + eq("Other Mode"), + eq(StatusBarIcon.Shape.FIXED_SPACE) + ) zenModeRepository.deactivateMode("other") runCurrent() @@ -441,7 +454,7 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { } @Test - @EnableFlags(android.app.Flags.FLAG_MODES_UI_ICONS) + @EnableFlags(android.app.Flags.FLAG_MODES_UI, android.app.Flags.FLAG_MODES_UI_ICONS) fun zenModeControllerOnGlobalZenChanged_doesNotUpdateDndIcon() { statusBarPolicy.init() reset(iconController) @@ -450,7 +463,8 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { verify(iconController, never()).setIconVisibility(eq(ZEN_SLOT), any()) verify(iconController, never()).setIcon(eq(ZEN_SLOT), anyInt(), any()) - verify(iconController, never()).setResourceIcon(eq(ZEN_SLOT), any(), any(), any(), any()) + verify(iconController, never()) + .setResourceIcon(eq(ZEN_SLOT), any(), any(), any(), any(), any()) } @Test @@ -466,7 +480,7 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { verify(iconController, never()).setIconVisibility(eq(ZEN_SLOT), any()) verify(iconController, never()).setIcon(eq(ZEN_SLOT), anyInt(), any()) verify(iconController, never()) - .setResourceIcon(eq(ZEN_SLOT), any(), any(), any(), any()) + .setResourceIcon(eq(ZEN_SLOT), any(), any(), any(), any(), any()) } @Test @@ -529,9 +543,11 @@ class PhoneStatusBarPolicyTest : SysuiTestCase() { } private class FakeConnectedDisplayStateProvider : ConnectedDisplayInteractor { - private val flow = MutableSharedFlow<State>() + private val flow = MutableStateFlow(State.DISCONNECTED) - suspend fun emit(value: State) = flow.emit(value) + fun setState(value: State) { + flow.value = value + } override val connectedDisplayState: Flow<State> get() = flow diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/IconManagerTest.kt new file mode 100644 index 000000000000..90732d0183d2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/IconManagerTest.kt @@ -0,0 +1,122 @@ +/* + * 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.phone.ui + +import android.app.Flags +import android.graphics.drawable.Icon +import android.os.UserHandle +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.statusbar.StatusBarIcon +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider +import com.android.systemui.statusbar.phone.StatusBarLocation +import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter +import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter +import com.android.systemui.util.Assert +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.RETURNS_DEEP_STUBS +import org.mockito.kotlin.mock + +@SmallTest +@RunWith(AndroidJUnit4::class) +class IconManagerTest : SysuiTestCase() { + + private lateinit var underTest: IconManager + private lateinit var viewGroup: ViewGroup + + @Before + fun setUp() { + Assert.setTestThread(Thread.currentThread()) + viewGroup = LinearLayout(context) + underTest = + IconManager( + viewGroup, + StatusBarLocation.HOME, + mock<WifiUiAdapter>(defaultAnswer = RETURNS_DEEP_STUBS), + mock<MobileUiAdapter>(defaultAnswer = RETURNS_DEEP_STUBS), + mock<MobileContextProvider>(defaultAnswer = RETURNS_DEEP_STUBS), + ) + } + + @Test + @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS) + fun addIcon_shapeWrapContent_addsIconViewWithVariableWidth() { + val sbIcon = newStatusBarIcon(StatusBarIcon.Shape.WRAP_CONTENT) + + underTest.addIcon(0, "slot", false, sbIcon) + + assertThat(viewGroup.childCount).isEqualTo(1) + val iconView = viewGroup.getChildAt(0) as StatusBarIconView + assertThat(iconView).isNotNull() + + assertThat(iconView.layoutParams.width).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT) + assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER) + } + + @Test + @EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS) + fun addIcon_shapeFixedSpace_addsIconViewWithFixedWidth() { + val sbIcon = newStatusBarIcon(StatusBarIcon.Shape.FIXED_SPACE) + + underTest.addIcon(0, "slot", false, sbIcon) + + assertThat(viewGroup.childCount).isEqualTo(1) + val iconView = viewGroup.getChildAt(0) as StatusBarIconView + assertThat(iconView).isNotNull() + + assertThat(iconView.layoutParams.width).isNotEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT) + assertThat(iconView.layoutParams.width).isEqualTo(iconView.layoutParams.height) + assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.FIT_CENTER) + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI_ICONS) + fun addIcon_iconsFlagOff_addsIconViewWithVariableWidth() { + val sbIcon = newStatusBarIcon(StatusBarIcon.Shape.FIXED_SPACE) + + underTest.addIcon(0, "slot", false, sbIcon) + + assertThat(viewGroup.childCount).isEqualTo(1) + val iconView = viewGroup.getChildAt(0) as StatusBarIconView + assertThat(iconView).isNotNull() + + assertThat(iconView.layoutParams.width).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT) + assertThat(iconView.scaleType).isEqualTo(ImageView.ScaleType.CENTER) + } + + private fun newStatusBarIcon(shape: StatusBarIcon.Shape) = + StatusBarIcon( + UserHandle.CURRENT, + context.packageName, + Icon.createWithResource(context, android.R.drawable.ic_media_next), + 0, + 0, + "", + StatusBarIcon.Type.ResourceIcon, + shape, + ) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt index 26a57e4c1ca9..50a13b93ea15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImplTest.kt @@ -424,7 +424,14 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { @EnableFlags(android.app.Flags.FLAG_MODES_UI, android.app.Flags.FLAG_MODES_UI_ICONS) fun setResourceIcon_setsIconAndPreloadedIconInHolder() { val drawable = ColorDrawable(1) - underTest.setResourceIcon("slot", "some.package", 123, drawable, "description") + underTest.setResourceIcon( + "slot", + "some.package", + 123, + drawable, + "description", + StatusBarIcon.Shape.FIXED_SPACE + ) val iconHolder = iconList.getIconHolder("slot", 0) assertThat(iconHolder).isNotNull() @@ -432,6 +439,7 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { assertThat(iconHolder?.icon?.icon?.resId).isEqualTo(123) assertThat(iconHolder?.icon?.icon?.resPackage).isEqualTo("some.package") assertThat(iconHolder?.icon?.contentDescription).isEqualTo("description") + assertThat(iconHolder?.icon?.shape).isEqualTo(StatusBarIcon.Shape.FIXED_SPACE) assertThat(iconHolder?.icon?.preloadedIcon).isEqualTo(drawable) } diff --git a/packages/SystemUI/tests/utils/src/com/android/settingslib/notification/modes/ZenIconLoaderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/settingslib/notification/modes/ZenIconLoaderKosmos.kt new file mode 100644 index 000000000000..8541d7704517 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/settingslib/notification/modes/ZenIconLoaderKosmos.kt @@ -0,0 +1,23 @@ +/* + * 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.settingslib.notification.modes + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.google.common.util.concurrent.MoreExecutors + +val Kosmos.zenIconLoader by Fixture { ZenIconLoader(MoreExecutors.newDirectExecutorService()) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt index 66be7e7a7a7e..61b53c9a2067 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorKosmos.kt @@ -17,8 +17,10 @@ package com.android.systemui.statusbar.policy.domain.interactor import android.content.testableContext +import com.android.settingslib.notification.modes.zenIconLoader import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.testDispatcher import com.android.systemui.shared.notifications.data.repository.notificationSettingsRepository import com.android.systemui.statusbar.policy.data.repository.zenModeRepository @@ -27,5 +29,7 @@ val Kosmos.zenModeInteractor by Fixture { context = testableContext, zenModeRepository = zenModeRepository, notificationSettingsRepository = notificationSettingsRepository, + bgDispatcher = testDispatcher, + iconLoader = zenIconLoader, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java index 2dbac670b298..0089199cfb88 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java @@ -64,7 +64,8 @@ public class FakeStatusBarIconController extends BaseLeakChecker<IconManager> @Override public void setResourceIcon(String slot, @Nullable String resPackage, int iconResId, - @Nullable Drawable preloadedIcon, CharSequence contentDescription) { + @Nullable Drawable preloadedIcon, CharSequence contentDescription, + StatusBarIcon.Shape shape) { } @Override diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java index 2b55ac52ab75..8271039af674 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java @@ -21,6 +21,8 @@ import static com.android.ravenwood.common.RavenwoodCommonUtils.isOnRavenwood; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.TYPE; +import android.util.Log; + import com.android.ravenwood.common.RavenwoodCommonUtils; import com.android.ravenwood.common.SneakyThrow; @@ -36,6 +38,7 @@ import org.junit.runner.manipulation.Orderable; import org.junit.runner.manipulation.Orderer; import org.junit.runner.manipulation.Sortable; import org.junit.runner.manipulation.Sorter; +import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.Statement; @@ -134,8 +137,10 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde return runner; } - private final TestClass mTestClsas; - private final Runner mRealRunner; + private TestClass mTestClass = null; + private Runner mRealRunner = null; + private Description mDescription = null; + private Throwable mExceptionInConstructor = null; /** Simple logging method. */ private void log(String message) { @@ -149,45 +154,62 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde } public TestClass getTestClass() { - return mTestClsas; + return mTestClass; } /** * Constructor. */ public RavenwoodAwareTestRunner(Class<?> testClass) { - mTestClsas = new TestClass(testClass); - - /* - * If the class has @DisabledOnRavenwood, then we'll delegate to ClassSkippingTestRunner, - * which simply skips it. - */ - if (isOnRavenwood() && !RavenwoodAwareTestRunnerHook.shouldRunClassOnRavenwood( - mTestClsas.getJavaClass())) { - mRealRunner = new ClassSkippingTestRunner(mTestClsas); - return; - } + try { + mTestClass = new TestClass(testClass); + + /* + * If the class has @DisabledOnRavenwood, then we'll delegate to + * ClassSkippingTestRunner, which simply skips it. + */ + if (isOnRavenwood() && !RavenwoodAwareTestRunnerHook.shouldRunClassOnRavenwood( + mTestClass.getJavaClass())) { + mRealRunner = new ClassSkippingTestRunner(mTestClass); + mDescription = mRealRunner.getDescription(); + return; + } - // Find the real runner. - final Class<? extends Runner> realRunner; - final InnerRunner innerRunnerAnnotation = mTestClsas.getAnnotation(InnerRunner.class); - if (innerRunnerAnnotation != null) { - realRunner = innerRunnerAnnotation.value(); - } else { - // Default runner. - realRunner = BlockJUnit4ClassRunner.class; - } + // Find the real runner. + final Class<? extends Runner> realRunner; + final InnerRunner innerRunnerAnnotation = mTestClass.getAnnotation(InnerRunner.class); + if (innerRunnerAnnotation != null) { + realRunner = innerRunnerAnnotation.value(); + } else { + // Default runner. + realRunner = BlockJUnit4ClassRunner.class; + } - onRunnerInitializing(); + onRunnerInitializing(); - try { - log("Initializing the inner runner: " + realRunner); + try { + log("Initializing the inner runner: " + realRunner); - mRealRunner = realRunner.getConstructor(Class.class).newInstance(testClass); + mRealRunner = realRunner.getConstructor(Class.class).newInstance(testClass); + mDescription = mRealRunner.getDescription(); - } catch (InstantiationException | IllegalAccessException - | InvocationTargetException | NoSuchMethodException e) { - throw logAndFail("Failed to instantiate " + realRunner, e); + } catch (InstantiationException | IllegalAccessException + | InvocationTargetException | NoSuchMethodException e) { + throw logAndFail("Failed to instantiate " + realRunner, e); + } + } catch (Throwable th) { + // If we throw in the constructor, Tradefed may not report it and just ignore the class, + // so record it and throw it when the test actually started. + log("Fatal: Exception detected in constructor: " + th.getMessage() + "\n" + + Log.getStackTraceString(th)); + mExceptionInConstructor = new RuntimeException("Exception detected in constructor", + th); + mDescription = Description.createTestDescription(testClass, "Constructor"); + + // This is for testing if tradefed is fixed. + if ("1".equals(System.getenv("RAVENWOOD_THROW_EXCEPTION_IN_TEST_RUNNER"))) { + throw th; + } } } @@ -202,7 +224,7 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde log("onRunnerInitializing"); - RavenwoodAwareTestRunnerHook.onRunnerInitializing(this, mTestClsas); + RavenwoodAwareTestRunnerHook.onRunnerInitializing(this, mTestClass); // Hook point to allow more customization. runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null); @@ -230,7 +252,7 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde @Override public Description getDescription() { - return mRealRunner.getDescription(); + return mDescription; } @Override @@ -241,6 +263,10 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde return; } + if (maybeReportExceptionFromConstructor(notifier)) { + return; + } + sCurrentRunner.set(this); try { runWithHooks(getDescription(), Scope.Runner, Order.First, @@ -250,6 +276,18 @@ public class RavenwoodAwareTestRunner extends Runner implements Filterable, Orde } } + /** Throw the exception detected in the constructor, if any. */ + private boolean maybeReportExceptionFromConstructor(RunNotifier notifier) { + if (mExceptionInConstructor == null) { + return false; + } + notifier.fireTestStarted(mDescription); + notifier.fireTestFailure(new Failure(mDescription, mExceptionInConstructor)); + notifier.fireTestFinished(mDescription); + + return true; + } + @Override public void filter(Filter filter) throws NoTestsRemainException { if (mRealRunner instanceof Filterable r) { diff --git a/ravenwood/runtime-helper-src/libcore-fake/libcore/util/FP16.java b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/FP16.java new file mode 100644 index 000000000000..478503b699a0 --- /dev/null +++ b/ravenwood/runtime-helper-src/libcore-fake/libcore/util/FP16.java @@ -0,0 +1,814 @@ +/* + * Copyright (C) 2019 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 libcore.util; + +/** + * <p>The {@code FP16} class is a wrapper and a utility class to manipulate half-precision 16-bit + * <a href="https://en.wikipedia.org/wiki/Half-precision_floating-point_format">IEEE 754</a> + * floating point data types (also called fp16 or binary16). A half-precision float can be + * created from or converted to single-precision floats, and is stored in a short data type. + * + * <p>The IEEE 754 standard specifies an fp16 as having the following format:</p> + * <ul> + * <li>Sign bit: 1 bit</li> + * <li>Exponent width: 5 bits</li> + * <li>Significand: 10 bits</li> + * </ul> + * + * <p>The format is laid out as follows:</p> + * <pre> + * 1 11111 1111111111 + * ^ --^-- -----^---- + * sign | |_______ significand + * | + * -- exponent + * </pre> + * + * <p>Half-precision floating points can be useful to save memory and/or + * bandwidth at the expense of range and precision when compared to single-precision + * floating points (fp32).</p> + * <p>To help you decide whether fp16 is the right storage type for you need, please + * refer to the table below that shows the available precision throughout the range of + * possible values. The <em>precision</em> column indicates the step size between two + * consecutive numbers in a specific part of the range.</p> + * + * <table summary="Precision of fp16 across the range"> + * <tr><th>Range start</th><th>Precision</th></tr> + * <tr><td>0</td><td>1 ⁄ 16,777,216</td></tr> + * <tr><td>1 ⁄ 16,384</td><td>1 ⁄ 16,777,216</td></tr> + * <tr><td>1 ⁄ 8,192</td><td>1 ⁄ 8,388,608</td></tr> + * <tr><td>1 ⁄ 4,096</td><td>1 ⁄ 4,194,304</td></tr> + * <tr><td>1 ⁄ 2,048</td><td>1 ⁄ 2,097,152</td></tr> + * <tr><td>1 ⁄ 1,024</td><td>1 ⁄ 1,048,576</td></tr> + * <tr><td>1 ⁄ 512</td><td>1 ⁄ 524,288</td></tr> + * <tr><td>1 ⁄ 256</td><td>1 ⁄ 262,144</td></tr> + * <tr><td>1 ⁄ 128</td><td>1 ⁄ 131,072</td></tr> + * <tr><td>1 ⁄ 64</td><td>1 ⁄ 65,536</td></tr> + * <tr><td>1 ⁄ 32</td><td>1 ⁄ 32,768</td></tr> + * <tr><td>1 ⁄ 16</td><td>1 ⁄ 16,384</td></tr> + * <tr><td>1 ⁄ 8</td><td>1 ⁄ 8,192</td></tr> + * <tr><td>1 ⁄ 4</td><td>1 ⁄ 4,096</td></tr> + * <tr><td>1 ⁄ 2</td><td>1 ⁄ 2,048</td></tr> + * <tr><td>1</td><td>1 ⁄ 1,024</td></tr> + * <tr><td>2</td><td>1 ⁄ 512</td></tr> + * <tr><td>4</td><td>1 ⁄ 256</td></tr> + * <tr><td>8</td><td>1 ⁄ 128</td></tr> + * <tr><td>16</td><td>1 ⁄ 64</td></tr> + * <tr><td>32</td><td>1 ⁄ 32</td></tr> + * <tr><td>64</td><td>1 ⁄ 16</td></tr> + * <tr><td>128</td><td>1 ⁄ 8</td></tr> + * <tr><td>256</td><td>1 ⁄ 4</td></tr> + * <tr><td>512</td><td>1 ⁄ 2</td></tr> + * <tr><td>1,024</td><td>1</td></tr> + * <tr><td>2,048</td><td>2</td></tr> + * <tr><td>4,096</td><td>4</td></tr> + * <tr><td>8,192</td><td>8</td></tr> + * <tr><td>16,384</td><td>16</td></tr> + * <tr><td>32,768</td><td>32</td></tr> + * </table> + * + * <p>This table shows that numbers higher than 1024 lose all fractional precision.</p> + * + * @hide + */ + +public final class FP16 { + /** + * The number of bits used to represent a half-precision float value. + * + * @hide + */ + public static final int SIZE = 16; + + /** + * Epsilon is the difference between 1.0 and the next value representable + * by a half-precision floating-point. + * + * @hide + */ + public static final short EPSILON = (short) 0x1400; + + /** + * Maximum exponent a finite half-precision float may have. + * + * @hide + */ + public static final int MAX_EXPONENT = 15; + /** + * Minimum exponent a normalized half-precision float may have. + * + * @hide + */ + public static final int MIN_EXPONENT = -14; + + /** + * Smallest negative value a half-precision float may have. + * + * @hide + */ + public static final short LOWEST_VALUE = (short) 0xfbff; + /** + * Maximum positive finite value a half-precision float may have. + * + * @hide + */ + public static final short MAX_VALUE = (short) 0x7bff; + /** + * Smallest positive normal value a half-precision float may have. + * + * @hide + */ + public static final short MIN_NORMAL = (short) 0x0400; + /** + * Smallest positive non-zero value a half-precision float may have. + * + * @hide + */ + public static final short MIN_VALUE = (short) 0x0001; + /** + * A Not-a-Number representation of a half-precision float. + * + * @hide + */ + public static final short NaN = (short) 0x7e00; + /** + * Negative infinity of type half-precision float. + * + * @hide + */ + public static final short NEGATIVE_INFINITY = (short) 0xfc00; + /** + * Negative 0 of type half-precision float. + * + * @hide + */ + public static final short NEGATIVE_ZERO = (short) 0x8000; + /** + * Positive infinity of type half-precision float. + * + * @hide + */ + public static final short POSITIVE_INFINITY = (short) 0x7c00; + /** + * Positive 0 of type half-precision float. + * + * @hide + */ + public static final short POSITIVE_ZERO = (short) 0x0000; + + /** + * The offset to shift by to obtain the sign bit. + * + * @hide + */ + public static final int SIGN_SHIFT = 15; + + /** + * The offset to shift by to obtain the exponent bits. + * + * @hide + */ + public static final int EXPONENT_SHIFT = 10; + + /** + * The bitmask to AND a number with to obtain the sign bit. + * + * @hide + */ + public static final int SIGN_MASK = 0x8000; + + /** + * The bitmask to AND a number shifted by {@link #EXPONENT_SHIFT} right, to obtain exponent bits. + * + * @hide + */ + public static final int SHIFTED_EXPONENT_MASK = 0x1f; + + /** + * The bitmask to AND a number with to obtain significand bits. + * + * @hide + */ + public static final int SIGNIFICAND_MASK = 0x3ff; + + /** + * The bitmask to AND with to obtain exponent and significand bits. + * + * @hide + */ + public static final int EXPONENT_SIGNIFICAND_MASK = 0x7fff; + + /** + * The offset of the exponent from the actual value. + * + * @hide + */ + public static final int EXPONENT_BIAS = 15; + + private static final int FP32_SIGN_SHIFT = 31; + private static final int FP32_EXPONENT_SHIFT = 23; + private static final int FP32_SHIFTED_EXPONENT_MASK = 0xff; + private static final int FP32_SIGNIFICAND_MASK = 0x7fffff; + private static final int FP32_EXPONENT_BIAS = 127; + private static final int FP32_QNAN_MASK = 0x400000; + private static final int FP32_DENORMAL_MAGIC = 126 << 23; + private static final float FP32_DENORMAL_FLOAT = Float.intBitsToFloat(FP32_DENORMAL_MAGIC); + + /** Hidden constructor to prevent instantiation. */ + private FP16() {} + + /** + * <p>Compares the two specified half-precision float values. The following + * conditions apply during the comparison:</p> + * + * <ul> + * <li>{@link #NaN} is considered by this method to be equal to itself and greater + * than all other half-precision float values (including {@code #POSITIVE_INFINITY})</li> + * <li>{@link #POSITIVE_ZERO} is considered by this method to be greater than + * {@link #NEGATIVE_ZERO}.</li> + * </ul> + * + * @param x The first half-precision float value to compare. + * @param y The second half-precision float value to compare + * + * @return The value {@code 0} if {@code x} is numerically equal to {@code y}, a + * value less than {@code 0} if {@code x} is numerically less than {@code y}, + * and a value greater than {@code 0} if {@code x} is numerically greater + * than {@code y} + * + * @hide + */ + public static int compare(short x, short y) { + if (less(x, y)) return -1; + if (greater(x, y)) return 1; + + // Collapse NaNs, akin to halfToIntBits(), but we want to keep + // (signed) short value types to preserve the ordering of -0.0 + // and +0.0 + short xBits = isNaN(x) ? NaN : x; + short yBits = isNaN(y) ? NaN : y; + + return (xBits == yBits ? 0 : (xBits < yBits ? -1 : 1)); + } + + /** + * Returns the closest integral half-precision float value to the specified + * half-precision float value. Special values are handled in the + * following ways: + * <ul> + * <li>If the specified half-precision float is NaN, the result is NaN</li> + * <li>If the specified half-precision float is infinity (negative or positive), + * the result is infinity (with the same sign)</li> + * <li>If the specified half-precision float is zero (negative or positive), + * the result is zero (with the same sign)</li> + * </ul> + * + * @param h A half-precision float value + * @return The value of the specified half-precision float rounded to the nearest + * half-precision float value + * + * @hide + */ + public static short rint(short h) { + int bits = h & 0xffff; + int abs = bits & EXPONENT_SIGNIFICAND_MASK; + int result = bits; + + if (abs < 0x3c00) { + result &= SIGN_MASK; + if (abs > 0x3800){ + result |= 0x3c00; + } + } else if (abs < 0x6400) { + int exp = 25 - (abs >> 10); + int mask = (1 << exp) - 1; + result += ((1 << (exp - 1)) - (~(abs >> exp) & 1)); + result &= ~mask; + } + if (isNaN((short) result)) { + // if result is NaN mask with qNaN + // (i.e. mask the most significant mantissa bit with 1) + // to comply with hardware implementations (ARM64, Intel, etc). + result |= NaN; + } + + return (short) result; + } + + /** + * Returns the smallest half-precision float value toward negative infinity + * greater than or equal to the specified half-precision float value. + * Special values are handled in the following ways: + * <ul> + * <li>If the specified half-precision float is NaN, the result is NaN</li> + * <li>If the specified half-precision float is infinity (negative or positive), + * the result is infinity (with the same sign)</li> + * <li>If the specified half-precision float is zero (negative or positive), + * the result is zero (with the same sign)</li> + * </ul> + * + * @param h A half-precision float value + * @return The smallest half-precision float value toward negative infinity + * greater than or equal to the specified half-precision float value + * + * @hide + */ + public static short ceil(short h) { + int bits = h & 0xffff; + int abs = bits & EXPONENT_SIGNIFICAND_MASK; + int result = bits; + + if (abs < 0x3c00) { + result &= SIGN_MASK; + result |= 0x3c00 & -(~(bits >> 15) & (abs != 0 ? 1 : 0)); + } else if (abs < 0x6400) { + abs = 25 - (abs >> 10); + int mask = (1 << abs) - 1; + result += mask & ((bits >> 15) - 1); + result &= ~mask; + } + if (isNaN((short) result)) { + // if result is NaN mask with qNaN + // (i.e. mask the most significant mantissa bit with 1) + // to comply with hardware implementations (ARM64, Intel, etc). + result |= NaN; + } + + return (short) result; + } + + /** + * Returns the largest half-precision float value toward positive infinity + * less than or equal to the specified half-precision float value. + * Special values are handled in the following ways: + * <ul> + * <li>If the specified half-precision float is NaN, the result is NaN</li> + * <li>If the specified half-precision float is infinity (negative or positive), + * the result is infinity (with the same sign)</li> + * <li>If the specified half-precision float is zero (negative or positive), + * the result is zero (with the same sign)</li> + * </ul> + * + * @param h A half-precision float value + * @return The largest half-precision float value toward positive infinity + * less than or equal to the specified half-precision float value + * + * @hide + */ + public static short floor(short h) { + int bits = h & 0xffff; + int abs = bits & EXPONENT_SIGNIFICAND_MASK; + int result = bits; + + if (abs < 0x3c00) { + result &= SIGN_MASK; + result |= 0x3c00 & (bits > 0x8000 ? 0xffff : 0x0); + } else if (abs < 0x6400) { + abs = 25 - (abs >> 10); + int mask = (1 << abs) - 1; + result += mask & -(bits >> 15); + result &= ~mask; + } + if (isNaN((short) result)) { + // if result is NaN mask with qNaN + // i.e. (Mask the most significant mantissa bit with 1) + result |= NaN; + } + + return (short) result; + } + + /** + * Returns the truncated half-precision float value of the specified + * half-precision float value. Special values are handled in the following ways: + * <ul> + * <li>If the specified half-precision float is NaN, the result is NaN</li> + * <li>If the specified half-precision float is infinity (negative or positive), + * the result is infinity (with the same sign)</li> + * <li>If the specified half-precision float is zero (negative or positive), + * the result is zero (with the same sign)</li> + * </ul> + * + * @param h A half-precision float value + * @return The truncated half-precision float value of the specified + * half-precision float value + * + * @hide + */ + public static short trunc(short h) { + int bits = h & 0xffff; + int abs = bits & EXPONENT_SIGNIFICAND_MASK; + int result = bits; + + if (abs < 0x3c00) { + result &= SIGN_MASK; + } else if (abs < 0x6400) { + abs = 25 - (abs >> 10); + int mask = (1 << abs) - 1; + result &= ~mask; + } + + return (short) result; + } + + /** + * Returns the smaller of two half-precision float values (the value closest + * to negative infinity). Special values are handled in the following ways: + * <ul> + * <li>If either value is NaN, the result is NaN</li> + * <li>{@link #NEGATIVE_ZERO} is smaller than {@link #POSITIVE_ZERO}</li> + * </ul> + * + * @param x The first half-precision value + * @param y The second half-precision value + * @return The smaller of the two specified half-precision values + * + * @hide + */ + public static short min(short x, short y) { + if (isNaN(x)) return NaN; + if (isNaN(y)) return NaN; + + if ((x & EXPONENT_SIGNIFICAND_MASK) == 0 && (y & EXPONENT_SIGNIFICAND_MASK) == 0) { + return (x & SIGN_MASK) != 0 ? x : y; + } + + return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) < + ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff) ? x : y; + } + + /** + * Returns the larger of two half-precision float values (the value closest + * to positive infinity). Special values are handled in the following ways: + * <ul> + * <li>If either value is NaN, the result is NaN</li> + * <li>{@link #POSITIVE_ZERO} is greater than {@link #NEGATIVE_ZERO}</li> + * </ul> + * + * @param x The first half-precision value + * @param y The second half-precision value + * + * @return The larger of the two specified half-precision values + * + * @hide + */ + public static short max(short x, short y) { + if (isNaN(x)) return NaN; + if (isNaN(y)) return NaN; + + if ((x & EXPONENT_SIGNIFICAND_MASK) == 0 && (y & EXPONENT_SIGNIFICAND_MASK) == 0) { + return (x & SIGN_MASK) != 0 ? y : x; + } + + return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) > + ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff) ? x : y; + } + + /** + * Returns true if the first half-precision float value is less (smaller + * toward negative infinity) than the second half-precision float value. + * If either of the values is NaN, the result is false. + * + * @param x The first half-precision value + * @param y The second half-precision value + * + * @return True if x is less than y, false otherwise + * + * @hide + */ + public static boolean less(short x, short y) { + if (isNaN(x)) return false; + if (isNaN(y)) return false; + + return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) < + ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff); + } + + /** + * Returns true if the first half-precision float value is less (smaller + * toward negative infinity) than or equal to the second half-precision + * float value. If either of the values is NaN, the result is false. + * + * @param x The first half-precision value + * @param y The second half-precision value + * + * @return True if x is less than or equal to y, false otherwise + * + * @hide + */ + public static boolean lessEquals(short x, short y) { + if (isNaN(x)) return false; + if (isNaN(y)) return false; + + return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) <= + ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff); + } + + /** + * Returns true if the first half-precision float value is greater (larger + * toward positive infinity) than the second half-precision float value. + * If either of the values is NaN, the result is false. + * + * @param x The first half-precision value + * @param y The second half-precision value + * + * @return True if x is greater than y, false otherwise + * + * @hide + */ + public static boolean greater(short x, short y) { + if (isNaN(x)) return false; + if (isNaN(y)) return false; + + return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) > + ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff); + } + + /** + * Returns true if the first half-precision float value is greater (larger + * toward positive infinity) than or equal to the second half-precision float + * value. If either of the values is NaN, the result is false. + * + * @param x The first half-precision value + * @param y The second half-precision value + * + * @return True if x is greater than y, false otherwise + * + * @hide + */ + public static boolean greaterEquals(short x, short y) { + if (isNaN(x)) return false; + if (isNaN(y)) return false; + + return ((x & SIGN_MASK) != 0 ? 0x8000 - (x & 0xffff) : x & 0xffff) >= + ((y & SIGN_MASK) != 0 ? 0x8000 - (y & 0xffff) : y & 0xffff); + } + + /** + * Returns true if the two half-precision float values are equal. + * If either of the values is NaN, the result is false. {@link #POSITIVE_ZERO} + * and {@link #NEGATIVE_ZERO} are considered equal. + * + * @param x The first half-precision value + * @param y The second half-precision value + * + * @return True if x is equal to y, false otherwise + * + * @hide + */ + public static boolean equals(short x, short y) { + if (isNaN(x)) return false; + if (isNaN(y)) return false; + + return x == y || ((x | y) & EXPONENT_SIGNIFICAND_MASK) == 0; + } + + /** + * Returns true if the specified half-precision float value represents + * infinity, false otherwise. + * + * @param h A half-precision float value + * @return True if the value is positive infinity or negative infinity, + * false otherwise + * + * @hide + */ + public static boolean isInfinite(short h) { + return (h & EXPONENT_SIGNIFICAND_MASK) == POSITIVE_INFINITY; + } + + /** + * Returns true if the specified half-precision float value represents + * a Not-a-Number, false otherwise. + * + * @param h A half-precision float value + * @return True if the value is a NaN, false otherwise + * + * @hide + */ + public static boolean isNaN(short h) { + return (h & EXPONENT_SIGNIFICAND_MASK) > POSITIVE_INFINITY; + } + + /** + * Returns true if the specified half-precision float value is normalized + * (does not have a subnormal representation). If the specified value is + * {@link #POSITIVE_INFINITY}, {@link #NEGATIVE_INFINITY}, + * {@link #POSITIVE_ZERO}, {@link #NEGATIVE_ZERO}, NaN or any subnormal + * number, this method returns false. + * + * @param h A half-precision float value + * @return True if the value is normalized, false otherwise + * + * @hide + */ + public static boolean isNormalized(short h) { + return (h & POSITIVE_INFINITY) != 0 && (h & POSITIVE_INFINITY) != POSITIVE_INFINITY; + } + + /** + * <p>Converts the specified half-precision float value into a + * single-precision float value. The following special cases are handled:</p> + * <ul> + * <li>If the input is {@link #NaN}, the returned value is {@link Float#NaN}</li> + * <li>If the input is {@link #POSITIVE_INFINITY} or + * {@link #NEGATIVE_INFINITY}, the returned value is respectively + * {@link Float#POSITIVE_INFINITY} or {@link Float#NEGATIVE_INFINITY}</li> + * <li>If the input is 0 (positive or negative), the returned value is +/-0.0f</li> + * <li>Otherwise, the returned value is a normalized single-precision float value</li> + * </ul> + * + * @param h The half-precision float value to convert to single-precision + * @return A normalized single-precision float value + * + * @hide + */ + public static float toFloat(short h) { + int bits = h & 0xffff; + int s = bits & SIGN_MASK; + int e = (bits >>> EXPONENT_SHIFT) & SHIFTED_EXPONENT_MASK; + int m = (bits ) & SIGNIFICAND_MASK; + + int outE = 0; + int outM = 0; + + if (e == 0) { // Denormal or 0 + if (m != 0) { + // Convert denorm fp16 into normalized fp32 + float o = Float.intBitsToFloat(FP32_DENORMAL_MAGIC + m); + o -= FP32_DENORMAL_FLOAT; + return s == 0 ? o : -o; + } + } else { + outM = m << 13; + if (e == 0x1f) { // Infinite or NaN + outE = 0xff; + if (outM != 0) { // SNaNs are quieted + outM |= FP32_QNAN_MASK; + } + } else { + outE = e - EXPONENT_BIAS + FP32_EXPONENT_BIAS; + } + } + + int out = (s << 16) | (outE << FP32_EXPONENT_SHIFT) | outM; + return Float.intBitsToFloat(out); + } + + /** + * <p>Converts the specified single-precision float value into a + * half-precision float value. The following special cases are handled:</p> + * <ul> + * <li>If the input is NaN (see {@link Float#isNaN(float)}), the returned + * value is {@link #NaN}</li> + * <li>If the input is {@link Float#POSITIVE_INFINITY} or + * {@link Float#NEGATIVE_INFINITY}, the returned value is respectively + * {@link #POSITIVE_INFINITY} or {@link #NEGATIVE_INFINITY}</li> + * <li>If the input is 0 (positive or negative), the returned value is + * {@link #POSITIVE_ZERO} or {@link #NEGATIVE_ZERO}</li> + * <li>If the input is a less than {@link #MIN_VALUE}, the returned value + * is flushed to {@link #POSITIVE_ZERO} or {@link #NEGATIVE_ZERO}</li> + * <li>If the input is a less than {@link #MIN_NORMAL}, the returned value + * is a denorm half-precision float</li> + * <li>Otherwise, the returned value is rounded to the nearest + * representable half-precision float value</li> + * </ul> + * + * @param f The single-precision float value to convert to half-precision + * @return A half-precision float value + * + * @hide + */ + public static short toHalf(float f) { + int bits = Float.floatToRawIntBits(f); + int s = (bits >>> FP32_SIGN_SHIFT ); + int e = (bits >>> FP32_EXPONENT_SHIFT) & FP32_SHIFTED_EXPONENT_MASK; + int m = (bits ) & FP32_SIGNIFICAND_MASK; + + int outE = 0; + int outM = 0; + + if (e == 0xff) { // Infinite or NaN + outE = 0x1f; + outM = m != 0 ? 0x200 : 0; + } else { + e = e - FP32_EXPONENT_BIAS + EXPONENT_BIAS; + if (e >= 0x1f) { // Overflow + outE = 0x1f; + } else if (e <= 0) { // Underflow + if (e < -10) { + // The absolute fp32 value is less than MIN_VALUE, flush to +/-0 + } else { + // The fp32 value is a normalized float less than MIN_NORMAL, + // we convert to a denorm fp16 + m = m | 0x800000; + int shift = 14 - e; + outM = m >> shift; + + int lowm = m & ((1 << shift) - 1); + int hway = 1 << (shift - 1); + // if above halfway or exactly halfway and outM is odd + if (lowm + (outM & 1) > hway){ + // Round to nearest even + // Can overflow into exponent bit, which surprisingly is OK. + // This increment relies on the +outM in the return statement below + outM++; + } + } + } else { + outE = e; + outM = m >> 13; + // if above halfway or exactly halfway and outM is odd + if ((m & 0x1fff) + (outM & 0x1) > 0x1000) { + // Round to nearest even + // Can overflow into exponent bit, which surprisingly is OK. + // This increment relies on the +outM in the return statement below + outM++; + } + } + } + // The outM is added here as the +1 increments for outM above can + // cause an overflow in the exponent bit which is OK. + return (short) ((s << SIGN_SHIFT) | (outE << EXPONENT_SHIFT) + outM); + } + + /** + * <p>Returns a hexadecimal string representation of the specified half-precision + * float value. If the value is a NaN, the result is <code>"NaN"</code>, + * otherwise the result follows this format:</p> + * <ul> + * <li>If the sign is positive, no sign character appears in the result</li> + * <li>If the sign is negative, the first character is <code>'-'</code></li> + * <li>If the value is inifinity, the string is <code>"Infinity"</code></li> + * <li>If the value is 0, the string is <code>"0x0.0p0"</code></li> + * <li>If the value has a normalized representation, the exponent and + * significand are represented in the string in two fields. The significand + * starts with <code>"0x1."</code> followed by its lowercase hexadecimal + * representation. Trailing zeroes are removed unless all digits are 0, then + * a single zero is used. The significand representation is followed by the + * exponent, represented by <code>"p"</code>, itself followed by a decimal + * string of the unbiased exponent</li> + * <li>If the value has a subnormal representation, the significand starts + * with <code>"0x0."</code> followed by its lowercase hexadecimal + * representation. Trailing zeroes are removed unless all digits are 0, then + * a single zero is used. The significand representation is followed by the + * exponent, represented by <code>"p-14"</code></li> + * </ul> + * + * @param h A half-precision float value + * @return A hexadecimal string representation of the specified value + * + * @hide + */ + public static String toHexString(short h) { + StringBuilder o = new StringBuilder(); + + int bits = h & 0xffff; + int s = (bits >>> SIGN_SHIFT ); + int e = (bits >>> EXPONENT_SHIFT) & SHIFTED_EXPONENT_MASK; + int m = (bits ) & SIGNIFICAND_MASK; + + if (e == 0x1f) { // Infinite or NaN + if (m == 0) { + if (s != 0) o.append('-'); + o.append("Infinity"); + } else { + o.append("NaN"); + } + } else { + if (s == 1) o.append('-'); + if (e == 0) { + if (m == 0) { + o.append("0x0.0p0"); + } else { + o.append("0x0."); + String significand = Integer.toHexString(m); + o.append(significand.replaceFirst("0{2,}$", "")); + o.append("p-14"); + } + } else { + o.append("0x1."); + String significand = Integer.toHexString(m); + o.append(significand.replaceFirst("0{2,}$", "")); + o.append('p'); + o.append(Integer.toString(e - EXPONENT_BIAS)); + } + } + + return o.toString(); + } +} diff --git a/ravenwood/scripts/remove-ravenizer-output.sh b/ravenwood/scripts/remove-ravenizer-output.sh new file mode 100755 index 000000000000..be15b711b980 --- /dev/null +++ b/ravenwood/scripts/remove-ravenizer-output.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# 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. + +# Delete all the ravenizer output jar files from Soong's intermediate directory. + +# `-a -prune` is needed because otherwise find would be confused if the directory disappears. + +find "${ANDROID_BUILD_TOP:?}/out/soong/.intermediates/" \ + -type d \ + -name 'ravenizer' \ + -print \ + -exec rm -fr \{\} \; \ + -a -prune diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt index d8366c58c50d..34239b826c67 100644 --- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt @@ -46,6 +46,7 @@ android.util.EmptyArray android.util.EventLog android.util.FloatProperty android.util.FloatMath +android.util.Half android.util.IndentingPrintWriter android.util.IntArray android.util.IntProperty @@ -277,7 +278,11 @@ android.graphics.ColorSpace android.graphics.Insets android.graphics.Interpolator android.graphics.Matrix +android.graphics.Matrix44 +android.graphics.Outline +android.graphics.ParcelableColorSpace android.graphics.Path +android.graphics.PixelFormat android.graphics.Point android.graphics.PointF android.graphics.Rect diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt index 3a7fab39e4ac..0dcd271562d1 100644 --- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Exceptions.kt @@ -17,7 +17,14 @@ package com.android.platform.test.ravenwood.ravenizer +import com.android.hoststubgen.UserErrorException + /** * Use it for internal exception that really shouldn't happen. */ class RavenizerInternalException(message: String) : Exception(message) + +/** + * Thrown when an invalid test is detected in the target jar. (e.g. JUni3 tests) + */ +class RavenizerInvalidTestException(message: String) : Exception(message), UserErrorException diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt index e92ef7216e25..a38512ec9f2d 100644 --- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt @@ -44,6 +44,9 @@ data class RavenizerStats( /** Time took to build [ClasNodes] */ var loadStructureTime: Double = .0, + /** Time took to validate the classes */ + var validationTime: Double = .0, + /** Total real time spent for converting the jar file */ var totalProcessTime: Double = .0, @@ -67,6 +70,7 @@ data class RavenizerStats( RavenizerStats{ totalTime=$totalTime, loadStructureTime=$loadStructureTime, + validationTime=$validationTime, totalProcessTime=$totalProcessTime, totalConversionTime=$totalConversionTime, totalCopyTime=$totalCopyTime, @@ -84,16 +88,44 @@ data class RavenizerStats( class Ravenizer(val options: RavenizerOptions) { fun run() { val stats = RavenizerStats() + + val fatalValidation = options.fatalValidation.get + stats.totalTime = log.nTime { - process(options.inJar.get, options.outJar.get, stats) + process( + options.inJar.get, + options.outJar.get, + options.enableValidation.get, + fatalValidation, + stats, + ) } log.i(stats.toString()) } - private fun process(inJar: String, outJar: String, stats: RavenizerStats) { + private fun process( + inJar: String, + outJar: String, + enableValidation: Boolean, + fatalValidation: Boolean, + stats: RavenizerStats, + ) { var allClasses = ClassNodes.loadClassStructures(inJar) { time -> stats.loadStructureTime = time } + if (enableValidation) { + stats.validationTime = log.iTime("Validating classes") { + if (!validateClasses(allClasses)) { + var message = "Invalid test class(es) detected." + + " See error log for details." + if (fatalValidation) { + throw RavenizerInvalidTestException(message) + } else { + log.w("Warning: $message") + } + } + } + } stats.totalProcessTime = log.iTime("$executableName processing $inJar") { ZipFile(inJar).use { inZip -> diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt index e85e3be31b77..e8341e5ceb06 100644 --- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt @@ -27,6 +27,12 @@ class RavenizerOptions( /** Output jar file */ var outJar: SetOnce<String> = SetOnce(""), + + /** Whether to enable test validation. */ + var enableValidation: SetOnce<Boolean> = SetOnce(true), + + /** Whether the validation failure is fatal or not. */ + var fatalValidation: SetOnce<Boolean> = SetOnce(false), ) { companion object { fun parseArgs(args: Array<String>): RavenizerOptions { @@ -52,6 +58,12 @@ class RavenizerOptions( "--in-jar" -> ret.inJar.set(nextArg()).ensureFileExists() "--out-jar" -> ret.outJar.set(nextArg()) + "--enable-validation" -> ret.enableValidation.set(true) + "--disable-validation" -> ret.enableValidation.set(false) + + "--fatal-validation" -> ret.fatalValidation.set(true) + "--no-fatal-validation" -> ret.fatalValidation.set(false) + else -> throw ArgumentsException("Unknown option: $arg") } } catch (e: SetOnce.SetMoreThanOnceException) { @@ -74,6 +86,8 @@ class RavenizerOptions( RavenizerOptions{ inJar=$inJar, outJar=$outJar, + enableValidation=$enableValidation, + fatalValidation=$fatalValidation, } """.trimIndent() } diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt index e026e7ab3679..059f5a4b4d47 100644 --- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt @@ -87,9 +87,12 @@ fun String.shouldByBypassed(): Boolean { return this.startsWithAny( "java/", // just in case... "javax/", + "junit/", "org/junit/", "org/mockito/", "kotlin/", + "androidx/", + "android/support/", // TODO -- anything else? ) } diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt new file mode 100644 index 000000000000..27092d28ae5e --- /dev/null +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.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.platform.test.ravenwood.ravenizer + +import com.android.hoststubgen.asm.ClassNodes +import com.android.hoststubgen.asm.startsWithAny +import com.android.hoststubgen.asm.toHumanReadableClassName +import com.android.hoststubgen.log +import org.objectweb.asm.tree.ClassNode + +fun validateClasses(classes: ClassNodes): Boolean { + var allOk = true + classes.forEach { allOk = checkClass(it, classes) && allOk } + + return allOk +} + +/** + * Validate a class. + * + * - A test class shouldn't extend + * + */ +fun checkClass(cn: ClassNode, classes: ClassNodes): Boolean { + if (cn.name.shouldByBypassed()) { + // Class doesn't need to be checked. + return true + } + var allOk = true + + // See if there's any class that extends a legacy base class. + // But ignore the base classes in android.test. + if (!cn.name.startsWithAny("android/test/")) { + allOk = checkSuperClass(cn, cn, classes) && allOk + } + return allOk +} + +fun checkSuperClass(targetClass: ClassNode, currentClass: ClassNode, classes: ClassNodes): Boolean { + if (currentClass.superName == null || currentClass.superName == "java/lang/Object") { + return true // No parent class + } + if (currentClass.superName.isLegacyTestBaseClass()) { + log.e("Error: Class ${targetClass.name.toHumanReadableClassName()} extends" + + " a legacy test class ${currentClass.superName.toHumanReadableClassName()}.") + return false + } + classes.findClass(currentClass.superName)?.let { + return checkSuperClass(targetClass, it, classes) + } + // Super class not found. + // log.w("Class ${currentClass.superName} not found.") + return true +} + +/** + * Check if a class internal name is a known legacy test base class. + */ +fun String.isLegacyTestBaseClass(): Boolean { + return this.startsWithAny( + "junit/framework/TestCase", + + // In case the test doesn't statically include JUnit, we need + "android/test/AndroidTestCase", + "android/test/InstrumentationTestCase", + "android/test/InstrumentationTestSuite", + ) +} diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt index 25cad0213b72..dc934ebabbf2 100644 --- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt +++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt @@ -183,7 +183,7 @@ class RunnerRewritingAdapter private constructor( av.visit("value", ravenwoodTestRunnerType.type) av.visitEnd() } - log.d("Processed ${classInternalName.toHumanReadableClassName()}") + log.i("Update the @RunWith: ${classInternalName.toHumanReadableClassName()}") } /* diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java index 440d2514537c..eb5361c84b89 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java @@ -26,6 +26,11 @@ import java.util.Set; * @hide */ public class CasResource { + /** + * Handle of the current resource. Should not be changed and should be aligned with the driver + * level implementation. + */ + final int mHandle; private final int mSystemId; @@ -39,11 +44,16 @@ public class CasResource { private Map<Integer, Integer> mOwnerClientIdsToSessionNum = new HashMap<>(); CasResource(Builder builder) { + this.mHandle = builder.mHandle; this.mSystemId = builder.mSystemId; this.mMaxSessionNum = builder.mMaxSessionNum; this.mAvailableSessionNum = builder.mMaxSessionNum; } + public int getHandle() { + return mHandle; + } + public int getSystemId() { return mSystemId; } @@ -136,10 +146,12 @@ public class CasResource { */ public static class Builder { + private final int mHandle; private int mSystemId; protected int mMaxSessionNum; - Builder(int systemId) { + Builder(int handle, int systemId) { + this.mHandle = handle; this.mSystemId = systemId; } diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CiCamResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CiCamResource.java index 31149f3590b8..5cef729627f0 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/CiCamResource.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CiCamResource.java @@ -42,8 +42,8 @@ public final class CiCamResource extends CasResource { * Builder class for {@link CiCamResource}. */ public static class Builder extends CasResource.Builder { - Builder(int systemId) { - super(systemId); + Builder(int handle, int systemId) { + super(handle, systemId); } /** diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index 0afb049d31c7..9229f7f016bc 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -203,13 +203,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde @Override public void unregisterClientProfile(int clientId) throws RemoteException { enforceTrmAccessPermission("unregisterClientProfile"); - synchronized (mLock) { - if (!checkClientExists(clientId)) { - Slog.e(TAG, "Unregistering non exists client:" + clientId); - return; - } - unregisterClientProfileInternal(clientId); - } + unregisterClientProfileInternal(clientId); } @Override @@ -291,20 +285,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde Slog.e(TAG, "frontendHandle can't be null"); return false; } - synchronized (mLock) { - if (!checkClientExists(request.clientId)) { - Slog.e(TAG, "Request frontend from unregistered client: " - + request.clientId); - return false; - } - // If the request client is holding or sharing a frontend, throw an exception. - if (!getClientProfile(request.clientId).getInUseFrontendHandles().isEmpty()) { - Slog.e(TAG, "Release frontend before requesting another one. Client id: " - + request.clientId); - return false; - } - return requestFrontendInternal(request, frontendHandle); - } + return requestFrontendInternal(request, frontendHandle); } @Override @@ -376,13 +357,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde if (demuxHandle == null) { throw new RemoteException("demuxHandle can't be null"); } - synchronized (mLock) { - if (!checkClientExists(request.clientId)) { - throw new RemoteException("Request demux from unregistered client:" - + request.clientId); - } - return requestDemuxInternal(request, demuxHandle); - } + return requestDemuxInternal(request, demuxHandle); } @Override @@ -409,13 +384,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde if (casSessionHandle == null) { throw new RemoteException("casSessionHandle can't be null"); } - synchronized (mLock) { - if (!checkClientExists(request.clientId)) { - throw new RemoteException("Request cas from unregistered client:" - + request.clientId); - } - return requestCasSessionInternal(request, casSessionHandle); - } + return requestCasSessionInternal(request, casSessionHandle); } @Override @@ -425,13 +394,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde if (ciCamHandle == null) { throw new RemoteException("ciCamHandle can't be null"); } - synchronized (mLock) { - if (!checkClientExists(request.clientId)) { - throw new RemoteException("Request ciCam from unregistered client:" - + request.clientId); - } - return requestCiCamInternal(request, ciCamHandle); - } + return requestCiCamInternal(request, ciCamHandle); } @Override @@ -442,42 +405,14 @@ public class TunerResourceManagerService extends SystemService implements IBinde if (lnbHandle == null) { throw new RemoteException("lnbHandle can't be null"); } - synchronized (mLock) { - if (!checkClientExists(request.clientId)) { - throw new RemoteException("Request lnb from unregistered client:" - + request.clientId); - } - return requestLnbInternal(request, lnbHandle); - } + return requestLnbInternal(request, lnbHandle); } @Override public void releaseFrontend(int frontendHandle, int clientId) throws RemoteException { enforceTunerAccessPermission("releaseFrontend"); enforceTrmAccessPermission("releaseFrontend"); - if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, - frontendHandle)) { - throw new RemoteException("frontendHandle can't be invalid"); - } - synchronized (mLock) { - if (!checkClientExists(clientId)) { - throw new RemoteException("Release frontend from unregistered client:" - + clientId); - } - FrontendResource fe = getFrontendResource(frontendHandle); - if (fe == null) { - throw new RemoteException("Releasing frontend does not exist."); - } - int ownerClientId = fe.getOwnerClientId(); - ClientProfile ownerProfile = getClientProfile(ownerClientId); - if (ownerClientId != clientId - && (ownerProfile != null - && !ownerProfile.getShareFeClientIds().contains(clientId))) { - throw new RemoteException( - "Client is not the current owner of the releasing fe."); - } - releaseFrontendInternal(fe, clientId); - } + releaseFrontendInternal(frontendHandle, clientId); } @Override @@ -746,17 +681,23 @@ public class TunerResourceManagerService extends SystemService implements IBinde @VisibleForTesting protected void unregisterClientProfileInternal(int clientId) { - if (DEBUG) { - Slog.d(TAG, "unregisterClientProfile(clientId=" + clientId + ")"); - } - removeClientProfile(clientId); - // Remove the Media Resource Manager callingPid to tvAppId mapping - if (mMediaResourceManager != null) { - try { - mMediaResourceManager.overridePid(Binder.getCallingPid(), -1); - } catch (RemoteException e) { - Slog.e(TAG, "Could not overridePid in resourceManagerSercice when unregister," - + " remote exception: " + e); + synchronized (mLock) { + if (!checkClientExists(clientId)) { + Slog.e(TAG, "Unregistering non exists client:" + clientId); + return; + } + if (DEBUG) { + Slog.d(TAG, "unregisterClientProfile(clientId=" + clientId + ")"); + } + removeClientProfile(clientId); + // Remove the Media Resource Manager callingPid to tvAppId mapping + if (mMediaResourceManager != null) { + try { + mMediaResourceManager.overridePid(Binder.getCallingPid(), -1); + } catch (RemoteException e) { + Slog.e(TAG, "Could not overridePid in resourceManagerSercice when unregister," + + " remote exception: " + e); + } } } } @@ -992,10 +933,14 @@ public class TunerResourceManagerService extends SystemService implements IBinde return; } // Add the new Cas Resource. - cas = new CasResource.Builder(casSystemId) + int casSessionHandle = generateResourceHandle( + TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, casSystemId); + cas = new CasResource.Builder(casSessionHandle, casSystemId) .maxSessionNum(maxSessionNum) .build(); - ciCam = new CiCamResource.Builder(casSystemId) + int ciCamHandle = generateResourceHandle( + TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, casSystemId); + ciCam = new CiCamResource.Builder(ciCamHandle, casSystemId) .maxSessionNum(maxSessionNum) .build(); addCasResource(cas); @@ -1007,86 +952,120 @@ public class TunerResourceManagerService extends SystemService implements IBinde if (DEBUG) { Slog.d(TAG, "requestFrontend(request=" + request + ")"); } - - frontendHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; - ClientProfile requestClient = getClientProfile(request.clientId); - // TODO: check if this is really needed - if (requestClient == null) { + int[] reclaimOwnerId = new int[1]; + if (!claimFrontend(request, frontendHandle, reclaimOwnerId)) { return false; } - clientPriorityUpdateOnRequest(requestClient); - int grantingFrontendHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; - int inUseLowestPriorityFrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; - // Priority max value is 1000 - int currentLowestPriority = MAX_CLIENT_PRIORITY + 1; - boolean isRequestFromSameProcess = false; - // If the desired frontend id was specified, we only need to check the frontend. - boolean hasDesiredFrontend = request.desiredId != TunerFrontendRequest.DEFAULT_DESIRED_ID; - for (FrontendResource fr : getFrontendResources().values()) { - int frontendId = getResourceIdFromHandle(fr.getHandle()); - if (fr.getType() == request.frontendType - && (!hasDesiredFrontend || frontendId == request.desiredId)) { - if (!fr.isInUse()) { - // Unused resource cannot be acquired if the max is already reached, but - // TRM still has to look for the reclaim candidate - if (isFrontendMaxNumUseReached(request.frontendType)) { - continue; - } - // Grant unused frontend with no exclusive group members first. - if (fr.getExclusiveGroupMemberFeHandles().isEmpty()) { - grantingFrontendHandle = fr.getHandle(); - break; - } else if (grantingFrontendHandle - == TunerResourceManager.INVALID_RESOURCE_HANDLE) { - // Grant the unused frontend with lower id first if all the unused - // frontends have exclusive group members. - grantingFrontendHandle = fr.getHandle(); - } - } else if (grantingFrontendHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) { - // Record the frontend id with the lowest client priority among all the - // in use frontends when no available frontend has been found. - int priority = getFrontendHighestClientPriority(fr.getOwnerClientId()); - if (currentLowestPriority > priority) { - // we need to check the max used num if the target frontend type is not - // currently in primary use (and simply blocked due to exclusive group) - ClientProfile targetOwnerProfile = getClientProfile(fr.getOwnerClientId()); - int primaryFeId = targetOwnerProfile.getPrimaryFrontend(); - FrontendResource primaryFe = getFrontendResource(primaryFeId); - if (fr.getType() != primaryFe.getType() - && isFrontendMaxNumUseReached(fr.getType())) { + if (frontendHandle[0] == TunerResourceManager.INVALID_RESOURCE_HANDLE) { + return false; + } + if (reclaimOwnerId[0] != INVALID_CLIENT_ID) { + if (!reclaimResource(reclaimOwnerId[0], TunerResourceManager + .TUNER_RESOURCE_TYPE_FRONTEND)) { + return false; + } + synchronized (mLock) { + if (getFrontendResource(frontendHandle[0]).isInUse()) { + Slog.e(TAG, "Reclaimed frontend still in use"); + return false; + } + updateFrontendClientMappingOnNewGrant(frontendHandle[0], request.clientId); + } + } + return true; + } + + protected boolean claimFrontend( + TunerFrontendRequest request, + int[] frontendHandle, + int[] reclaimOwnerId + ) { + frontendHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; + reclaimOwnerId[0] = INVALID_CLIENT_ID; + synchronized (mLock) { + if (!checkClientExists(request.clientId)) { + Slog.e(TAG, "Request frontend from unregistered client: " + + request.clientId); + return false; + } + // If the request client is holding or sharing a frontend, throw an exception. + if (!getClientProfile(request.clientId).getInUseFrontendHandles().isEmpty()) { + Slog.e(TAG, "Release frontend before requesting another one. Client id: " + + request.clientId); + return false; + } + ClientProfile requestClient = getClientProfile(request.clientId); + clientPriorityUpdateOnRequest(requestClient); + FrontendResource grantingFrontend = null; + FrontendResource inUseLowestPriorityFrontend = null; + // Priority max value is 1000 + int currentLowestPriority = MAX_CLIENT_PRIORITY + 1; + boolean isRequestFromSameProcess = false; + // If the desired frontend id was specified, we only need to check the frontend. + boolean hasDesiredFrontend = request.desiredId != TunerFrontendRequest + .DEFAULT_DESIRED_ID; + for (FrontendResource fr : getFrontendResources().values()) { + int frontendId = getResourceIdFromHandle(fr.getHandle()); + if (fr.getType() == request.frontendType + && (!hasDesiredFrontend || frontendId == request.desiredId)) { + if (!fr.isInUse()) { + // Unused resource cannot be acquired if the max is already reached, but + // TRM still has to look for the reclaim candidate + if (isFrontendMaxNumUseReached(request.frontendType)) { continue; } - // update the target frontend - inUseLowestPriorityFrHandle = fr.getHandle(); - currentLowestPriority = priority; - isRequestFromSameProcess = (requestClient.getProcessId() - == (getClientProfile(fr.getOwnerClientId())).getProcessId()); + // Grant unused frontend with no exclusive group members first. + if (fr.getExclusiveGroupMemberFeHandles().isEmpty()) { + grantingFrontend = fr; + break; + } else if (grantingFrontend == null) { + // Grant the unused frontend with lower id first if all the unused + // frontends have exclusive group members. + grantingFrontend = fr; + } + } else if (grantingFrontend == null) { + // Record the frontend id with the lowest client priority among all the + // in use frontends when no available frontend has been found. + int priority = getFrontendHighestClientPriority(fr.getOwnerClientId()); + if (currentLowestPriority > priority) { + // we need to check the max used num if the target frontend type is not + // currently in primary use (and simply blocked due to exclusive group) + ClientProfile targetOwnerProfile = + getClientProfile(fr.getOwnerClientId()); + int primaryFeId = targetOwnerProfile.getPrimaryFrontend(); + FrontendResource primaryFe = getFrontendResource(primaryFeId); + if (fr.getType() != primaryFe.getType() + && isFrontendMaxNumUseReached(fr.getType())) { + continue; + } + // update the target frontend + inUseLowestPriorityFrontend = fr; + currentLowestPriority = priority; + isRequestFromSameProcess = (requestClient.getProcessId() + == (getClientProfile(fr.getOwnerClientId())).getProcessId()); + } } } } - } - // Grant frontend when there is unused resource. - if (grantingFrontendHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) { - frontendHandle[0] = grantingFrontendHandle; - updateFrontendClientMappingOnNewGrant(grantingFrontendHandle, request.clientId); - return true; - } + // Grant frontend when there is unused resource. + if (grantingFrontend != null) { + updateFrontendClientMappingOnNewGrant(grantingFrontend.getHandle(), + request.clientId); + frontendHandle[0] = grantingFrontend.getHandle(); + return true; + } - // When all the resources are occupied, grant the lowest priority resource if the - // request client has higher priority. - if (inUseLowestPriorityFrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE - && ((requestClient.getPriority() > currentLowestPriority) || ( - (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) { - if (!reclaimResource( - getFrontendResource(inUseLowestPriorityFrHandle).getOwnerClientId(), - TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND)) { - return false; + // When all the resources are occupied, grant the lowest priority resource if the + // request client has higher priority. + if (inUseLowestPriorityFrontend != null + && ((requestClient.getPriority() > currentLowestPriority) + || ((requestClient.getPriority() == currentLowestPriority) + && isRequestFromSameProcess))) { + frontendHandle[0] = inUseLowestPriorityFrontend.getHandle(); + reclaimOwnerId[0] = inUseLowestPriorityFrontend.getOwnerClientId(); + return true; } - frontendHandle[0] = inUseLowestPriorityFrHandle; - updateFrontendClientMappingOnNewGrant( - inUseLowestPriorityFrHandle, request.clientId); - return true; } return false; @@ -1192,165 +1171,257 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @VisibleForTesting - protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) { + protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) + throws RemoteException { if (DEBUG) { Slog.d(TAG, "requestLnb(request=" + request + ")"); } + int[] reclaimOwnerId = new int[1]; + if (!claimLnb(request, lnbHandle, reclaimOwnerId)) { + return false; + } + if (lnbHandle[0] == TunerResourceManager.INVALID_RESOURCE_HANDLE) { + return false; + } + if (reclaimOwnerId[0] != INVALID_CLIENT_ID) { + if (!reclaimResource(reclaimOwnerId[0], + TunerResourceManager.TUNER_RESOURCE_TYPE_LNB)) { + return false; + } + synchronized (mLock) { + if (getLnbResource(lnbHandle[0]).isInUse()) { + Slog.e(TAG, "Reclaimed lnb still in use"); + return false; + } + updateLnbClientMappingOnNewGrant(lnbHandle[0], request.clientId); + } + } + return true; + } + protected boolean claimLnb(TunerLnbRequest request, int[] lnbHandle, int[] reclaimOwnerId) + throws RemoteException { lnbHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; - ClientProfile requestClient = getClientProfile(request.clientId); - clientPriorityUpdateOnRequest(requestClient); - int grantingLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; - int inUseLowestPriorityLnbHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; - // Priority max value is 1000 - int currentLowestPriority = MAX_CLIENT_PRIORITY + 1; - boolean isRequestFromSameProcess = false; - for (LnbResource lnb : getLnbResources().values()) { - if (!lnb.isInUse()) { - // Grant the unused lnb with lower handle first - grantingLnbHandle = lnb.getHandle(); - break; - } else { - // Record the lnb id with the lowest client priority among all the - // in use lnb when no available lnb has been found. - int priority = updateAndGetOwnerClientPriority(lnb.getOwnerClientId()); - if (currentLowestPriority > priority) { - inUseLowestPriorityLnbHandle = lnb.getHandle(); - currentLowestPriority = priority; - isRequestFromSameProcess = (requestClient.getProcessId() - == (getClientProfile(lnb.getOwnerClientId())).getProcessId()); + reclaimOwnerId[0] = INVALID_CLIENT_ID; + synchronized (mLock) { + if (!checkClientExists(request.clientId)) { + throw new RemoteException("Request lnb from unregistered client:" + + request.clientId); + } + ClientProfile requestClient = getClientProfile(request.clientId); + clientPriorityUpdateOnRequest(requestClient); + LnbResource grantingLnb = null; + LnbResource inUseLowestPriorityLnb = null; + // Priority max value is 1000 + int currentLowestPriority = MAX_CLIENT_PRIORITY + 1; + boolean isRequestFromSameProcess = false; + for (LnbResource lnb : getLnbResources().values()) { + if (!lnb.isInUse()) { + // Grant the unused lnb with lower handle first + grantingLnb = lnb; + break; + } else { + // Record the lnb id with the lowest client priority among all the + // in use lnb when no available lnb has been found. + int priority = updateAndGetOwnerClientPriority(lnb.getOwnerClientId()); + if (currentLowestPriority > priority) { + inUseLowestPriorityLnb = lnb; + currentLowestPriority = priority; + isRequestFromSameProcess = (requestClient.getProcessId() + == (getClientProfile(lnb.getOwnerClientId())).getProcessId()); + } } } - } - // Grant Lnb when there is unused resource. - if (grantingLnbHandle > -1) { - lnbHandle[0] = grantingLnbHandle; - updateLnbClientMappingOnNewGrant(grantingLnbHandle, request.clientId); - return true; - } + // Grant Lnb when there is unused resource. + if (grantingLnb != null) { + updateLnbClientMappingOnNewGrant(grantingLnb.getHandle(), request.clientId); + lnbHandle[0] = grantingLnb.getHandle(); + return true; + } - // When all the resources are occupied, grant the lowest priority resource if the - // request client has higher priority. - if (inUseLowestPriorityLnbHandle > TunerResourceManager.INVALID_RESOURCE_HANDLE - && ((requestClient.getPriority() > currentLowestPriority) || ( - (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) { - if (!reclaimResource(getLnbResource(inUseLowestPriorityLnbHandle).getOwnerClientId(), - TunerResourceManager.TUNER_RESOURCE_TYPE_LNB)) { - return false; + // When all the resources are occupied, grant the lowest priority resource if the + // request client has higher priority. + if (inUseLowestPriorityLnb != null + && ((requestClient.getPriority() > currentLowestPriority) || ( + (requestClient.getPriority() == currentLowestPriority) + && isRequestFromSameProcess))) { + lnbHandle[0] = inUseLowestPriorityLnb.getHandle(); + reclaimOwnerId[0] = inUseLowestPriorityLnb.getOwnerClientId(); + return true; } - lnbHandle[0] = inUseLowestPriorityLnbHandle; - updateLnbClientMappingOnNewGrant(inUseLowestPriorityLnbHandle, request.clientId); - return true; } return false; } @VisibleForTesting - protected boolean requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle) { + protected boolean requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle) + throws RemoteException { if (DEBUG) { Slog.d(TAG, "requestCasSession(request=" + request + ")"); } - CasResource cas = getCasResource(request.casSystemId); - // Unregistered Cas System is treated as having unlimited sessions. - if (cas == null) { - cas = new CasResource.Builder(request.casSystemId) - .maxSessionNum(Integer.MAX_VALUE) - .build(); - addCasResource(cas); + int[] reclaimOwnerId = new int[1]; + if (!claimCasSession(request, casSessionHandle, reclaimOwnerId)) { + return false; } - casSessionHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; - ClientProfile requestClient = getClientProfile(request.clientId); - clientPriorityUpdateOnRequest(requestClient); - int lowestPriorityOwnerId = -1; - // Priority max value is 1000 - int currentLowestPriority = MAX_CLIENT_PRIORITY + 1; - boolean isRequestFromSameProcess = false; - if (!cas.isFullyUsed()) { - casSessionHandle[0] = generateResourceHandle( - TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId()); - updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId); - return true; + if (casSessionHandle[0] == TunerResourceManager.INVALID_RESOURCE_HANDLE) { + return false; } - for (int ownerId : cas.getOwnerClientIds()) { - // Record the client id with lowest priority that is using the current Cas system. - int priority = updateAndGetOwnerClientPriority(ownerId); - if (currentLowestPriority > priority) { - lowestPriorityOwnerId = ownerId; - currentLowestPriority = priority; - isRequestFromSameProcess = (requestClient.getProcessId() - == (getClientProfile(ownerId)).getProcessId()); + if (reclaimOwnerId[0] != INVALID_CLIENT_ID) { + if (!reclaimResource(reclaimOwnerId[0], + TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION)) { + return false; + } + synchronized (mLock) { + if (getCasResource(request.casSystemId).isFullyUsed()) { + Slog.e(TAG, "Reclaimed cas still fully used"); + return false; + } + updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId); } } + return true; + } - // When all the Cas sessions are occupied, reclaim the lowest priority client if the - // request client has higher priority. - if (lowestPriorityOwnerId > -1 && ((requestClient.getPriority() > currentLowestPriority) - || ((requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) { - if (!reclaimResource(lowestPriorityOwnerId, - TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION)) { - return false; + protected boolean claimCasSession(CasSessionRequest request, int[] casSessionHandle, + int[] reclaimOwnerId) throws RemoteException { + casSessionHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; + reclaimOwnerId[0] = INVALID_CLIENT_ID; + synchronized (mLock) { + if (!checkClientExists(request.clientId)) { + throw new RemoteException("Request cas from unregistered client:" + + request.clientId); + } + CasResource cas = getCasResource(request.casSystemId); + // Unregistered Cas System is treated as having unlimited sessions. + if (cas == null) { + int resourceHandle = generateResourceHandle( + TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, request.clientId); + cas = new CasResource.Builder(resourceHandle, request.casSystemId) + .maxSessionNum(Integer.MAX_VALUE) + .build(); + addCasResource(cas); + } + ClientProfile requestClient = getClientProfile(request.clientId); + clientPriorityUpdateOnRequest(requestClient); + int lowestPriorityOwnerId = INVALID_CLIENT_ID; + // Priority max value is 1000 + int currentLowestPriority = MAX_CLIENT_PRIORITY + 1; + boolean isRequestFromSameProcess = false; + if (!cas.isFullyUsed()) { + updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId); + casSessionHandle[0] = cas.getHandle(); + return true; + } + for (int ownerId : cas.getOwnerClientIds()) { + // Record the client id with lowest priority that is using the current Cas system. + int priority = updateAndGetOwnerClientPriority(ownerId); + if (currentLowestPriority > priority) { + lowestPriorityOwnerId = ownerId; + currentLowestPriority = priority; + isRequestFromSameProcess = (requestClient.getProcessId() + == (getClientProfile(ownerId)).getProcessId()); + } + } + + // When all the Cas sessions are occupied, reclaim the lowest priority client if the + // request client has higher priority. + if (lowestPriorityOwnerId != INVALID_CLIENT_ID + && ((requestClient.getPriority() > currentLowestPriority) + || ((requestClient.getPriority() == currentLowestPriority) + && isRequestFromSameProcess))) { + casSessionHandle[0] = cas.getHandle(); + reclaimOwnerId[0] = lowestPriorityOwnerId; + return true; } - casSessionHandle[0] = generateResourceHandle( - TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId()); - updateCasClientMappingOnNewGrant(request.casSystemId, request.clientId); - return true; } + return false; } @VisibleForTesting - protected boolean requestCiCamInternal(TunerCiCamRequest request, int[] ciCamHandle) { + protected boolean requestCiCamInternal(TunerCiCamRequest request, int[] ciCamHandle) + throws RemoteException { if (DEBUG) { Slog.d(TAG, "requestCiCamInternal(TunerCiCamRequest=" + request + ")"); } - CiCamResource ciCam = getCiCamResource(request.ciCamId); - // Unregistered Cas System is treated as having unlimited sessions. - if (ciCam == null) { - ciCam = new CiCamResource.Builder(request.ciCamId) - .maxSessionNum(Integer.MAX_VALUE) - .build(); - addCiCamResource(ciCam); + int[] reclaimOwnerId = new int[1]; + if (!claimCiCam(request, ciCamHandle, reclaimOwnerId)) { + return false; } - ciCamHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; - ClientProfile requestClient = getClientProfile(request.clientId); - clientPriorityUpdateOnRequest(requestClient); - int lowestPriorityOwnerId = -1; - // Priority max value is 1000 - int currentLowestPriority = MAX_CLIENT_PRIORITY + 1; - boolean isRequestFromSameProcess = false; - if (!ciCam.isFullyUsed()) { - ciCamHandle[0] = generateResourceHandle( - TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId()); - updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId); - return true; + if (ciCamHandle[0] == TunerResourceManager.INVALID_RESOURCE_HANDLE) { + return false; } - for (int ownerId : ciCam.getOwnerClientIds()) { - // Record the client id with lowest priority that is using the current Cas system. - int priority = updateAndGetOwnerClientPriority(ownerId); - if (currentLowestPriority > priority) { - lowestPriorityOwnerId = ownerId; - currentLowestPriority = priority; - isRequestFromSameProcess = (requestClient.getProcessId() - == (getClientProfile(ownerId)).getProcessId()); - } - } - - // When all the CiCam sessions are occupied, reclaim the lowest priority client if the - // request client has higher priority. - if (lowestPriorityOwnerId > -1 && ((requestClient.getPriority() > currentLowestPriority) - || ((requestClient.getPriority() == currentLowestPriority) - && isRequestFromSameProcess))) { - if (!reclaimResource(lowestPriorityOwnerId, + if (reclaimOwnerId[0] != INVALID_CLIENT_ID) { + if (!reclaimResource(reclaimOwnerId[0], TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM)) { return false; } - ciCamHandle[0] = generateResourceHandle( - TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, ciCam.getCiCamId()); - updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId); - return true; + synchronized (mLock) { + if (getCiCamResource(request.ciCamId).isFullyUsed()) { + Slog.e(TAG, "Reclaimed ciCam still fully used"); + return false; + } + updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId); + } + } + return true; + } + + protected boolean claimCiCam(TunerCiCamRequest request, int[] ciCamHandle, + int[] reclaimOwnerId) throws RemoteException { + ciCamHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; + reclaimOwnerId[0] = INVALID_CLIENT_ID; + synchronized (mLock) { + if (!checkClientExists(request.clientId)) { + throw new RemoteException("Request ciCam from unregistered client:" + + request.clientId); + } + CiCamResource ciCam = getCiCamResource(request.ciCamId); + // Unregistered CiCam is treated as having unlimited sessions. + if (ciCam == null) { + int resourceHandle = generateResourceHandle( + TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND_CICAM, request.ciCamId); + ciCam = new CiCamResource.Builder(resourceHandle, request.ciCamId) + .maxSessionNum(Integer.MAX_VALUE) + .build(); + addCiCamResource(ciCam); + } + ClientProfile requestClient = getClientProfile(request.clientId); + clientPriorityUpdateOnRequest(requestClient); + int lowestPriorityOwnerId = INVALID_CLIENT_ID; + // Priority max value is 1000 + int currentLowestPriority = MAX_CLIENT_PRIORITY + 1; + boolean isRequestFromSameProcess = false; + if (!ciCam.isFullyUsed()) { + updateCiCamClientMappingOnNewGrant(request.ciCamId, request.clientId); + ciCamHandle[0] = ciCam.getHandle(); + return true; + } + for (int ownerId : ciCam.getOwnerClientIds()) { + // Record the client id with lowest priority that is using the current CiCam. + int priority = updateAndGetOwnerClientPriority(ownerId); + if (currentLowestPriority > priority) { + lowestPriorityOwnerId = ownerId; + currentLowestPriority = priority; + isRequestFromSameProcess = (requestClient.getProcessId() + == (getClientProfile(ownerId)).getProcessId()); + } + } + + // When all the CiCam sessions are occupied, reclaim the lowest priority client if the + // request client has higher priority. + if (lowestPriorityOwnerId != INVALID_CLIENT_ID + && ((requestClient.getPriority() > currentLowestPriority) + || ((requestClient.getPriority() == currentLowestPriority) + && isRequestFromSameProcess))) { + ciCamHandle[0] = ciCam.getHandle(); + reclaimOwnerId[0] = lowestPriorityOwnerId; + return true; + } } + return false; } @@ -1383,20 +1454,49 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @VisibleForTesting - protected void releaseFrontendInternal(FrontendResource fe, int clientId) { + protected void releaseFrontendInternal(int frontendHandle, int clientId) + throws RemoteException { if (DEBUG) { - Slog.d(TAG, "releaseFrontend(id=" + fe.getHandle() + ", clientId=" + clientId + " )"); + Slog.d(TAG, "releaseFrontend(id=" + frontendHandle + ", clientId=" + clientId + " )"); } - if (clientId == fe.getOwnerClientId()) { - ClientProfile ownerClient = getClientProfile(fe.getOwnerClientId()); - if (ownerClient != null) { - for (int shareOwnerId : ownerClient.getShareFeClientIds()) { - reclaimResource(shareOwnerId, - TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND); + if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND, + frontendHandle)) { + throw new RemoteException("frontendHandle can't be invalid"); + } + Set<Integer> reclaimedResourceOwnerIds = unclaimFrontend(frontendHandle, clientId); + if (reclaimedResourceOwnerIds != null) { + for (int shareOwnerId : reclaimedResourceOwnerIds) { + reclaimResource(shareOwnerId, + TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND); + } + } + synchronized (mLock) { + clearFrontendAndClientMapping(getClientProfile(clientId)); + } + } + + private Set<Integer> unclaimFrontend(int frontendHandle, int clientId) throws RemoteException { + Set<Integer> reclaimedResourceOwnerIds = null; + synchronized (mLock) { + if (!checkClientExists(clientId)) { + throw new RemoteException("Release frontend from unregistered client:" + + clientId); + } + FrontendResource fe = getFrontendResource(frontendHandle); + if (fe == null) { + throw new RemoteException("Releasing frontend does not exist."); + } + int ownerClientId = fe.getOwnerClientId(); + ClientProfile ownerProfile = getClientProfile(ownerClientId); + if (ownerClientId == clientId) { + reclaimedResourceOwnerIds = ownerProfile.getShareFeClientIds(); + } else { + if (!ownerProfile.getShareFeClientIds().contains(clientId)) { + throw new RemoteException("Client is not a sharee of the releasing fe."); } } } - clearFrontendAndClientMapping(getClientProfile(clientId)); + return reclaimedResourceOwnerIds; } @VisibleForTesting @@ -1432,103 +1532,129 @@ public class TunerResourceManagerService extends SystemService implements IBinde } @VisibleForTesting - protected boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) { + public boolean requestDemuxInternal(@NonNull TunerDemuxRequest request, + @NonNull int[] demuxHandle) throws RemoteException { if (DEBUG) { Slog.d(TAG, "requestDemux(request=" + request + ")"); } - - // For Tuner 2.0 and below or any HW constraint devices that are unable to support - // ITuner.openDemuxById(), demux resources are not really managed under TRM and - // mDemuxResources.size() will be zero - if (mDemuxResources.size() == 0) { - // There are enough Demux resources, so we don't manage Demux in R. - demuxHandle[0] = - generateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, 0); - return true; + int[] reclaimOwnerId = new int[1]; + if (!claimDemux(request, demuxHandle, reclaimOwnerId)) { + return false; } - - demuxHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; - ClientProfile requestClient = getClientProfile(request.clientId); - - if (requestClient == null) { + if (demuxHandle[0] == TunerResourceManager.INVALID_RESOURCE_HANDLE) { return false; } + if (reclaimOwnerId[0] != INVALID_CLIENT_ID) { + if (!reclaimResource(reclaimOwnerId[0], + TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { + return false; + } + synchronized (mLock) { + if (getDemuxResource(demuxHandle[0]).isInUse()) { + Slog.e(TAG, "Reclaimed demux still in use"); + return false; + } + updateDemuxClientMappingOnNewGrant(demuxHandle[0], request.clientId); + } + } + return true; + } - clientPriorityUpdateOnRequest(requestClient); - int grantingDemuxHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; - int inUseLowestPriorityDrHandle = TunerResourceManager.INVALID_RESOURCE_HANDLE; - // Priority max value is 1000 - int currentLowestPriority = MAX_CLIENT_PRIORITY + 1; - boolean isRequestFromSameProcess = false; - // If the desired demux id was specified, we only need to check the demux. - boolean hasDesiredDemuxCap = request.desiredFilterTypes - != DemuxFilterMainType.UNDEFINED; - int smallestNumOfSupportedCaps = Integer.SIZE + 1; - int smallestNumOfSupportedCapsInUse = Integer.SIZE + 1; - for (DemuxResource dr : getDemuxResources().values()) { - if (!hasDesiredDemuxCap || dr.hasSufficientCaps(request.desiredFilterTypes)) { - if (!dr.isInUse()) { - int numOfSupportedCaps = dr.getNumOfCaps(); - - // look for the demux with minimum caps supporting the desired caps - if (smallestNumOfSupportedCaps > numOfSupportedCaps) { - smallestNumOfSupportedCaps = numOfSupportedCaps; - grantingDemuxHandle = dr.getHandle(); - } - } else if (grantingDemuxHandle == TunerResourceManager.INVALID_RESOURCE_HANDLE) { - // Record the demux id with the lowest client priority among all the - // in use demuxes when no availabledemux has been found. - int priority = updateAndGetOwnerClientPriority(dr.getOwnerClientId()); - if (currentLowestPriority >= priority) { - int numOfSupportedCaps = dr.getNumOfCaps(); - boolean shouldUpdate = false; - // update lowest priority - if (currentLowestPriority > priority) { - currentLowestPriority = priority; - isRequestFromSameProcess = (requestClient.getProcessId() - == (getClientProfile(dr.getOwnerClientId())).getProcessId()); + protected boolean claimDemux(TunerDemuxRequest request, int[] demuxHandle, int[] reclaimOwnerId) + throws RemoteException { + demuxHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; + reclaimOwnerId[0] = INVALID_CLIENT_ID; + synchronized (mLock) { + if (!checkClientExists(request.clientId)) { + throw new RemoteException("Request demux from unregistered client:" + + request.clientId); + } + + // For Tuner 2.0 and below or any HW constraint devices that are unable to support + // ITuner.openDemuxById(), demux resources are not really managed under TRM and + // mDemuxResources.size() will be zero + if (mDemuxResources.size() == 0) { + // There are enough Demux resources, so we don't manage Demux in R. + demuxHandle[0] = + generateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX, 0); + return true; + } - // reset the smallest caps when lower priority resource is found - smallestNumOfSupportedCapsInUse = numOfSupportedCaps; + ClientProfile requestClient = getClientProfile(request.clientId); + if (requestClient == null) { + return false; + } + clientPriorityUpdateOnRequest(requestClient); + DemuxResource grantingDemux = null; + DemuxResource inUseLowestPriorityDemux = null; + // Priority max value is 1000 + int currentLowestPriority = MAX_CLIENT_PRIORITY + 1; + boolean isRequestFromSameProcess = false; + // If the desired demux id was specified, we only need to check the demux. + boolean hasDesiredDemuxCap = request.desiredFilterTypes + != DemuxFilterMainType.UNDEFINED; + int smallestNumOfSupportedCaps = Integer.SIZE + 1; + int smallestNumOfSupportedCapsInUse = Integer.SIZE + 1; + for (DemuxResource dr : getDemuxResources().values()) { + if (!hasDesiredDemuxCap || dr.hasSufficientCaps(request.desiredFilterTypes)) { + if (!dr.isInUse()) { + int numOfSupportedCaps = dr.getNumOfCaps(); - shouldUpdate = true; - } else { - // This is the case when the priority is the same as previously found - // one. Update smallest caps when priority. - if (smallestNumOfSupportedCapsInUse > numOfSupportedCaps) { + // look for the demux with minimum caps supporting the desired caps + if (smallestNumOfSupportedCaps > numOfSupportedCaps) { + smallestNumOfSupportedCaps = numOfSupportedCaps; + grantingDemux = dr; + } + } else if (grantingDemux == null) { + // Record the demux id with the lowest client priority among all the + // in use demuxes when no availabledemux has been found. + int priority = updateAndGetOwnerClientPriority(dr.getOwnerClientId()); + if (currentLowestPriority >= priority) { + int numOfSupportedCaps = dr.getNumOfCaps(); + boolean shouldUpdate = false; + // update lowest priority + if (currentLowestPriority > priority) { + currentLowestPriority = priority; + isRequestFromSameProcess = (requestClient.getProcessId() + == (getClientProfile(dr.getOwnerClientId())).getProcessId()); + + // reset the smallest caps when lower priority resource is found smallestNumOfSupportedCapsInUse = numOfSupportedCaps; + shouldUpdate = true; + } else { + // This is the case when the priority is the same as previously + // found one. Update smallest caps when priority. + if (smallestNumOfSupportedCapsInUse > numOfSupportedCaps) { + smallestNumOfSupportedCapsInUse = numOfSupportedCaps; + shouldUpdate = true; + } + } + if (shouldUpdate) { + inUseLowestPriorityDemux = dr; } - } - if (shouldUpdate) { - inUseLowestPriorityDrHandle = dr.getHandle(); } } } } - } - // Grant demux when there is unused resource. - if (grantingDemuxHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) { - demuxHandle[0] = grantingDemuxHandle; - updateDemuxClientMappingOnNewGrant(grantingDemuxHandle, request.clientId); - return true; - } + // Grant demux when there is unused resource. + if (grantingDemux != null) { + updateDemuxClientMappingOnNewGrant(grantingDemux.getHandle(), request.clientId); + demuxHandle[0] = grantingDemux.getHandle(); + return true; + } - // When all the resources are occupied, grant the lowest priority resource if the - // request client has higher priority. - if (inUseLowestPriorityDrHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE - && ((requestClient.getPriority() > currentLowestPriority) || ( - (requestClient.getPriority() == currentLowestPriority) && isRequestFromSameProcess))) { - if (!reclaimResource( - getDemuxResource(inUseLowestPriorityDrHandle).getOwnerClientId(), - TunerResourceManager.TUNER_RESOURCE_TYPE_DEMUX)) { - return false; + // When all the resources are occupied, grant the lowest priority resource if the + // request client has higher priority. + if (inUseLowestPriorityDemux != null + && ((requestClient.getPriority() > currentLowestPriority) || ( + (requestClient.getPriority() == currentLowestPriority) + && isRequestFromSameProcess))) { + demuxHandle[0] = inUseLowestPriorityDemux.getHandle(); + reclaimOwnerId[0] = inUseLowestPriorityDemux.getOwnerClientId(); + return true; } - demuxHandle[0] = inUseLowestPriorityDrHandle; - updateDemuxClientMappingOnNewGrant( - inUseLowestPriorityDrHandle, request.clientId); - return true; } return false; @@ -1792,7 +1918,9 @@ public class TunerResourceManagerService extends SystemService implements IBinde return; } - mListeners.put(clientId, record); + synchronized (mLock) { + mListeners.put(clientId, record); + } } @VisibleForTesting @@ -1808,33 +1936,44 @@ public class TunerResourceManagerService extends SystemService implements IBinde // Reclaim all the resources of the share owners of the frontend that is used by the current // resource reclaimed client. - ClientProfile profile = getClientProfile(reclaimingClientId); - // TODO: check if this check is really needed. - if (profile == null) { - return true; + Set<Integer> shareFeClientIds; + synchronized (mLock) { + ClientProfile profile = getClientProfile(reclaimingClientId); + if (profile == null) { + return true; + } + shareFeClientIds = profile.getShareFeClientIds(); } - Set<Integer> shareFeClientIds = profile.getShareFeClientIds(); + ResourcesReclaimListenerRecord listenerRecord = null; for (int clientId : shareFeClientIds) { - try { - mListeners.get(clientId).getListener().onReclaimResources(); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to reclaim resources on client " + clientId, e); - return false; + synchronized (mLock) { + listenerRecord = mListeners.get(clientId); + } + if (listenerRecord != null) { + try { + listenerRecord.getListener().onReclaimResources(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to reclaim resources on client " + clientId, e); + } } - clearAllResourcesAndClientMapping(getClientProfile(clientId)); } if (DEBUG) { Slog.d(TAG, "Reclaiming resources because higher priority client request resource type " + resourceType + ", clientId:" + reclaimingClientId); } - try { - mListeners.get(reclaimingClientId).getListener().onReclaimResources(); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to reclaim resources on client " + reclaimingClientId, e); - return false; + + synchronized (mLock) { + listenerRecord = mListeners.get(reclaimingClientId); + } + if (listenerRecord != null) { + try { + listenerRecord.getListener().onReclaimResources(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to reclaim resources on client " + reclaimingClientId, e); + } } - clearAllResourcesAndClientMapping(profile); + return true; } @@ -2258,6 +2397,7 @@ public class TunerResourceManagerService extends SystemService implements IBinde addResourcesReclaimListener(clientId, listener); } + @SuppressWarnings("GuardedBy") // Lock is held on mListeners private void removeClientProfile(int clientId) { for (int shareOwnerId : getClientProfile(clientId).getShareFeClientIds()) { clearFrontendAndClientMapping(getClientProfile(shareOwnerId)); @@ -2265,12 +2405,9 @@ public class TunerResourceManagerService extends SystemService implements IBinde clearAllResourcesAndClientMapping(getClientProfile(clientId)); mClientProfiles.remove(clientId); - // it may be called by unregisterClientProfileInternal under test - synchronized (mLock) { - ResourcesReclaimListenerRecord record = mListeners.remove(clientId); - if (record != null) { - record.getListener().asBinder().unlinkToDeath(record, 0); - } + ResourcesReclaimListenerRecord record = mListeners.remove(clientId); + if (record != null) { + record.getListener().asBinder().unlinkToDeath(record, 0); } } @@ -2304,7 +2441,8 @@ public class TunerResourceManagerService extends SystemService implements IBinde profile.releaseFrontend(); } - private void clearAllResourcesAndClientMapping(ClientProfile profile) { + @VisibleForTesting + protected void clearAllResourcesAndClientMapping(ClientProfile profile) { // TODO: check if this check is really needed. Maybe needed for reclaimResource path. if (profile == null) { return; diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java index 963b27e010fa..bf58443194e5 100644 --- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java @@ -38,6 +38,7 @@ import android.media.tv.tunerresourcemanager.TunerFrontendInfo; import android.media.tv.tunerresourcemanager.TunerFrontendRequest; import android.media.tv.tunerresourcemanager.TunerLnbRequest; import android.media.tv.tunerresourcemanager.TunerResourceManager; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; @@ -69,6 +70,61 @@ public class TunerResourceManagerServiceTest { private TunerResourceManagerService mTunerResourceManagerService; private boolean mIsForeground; + private final class TunerClient extends IResourcesReclaimListener.Stub { + int[] mClientId; + ClientProfile mProfile; + boolean mReclaimed; + + TunerClient() { + mClientId = new int[1]; + mClientId[0] = TunerResourceManagerService.INVALID_CLIENT_ID; + } + + public void register(String sessionId, int useCase) { + ResourceClientProfile profile = new ResourceClientProfile(); + profile.tvInputSessionId = sessionId; + profile.useCase = useCase; + mTunerResourceManagerService.registerClientProfileInternal( + profile, this, mClientId); + assertThat(mClientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + mProfile = mTunerResourceManagerService.getClientProfile(mClientId[0]); + } + + public void register(String sessionId, int useCase, int priority, int niceValue) { + register(sessionId, useCase); + mTunerResourceManagerService.updateClientPriorityInternal( + mClientId[0], priority, niceValue); + } + + public void register(String sessionId, int useCase, int priority) { + register(sessionId, useCase, priority, 0); + } + + public void unregister() { + mTunerResourceManagerService.unregisterClientProfileInternal(mClientId[0]); + mClientId[0] = TunerResourceManagerService.INVALID_CLIENT_ID; + mReclaimed = false; + } + + public int getId() { + return mClientId[0]; + } + + public ClientProfile getProfile() { + return mProfile; + } + + @Override + public void onReclaimResources() { + mTunerResourceManagerService.clearAllResourcesAndClientMapping(mProfile); + mReclaimed = true; + } + + public boolean isReclaimed() { + return mReclaimed; + } + } + private static final class TestResourcesReclaimListener extends IResourcesReclaimListener.Stub { boolean mReclaimed; @@ -247,13 +303,11 @@ public class TunerResourceManagerServiceTest { } @Test - public void requestFrontendTest_NoFrontendWithGiveTypeAvailable() { - ResourceClientProfile profile = resourceClientProfile("0" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - int[] clientId = new int[1]; - mTunerResourceManagerService.registerClientProfileInternal( - profile, null /*listener*/, clientId); - assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + public void requestFrontendTest_NoFrontendWithGiveTypeAvailable() throws RemoteException { + // Register clients + TunerClient client0 = new TunerClient(); + client0.register("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); // Init frontend resources. TunerFrontendInfo[] infos = new TunerFrontendInfo[1]; @@ -262,21 +316,20 @@ public class TunerResourceManagerServiceTest { mTunerResourceManagerService.setFrontendInfoListInternal(infos); TunerFrontendRequest request = - tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isFalse(); assertThat(frontendHandle[0]).isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE); + client0.unregister(); } @Test - public void requestFrontendTest_FrontendWithNoExclusiveGroupAvailable() { - ResourceClientProfile profile = resourceClientProfile("0" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - int[] clientId = new int[1]; - mTunerResourceManagerService.registerClientProfileInternal( - profile, null /*listener*/, clientId); - assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + public void requestFrontendTest_FrontendWithNoExclusiveGroupAvailable() throws RemoteException { + // Register clients + TunerClient client0 = new TunerClient(); + client0.register("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); // Init frontend resources. TunerFrontendInfo[] infos = new TunerFrontendInfo[3]; @@ -295,27 +348,23 @@ public class TunerResourceManagerServiceTest { mTunerResourceManagerService.setFrontendInfoListInternal(infos); TunerFrontendRequest request = - tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(frontendHandle[0]).isEqualTo(0); + client0.unregister(); } @Test - public void requestFrontendTest_FrontendWithExclusiveGroupAvailable() { - ResourceClientProfile profile0 = resourceClientProfile("0" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - ResourceClientProfile profile1 = resourceClientProfile("1" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - int[] clientId0 = new int[1]; - int[] clientId1 = new int[1]; - mTunerResourceManagerService.registerClientProfileInternal( - profile0, null /*listener*/, clientId0); - mTunerResourceManagerService.registerClientProfileInternal( - profile1, null /*listener*/, clientId1); - assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + public void requestFrontendTest_FrontendWithExclusiveGroupAvailable() throws RemoteException { + // Register clients + TunerClient client0 = new TunerClient(); + TunerClient client1 = new TunerClient(); + client0.register("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + client1.register("1" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); // Init frontend resources. TunerFrontendInfo[] infos = new TunerFrontendInfo[3]; @@ -335,13 +384,13 @@ public class TunerResourceManagerServiceTest { int[] frontendHandle = new int[1]; TunerFrontendRequest request = - tunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBT); assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(frontendHandle[0]).isEqualTo(infos[0].handle); request = - tunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT); assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(frontendHandle[0]).isEqualTo(infos[1].handle); @@ -349,31 +398,20 @@ public class TunerResourceManagerServiceTest { .isTrue(); assertThat(mTunerResourceManagerService.getFrontendResource(infos[2].handle).isInUse()) .isTrue(); + client0.unregister(); + client1.unregister(); } @Test - public void requestFrontendTest_NoFrontendAvailable_RequestWithLowerPriority() { + public void requestFrontendTest_NoFrontendAvailable_RequestWithLowerPriority() + throws RemoteException { // Register clients - ResourceClientProfile[] profiles = new ResourceClientProfile[2]; - profiles[0] = resourceClientProfile("0" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - profiles[1] = resourceClientProfile("1" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - int[] clientPriorities = {100, 50}; - int[] clientId0 = new int[1]; - int[] clientId1 = new int[1]; - TestResourcesReclaimListener listener = new TestResourcesReclaimListener(); - - mTunerResourceManagerService.registerClientProfileInternal( - profiles[0], listener, clientId0); - assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - mTunerResourceManagerService.updateClientPriorityInternal( - clientId0[0], clientPriorities[0], 0/*niceValue*/); - mTunerResourceManagerService.registerClientProfileInternal( - profiles[1], new TestResourcesReclaimListener(), clientId1); - assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - mTunerResourceManagerService.updateClientPriorityInternal( - clientId1[0], clientPriorities[1], 0/*niceValue*/); + TunerClient client0 = new TunerClient(); + TunerClient client1 = new TunerClient(); + client0.register("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100); + client1.register("1" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 50); // Init frontend resources. TunerFrontendInfo[] infos = new TunerFrontendInfo[2]; @@ -384,46 +422,36 @@ public class TunerResourceManagerServiceTest { mTunerResourceManagerService.setFrontendInfoListInternal(infos); TunerFrontendRequest request = - tunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isTrue(); request = - tunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBT); assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isFalse(); - assertThat(listener.isReclaimed()).isFalse(); + assertThat(client0.isReclaimed()).isFalse(); request = - tunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS); + tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBS); assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isFalse(); - assertThat(listener.isReclaimed()).isFalse(); + assertThat(client0.isReclaimed()).isFalse(); + client0.unregister(); + client1.unregister(); } @Test - public void requestFrontendTest_NoFrontendAvailable_RequestWithHigherPriority() { + public void requestFrontendTest_NoFrontendAvailable_RequestWithHigherPriority() + throws RemoteException { // Register clients - ResourceClientProfile[] profiles = new ResourceClientProfile[2]; - profiles[0] = resourceClientProfile("0" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - profiles[1] = resourceClientProfile("1" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - int[] clientPriorities = {100, 500}; - int[] clientId0 = new int[1]; - int[] clientId1 = new int[1]; - TestResourcesReclaimListener listener = new TestResourcesReclaimListener(); - mTunerResourceManagerService.registerClientProfileInternal( - profiles[0], listener, clientId0); - assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - mTunerResourceManagerService.updateClientPriorityInternal( - clientId0[0], clientPriorities[0], 0/*niceValue*/); - mTunerResourceManagerService.registerClientProfileInternal( - profiles[1], new TestResourcesReclaimListener(), clientId1); - assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - mTunerResourceManagerService.updateClientPriorityInternal( - clientId1[0], clientPriorities[1], 0/*niceValue*/); + TunerClient client0 = new TunerClient(); + TunerClient client1 = new TunerClient(); + client0.register("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100); + client1.register("1" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 500); // Init frontend resources. TunerFrontendInfo[] infos = new TunerFrontendInfo[2]; @@ -434,17 +462,16 @@ public class TunerResourceManagerServiceTest { mTunerResourceManagerService.setFrontendInfoListInternal(infos); TunerFrontendRequest request = - tunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(frontendHandle[0]).isEqualTo(infos[0].handle); - assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]) - .getInUseFrontendHandles()).isEqualTo(new HashSet<Integer>(Arrays.asList( - infos[0].handle, infos[1].handle))); + assertThat(client0.getProfile().getInUseFrontendHandles()) + .isEqualTo(new HashSet<Integer>(Arrays.asList(infos[0].handle, infos[1].handle))); request = - tunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS); + tunerFrontendRequest(client1.getId() /*clientId*/, FrontendSettings.TYPE_DVBS); assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(frontendHandle[0]).isEqualTo(infos[1].handle); @@ -453,22 +480,20 @@ public class TunerResourceManagerServiceTest { assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle) .isInUse()).isTrue(); assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle) - .getOwnerClientId()).isEqualTo(clientId1[0]); + .getOwnerClientId()).isEqualTo(client1.getId()); assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle) - .getOwnerClientId()).isEqualTo(clientId1[0]); - assertThat(listener.isReclaimed()).isTrue(); + .getOwnerClientId()).isEqualTo(client1.getId()); + assertThat(client0.isReclaimed()).isTrue(); + client0.unregister(); + client1.unregister(); } @Test - public void releaseFrontendTest_UnderTheSameExclusiveGroup() { + public void releaseFrontendTest_UnderTheSameExclusiveGroup() throws RemoteException { // Register clients - ResourceClientProfile[] profiles = new ResourceClientProfile[1]; - profiles[0] = resourceClientProfile("0" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - int[] clientId = new int[1]; - TestResourcesReclaimListener listener = new TestResourcesReclaimListener(); - mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId); - assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + TunerClient client0 = new TunerClient(); + client0.register("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); // Init frontend resources. TunerFrontendInfo[] infos = new TunerFrontendInfo[2]; @@ -479,7 +504,7 @@ public class TunerResourceManagerServiceTest { mTunerResourceManagerService.setFrontendInfoListInternal(infos); TunerFrontendRequest request = - tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isTrue(); @@ -488,43 +513,29 @@ public class TunerResourceManagerServiceTest { .getFrontendResource(infos[1].handle).isInUse()).isTrue(); // Release frontend - mTunerResourceManagerService.releaseFrontendInternal(mTunerResourceManagerService - .getFrontendResource(frontendHandle[0]), clientId[0]); + mTunerResourceManagerService.releaseFrontendInternal(frontendHandle[0], client0.getId()); assertThat(mTunerResourceManagerService .getFrontendResource(frontendHandle[0]).isInUse()).isFalse(); assertThat(mTunerResourceManagerService .getFrontendResource(infos[1].handle).isInUse()).isFalse(); - assertThat(mTunerResourceManagerService - .getClientProfile(clientId[0]).getInUseFrontendHandles().size()).isEqualTo(0); + assertThat(client0.getProfile().getInUseFrontendHandles().size()).isEqualTo(0); + client0.unregister(); } @Test - public void requestCasTest_NoCasAvailable_RequestWithHigherPriority() { + public void requestCasTest_NoCasAvailable_RequestWithHigherPriority() throws RemoteException { // Register clients - ResourceClientProfile[] profiles = new ResourceClientProfile[2]; - profiles[0] = resourceClientProfile("0" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - profiles[1] = resourceClientProfile("1" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - int[] clientPriorities = {100, 500}; - int[] clientId0 = new int[1]; - int[] clientId1 = new int[1]; - TestResourcesReclaimListener listener = new TestResourcesReclaimListener(); - mTunerResourceManagerService.registerClientProfileInternal( - profiles[0], listener, clientId0); - assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - mTunerResourceManagerService.updateClientPriorityInternal( - clientId0[0], clientPriorities[0], 0/*niceValue*/); - mTunerResourceManagerService.registerClientProfileInternal( - profiles[1], new TestResourcesReclaimListener(), clientId1); - assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - mTunerResourceManagerService.updateClientPriorityInternal( - clientId1[0], clientPriorities[1], 0/*niceValue*/); + TunerClient client0 = new TunerClient(); + TunerClient client1 = new TunerClient(); + client0.register("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100); + client1.register("1" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 500); // Init cas resources. mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/); - CasSessionRequest request = casSessionRequest(clientId0[0], 1 /*casSystemId*/); + CasSessionRequest request = casSessionRequest(client0.getId(), 1 /*casSystemId*/); int[] casSessionHandle = new int[1]; // Request for 2 cas sessions. assertThat(mTunerResourceManagerService @@ -533,54 +544,45 @@ public class TunerResourceManagerServiceTest { .requestCasSessionInternal(request, casSessionHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0])) .isEqualTo(1); - assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]) - .getInUseCasSystemId()).isEqualTo(1); + assertThat(client0.getProfile().getInUseCasSystemId()) + .isEqualTo(1); assertThat(mTunerResourceManagerService.getCasResource(1) - .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId0[0]))); + .getOwnerClientIds()).isEqualTo( + new HashSet<Integer>(Arrays.asList(client0.getId()))); assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue(); - request = casSessionRequest(clientId1[0], 1); + request = casSessionRequest(client1.getId(), 1); assertThat(mTunerResourceManagerService .requestCasSessionInternal(request, casSessionHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0])) .isEqualTo(1); - assertThat(mTunerResourceManagerService.getClientProfile(clientId1[0]) - .getInUseCasSystemId()).isEqualTo(1); - assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]) - .getInUseCasSystemId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID); + assertThat(client1.getProfile().getInUseCasSystemId()).isEqualTo(1); + assertThat(client0.getProfile().getInUseCasSystemId()) + .isEqualTo(ClientProfile.INVALID_RESOURCE_ID); assertThat(mTunerResourceManagerService.getCasResource(1) - .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId1[0]))); + .getOwnerClientIds()).isEqualTo( + new HashSet<Integer>(Arrays.asList(client1.getId()))); assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse(); - assertThat(listener.isReclaimed()).isTrue(); + assertThat(client0.isReclaimed()).isTrue(); + client0.unregister(); + client1.unregister(); } @Test - public void requestCiCamTest_NoCiCamAvailable_RequestWithHigherPriority() { + public void requestCiCamTest_NoCiCamAvailable_RequestWithHigherPriority() + throws RemoteException { // Register clients - ResourceClientProfile[] profiles = new ResourceClientProfile[2]; - profiles[0] = resourceClientProfile("0" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - profiles[1] = resourceClientProfile("1" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - int[] clientPriorities = {100, 500}; - int[] clientId0 = new int[1]; - int[] clientId1 = new int[1]; - TestResourcesReclaimListener listener = new TestResourcesReclaimListener(); - mTunerResourceManagerService.registerClientProfileInternal( - profiles[0], listener, clientId0); - assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - mTunerResourceManagerService.updateClientPriorityInternal( - clientId0[0], clientPriorities[0], 0/*niceValue*/); - mTunerResourceManagerService.registerClientProfileInternal( - profiles[1], new TestResourcesReclaimListener(), clientId1); - assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - mTunerResourceManagerService.updateClientPriorityInternal( - clientId1[0], clientPriorities[1], 0/*niceValue*/); + TunerClient client0 = new TunerClient(); + TunerClient client1 = new TunerClient(); + client0.register("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100); + client1.register("1" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 500); // Init cicam/cas resources. mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/); - TunerCiCamRequest request = tunerCiCamRequest(clientId0[0], 1 /*ciCamId*/); + TunerCiCamRequest request = tunerCiCamRequest(client0.getId(), 1 /*ciCamId*/); int[] ciCamHandle = new int[1]; // Request for 2 ciCam sessions. assertThat(mTunerResourceManagerService @@ -589,139 +591,125 @@ public class TunerResourceManagerServiceTest { .requestCiCamInternal(request, ciCamHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(ciCamHandle[0])) .isEqualTo(1); - assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]) - .getInUseCiCamId()).isEqualTo(1); + assertThat(client0.getProfile().getInUseCiCamId()).isEqualTo(1); assertThat(mTunerResourceManagerService.getCiCamResource(1) - .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId0[0]))); + .getOwnerClientIds()).isEqualTo( + new HashSet<Integer>(Arrays.asList(client0.getId()))); assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isTrue(); - request = tunerCiCamRequest(clientId1[0], 1); + request = tunerCiCamRequest(client1.getId(), 1); assertThat(mTunerResourceManagerService .requestCiCamInternal(request, ciCamHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(ciCamHandle[0])) .isEqualTo(1); - assertThat(mTunerResourceManagerService.getClientProfile(clientId1[0]) - .getInUseCiCamId()).isEqualTo(1); - assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]) - .getInUseCiCamId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID); + assertThat(client1.getProfile().getInUseCiCamId()).isEqualTo(1); + assertThat(client0.getProfile().getInUseCiCamId()) + .isEqualTo(ClientProfile.INVALID_RESOURCE_ID); assertThat(mTunerResourceManagerService.getCiCamResource(1) - .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId1[0]))); - assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isFalse(); - assertThat(listener.isReclaimed()).isTrue(); + .getOwnerClientIds()).isEqualTo( + new HashSet<Integer>(Arrays.asList(client1.getId()))); + assertThat(mTunerResourceManagerService + .getCiCamResource(1).isFullyUsed()).isFalse(); + assertThat(client0.isReclaimed()).isTrue(); + client0.unregister(); + client1.unregister(); } @Test - public void releaseCasTest() { + public void releaseCasTest() throws RemoteException { // Register clients - ResourceClientProfile[] profiles = new ResourceClientProfile[1]; - profiles[0] = resourceClientProfile("0" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - int[] clientId = new int[1]; - TestResourcesReclaimListener listener = new TestResourcesReclaimListener(); - mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId); - assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + TunerClient client0 = new TunerClient(); + client0.register("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); // Init cas resources. mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/); - CasSessionRequest request = casSessionRequest(clientId[0], 1 /*casSystemId*/); + CasSessionRequest request = casSessionRequest(client0.getId(), 1 /*casSystemId*/); int[] casSessionHandle = new int[1]; // Request for 1 cas sessions. assertThat(mTunerResourceManagerService .requestCasSessionInternal(request, casSessionHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0])) .isEqualTo(1); - assertThat(mTunerResourceManagerService.getClientProfile(clientId[0]) - .getInUseCasSystemId()).isEqualTo(1); + assertThat(client0.getProfile().getInUseCasSystemId()).isEqualTo(1); assertThat(mTunerResourceManagerService.getCasResource(1) - .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId[0]))); + .getOwnerClientIds()).isEqualTo( + new HashSet<Integer>(Arrays.asList(client0.getId()))); assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse(); // Release cas mTunerResourceManagerService.releaseCasSessionInternal(mTunerResourceManagerService - .getCasResource(1), clientId[0]); - assertThat(mTunerResourceManagerService.getClientProfile(clientId[0]) - .getInUseCasSystemId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID); + .getCasResource(1), client0.getId()); + assertThat(client0.getProfile().getInUseCasSystemId()) + .isEqualTo(ClientProfile.INVALID_RESOURCE_ID); assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse(); assertThat(mTunerResourceManagerService.getCasResource(1) .getOwnerClientIds()).isEmpty(); + client0.unregister(); } @Test - public void releaseCiCamTest() { + public void releaseCiCamTest() throws RemoteException { // Register clients - ResourceClientProfile[] profiles = new ResourceClientProfile[1]; - profiles[0] = resourceClientProfile("0" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - int[] clientId = new int[1]; - TestResourcesReclaimListener listener = new TestResourcesReclaimListener(); - mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId); - assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + TunerClient client0 = new TunerClient(); + client0.register("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); // Init cas resources. mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/); - TunerCiCamRequest request = tunerCiCamRequest(clientId[0], 1 /*ciCamId*/); + TunerCiCamRequest request = tunerCiCamRequest(client0.getId(), 1 /*ciCamId*/); int[] ciCamHandle = new int[1]; // Request for 1 ciCam sessions. assertThat(mTunerResourceManagerService .requestCiCamInternal(request, ciCamHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(ciCamHandle[0])) .isEqualTo(1); - assertThat(mTunerResourceManagerService.getClientProfile(clientId[0]) - .getInUseCiCamId()).isEqualTo(1); + assertThat(client0.getProfile().getInUseCiCamId()).isEqualTo(1); assertThat(mTunerResourceManagerService.getCiCamResource(1) - .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId[0]))); - assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isFalse(); + .getOwnerClientIds()).isEqualTo( + new HashSet<Integer>(Arrays.asList(client0.getId()))); + assertThat(mTunerResourceManagerService + .getCiCamResource(1).isFullyUsed()).isFalse(); // Release ciCam mTunerResourceManagerService.releaseCiCamInternal(mTunerResourceManagerService - .getCiCamResource(1), clientId[0]); - assertThat(mTunerResourceManagerService.getClientProfile(clientId[0]) - .getInUseCiCamId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID); - assertThat(mTunerResourceManagerService.getCiCamResource(1).isFullyUsed()).isFalse(); + .getCiCamResource(1), client0.getId()); + assertThat(client0.getProfile().getInUseCiCamId()) + .isEqualTo(ClientProfile.INVALID_RESOURCE_ID); + assertThat(mTunerResourceManagerService + .getCiCamResource(1).isFullyUsed()).isFalse(); assertThat(mTunerResourceManagerService.getCiCamResource(1) .getOwnerClientIds()).isEmpty(); + client0.unregister(); } @Test - public void requestLnbTest_NoLnbAvailable_RequestWithHigherPriority() { + public void requestLnbTest_NoLnbAvailable_RequestWithHigherPriority() throws RemoteException { // Register clients - ResourceClientProfile[] profiles = new ResourceClientProfile[2]; - profiles[0] = resourceClientProfile("0" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - profiles[1] = resourceClientProfile("1" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - int[] clientPriorities = {100, 500}; - int[] clientId0 = new int[1]; - int[] clientId1 = new int[1]; - TestResourcesReclaimListener listener = new TestResourcesReclaimListener(); - mTunerResourceManagerService.registerClientProfileInternal( - profiles[0], listener, clientId0); - assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - mTunerResourceManagerService.updateClientPriorityInternal( - clientId0[0], clientPriorities[0], 0/*niceValue*/); - mTunerResourceManagerService.registerClientProfileInternal( - profiles[1], new TestResourcesReclaimListener(), clientId1); - assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - mTunerResourceManagerService.updateClientPriorityInternal( - clientId1[0], clientPriorities[1], 0/*niceValue*/); + TunerClient client0 = new TunerClient(); + TunerClient client1 = new TunerClient(); + client0.register("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 100); + client1.register("1" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, 500); // Init lnb resources. int[] lnbHandles = {1}; mTunerResourceManagerService.setLnbInfoListInternal(lnbHandles); TunerLnbRequest request = new TunerLnbRequest(); - request.clientId = clientId0[0]; + request.clientId = client0.getId(); int[] lnbHandle = new int[1]; assertThat(mTunerResourceManagerService .requestLnbInternal(request, lnbHandle)).isTrue(); assertThat(lnbHandle[0]).isEqualTo(lnbHandles[0]); - assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]).getInUseLnbHandles()) + assertThat(client0.getProfile().getInUseLnbHandles()) .isEqualTo(new HashSet<Integer>(Arrays.asList(lnbHandles[0]))); request = new TunerLnbRequest(); - request.clientId = clientId1[0]; + request.clientId = client1.getId(); assertThat(mTunerResourceManagerService .requestLnbInternal(request, lnbHandle)).isTrue(); @@ -729,29 +717,26 @@ public class TunerResourceManagerServiceTest { assertThat(mTunerResourceManagerService.getLnbResource(lnbHandles[0]) .isInUse()).isTrue(); assertThat(mTunerResourceManagerService.getLnbResource(lnbHandles[0]) - .getOwnerClientId()).isEqualTo(clientId1[0]); - assertThat(listener.isReclaimed()).isTrue(); - assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]) - .getInUseLnbHandles().size()).isEqualTo(0); + .getOwnerClientId()).isEqualTo(client1.getId()); + assertThat(client0.isReclaimed()).isTrue(); + assertThat(client0.getProfile().getInUseLnbHandles().size()).isEqualTo(0); + client0.unregister(); + client1.unregister(); } @Test - public void releaseLnbTest() { + public void releaseLnbTest() throws RemoteException { // Register clients - ResourceClientProfile[] profiles = new ResourceClientProfile[1]; - profiles[0] = resourceClientProfile("0" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - int[] clientId = new int[1]; - TestResourcesReclaimListener listener = new TestResourcesReclaimListener(); - mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId); - assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + TunerClient client0 = new TunerClient(); + client0.register("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); // Init lnb resources. int[] lnbHandles = {0}; mTunerResourceManagerService.setLnbInfoListInternal(lnbHandles); TunerLnbRequest request = new TunerLnbRequest(); - request.clientId = clientId[0]; + request.clientId = client0.getId(); int[] lnbHandle = new int[1]; assertThat(mTunerResourceManagerService .requestLnbInternal(request, lnbHandle)).isTrue(); @@ -762,19 +747,16 @@ public class TunerResourceManagerServiceTest { .getLnbResource(lnbHandle[0])); assertThat(mTunerResourceManagerService .getLnbResource(lnbHandle[0]).isInUse()).isFalse(); - assertThat(mTunerResourceManagerService - .getClientProfile(clientId[0]).getInUseLnbHandles().size()).isEqualTo(0); + assertThat(client0.getProfile().getInUseLnbHandles().size()).isEqualTo(0); + client0.unregister(); } @Test - public void unregisterClientTest_usingFrontend() { - // Register client - ResourceClientProfile profile = resourceClientProfile("0" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - int[] clientId = new int[1]; - mTunerResourceManagerService.registerClientProfileInternal( - profile, null /*listener*/, clientId); - assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + public void unregisterClientTest_usingFrontend() throws RemoteException { + // Register clients + TunerClient client0 = new TunerClient(); + client0.register("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); // Init frontend resources. TunerFrontendInfo[] infos = new TunerFrontendInfo[2]; @@ -785,7 +767,7 @@ public class TunerResourceManagerServiceTest { mTunerResourceManagerService.setFrontendInfoListInternal(infos); TunerFrontendRequest request = - tunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); + tunerFrontendRequest(client0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; assertThat(mTunerResourceManagerService .requestFrontendInternal(request, frontendHandle)).isTrue(); @@ -796,26 +778,20 @@ public class TunerResourceManagerServiceTest { .isInUse()).isTrue(); // Unregister client when using frontend - mTunerResourceManagerService.unregisterClientProfileInternal(clientId[0]); + client0.unregister(); assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle) .isInUse()).isFalse(); assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle) .isInUse()).isFalse(); - assertThat(mTunerResourceManagerService.checkClientExists(clientId[0])).isFalse(); - + assertThat(mTunerResourceManagerService.checkClientExists(client0.getId())).isFalse(); } @Test - public void requestDemuxTest() { - // Register client - ResourceClientProfile profile0 = resourceClientProfile("0" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - ResourceClientProfile profile1 = resourceClientProfile("1" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - int[] clientId0 = new int[1]; - mTunerResourceManagerService.registerClientProfileInternal( - profile0, null /*listener*/, clientId0); - assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + public void requestDemuxTest() throws RemoteException { + // Register clients + TunerClient client0 = new TunerClient(); + client0.register("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); TunerDemuxInfo[] infos = new TunerDemuxInfo[3]; infos[0] = tunerDemuxInfo(0 /* handle */, Filter.TYPE_TS | Filter.TYPE_IP); @@ -825,7 +801,7 @@ public class TunerResourceManagerServiceTest { int[] demuxHandle0 = new int[1]; // first with undefined type (should be the first one with least # of caps) - TunerDemuxRequest request = tunerDemuxRequest(clientId0[0], Filter.TYPE_UNDEFINED); + TunerDemuxRequest request = tunerDemuxRequest(client0.getId(), Filter.TYPE_UNDEFINED); assertThat(mTunerResourceManagerService.requestDemuxInternal(request, demuxHandle0)) .isTrue(); assertThat(demuxHandle0[0]).isEqualTo(1); @@ -846,16 +822,16 @@ public class TunerResourceManagerServiceTest { assertThat(demuxHandle0[0]).isEqualTo(2); // request for another TS - int[] clientId1 = new int[1]; - mTunerResourceManagerService.registerClientProfileInternal( - profile1, null /*listener*/, clientId1); - assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + TunerClient client1 = new TunerClient(); + client1.register("1" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + int[] demuxHandle1 = new int[1]; - TunerDemuxRequest request1 = tunerDemuxRequest(clientId1[0], Filter.TYPE_TS); + TunerDemuxRequest request1 = tunerDemuxRequest(client1.getId(), Filter.TYPE_TS); assertThat(mTunerResourceManagerService.requestDemuxInternal(request1, demuxHandle1)) .isTrue(); assertThat(demuxHandle1[0]).isEqualTo(0); - assertThat(mTunerResourceManagerService.getResourceIdFromHandle(demuxHandle1[0])) + assertThat(mTunerResourceManagerService.getResourceIdFromHandle(client1.getId())) .isEqualTo(0); // release demuxes @@ -863,33 +839,23 @@ public class TunerResourceManagerServiceTest { mTunerResourceManagerService.releaseDemuxInternal(dr); dr = mTunerResourceManagerService.getDemuxResource(demuxHandle1[0]); mTunerResourceManagerService.releaseDemuxInternal(dr); + + client0.unregister(); + client1.unregister(); } @Test - public void requestDemuxTest_ResourceReclaim() { + public void requestDemuxTest_ResourceReclaim() throws RemoteException { // Register clients - ResourceClientProfile profile0 = resourceClientProfile("0" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - ResourceClientProfile profile1 = resourceClientProfile("1" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN); - ResourceClientProfile profile2 = resourceClientProfile("2" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN); - int[] clientId0 = new int[1]; - int[] clientId1 = new int[1]; - int[] clientId2 = new int[1]; - TestResourcesReclaimListener listener0 = new TestResourcesReclaimListener(); - TestResourcesReclaimListener listener1 = new TestResourcesReclaimListener(); - TestResourcesReclaimListener listener2 = new TestResourcesReclaimListener(); - - mTunerResourceManagerService.registerClientProfileInternal( - profile0, listener0, clientId0); - assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - mTunerResourceManagerService.registerClientProfileInternal( - profile1, listener1, clientId1); - assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - mTunerResourceManagerService.registerClientProfileInternal( - profile2, listener2, clientId1); - assertThat(clientId2[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + TunerClient client0 = new TunerClient(); + TunerClient client1 = new TunerClient(); + TunerClient client2 = new TunerClient(); + client0.register("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + client1.register("1" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN); + client2.register("2" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN); // Init demux resources. TunerDemuxInfo[] infos = new TunerDemuxInfo[2]; @@ -897,66 +863,67 @@ public class TunerResourceManagerServiceTest { infos[1] = tunerDemuxInfo(1 /*handle*/, Filter.TYPE_TS); mTunerResourceManagerService.setDemuxInfoListInternal(infos); - // let clientId0(prio:100) request for IP - should succeed - TunerDemuxRequest request0 = tunerDemuxRequest(clientId0[0], Filter.TYPE_IP); + // let client0(prio:100) request for IP - should succeed + TunerDemuxRequest request0 = tunerDemuxRequest(client0.getId(), Filter.TYPE_IP); int[] demuxHandle0 = new int[1]; assertThat(mTunerResourceManagerService .requestDemuxInternal(request0, demuxHandle0)).isTrue(); assertThat(demuxHandle0[0]).isEqualTo(0); - // let clientId1(prio:50) request for IP - should fail - TunerDemuxRequest request1 = tunerDemuxRequest(clientId1[0], Filter.TYPE_IP); + // let client1(prio:50) request for IP - should fail + TunerDemuxRequest request1 = tunerDemuxRequest(client1.getId(), Filter.TYPE_IP); int[] demuxHandle1 = new int[1]; demuxHandle1[0] = -1; assertThat(mTunerResourceManagerService .requestDemuxInternal(request1, demuxHandle1)).isFalse(); - assertThat(listener0.isReclaimed()).isFalse(); + assertThat(client0.isReclaimed()).isFalse(); assertThat(demuxHandle1[0]).isEqualTo(-1); - // let clientId1(prio:50) request for TS - should succeed + // let client1(prio:50) request for TS - should succeed request1.desiredFilterTypes = Filter.TYPE_TS; assertThat(mTunerResourceManagerService .requestDemuxInternal(request1, demuxHandle1)).isTrue(); assertThat(demuxHandle1[0]).isEqualTo(1); - assertThat(listener0.isReclaimed()).isFalse(); + assertThat(client0.isReclaimed()).isFalse(); - // now release demux for the clientId0 (higher priority) and request demux + // now release demux for the client0 (higher priority) and request demux DemuxResource dr = mTunerResourceManagerService.getDemuxResource(demuxHandle0[0]); mTunerResourceManagerService.releaseDemuxInternal(dr); - // let clientId2(prio:50) request for TS - should succeed - TunerDemuxRequest request2 = tunerDemuxRequest(clientId2[0], Filter.TYPE_TS); + // let client2(prio:50) request for TS - should succeed + TunerDemuxRequest request2 = tunerDemuxRequest(client2.getId(), Filter.TYPE_TS); int[] demuxHandle2 = new int[1]; assertThat(mTunerResourceManagerService .requestDemuxInternal(request2, demuxHandle2)).isTrue(); assertThat(demuxHandle2[0]).isEqualTo(0); - assertThat(listener1.isReclaimed()).isFalse(); + assertThat(client1.isReclaimed()).isFalse(); - // let clientId0(prio:100) request for TS - should reclaim from clientId2 + // let client0(prio:100) request for TS - should reclaim from client1 // , who has the smaller caps request0.desiredFilterTypes = Filter.TYPE_TS; assertThat(mTunerResourceManagerService .requestDemuxInternal(request0, demuxHandle0)).isTrue(); - assertThat(listener1.isReclaimed()).isFalse(); - assertThat(listener2.isReclaimed()).isTrue(); + assertThat(client1.isReclaimed()).isTrue(); + assertThat(client2.isReclaimed()).isFalse(); + client0.unregister(); + client1.unregister(); + client2.unregister(); } @Test public void requestDescramblerTest() { - // Register client - ResourceClientProfile profile = resourceClientProfile("0" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); - int[] clientId = new int[1]; - mTunerResourceManagerService.registerClientProfileInternal( - profile, null /*listener*/, clientId); - assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + // Register clients + TunerClient client0 = new TunerClient(); + client0.register("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); int[] desHandle = new int[1]; TunerDescramblerRequest request = new TunerDescramblerRequest(); - request.clientId = clientId[0]; + request.clientId = client0.getId(); assertThat(mTunerResourceManagerService.requestDescramblerInternal(request, desHandle)) .isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(desHandle[0])).isEqualTo(0); + client0.unregister(); } @Test @@ -978,74 +945,26 @@ public class TunerResourceManagerServiceTest { } @Test - public void shareFrontendTest_FrontendWithExclusiveGroupReadyToShare() { + public void shareFrontendTest_FrontendWithExclusiveGroupReadyToShare() throws RemoteException { /**** Register Clients and Set Priority ****/ + TunerClient ownerClient0 = new TunerClient(); + TunerClient ownerClient1 = new TunerClient(); + TunerClient shareClient0 = new TunerClient(); + TunerClient shareClient1 = new TunerClient(); + ownerClient0.register("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, 100); + ownerClient1.register("1" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, 300); + shareClient0.register("2" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD, 200); + shareClient1.register("3" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD, 400); - // Int array to save the returned client ids - int[] ownerClientId0 = new int[1]; - int[] ownerClientId1 = new int[1]; - int[] shareClientId0 = new int[1]; - int[] shareClientId1 = new int[1]; - - // Predefined client profiles - ResourceClientProfile[] ownerProfiles = new ResourceClientProfile[2]; - ResourceClientProfile[] shareProfiles = new ResourceClientProfile[2]; - ownerProfiles[0] = resourceClientProfile( - "0" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE); - ownerProfiles[1] = resourceClientProfile( - "1" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE); - shareProfiles[0] = resourceClientProfile( - "2" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD); - shareProfiles[1] = resourceClientProfile( - "3" /*sessionId*/, - TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD); - - // Predefined client reclaim listeners - TestResourcesReclaimListener ownerListener0 = new TestResourcesReclaimListener(); - TestResourcesReclaimListener shareListener0 = new TestResourcesReclaimListener(); - TestResourcesReclaimListener ownerListener1 = new TestResourcesReclaimListener(); - TestResourcesReclaimListener shareListener1 = new TestResourcesReclaimListener(); - // Register clients and validate the returned client ids - mTunerResourceManagerService - .registerClientProfileInternal(ownerProfiles[0], ownerListener0, ownerClientId0); - mTunerResourceManagerService - .registerClientProfileInternal(shareProfiles[0], shareListener0, shareClientId0); - mTunerResourceManagerService - .registerClientProfileInternal(ownerProfiles[1], ownerListener1, ownerClientId1); - mTunerResourceManagerService - .registerClientProfileInternal(shareProfiles[1], shareListener1, shareClientId1); - assertThat(ownerClientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - assertThat(shareClientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - assertThat(ownerClientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - assertThat(shareClientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); - - mTunerResourceManagerService.updateClientPriorityInternal( - ownerClientId0[0], - 100/*priority*/, - 0/*niceValue*/); - mTunerResourceManagerService.updateClientPriorityInternal( - shareClientId0[0], - 200/*priority*/, - 0/*niceValue*/); - mTunerResourceManagerService.updateClientPriorityInternal( - ownerClientId1[0], - 300/*priority*/, - 0/*niceValue*/); - mTunerResourceManagerService.updateClientPriorityInternal( - shareClientId1[0], - 400/*priority*/, - 0/*niceValue*/); mTunerResourceManagerService.updateClientPriorityInternal( - shareClientId1[0], + shareClient1.getId(), -1/*invalid priority*/, 0/*niceValue*/); - assertThat(mTunerResourceManagerService - .getClientProfile(shareClientId1[0]) - .getPriority()) - .isEqualTo(400); + assertThat(shareClient1.getProfile().getPriority()).isEqualTo(400); /**** Init Frontend Resources ****/ @@ -1072,7 +991,7 @@ public class TunerResourceManagerServiceTest { // Predefined frontend request and array to save returned frontend handle int[] frontendHandle = new int[1]; TunerFrontendRequest request = tunerFrontendRequest( - ownerClientId0[0] /*clientId*/, + ownerClient0.getId() /*clientId*/, FrontendSettings.TYPE_DVBT); // Request call and validate granted resource and internal mapping @@ -1080,9 +999,7 @@ public class TunerResourceManagerServiceTest { .requestFrontendInternal(request, frontendHandle)) .isTrue(); assertThat(frontendHandle[0]).isEqualTo(infos[0].handle); - assertThat(mTunerResourceManagerService - .getClientProfile(ownerClientId0[0]) - .getInUseFrontendHandles()) + assertThat(ownerClient0.getProfile().getInUseFrontendHandles()) .isEqualTo(new HashSet<Integer>(Arrays.asList( infos[0].handle, infos[1].handle))); @@ -1091,11 +1008,11 @@ public class TunerResourceManagerServiceTest { // Share frontend call and validate the internal mapping mTunerResourceManagerService.shareFrontendInternal( - shareClientId0[0]/*selfClientId*/, - ownerClientId0[0]/*targetClientId*/); + shareClient0.getId()/*selfClientId*/, + ownerClient0.getId()/*targetClientId*/); mTunerResourceManagerService.shareFrontendInternal( - shareClientId1[0]/*selfClientId*/, - ownerClientId0[0]/*targetClientId*/); + shareClient1.getId()/*selfClientId*/, + ownerClient0.getId()/*targetClientId*/); // Verify fe in use status assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle) .isInUse()).isTrue(); @@ -1103,31 +1020,24 @@ public class TunerResourceManagerServiceTest { .isInUse()).isTrue(); // Verify fe owner status assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle) - .getOwnerClientId()).isEqualTo(ownerClientId0[0]); + .getOwnerClientId()).isEqualTo(ownerClient0.getId()); assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle) - .getOwnerClientId()).isEqualTo(ownerClientId0[0]); + .getOwnerClientId()).isEqualTo(ownerClient0.getId()); // Verify share fe client status in the primary owner client - assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId0[0]) - .getShareFeClientIds()) + assertThat(ownerClient0.getProfile().getShareFeClientIds()) .isEqualTo(new HashSet<Integer>(Arrays.asList( - shareClientId0[0], - shareClientId1[0]))); + shareClient0.getId(), + shareClient1.getId()))); // Verify in use frontend list in all the primary owner and share owner clients - assertThat(mTunerResourceManagerService - .getClientProfile(ownerClientId0[0]) - .getInUseFrontendHandles()) + assertThat(ownerClient0.getProfile().getInUseFrontendHandles()) .isEqualTo(new HashSet<Integer>(Arrays.asList( infos[0].handle, infos[1].handle))); - assertThat(mTunerResourceManagerService - .getClientProfile(shareClientId0[0]) - .getInUseFrontendHandles()) + assertThat(shareClient0.getProfile().getInUseFrontendHandles()) .isEqualTo(new HashSet<Integer>(Arrays.asList( infos[0].handle, infos[1].handle))); - assertThat(mTunerResourceManagerService - .getClientProfile(shareClientId1[0]) - .getInUseFrontendHandles()) + assertThat(shareClient1.getProfile().getInUseFrontendHandles()) .isEqualTo(new HashSet<Integer>(Arrays.asList( infos[0].handle, infos[1].handle))); @@ -1135,21 +1045,17 @@ public class TunerResourceManagerServiceTest { /**** Remove Frontend Share Owner ****/ // Unregister the second share fe client - mTunerResourceManagerService.unregisterClientProfileInternal(shareClientId1[0]); + shareClient1.unregister(); // Validate the internal mapping - assertThat(mTunerResourceManagerService.getClientProfile(ownerClientId0[0]) - .getShareFeClientIds()) + assertThat(ownerClient0.getProfile().getShareFeClientIds()) .isEqualTo(new HashSet<Integer>(Arrays.asList( - shareClientId0[0]))); - assertThat(mTunerResourceManagerService - .getClientProfile(ownerClientId0[0]) - .getInUseFrontendHandles()) + shareClient0.getId()))); + assertThat(ownerClient0.getProfile().getInUseFrontendHandles()) .isEqualTo(new HashSet<Integer>(Arrays.asList( infos[0].handle, infos[1].handle))); - assertThat(mTunerResourceManagerService - .getClientProfile(shareClientId0[0]) + assertThat(shareClient0.getProfile() .getInUseFrontendHandles()) .isEqualTo(new HashSet<Integer>(Arrays.asList( infos[0].handle, @@ -1159,7 +1065,7 @@ public class TunerResourceManagerServiceTest { // Predefined second frontend request request = tunerFrontendRequest( - ownerClientId1[0] /*clientId*/, + ownerClient1.getId() /*clientId*/, FrontendSettings.TYPE_DVBT); // Second request call @@ -1170,43 +1076,35 @@ public class TunerResourceManagerServiceTest { // Validate granted resource and internal mapping assertThat(frontendHandle[0]).isEqualTo(infos[0].handle); assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle) - .getOwnerClientId()).isEqualTo(ownerClientId1[0]); + .getOwnerClientId()).isEqualTo(ownerClient1.getId()); assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle) - .getOwnerClientId()).isEqualTo(ownerClientId1[0]); - assertThat(mTunerResourceManagerService - .getClientProfile(ownerClientId1[0]) - .getInUseFrontendHandles()) + .getOwnerClientId()).isEqualTo(ownerClient1.getId()); + assertThat(ownerClient1.getProfile().getInUseFrontendHandles()) .isEqualTo(new HashSet<Integer>(Arrays.asList( infos[0].handle, infos[1].handle))); - assertThat(mTunerResourceManagerService - .getClientProfile(ownerClientId0[0]) - .getInUseFrontendHandles() + assertThat(ownerClient0.getProfile().getInUseFrontendHandles() .isEmpty()) .isTrue(); - assertThat(mTunerResourceManagerService - .getClientProfile(shareClientId0[0]) - .getInUseFrontendHandles() + assertThat(shareClient0.getProfile().getInUseFrontendHandles() .isEmpty()) .isTrue(); - assertThat(mTunerResourceManagerService - .getClientProfile(ownerClientId0[0]) - .getShareFeClientIds() + assertThat(ownerClient0.getProfile().getShareFeClientIds() .isEmpty()) .isTrue(); - assertThat(ownerListener0.isReclaimed()).isTrue(); - assertThat(shareListener0.isReclaimed()).isTrue(); + assertThat(ownerClient0.isReclaimed()).isTrue(); + assertThat(shareClient0.isReclaimed()).isTrue(); /**** Release Frontend Resource From Primary Owner ****/ // Reshare the frontend mTunerResourceManagerService.shareFrontendInternal( - shareClientId0[0]/*selfClientId*/, - ownerClientId1[0]/*targetClientId*/); + shareClient0.getId()/*selfClientId*/, + ownerClient1.getId()/*targetClientId*/); // Release the frontend resource from the primary owner - mTunerResourceManagerService.releaseFrontendInternal(mTunerResourceManagerService - .getFrontendResource(infos[0].handle), ownerClientId1[0]); + mTunerResourceManagerService.releaseFrontendInternal(infos[0].handle, + ownerClient1.getId()); // Validate the internal mapping assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle) @@ -1214,19 +1112,13 @@ public class TunerResourceManagerServiceTest { assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle) .isInUse()).isFalse(); // Verify client status - assertThat(mTunerResourceManagerService - .getClientProfile(ownerClientId1[0]) - .getInUseFrontendHandles() + assertThat(ownerClient1.getProfile().getInUseFrontendHandles() .isEmpty()) .isTrue(); - assertThat(mTunerResourceManagerService - .getClientProfile(shareClientId0[0]) - .getInUseFrontendHandles() + assertThat(shareClient0.getProfile().getInUseFrontendHandles() .isEmpty()) .isTrue(); - assertThat(mTunerResourceManagerService - .getClientProfile(ownerClientId1[0]) - .getShareFeClientIds() + assertThat(ownerClient1.getProfile().getShareFeClientIds() .isEmpty()) .isTrue(); @@ -1234,7 +1126,7 @@ public class TunerResourceManagerServiceTest { // Predefined Lnb request and handle array TunerLnbRequest requestLnb = new TunerLnbRequest(); - requestLnb.clientId = shareClientId0[0]; + requestLnb.clientId = shareClient0.getId(); int[] lnbHandle = new int[1]; // Request for an Lnb @@ -1247,11 +1139,11 @@ public class TunerResourceManagerServiceTest { .requestFrontendInternal(request, frontendHandle)) .isTrue(); mTunerResourceManagerService.shareFrontendInternal( - shareClientId0[0]/*selfClientId*/, - ownerClientId1[0]/*targetClientId*/); + shareClient0.getId()/*selfClientId*/, + ownerClient1.getId()/*targetClientId*/); // Unregister the primary owner of the shared frontend - mTunerResourceManagerService.unregisterClientProfileInternal(ownerClientId1[0]); + ownerClient1.unregister(); // Validate the internal mapping assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].handle) @@ -1259,16 +1151,15 @@ public class TunerResourceManagerServiceTest { assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].handle) .isInUse()).isFalse(); // Verify client status - assertThat(mTunerResourceManagerService - .getClientProfile(shareClientId0[0]) - .getInUseFrontendHandles() + assertThat(shareClient0.getProfile().getInUseFrontendHandles() .isEmpty()) .isTrue(); - assertThat(mTunerResourceManagerService - .getClientProfile(shareClientId0[0]) - .getInUseLnbHandles()) + assertThat(shareClient0.getProfile().getInUseLnbHandles()) .isEqualTo(new HashSet<Integer>(Arrays.asList( lnbHandles[0]))); + + ownerClient0.unregister(); + shareClient0.unregister(); } private TunerFrontendInfo tunerFrontendInfo( diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index cba2eeae5c9f..13bd5eb67e44 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -10014,6 +10014,19 @@ public class CarrierConfigManager { public static final String KEY_SATELLITE_NIDD_APN_NAME_STRING = "satellite_nidd_apn_name_string"; + /** + * Default value {@code true}, meaning when an emergency call request comes in, if the device is + * in emergency satellite mode but hasn't sent the first satellite datagram, then exits + * satellite mode to allow the emergency call to go through. + * + * If {@code false}, the emergency call is always blocked if device is in emergency satellite + * mode. Note if device is NOT in emergency satellite mode, emergency call is always allowed. + * + * @hide + */ + public static final String KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL = + "satellite_roaming_turn_off_session_for_emergency_call_bool"; + /** @hide */ @IntDef({ CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC, @@ -11276,6 +11289,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_SATELLITE_ESOS_SUPPORTED_BOOL, false); sDefaults.putBoolean(KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL, false); sDefaults.putString(KEY_SATELLITE_NIDD_APN_NAME_STRING, ""); + sDefaults.putBoolean(KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL, true); sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT, 0); sDefaults.putInt(KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT, SatelliteManager.EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911); |