summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt7
-rw-r--r--core/java/android/widget/Editor.java28
-rw-r--r--core/java/android/widget/SelectionActionModeHelper.java6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt194
-rw-r--r--media/java/android/media/IMediaRouter2.aidl4
-rw-r--r--media/java/android/media/IMediaRouter2Manager.aidl4
-rw-r--r--media/java/android/media/MediaRouter2.java92
-rw-r--r--media/java/android/media/MediaRouter2Manager.java85
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java189
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java43
-rw-r--r--packages/SettingsLib/Spa/OWNERS4
-rw-r--r--packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt2
-rw-r--r--packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt8
-rw-r--r--packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt107
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt91
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt20
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt205
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt144
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt185
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java12
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java12
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java319
22 files changed, 1406 insertions, 355 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index ea2ae0c9e15f..4734e8a75de6 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -23189,9 +23189,10 @@ package android.media {
public abstract static class MediaRouter2.RouteCallback {
ctor public MediaRouter2.RouteCallback();
- method public void onRoutesAdded(@NonNull java.util.List<android.media.MediaRoute2Info>);
- method public void onRoutesChanged(@NonNull java.util.List<android.media.MediaRoute2Info>);
- method public void onRoutesRemoved(@NonNull java.util.List<android.media.MediaRoute2Info>);
+ method @Deprecated public void onRoutesAdded(@NonNull java.util.List<android.media.MediaRoute2Info>);
+ method @Deprecated public void onRoutesChanged(@NonNull java.util.List<android.media.MediaRoute2Info>);
+ method @Deprecated public void onRoutesRemoved(@NonNull java.util.List<android.media.MediaRoute2Info>);
+ method public void onRoutesUpdated(@NonNull java.util.List<android.media.MediaRoute2Info>);
}
public class MediaRouter2.RoutingController {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index b233e5453c05..b21c5b35e24b 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -226,6 +226,8 @@ public class Editor {
final UndoInputFilter mUndoInputFilter = new UndoInputFilter(this);
boolean mAllowUndo = true;
+ private int mLastToolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+
private final MetricsLogger mMetricsLogger = new MetricsLogger();
// Cursor Controllers.
@@ -1732,6 +1734,9 @@ public class Editor {
@VisibleForTesting
public void onTouchEvent(MotionEvent event) {
final boolean filterOutEvent = shouldFilterOutTouchEvent(event);
+
+ mLastToolType = event.getToolType(event.getActionIndex());
+
mLastButtonState = event.getButtonState();
if (filterOutEvent) {
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
@@ -1784,7 +1789,7 @@ public class Editor {
}
private void showFloatingToolbar() {
- if (mTextActionMode != null) {
+ if (mTextActionMode != null && showUIForFingerInput()) {
// Delay "show" so it doesn't interfere with click confirmations
// or double-clicks that could "dismiss" the floating toolbar.
int delay = ViewConfiguration.getDoubleTapTimeout();
@@ -1864,7 +1869,8 @@ public class Editor {
final CursorController cursorController = mTextView.hasSelection()
? getSelectionController() : getInsertionController();
if (cursorController != null && !cursorController.isActive()
- && !cursorController.isCursorBeingModified()) {
+ && !cursorController.isCursorBeingModified()
+ && showUIForFingerInput()) {
cursorController.show();
}
}
@@ -2515,6 +2521,10 @@ public class Editor {
return false;
}
+ if (!showUIForFingerInput()) {
+ return false;
+ }
+
ActionMode.Callback actionModeCallback = new TextActionModeCallback(actionMode);
mTextActionMode = mTextView.startActionMode(actionModeCallback, ActionMode.TYPE_FLOATING);
registerOnBackInvokedCallback();
@@ -2667,7 +2677,7 @@ public class Editor {
mTextView.postDelayed(mShowSuggestionRunnable,
ViewConfiguration.getDoubleTapTimeout());
} else if (hasInsertionController()) {
- if (shouldInsertCursor) {
+ if (shouldInsertCursor && showUIForFingerInput()) {
getInsertionController().show();
} else {
getInsertionController().hide();
@@ -5397,7 +5407,8 @@ public class Editor {
final PointF showPosInView = new PointF();
final boolean shouldShow = checkForTransforms() /*check not rotated and compute scale*/
&& !tooLargeTextForMagnifier()
- && obtainMagnifierShowCoordinates(event, showPosInView);
+ && obtainMagnifierShowCoordinates(event, showPosInView)
+ && showUIForFingerInput();
if (shouldShow) {
// Make the cursor visible and stop blinking.
mRenderCursorRegardlessTiming = true;
@@ -6343,6 +6354,15 @@ public class Editor {
}
}
+ /**
+ * Returns true when need to show UIs, e.g. floating toolbar, etc, for finger based interaction.
+ *
+ * @return true if UIs need to show for finger interaciton. false if UIs are not necessary.
+ */
+ public boolean showUIForFingerInput() {
+ return mLastToolType != MotionEvent.TOOL_TYPE_MOUSE;
+ }
+
/** Controller for the insertion cursor. */
@VisibleForTesting
public class InsertionPointCursorController implements CursorController {
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index a0ec48bc6beb..54a415cfa01b 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -301,7 +301,11 @@ public final class SelectionActionModeHelper {
final SelectionModifierCursorController controller = mEditor.getSelectionController();
if (controller != null
&& (mTextView.isTextSelectable() || mTextView.isTextEditable())) {
- controller.show();
+ if (mEditor.showUIForFingerInput()) {
+ controller.show();
+ } else {
+ controller.hide();
+ }
}
if (result != null) {
switch (actionMode) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
new file mode 100644
index 000000000000..c23cdb610671
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test switch back to split pair from recent.
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchBackToSplitFromRecent`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+
+ // TODO(b/231399940): Remove this once we can use recent shortcut to enter split.
+ @Before
+ open fun before() {
+ Assume.assumeTrue(tapl.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ primaryApp.launchViaIntent(wmHelper)
+ // TODO(b/231399940): Use recent shortcut to enter split.
+ tapl.launchedAppState.taskbar
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+
+ tapl.goHome()
+ wmHelper.StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withHomeActivityVisible()
+ .waitForAndVerify()
+ }
+ }
+ transitions {
+ tapl.workspace.switchToOverview()
+ .currentTask
+ .open()
+ SplitScreenHelper.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ primaryApp, splitLeftTop = false)
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ secondaryApp, splitLeftTop = true)
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun entireScreenCovered() =
+ super.entireScreenCovered()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerIsVisibleAtStartAndEnd() =
+ super.navBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarLayerPositionAtStartAndEnd() =
+ super.navBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun navBarWindowIsAlwaysVisible() =
+ super.navBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerIsVisibleAtStartAndEnd() =
+ super.statusBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarLayerPositionAtStartAndEnd() =
+ super.statusBarLayerPositionAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun statusBarWindowIsAlwaysVisible() =
+ super.statusBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarLayerIsVisibleAtStartAndEnd() =
+ super.taskBarLayerIsVisibleAtStartAndEnd()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun taskBarWindowIsAlwaysVisible() =
+ super.taskBarWindowIsAlwaysVisible()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+ super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+ /** {@inheritDoc} */
+ @Postsubmit
+ @Test
+ override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+ super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ }
+ }
+}
diff --git a/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl
index fe15f0e67b1d..29bfd1acae17 100644
--- a/media/java/android/media/IMediaRouter2.aidl
+++ b/media/java/android/media/IMediaRouter2.aidl
@@ -26,9 +26,7 @@ import android.os.Bundle;
oneway interface IMediaRouter2 {
void notifyRouterRegistered(in List<MediaRoute2Info> currentRoutes,
in RoutingSessionInfo currentSystemSessionInfo);
- void notifyRoutesAdded(in List<MediaRoute2Info> routes);
- void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
- void notifyRoutesChanged(in List<MediaRoute2Info> routes);
+ void notifyRoutesUpdated(in List<MediaRoute2Info> routes);
void notifySessionCreated(int requestId, in @nullable RoutingSessionInfo sessionInfo);
void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo);
void notifySessionReleased(in RoutingSessionInfo sessionInfo);
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index 71dc2a781ba9..9f3c3ff89032 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -30,8 +30,6 @@ oneway interface IMediaRouter2Manager {
void notifySessionReleased(in RoutingSessionInfo session);
void notifyDiscoveryPreferenceChanged(String packageName,
in RouteDiscoveryPreference discoveryPreference);
- void notifyRoutesAdded(in List<MediaRoute2Info> routes);
- void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
- void notifyRoutesChanged(in List<MediaRoute2Info> routes);
+ void notifyRoutesUpdated(in List<MediaRoute2Info> routes);
void notifyRequestFailed(int requestId, int reason);
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index a7a21e7a2013..26cb9f8e9ee1 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -132,7 +132,7 @@ public final class MediaRouter2 {
/**
* Stores an auxiliary copy of {@link #mFilteredRoutes} at the time of the last route callback
* dispatch. This is only used to determine what callback a route should be assigned to (added,
- * removed, changed) in {@link #dispatchFilteredRoutesChangedLocked(List)}.
+ * removed, changed) in {@link #dispatchFilteredRoutesUpdatedOnHandler(List)}.
*/
private volatile ArrayMap<String, MediaRoute2Info> mPreviousRoutes = new ArrayMap<>();
@@ -820,7 +820,7 @@ public final class MediaRouter2 {
}
}
- void dispatchFilteredRoutesChangedLocked(List<MediaRoute2Info> newRoutes) {
+ void dispatchFilteredRoutesUpdatedOnHandler(List<MediaRoute2Info> newRoutes) {
List<MediaRoute2Info> addedRoutes = new ArrayList<>();
List<MediaRoute2Info> removedRoutes = new ArrayList<>();
List<MediaRoute2Info> changedRoutes = new ArrayList<>();
@@ -863,29 +863,16 @@ public final class MediaRouter2 {
if (!changedRoutes.isEmpty()) {
notifyRoutesChanged(changedRoutes);
}
- }
- void addRoutesOnHandler(List<MediaRoute2Info> routes) {
- synchronized (mLock) {
- for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getId(), route);
- }
- updateFilteredRoutesLocked();
+ // Note: We don't notify clients of changes in route ordering.
+ if (!addedRoutes.isEmpty() || !removedRoutes.isEmpty() || !changedRoutes.isEmpty()) {
+ notifyRoutesUpdated(newRoutes);
}
}
- void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
- synchronized (mLock) {
- for (MediaRoute2Info route : routes) {
- mRoutes.remove(route.getId());
- }
- updateFilteredRoutesLocked();
- }
- }
-
- void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
- List<MediaRoute2Info> changedRoutes = new ArrayList<>();
+ void updateRoutesOnHandler(List<MediaRoute2Info> routes) {
synchronized (mLock) {
+ mRoutes.clear();
for (MediaRoute2Info route : routes) {
mRoutes.put(route.getId(), route);
}
@@ -900,8 +887,10 @@ public final class MediaRouter2 {
Collections.unmodifiableList(
filterRoutesWithCompositePreferenceLocked(List.copyOf(mRoutes.values())));
mHandler.sendMessage(
- obtainMessage(MediaRouter2::dispatchFilteredRoutesChangedLocked,
- this, mFilteredRoutes));
+ obtainMessage(
+ MediaRouter2::dispatchFilteredRoutesUpdatedOnHandler,
+ this,
+ mFilteredRoutes));
}
/**
@@ -1211,6 +1200,14 @@ public final class MediaRouter2 {
}
}
+ private void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
+ for (RouteCallbackRecord record : mRouteCallbackRecords) {
+ List<MediaRoute2Info> filteredRoutes =
+ filterRoutesWithIndividualPreference(routes, record.mPreference);
+ record.mExecutor.execute(() -> record.mRouteCallback.onRoutesUpdated(filteredRoutes));
+ }
+ }
+
private void notifyPreferredFeaturesChanged(List<String> features) {
for (RouteCallbackRecord record : mRouteCallbackRecords) {
record.mExecutor.execute(
@@ -1246,29 +1243,44 @@ public final class MediaRouter2 {
/** Callback for receiving events about media route discovery. */
public abstract static class RouteCallback {
/**
- * Called when routes are added. Whenever you registers a callback, this will be invoked
- * with known routes.
+ * Called when routes are added. Whenever you register a callback, this will be invoked with
+ * known routes.
*
* @param routes the list of routes that have been added. It's never empty.
+ * @deprecated Use {@link #onRoutesUpdated(List)} instead.
*/
+ @Deprecated
public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {}
/**
* Called when routes are removed.
*
* @param routes the list of routes that have been removed. It's never empty.
+ * @deprecated Use {@link #onRoutesUpdated(List)} instead.
*/
+ @Deprecated
public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {}
/**
- * Called when routes are changed. For example, it is called when the route's name or volume
- * have been changed.
+ * Called when the properties of one or more existing routes are changed. For example, it is
+ * called when a route's name or volume have changed.
*
* @param routes the list of routes that have been changed. It's never empty.
+ * @deprecated Use {@link #onRoutesUpdated(List)} instead.
*/
+ @Deprecated
public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
/**
+ * Called when the route list is updated, which can happen when routes are added, removed,
+ * or modified. It will also be called when a route callback is registered.
+ *
+ * @param routes the updated list of routes filtered by the callback's individual discovery
+ * preferences.
+ */
+ public void onRoutesUpdated(@NonNull List<MediaRoute2Info> routes) {}
+
+ /**
* Called when the client app's preferred features are changed. When this is called, it is
* recommended to {@link #getRoutes()} to get the routes that are currently available to the
* app.
@@ -1985,21 +1997,9 @@ public final class MediaRouter2 {
}
@Override
- public void notifyRoutesAdded(List<MediaRoute2Info> routes) {
- mHandler.sendMessage(
- obtainMessage(MediaRouter2::addRoutesOnHandler, MediaRouter2.this, routes));
- }
-
- @Override
- public void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
- mHandler.sendMessage(
- obtainMessage(MediaRouter2::removeRoutesOnHandler, MediaRouter2.this, routes));
- }
-
- @Override
- public void notifyRoutesChanged(List<MediaRoute2Info> routes) {
+ public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
mHandler.sendMessage(
- obtainMessage(MediaRouter2::changeRoutesOnHandler, MediaRouter2.this, routes));
+ obtainMessage(MediaRouter2::updateRoutesOnHandler, MediaRouter2.this, routes));
}
@Override
@@ -2047,17 +2047,7 @@ public final class MediaRouter2 {
class ManagerCallback implements MediaRouter2Manager.Callback {
@Override
- public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {
- updateAllRoutesFromManager();
- }
-
- @Override
- public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {
- updateAllRoutesFromManager();
- }
-
- @Override
- public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {
+ public void onRoutesUpdated() {
updateAllRoutesFromManager();
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 44c0b54546be..8afc7d999d2e 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -546,37 +546,15 @@ public final class MediaRouter2Manager {
}
}
- void addRoutesOnHandler(List<MediaRoute2Info> routes) {
+ void updateRoutesOnHandler(@NonNull List<MediaRoute2Info> routes) {
synchronized (mRoutesLock) {
+ mRoutes.clear();
for (MediaRoute2Info route : routes) {
mRoutes.put(route.getId(), route);
}
}
- if (routes.size() > 0) {
- notifyRoutesAdded(routes);
- }
- }
- void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
- synchronized (mRoutesLock) {
- for (MediaRoute2Info route : routes) {
- mRoutes.remove(route.getId());
- }
- }
- if (routes.size() > 0) {
- notifyRoutesRemoved(routes);
- }
- }
-
- void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
- synchronized (mRoutesLock) {
- for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getId(), route);
- }
- }
- if (routes.size() > 0) {
- notifyRoutesChanged(routes);
- }
+ notifyRoutesUpdated();
}
void createSessionOnHandler(int requestId, RoutingSessionInfo sessionInfo) {
@@ -650,24 +628,9 @@ public final class MediaRouter2Manager {
notifySessionUpdated(sessionInfo);
}
- private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
- for (CallbackRecord record: mCallbackRecords) {
- record.mExecutor.execute(
- () -> record.mCallback.onRoutesAdded(routes));
- }
- }
-
- private void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
+ private void notifyRoutesUpdated() {
for (CallbackRecord record: mCallbackRecords) {
- record.mExecutor.execute(
- () -> record.mCallback.onRoutesRemoved(routes));
- }
- }
-
- private void notifyRoutesChanged(List<MediaRoute2Info> routes) {
- for (CallbackRecord record: mCallbackRecords) {
- record.mExecutor.execute(
- () -> record.mCallback.onRoutesChanged(routes));
+ record.mExecutor.execute(() -> record.mCallback.onRoutesUpdated());
}
}
@@ -963,23 +926,12 @@ public final class MediaRouter2Manager {
* Interface for receiving events about media routing changes.
*/
public interface Callback {
- /**
- * Called when routes are added.
- * @param routes the list of routes that have been added. It's never empty.
- */
- default void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {}
/**
- * Called when routes are removed.
- * @param routes the list of routes that have been removed. It's never empty.
+ * Called when the routes list changes. This includes adding, modifying, or removing
+ * individual routes.
*/
- default void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {}
-
- /**
- * Called when routes are changed.
- * @param routes the list of routes that have been changed. It's never empty.
- */
- default void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
+ default void onRoutesUpdated() {}
/**
* Called when a session is changed.
@@ -1115,21 +1067,12 @@ public final class MediaRouter2Manager {
}
@Override
- public void notifyRoutesAdded(List<MediaRoute2Info> routes) {
- mHandler.sendMessage(obtainMessage(MediaRouter2Manager::addRoutesOnHandler,
- MediaRouter2Manager.this, routes));
- }
-
- @Override
- public void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
- mHandler.sendMessage(obtainMessage(MediaRouter2Manager::removeRoutesOnHandler,
- MediaRouter2Manager.this, routes));
- }
-
- @Override
- public void notifyRoutesChanged(List<MediaRoute2Info> routes) {
- mHandler.sendMessage(obtainMessage(MediaRouter2Manager::changeRoutesOnHandler,
- MediaRouter2Manager.this, routes));
+ public void notifyRoutesUpdated(List<MediaRoute2Info> routes) {
+ mHandler.sendMessage(
+ obtainMessage(
+ MediaRouter2Manager::updateRoutesOnHandler,
+ MediaRouter2Manager.this,
+ routes));
}
}
}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 4086dec99218..37c836762da0 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -32,7 +32,6 @@ import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_I
import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_FIXED_VOLUME;
import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_SPECIAL_FEATURE;
import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_ID_VARIABLE_VOLUME;
-import static com.android.mediaroutertest.StubMediaRoute2ProviderService.ROUTE_NAME2;
import static com.android.mediaroutertest.StubMediaRoute2ProviderService.VOLUME_MAX;
import static org.junit.Assert.assertEquals;
@@ -56,10 +55,10 @@ import android.media.RoutingSessionInfo;
import android.os.Bundle;
import android.text.TextUtils;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.PollingCheck;
@@ -69,6 +68,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -115,7 +115,7 @@ public class MediaRouter2ManagerTest {
@Before
public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.MEDIA_CONTENT_CONTROL,
Manifest.permission.MODIFY_AUDIO_ROUTING);
@@ -170,51 +170,95 @@ public class MediaRouter2ManagerTest {
}
@Test
- public void testOnRoutesRemovedAndAdded() throws Exception {
- RouteCallback routeCallback = new RouteCallback() {};
- mRouteCallbacks.add(routeCallback);
- mRouter2.registerRouteCallback(mExecutor, routeCallback,
- new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
+ public void testOnRoutesUpdated() throws Exception {
+ final String routeId0 = "routeId0";
+ final String routeName0 = "routeName0";
+ final String routeId1 = "routeId1";
+ final String routeName1 = "routeName1";
+ final List<String> features = Collections.singletonList("customFeature");
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
+ final int newConnectionState = MediaRoute2Info.CONNECTION_STATE_CONNECTED;
+
+ final List<MediaRoute2Info> routes = new ArrayList<>();
+ routes.add(new MediaRoute2Info.Builder(routeId0, routeName0).addFeatures(features).build());
+ routes.add(new MediaRoute2Info.Builder(routeId1, routeName1).addFeatures(features).build());
- CountDownLatch removedLatch = new CountDownLatch(1);
CountDownLatch addedLatch = new CountDownLatch(1);
+ CountDownLatch changedLatch = new CountDownLatch(1);
+ CountDownLatch removedLatch = new CountDownLatch(1);
- addManagerCallback(new MediaRouter2Manager.Callback() {
- @Override
- public void onRoutesRemoved(List<MediaRoute2Info> routes) {
- assertTrue(routes.size() > 0);
- for (MediaRoute2Info route : routes) {
- if (route.getOriginalId().equals(ROUTE_ID2)
- && route.getName().equals(ROUTE_NAME2)) {
- removedLatch.countDown();
+ addManagerCallback(
+ new MediaRouter2Manager.Callback() {
+ @Override
+ public void onRoutesUpdated() {
+ if (addedLatch.getCount() == 1
+ && checkRoutesMatch(mManager.getAllRoutes(), routes)) {
+ addedLatch.countDown();
+ } else if (changedLatch.getCount() == 1
+ && checkRoutesMatch(
+ mManager.getAllRoutes(), routes.subList(1, 2))) {
+ changedLatch.countDown();
+ } else if (removedLatch.getCount() == 1
+ && checkRoutesRemoved(mManager.getAllRoutes(), routes)) {
+ removedLatch.countDown();
+ }
}
- }
- }
- @Override
- public void onRoutesAdded(List<MediaRoute2Info> routes) {
- assertTrue(routes.size() > 0);
- if (removedLatch.getCount() > 0) {
- return;
- }
- for (MediaRoute2Info route : routes) {
- if (route.getOriginalId().equals(ROUTE_ID2)
- && route.getName().equals(ROUTE_NAME2)) {
- addedLatch.countDown();
- }
- }
- }
- });
+ });
- MediaRoute2Info routeToRemove = routes.get(ROUTE_ID2);
- assertNotNull(routeToRemove);
+ mService.addRoutes(routes);
+ assertTrue(
+ "Added routes not found or onRoutesUpdated() never called.",
+ addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- mService.removeRoute(ROUTE_ID2);
- assertTrue(removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ MediaRoute2Info newRoute2 =
+ new MediaRoute2Info.Builder(routes.get(1))
+ .setConnectionState(newConnectionState)
+ .build();
+ routes.set(1, newRoute2);
+ mService.addRoute(routes.get(1));
+ assertTrue(
+ "Modified route not found or onRoutesUpdated() never called.",
+ changedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ List<String> routeIds = new ArrayList<>();
+ routeIds.add(routeId0);
+ routeIds.add(routeId1);
+
+ mService.removeRoutes(routeIds);
+ assertTrue(
+ "Removed routes not found or onRoutesUpdated() never called.",
+ removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
- mService.addRoute(routeToRemove);
- assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ private static boolean checkRoutesMatch(
+ List<MediaRoute2Info> routesReceived, List<MediaRoute2Info> expectedRoutes) {
+ for (MediaRoute2Info expectedRoute : expectedRoutes) {
+ MediaRoute2Info matchingRoute =
+ routesReceived.stream()
+ .filter(r -> r.getOriginalId().equals(expectedRoute.getOriginalId()))
+ .findFirst()
+ .orElse(null);
+
+ if (matchingRoute == null) {
+ return false;
+ }
+ assertTrue(TextUtils.equals(expectedRoute.getName(), matchingRoute.getName()));
+ assertEquals(expectedRoute.getFeatures(), matchingRoute.getFeatures());
+ assertEquals(expectedRoute.getConnectionState(), matchingRoute.getConnectionState());
+ }
+
+ return true;
+ }
+
+ private static boolean checkRoutesRemoved(
+ List<MediaRoute2Info> routesReceived, List<MediaRoute2Info> routesRemoved) {
+ for (MediaRoute2Info removedRoute : routesRemoved) {
+ if (routesReceived.stream()
+ .anyMatch(r -> r.getOriginalId().equals(removedRoute.getOriginalId()))) {
+ return false;
+ }
+ }
+ return true;
}
@Test
@@ -874,28 +918,31 @@ public class MediaRouter2ManagerTest {
// A dummy callback is required to send route feature info.
RouteCallback routeCallback = new RouteCallback() {};
- MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
- @Override
- public void onRoutesAdded(List<MediaRoute2Info> routes) {
- for (MediaRoute2Info route : routes) {
- if (!route.isSystemRoute()
- && hasMatchingFeature(route.getFeatures(), preference
- .getPreferredFeatures())) {
- addedLatch.countDown();
- break;
+ MediaRouter2Manager.Callback managerCallback =
+ new MediaRouter2Manager.Callback() {
+ @Override
+ public void onRoutesUpdated() {
+ List<MediaRoute2Info> routes = mManager.getAllRoutes();
+ for (MediaRoute2Info route : routes) {
+ if (!route.isSystemRoute()
+ && hasMatchingFeature(
+ route.getFeatures(),
+ preference.getPreferredFeatures())) {
+ addedLatch.countDown();
+ break;
+ }
+ }
}
- }
- }
- @Override
- public void onDiscoveryPreferenceChanged(String packageName,
- RouteDiscoveryPreference discoveryPreference) {
- if (TextUtils.equals(mPackageName, packageName)
- && Objects.equals(preference, discoveryPreference)) {
- preferenceLatch.countDown();
- }
- }
- };
+ @Override
+ public void onDiscoveryPreferenceChanged(
+ String packageName, RouteDiscoveryPreference discoveryPreference) {
+ if (TextUtils.equals(mPackageName, packageName)
+ && Objects.equals(preference, discoveryPreference)) {
+ preferenceLatch.countDown();
+ }
+ }
+ };
mManager.registerCallback(mExecutor, managerCallback);
mRouter2.registerRouteCallback(mExecutor, routeCallback, preference);
@@ -923,15 +970,17 @@ public class MediaRouter2ManagerTest {
void awaitOnRouteChangedManager(Runnable task, String routeId,
Predicate<MediaRoute2Info> predicate) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- MediaRouter2Manager.Callback callback = new MediaRouter2Manager.Callback() {
- @Override
- public void onRoutesChanged(List<MediaRoute2Info> changed) {
- MediaRoute2Info route = createRouteMap(changed).get(routeId);
- if (route != null && predicate.test(route)) {
- latch.countDown();
- }
- }
- };
+ MediaRouter2Manager.Callback callback =
+ new MediaRouter2Manager.Callback() {
+ @Override
+ public void onRoutesUpdated() {
+ MediaRoute2Info route =
+ createRouteMap(mManager.getAllRoutes()).get(routeId);
+ if (route != null && predicate.test(route)) {
+ latch.countDown();
+ }
+ }
+ };
mManager.registerCallback(mExecutor, callback);
try {
task.run();
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
index a51e3714b6f7..a7ae5f45b795 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
@@ -30,7 +30,9 @@ import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -146,19 +148,44 @@ public class StubMediaRoute2ProviderService extends MediaRoute2ProviderService {
* they have the same route id.
*/
public void addRoute(@NonNull MediaRoute2Info route) {
- Objects.requireNonNull(route, "route must not be null");
- mRoutes.put(route.getOriginalId(), route);
- publishRoutes();
+ addRoutes(Collections.singletonList(route));
}
/**
- * Removes a route and publishes it.
+ * Adds a list of routes and publishes it. It will replace existing routes with matching ids.
+ *
+ * @param routes list of routes to be added.
*/
+ public void addRoutes(@NonNull List<MediaRoute2Info> routes) {
+ Objects.requireNonNull(routes, "Routes must not be null.");
+ for (MediaRoute2Info route : routes) {
+ Objects.requireNonNull(route, "Route must not be null");
+ mRoutes.put(route.getOriginalId(), route);
+ }
+ publishRoutes();
+ }
+
+ /** Removes a route and publishes it. */
public void removeRoute(@NonNull String routeId) {
- Objects.requireNonNull(routeId, "routeId must not be null");
- MediaRoute2Info route = mRoutes.get(routeId);
- if (route != null) {
- mRoutes.remove(routeId);
+ removeRoutes(Collections.singletonList(routeId));
+ }
+
+ /**
+ * Removes a list of routes and publishes the changes.
+ *
+ * @param routes list of route ids to be removed.
+ */
+ public void removeRoutes(@NonNull List<String> routes) {
+ Objects.requireNonNull(routes, "Routes must not be null");
+ boolean hasRemovedRoutes = false;
+ for (String routeId : routes) {
+ MediaRoute2Info route = mRoutes.get(routeId);
+ if (route != null) {
+ mRoutes.remove(routeId);
+ hasRemovedRoutes = true;
+ }
+ }
+ if (hasRemovedRoutes) {
publishRoutes();
}
}
diff --git a/packages/SettingsLib/Spa/OWNERS b/packages/SettingsLib/Spa/OWNERS
index b352b045c352..288787241caa 100644
--- a/packages/SettingsLib/Spa/OWNERS
+++ b/packages/SettingsLib/Spa/OWNERS
@@ -1,6 +1,6 @@
+set noparent
+
chaohuiw@google.com
hanxu@google.com
kellyz@google.com
pierreqian@google.com
-
-per-file *.xml = set noparent
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt
index 5adbc32c9ece..171a16118052 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/HomePage.kt
@@ -52,6 +52,8 @@ private fun HomePage() {
PreferencePageProvider.EntryItem()
ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = 0)
+
+ SliderPageProvider.EntryItem()
}
}
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt
index da278d1f1e7a..c24541a903da 100644
--- a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/PageRepository.kt
@@ -22,9 +22,15 @@ object Destinations {
const val Home = "Home"
const val Preference = "Preference"
const val Argument = "Argument"
+ const val Slider = "Slider"
}
val codelabPageRepository = SettingsPageRepository(
- allPages = listOf(HomePageProvider, PreferencePageProvider, ArgumentPageProvider),
+ allPages = listOf(
+ HomePageProvider,
+ PreferencePageProvider,
+ ArgumentPageProvider,
+ SliderPageProvider,
+ ),
startDestination = Destinations.Home,
)
diff --git a/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt
new file mode 100644
index 000000000000..6e965813afc4
--- /dev/null
+++ b/packages/SettingsLib/Spa/codelab/src/com/android/settingslib/spa/codelab/page/SliderPage.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.codelab.page
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AccessAlarm
+import androidx.compose.material.icons.outlined.MusicNote
+import androidx.compose.material.icons.outlined.MusicOff
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.ui.SettingsSlider
+import com.android.settingslib.spa.widget.ui.SettingsSliderModel
+
+object SliderPageProvider : SettingsPageProvider {
+ override val name = Destinations.Slider
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ SliderPage()
+ }
+
+ @Composable
+ fun EntryItem() {
+ Preference(object : PreferenceModel {
+ override val title = "Sample Slider"
+ override val onClick = navigator(Destinations.Slider)
+ })
+ }
+}
+
+@Composable
+private fun SliderPage() {
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ SettingsSlider(object : SettingsSliderModel {
+ override val title = "Slider"
+ override val initValue = 40
+ })
+
+ SettingsSlider(object : SettingsSliderModel {
+ override val title = "Slider with icon"
+ override val initValue = 30
+ override val onValueChangeFinished = {
+ println("onValueChangeFinished")
+ }
+ override val icon = Icons.Outlined.AccessAlarm
+ })
+
+ val initValue = 0
+ var icon by remember { mutableStateOf(Icons.Outlined.MusicOff) }
+ var sliderPosition by remember { mutableStateOf(initValue) }
+ SettingsSlider(object : SettingsSliderModel {
+ override val title = "Slider with changeable icon"
+ override val initValue = initValue
+ override val onValueChange = { it: Int ->
+ sliderPosition = it
+ icon = if (it > 0) Icons.Outlined.MusicNote else Icons.Outlined.MusicOff
+ }
+ override val onValueChangeFinished = {
+ println("onValueChangeFinished: the value is $sliderPosition")
+ }
+ override val icon = icon
+ })
+
+ SettingsSlider(object : SettingsSliderModel {
+ override val title = "Slider with steps"
+ override val initValue = 2
+ override val valueRange = 1..5
+ override val showSteps = true
+ })
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SliderPagePreview() {
+ SettingsTheme {
+ SliderPage()
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
new file mode 100644
index 000000000000..27fdc916a434
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.theme
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+
+data class SettingsColorScheme(
+ val background: Color = Color.Unspecified,
+ val categoryTitle: Color = Color.Unspecified,
+ val surface: Color = Color.Unspecified,
+ val surfaceHeader: Color = Color.Unspecified,
+ val secondaryText: Color = Color.Unspecified,
+ val primaryContainer: Color = Color.Unspecified,
+ val onPrimaryContainer: Color = Color.Unspecified,
+)
+
+internal val LocalColorScheme = staticCompositionLocalOf { SettingsColorScheme() }
+
+@Composable
+internal fun settingsColorScheme(isDarkTheme: Boolean): SettingsColorScheme {
+ val context = LocalContext.current
+ return remember(isDarkTheme) {
+ when {
+ isDarkTheme -> dynamicDarkColorScheme(context)
+ else -> dynamicLightColorScheme(context)
+ }
+ }
+}
+
+/**
+ * Creates a light dynamic color scheme.
+ *
+ * Use this function to create a color scheme based off the system wallpaper. If the developer
+ * changes the wallpaper this color scheme will change accordingly. This dynamic scheme is a
+ * light theme variant.
+ *
+ * @param context The context required to get system resource data.
+ */
+private fun dynamicLightColorScheme(context: Context): SettingsColorScheme {
+ val tonalPalette = dynamicTonalPalette(context)
+ return SettingsColorScheme(
+ background = tonalPalette.neutral95,
+ categoryTitle = tonalPalette.primary40,
+ surface = tonalPalette.neutral99,
+ surfaceHeader = tonalPalette.neutral90,
+ secondaryText = tonalPalette.neutralVariant30,
+ primaryContainer = tonalPalette.primary90,
+ onPrimaryContainer = tonalPalette.neutral10,
+ )
+}
+
+/**
+ * Creates a dark dynamic color scheme.
+ *
+ * Use this function to create a color scheme based off the system wallpaper. If the developer
+ * changes the wallpaper this color scheme will change accordingly. This dynamic scheme is a dark
+ * theme variant.
+ *
+ * @param context The context required to get system resource data.
+ */
+private fun dynamicDarkColorScheme(context: Context): SettingsColorScheme {
+ val tonalPalette = dynamicTonalPalette(context)
+ return SettingsColorScheme(
+ background = tonalPalette.neutral10,
+ categoryTitle = tonalPalette.primary90,
+ surface = tonalPalette.neutral20,
+ surfaceHeader = tonalPalette.neutral30,
+ secondaryText = tonalPalette.neutralVariant80,
+ primaryContainer = tonalPalette.secondary90,
+ onPrimaryContainer = tonalPalette.neutral10,
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
index 29998258ed37..e6fa74e34cc8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
@@ -19,6 +19,8 @@ package com.android.settingslib.spa.framework.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.ReadOnlyComposable
/**
* The Material 3 Theme for Settings.
@@ -26,9 +28,21 @@ import androidx.compose.runtime.Composable
@Composable
fun SettingsTheme(content: @Composable () -> Unit) {
val isDarkTheme = isSystemInDarkTheme()
- val colorScheme = materialColorScheme(isDarkTheme)
+ val settingsColorScheme = settingsColorScheme(isDarkTheme)
+ val colorScheme = materialColorScheme(isDarkTheme).copy(
+ background = settingsColorScheme.background,
+ )
- MaterialTheme(colorScheme = colorScheme) {
- content()
+ CompositionLocalProvider(LocalColorScheme provides settingsColorScheme(isDarkTheme)) {
+ MaterialTheme(colorScheme = colorScheme, typography = rememberSettingsTypography()) {
+ content()
+ }
}
}
+
+object SettingsTheme {
+ val colorScheme: SettingsColorScheme
+ @Composable
+ @ReadOnlyComposable
+ get() = LocalColorScheme.current
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt
new file mode 100644
index 000000000000..f81f5e734fb4
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTonalPalette.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.theme
+
+import android.R
+import android.content.Context
+import androidx.annotation.ColorRes
+import androidx.annotation.DoNotInline
+import androidx.compose.ui.graphics.Color
+
+/**
+ * Tonal Palette structure in Material.
+ *
+ * A tonal palette is comprised of 5 tonal ranges. Each tonal range includes the 13 stops, or
+ * tonal swatches.
+ *
+ * Tonal range names are:
+ * - Neutral (N)
+ * - Neutral variant (NV)
+ * - Primary (P)
+ * - Secondary (S)
+ * - Tertiary (T)
+ */
+internal class SettingsTonalPalette(
+ // The neutral tonal range from the generated dynamic color palette.
+ // Ordered from the lightest shade [neutral100] to the darkest shade [neutral0].
+ val neutral100: Color,
+ val neutral99: Color,
+ val neutral95: Color,
+ val neutral90: Color,
+ val neutral80: Color,
+ val neutral70: Color,
+ val neutral60: Color,
+ val neutral50: Color,
+ val neutral40: Color,
+ val neutral30: Color,
+ val neutral20: Color,
+ val neutral10: Color,
+ val neutral0: Color,
+
+ // The neutral variant tonal range, sometimes called "neutral 2", from the
+ // generated dynamic color palette.
+ // Ordered from the lightest shade [neutralVariant100] to the darkest shade [neutralVariant0].
+ val neutralVariant100: Color,
+ val neutralVariant99: Color,
+ val neutralVariant95: Color,
+ val neutralVariant90: Color,
+ val neutralVariant80: Color,
+ val neutralVariant70: Color,
+ val neutralVariant60: Color,
+ val neutralVariant50: Color,
+ val neutralVariant40: Color,
+ val neutralVariant30: Color,
+ val neutralVariant20: Color,
+ val neutralVariant10: Color,
+ val neutralVariant0: Color,
+
+ // The primary tonal range from the generated dynamic color palette.
+ // Ordered from the lightest shade [primary100] to the darkest shade [primary0].
+ val primary100: Color,
+ val primary99: Color,
+ val primary95: Color,
+ val primary90: Color,
+ val primary80: Color,
+ val primary70: Color,
+ val primary60: Color,
+ val primary50: Color,
+ val primary40: Color,
+ val primary30: Color,
+ val primary20: Color,
+ val primary10: Color,
+ val primary0: Color,
+
+ // The secondary tonal range from the generated dynamic color palette.
+ // Ordered from the lightest shade [secondary100] to the darkest shade [secondary0].
+ val secondary100: Color,
+ val secondary99: Color,
+ val secondary95: Color,
+ val secondary90: Color,
+ val secondary80: Color,
+ val secondary70: Color,
+ val secondary60: Color,
+ val secondary50: Color,
+ val secondary40: Color,
+ val secondary30: Color,
+ val secondary20: Color,
+ val secondary10: Color,
+ val secondary0: Color,
+
+ // The tertiary tonal range from the generated dynamic color palette.
+ // Ordered from the lightest shade [tertiary100] to the darkest shade [tertiary0].
+ val tertiary100: Color,
+ val tertiary99: Color,
+ val tertiary95: Color,
+ val tertiary90: Color,
+ val tertiary80: Color,
+ val tertiary70: Color,
+ val tertiary60: Color,
+ val tertiary50: Color,
+ val tertiary40: Color,
+ val tertiary30: Color,
+ val tertiary20: Color,
+ val tertiary10: Color,
+ val tertiary0: Color,
+)
+
+/** Dynamic colors in Material. */
+internal fun dynamicTonalPalette(context: Context) = SettingsTonalPalette(
+ // The neutral tonal range from the generated dynamic color palette.
+ neutral100 = ColorResourceHelper.getColor(context, R.color.system_neutral1_0),
+ neutral99 = ColorResourceHelper.getColor(context, R.color.system_neutral1_10),
+ neutral95 = ColorResourceHelper.getColor(context, R.color.system_neutral1_50),
+ neutral90 = ColorResourceHelper.getColor(context, R.color.system_neutral1_100),
+ neutral80 = ColorResourceHelper.getColor(context, R.color.system_neutral1_200),
+ neutral70 = ColorResourceHelper.getColor(context, R.color.system_neutral1_300),
+ neutral60 = ColorResourceHelper.getColor(context, R.color.system_neutral1_400),
+ neutral50 = ColorResourceHelper.getColor(context, R.color.system_neutral1_500),
+ neutral40 = ColorResourceHelper.getColor(context, R.color.system_neutral1_600),
+ neutral30 = ColorResourceHelper.getColor(context, R.color.system_neutral1_700),
+ neutral20 = ColorResourceHelper.getColor(context, R.color.system_neutral1_800),
+ neutral10 = ColorResourceHelper.getColor(context, R.color.system_neutral1_900),
+ neutral0 = ColorResourceHelper.getColor(context, R.color.system_neutral1_1000),
+
+ // The neutral variant tonal range, sometimes called "neutral 2", from the
+ // generated dynamic color palette.
+ neutralVariant100 = ColorResourceHelper.getColor(context, R.color.system_neutral2_0),
+ neutralVariant99 = ColorResourceHelper.getColor(context, R.color.system_neutral2_10),
+ neutralVariant95 = ColorResourceHelper.getColor(context, R.color.system_neutral2_50),
+ neutralVariant90 = ColorResourceHelper.getColor(context, R.color.system_neutral2_100),
+ neutralVariant80 = ColorResourceHelper.getColor(context, R.color.system_neutral2_200),
+ neutralVariant70 = ColorResourceHelper.getColor(context, R.color.system_neutral2_300),
+ neutralVariant60 = ColorResourceHelper.getColor(context, R.color.system_neutral2_400),
+ neutralVariant50 = ColorResourceHelper.getColor(context, R.color.system_neutral2_500),
+ neutralVariant40 = ColorResourceHelper.getColor(context, R.color.system_neutral2_600),
+ neutralVariant30 = ColorResourceHelper.getColor(context, R.color.system_neutral2_700),
+ neutralVariant20 = ColorResourceHelper.getColor(context, R.color.system_neutral2_800),
+ neutralVariant10 = ColorResourceHelper.getColor(context, R.color.system_neutral2_900),
+ neutralVariant0 = ColorResourceHelper.getColor(context, R.color.system_neutral2_1000),
+
+ // The primary tonal range from the generated dynamic color palette.
+ primary100 = ColorResourceHelper.getColor(context, R.color.system_accent1_0),
+ primary99 = ColorResourceHelper.getColor(context, R.color.system_accent1_10),
+ primary95 = ColorResourceHelper.getColor(context, R.color.system_accent1_50),
+ primary90 = ColorResourceHelper.getColor(context, R.color.system_accent1_100),
+ primary80 = ColorResourceHelper.getColor(context, R.color.system_accent1_200),
+ primary70 = ColorResourceHelper.getColor(context, R.color.system_accent1_300),
+ primary60 = ColorResourceHelper.getColor(context, R.color.system_accent1_400),
+ primary50 = ColorResourceHelper.getColor(context, R.color.system_accent1_500),
+ primary40 = ColorResourceHelper.getColor(context, R.color.system_accent1_600),
+ primary30 = ColorResourceHelper.getColor(context, R.color.system_accent1_700),
+ primary20 = ColorResourceHelper.getColor(context, R.color.system_accent1_800),
+ primary10 = ColorResourceHelper.getColor(context, R.color.system_accent1_900),
+ primary0 = ColorResourceHelper.getColor(context, R.color.system_accent1_1000),
+
+ // The secondary tonal range from the generated dynamic color palette.
+ secondary100 = ColorResourceHelper.getColor(context, R.color.system_accent2_0),
+ secondary99 = ColorResourceHelper.getColor(context, R.color.system_accent2_10),
+ secondary95 = ColorResourceHelper.getColor(context, R.color.system_accent2_50),
+ secondary90 = ColorResourceHelper.getColor(context, R.color.system_accent2_100),
+ secondary80 = ColorResourceHelper.getColor(context, R.color.system_accent2_200),
+ secondary70 = ColorResourceHelper.getColor(context, R.color.system_accent2_300),
+ secondary60 = ColorResourceHelper.getColor(context, R.color.system_accent2_400),
+ secondary50 = ColorResourceHelper.getColor(context, R.color.system_accent2_500),
+ secondary40 = ColorResourceHelper.getColor(context, R.color.system_accent2_600),
+ secondary30 = ColorResourceHelper.getColor(context, R.color.system_accent2_700),
+ secondary20 = ColorResourceHelper.getColor(context, R.color.system_accent2_800),
+ secondary10 = ColorResourceHelper.getColor(context, R.color.system_accent2_900),
+ secondary0 = ColorResourceHelper.getColor(context, R.color.system_accent2_1000),
+
+ // The tertiary tonal range from the generated dynamic color palette.
+ tertiary100 = ColorResourceHelper.getColor(context, R.color.system_accent3_0),
+ tertiary99 = ColorResourceHelper.getColor(context, R.color.system_accent3_10),
+ tertiary95 = ColorResourceHelper.getColor(context, R.color.system_accent3_50),
+ tertiary90 = ColorResourceHelper.getColor(context, R.color.system_accent3_100),
+ tertiary80 = ColorResourceHelper.getColor(context, R.color.system_accent3_200),
+ tertiary70 = ColorResourceHelper.getColor(context, R.color.system_accent3_300),
+ tertiary60 = ColorResourceHelper.getColor(context, R.color.system_accent3_400),
+ tertiary50 = ColorResourceHelper.getColor(context, R.color.system_accent3_500),
+ tertiary40 = ColorResourceHelper.getColor(context, R.color.system_accent3_600),
+ tertiary30 = ColorResourceHelper.getColor(context, R.color.system_accent3_700),
+ tertiary20 = ColorResourceHelper.getColor(context, R.color.system_accent3_800),
+ tertiary10 = ColorResourceHelper.getColor(context, R.color.system_accent3_900),
+ tertiary0 = ColorResourceHelper.getColor(context, R.color.system_accent3_1000),
+)
+
+private object ColorResourceHelper {
+ @DoNotInline
+ fun getColor(context: Context, @ColorRes id: Int): Color {
+ return Color(context.resources.getColor(id, context.theme))
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
new file mode 100644
index 000000000000..07f09ba95ca3
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.em
+import androidx.compose.ui.unit.sp
+
+private class SettingsTypography {
+ private val brand = FontFamily.Default
+ private val plain = FontFamily.Default
+
+ val typography = Typography(
+ displayLarge = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 57.sp,
+ lineHeight = 64.sp,
+ letterSpacing = (-0.2).sp
+ ),
+ displayMedium = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 45.sp,
+ lineHeight = 52.sp,
+ letterSpacing = 0.0.sp
+ ),
+ displaySmall = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 36.sp,
+ lineHeight = 44.sp,
+ letterSpacing = 0.0.sp
+ ),
+ headlineLarge = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 32.sp,
+ lineHeight = 40.sp,
+ letterSpacing = 0.0.sp
+ ),
+ headlineMedium = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 28.sp,
+ lineHeight = 36.sp,
+ letterSpacing = 0.0.sp
+ ),
+ headlineSmall = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 24.sp,
+ lineHeight = 32.sp,
+ letterSpacing = 0.0.sp
+ ),
+ titleLarge = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 22.sp,
+ lineHeight = 28.sp,
+ letterSpacing = 0.02.em,
+ ),
+ titleMedium = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 20.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.02.em,
+ ),
+ titleSmall = TextStyle(
+ fontFamily = brand,
+ fontWeight = FontWeight.Normal,
+ fontSize = 18.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.02.em,
+ ),
+ bodyLarge = TextStyle(
+ fontFamily = plain,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.01.em,
+ ),
+ bodyMedium = TextStyle(
+ fontFamily = plain,
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.01.em,
+ ),
+ bodySmall = TextStyle(
+ fontFamily = plain,
+ fontWeight = FontWeight.Normal,
+ fontSize = 12.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.01.em,
+ ),
+ labelLarge = TextStyle(
+ fontFamily = plain,
+ fontWeight = FontWeight.Medium,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.01.em,
+ ),
+ labelMedium = TextStyle(
+ fontFamily = plain,
+ fontWeight = FontWeight.Medium,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.01.em,
+ ),
+ labelSmall = TextStyle(
+ fontFamily = plain,
+ fontWeight = FontWeight.Medium,
+ fontSize = 12.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.01.em,
+ ),
+ )
+}
+
+@Composable
+internal fun rememberSettingsTypography(): Typography {
+ return remember { SettingsTypography().typography }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt
new file mode 100644
index 000000000000..0454ac37cfb7
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/SettingsSlider.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.ui
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AccessAlarm
+import androidx.compose.material.icons.outlined.MusicNote
+import androidx.compose.material.icons.outlined.MusicOff
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Slider
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.BaseLayout
+import kotlin.math.roundToInt
+
+/**
+ * The widget model for [SettingsSlider] widget.
+ */
+interface SettingsSliderModel {
+ /**
+ * The title of this [SettingsSlider].
+ */
+ val title: String
+
+ /**
+ * The initial position of the [SettingsSlider].
+ */
+ val initValue: Int
+
+ /**
+ * The value range for this [SettingsSlider].
+ */
+ val valueRange: IntRange
+ get() = 0..100
+
+ /**
+ * The lambda to be invoked during the value change by dragging or a click. This callback is
+ * used to get the real time value of the [SettingsSlider].
+ */
+ val onValueChange: ((value: Int) -> Unit)?
+ get() = null
+
+ /**
+ * The lambda to be invoked when value change has ended. This callback is used to get when the
+ * user has completed selecting a new value by ending a drag or a click.
+ */
+ val onValueChangeFinished: (() -> Unit)?
+ get() = null
+
+ /**
+ * The icon image for [SettingsSlider]. If not specified, the slider hides the icon by default.
+ */
+ val icon: ImageVector?
+ get() = null
+
+ /**
+ * Indicates whether to show step marks. If show step marks, when user finish sliding,
+ * the slider will automatically jump to the nearest step mark. Otherwise, the slider hides
+ * the step marks by default.
+ *
+ * The step is fixed to 1.
+ */
+ val showSteps: Boolean
+ get() = false
+}
+
+/**
+ * Settings slider widget.
+ *
+ * Data is provided through [SettingsSliderModel].
+ */
+@Composable
+fun SettingsSlider(model: SettingsSliderModel) {
+ SettingsSlider(
+ title = model.title,
+ initValue = model.initValue,
+ valueRange = model.valueRange,
+ onValueChange = model.onValueChange,
+ onValueChangeFinished = model.onValueChangeFinished,
+ icon = model.icon,
+ showSteps = model.showSteps,
+ )
+}
+
+@Composable
+internal fun SettingsSlider(
+ title: String,
+ initValue: Int,
+ valueRange: IntRange = 0..100,
+ onValueChange: ((value: Int) -> Unit)? = null,
+ onValueChangeFinished: (() -> Unit)? = null,
+ icon: ImageVector? = null,
+ showSteps: Boolean = false,
+ modifier: Modifier = Modifier,
+) {
+ var sliderPosition by rememberSaveable { mutableStateOf(initValue.toFloat()) }
+ BaseLayout(
+ title = title,
+ subTitle = {
+ Slider(
+ value = sliderPosition,
+ onValueChange = {
+ sliderPosition = it
+ onValueChange?.invoke(sliderPosition.roundToInt())
+ },
+ modifier = modifier,
+ valueRange = valueRange.first.toFloat()..valueRange.last.toFloat(),
+ steps = if (showSteps) (valueRange.count() - 2) else 0,
+ onValueChangeFinished = onValueChangeFinished,
+ )
+ },
+ icon = if (icon != null) ({
+ Icon(imageVector = icon, contentDescription = null)
+ }) else null,
+ )
+}
+
+@Preview
+@Composable
+private fun SettingsSliderPreview() {
+ SettingsTheme {
+ val initValue = 30
+ var sliderPosition by rememberSaveable { mutableStateOf(initValue) }
+ SettingsSlider(
+ title = "Alarm Volume",
+ initValue = 30,
+ onValueChange = { sliderPosition = it },
+ onValueChangeFinished = {
+ println("onValueChangeFinished: the value is $sliderPosition")
+ },
+ icon = Icons.Outlined.AccessAlarm,
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun SettingsSliderIconChangePreview() {
+ SettingsTheme {
+ var icon by remember { mutableStateOf(Icons.Outlined.MusicNote) }
+ SettingsSlider(
+ title = "Media Volume",
+ initValue = 40,
+ onValueChange = { it: Int ->
+ icon = if (it > 0) Icons.Outlined.MusicNote else Icons.Outlined.MusicOff
+ },
+ icon = icon,
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun SettingsSliderStepsPreview() {
+ SettingsTheme {
+ SettingsSlider(
+ title = "Display Text",
+ initValue = 2,
+ valueRange = 1..5,
+ showSteps = true,
+ )
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index d9262cce3cb9..766c036d521c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -528,7 +528,7 @@ public class InfoMediaManager extends MediaManager {
class RouterManagerCallback implements MediaRouter2Manager.Callback {
@Override
- public void onRoutesAdded(List<MediaRoute2Info> routes) {
+ public void onRoutesUpdated() {
refreshDevices();
}
@@ -540,16 +540,6 @@ public class InfoMediaManager extends MediaManager {
}
@Override
- public void onRoutesChanged(List<MediaRoute2Info> routes) {
- refreshDevices();
- }
-
- @Override
- public void onRoutesRemoved(List<MediaRoute2Info> routes) {
- refreshDevices();
- }
-
- @Override
public void onTransferred(RoutingSessionInfo oldSession, RoutingSessionInfo newSession) {
if (DEBUG) {
Log.d(TAG, "onTransferred() oldSession : " + oldSession.getName()
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index ee7b7d6b180f..f4af6e852580 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -112,7 +112,7 @@ public class InfoMediaManagerTest {
final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
assertThat(mediaDevice).isNull();
- mInfoMediaManager.mMediaRouterCallback.onRoutesAdded(routes);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -135,7 +135,7 @@ public class InfoMediaManagerTest {
assertThat(mediaDevice).isNull();
mInfoMediaManager.mPackageName = "";
- mInfoMediaManager.mMediaRouterCallback.onRoutesAdded(routes);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -199,7 +199,7 @@ public class InfoMediaManagerTest {
final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
assertThat(mediaDevice).isNull();
- mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -222,7 +222,7 @@ public class InfoMediaManagerTest {
assertThat(mediaDevice).isNull();
mInfoMediaManager.mPackageName = "";
- mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -263,7 +263,7 @@ public class InfoMediaManagerTest {
final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
assertThat(mediaDevice).isNull();
- mInfoMediaManager.mMediaRouterCallback.onRoutesRemoved(routes);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
@@ -286,7 +286,7 @@ public class InfoMediaManagerTest {
assertThat(mediaDevice).isNull();
mInfoMediaManager.mPackageName = "";
- mInfoMediaManager.mMediaRouterCallback.onRoutesRemoved(routes);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index e27cbeaab139..bfa8af957208 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -927,8 +927,9 @@ class MediaRouter2ServiceImpl {
routerRecord.mUserRecord.mHandler, routerRecord, manager));
}
- userRecord.mHandler.sendMessage(obtainMessage(UserHandler::notifyRoutesToManager,
- userRecord.mHandler, manager));
+ userRecord.mHandler.sendMessage(
+ obtainMessage(
+ UserHandler::notifyInitialRoutesToManager, userRecord.mHandler, manager));
}
private void unregisterManagerLocked(@NonNull IMediaRouter2Manager manager, boolean died) {
@@ -1311,6 +1312,36 @@ class MediaRouter2ServiceImpl {
new CopyOnWriteArrayList<>();
private final Map<String, RouterRecord> mSessionToRouterMap = new ArrayMap<>();
+ /**
+ * Latest list of routes sent to privileged {@link android.media.MediaRouter2 routers} and
+ * {@link android.media.MediaRouter2Manager managers}.
+ *
+ * <p>Privileged routers are instances of {@link android.media.MediaRouter2 MediaRouter2}
+ * that have {@code MODIFY_AUDIO_ROUTING} permission.
+ *
+ * <p>This list contains all routes exposed by route providers. This includes routes from
+ * both system route providers and user route providers.
+ *
+ * <p>See {@link #getRouters(boolean hasModifyAudioRoutingPermission)}.
+ */
+ private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToPrivilegedRouters =
+ new ArrayMap<>();
+
+ /**
+ * Latest list of routes sent to non-privileged {@link android.media.MediaRouter2 routers}.
+ *
+ * <p>Non-privileged routers are instances of {@link android.media.MediaRouter2
+ * MediaRouter2} that do <i><b>not</b></i> have {@code MODIFY_AUDIO_ROUTING} permission.
+ *
+ * <p>This list contains all routes exposed by user route providers. It might also include
+ * the current default route from {@link #mSystemProvider} to expose local route updates
+ * (e.g. volume changes) to non-privileged routers.
+ *
+ * <p>See {@link SystemMediaRoute2Provider#mDefaultRoute}.
+ */
+ private final Map<String, MediaRoute2Info> mLastNotifiedRoutesToNonPrivilegedRouters =
+ new ArrayMap<>();
+
private boolean mRunning;
// TODO: (In Android S+) Pull out SystemMediaRoute2Provider out of UserHandler.
@@ -1425,91 +1456,182 @@ class MediaRouter2ServiceImpl {
}
private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
- int providerInfoIndex = getLastProviderInfoIndex(provider.getUniqueId());
MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo();
+
+ int providerInfoIndex =
+ indexOfRouteProviderInfoByUniqueId(provider.getUniqueId(), mLastProviderInfos);
+
MediaRoute2ProviderInfo prevInfo =
- (providerInfoIndex < 0) ? null : mLastProviderInfos.get(providerInfoIndex);
- if (Objects.equals(prevInfo, currentInfo)) return;
+ providerInfoIndex == -1 ? null : mLastProviderInfos.get(providerInfoIndex);
+
+ // Ignore if no changes
+ if (Objects.equals(prevInfo, currentInfo)) {
+ return;
+ }
+
+ boolean hasAddedOrModifiedRoutes = false;
+ boolean hasRemovedRoutes = false;
+
+ boolean isSystemProvider = provider.mIsSystemRouteProvider;
- List<MediaRoute2Info> addedRoutes = new ArrayList<>();
- List<MediaRoute2Info> removedRoutes = new ArrayList<>();
- List<MediaRoute2Info> changedRoutes = new ArrayList<>();
if (prevInfo == null) {
+ // Provider is being added.
mLastProviderInfos.add(currentInfo);
- addedRoutes.addAll(currentInfo.getRoutes());
+ addToRoutesMap(currentInfo.getRoutes(), isSystemProvider);
+ // Check if new provider exposes routes.
+ hasAddedOrModifiedRoutes = !currentInfo.getRoutes().isEmpty();
} else if (currentInfo == null) {
+ // Provider is being removed.
+ hasRemovedRoutes = true;
mLastProviderInfos.remove(prevInfo);
- removedRoutes.addAll(prevInfo.getRoutes());
+ removeFromRoutesMap(prevInfo.getRoutes(), isSystemProvider);
} else {
+ // Provider is being updated.
mLastProviderInfos.set(providerInfoIndex, currentInfo);
- final Collection<MediaRoute2Info> prevRoutes = prevInfo.getRoutes();
final Collection<MediaRoute2Info> currentRoutes = currentInfo.getRoutes();
+ // Checking for individual routes.
for (MediaRoute2Info route : currentRoutes) {
if (!route.isValid()) {
- Slog.w(TAG, "onProviderStateChangedOnHandler: Ignoring invalid route : "
- + route);
+ Slog.w(
+ TAG,
+ "onProviderStateChangedOnHandler: Ignoring invalid route : "
+ + route);
continue;
}
+
MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId());
- if (prevRoute == null) {
- addedRoutes.add(route);
- } else if (!Objects.equals(prevRoute, route)) {
- changedRoutes.add(route);
+ if (prevRoute == null || !Objects.equals(prevRoute, route)) {
+ hasAddedOrModifiedRoutes = true;
+ mLastNotifiedRoutesToPrivilegedRouters.put(route.getId(), route);
+ if (!isSystemProvider) {
+ mLastNotifiedRoutesToNonPrivilegedRouters.put(route.getId(), route);
+ }
}
}
+ // Checking for individual removals
for (MediaRoute2Info prevRoute : prevInfo.getRoutes()) {
if (currentInfo.getRoute(prevRoute.getOriginalId()) == null) {
- removedRoutes.add(prevRoute);
+ hasRemovedRoutes = true;
+ mLastNotifiedRoutesToPrivilegedRouters.remove(prevRoute.getId());
+ if (!isSystemProvider) {
+ mLastNotifiedRoutesToNonPrivilegedRouters.remove(prevRoute.getId());
+ }
}
}
}
+ dispatchUpdates(
+ hasAddedOrModifiedRoutes,
+ hasRemovedRoutes,
+ isSystemProvider,
+ mSystemProvider.getDefaultRoute());
+ }
+
+ /**
+ * Adds provided routes to {@link #mLastNotifiedRoutesToPrivilegedRouters}. Also adds them
+ * to {@link #mLastNotifiedRoutesToNonPrivilegedRouters} if they were provided by a
+ * non-system route provider. Overwrites any route with matching id that already exists.
+ *
+ * @param routes list of routes to be added.
+ * @param isSystemRoutes indicates whether routes come from a system route provider.
+ */
+ private void addToRoutesMap(
+ @NonNull Collection<MediaRoute2Info> routes, boolean isSystemRoutes) {
+ for (MediaRoute2Info route : routes) {
+ if (!isSystemRoutes) {
+ mLastNotifiedRoutesToNonPrivilegedRouters.put(route.getId(), route);
+ }
+ mLastNotifiedRoutesToPrivilegedRouters.put(route.getId(), route);
+ }
+ }
+
+ /**
+ * Removes provided routes from {@link #mLastNotifiedRoutesToPrivilegedRouters}. Also
+ * removes them from {@link #mLastNotifiedRoutesToNonPrivilegedRouters} if they were
+ * provided by a non-system route provider.
+ *
+ * @param routes list of routes to be removed.
+ * @param isSystemRoutes whether routes come from a system route provider.
+ */
+ private void removeFromRoutesMap(
+ @NonNull Collection<MediaRoute2Info> routes, boolean isSystemRoutes) {
+ for (MediaRoute2Info route : routes) {
+ if (!isSystemRoutes) {
+ mLastNotifiedRoutesToNonPrivilegedRouters.remove(route.getId());
+ }
+ mLastNotifiedRoutesToPrivilegedRouters.remove(route.getId());
+ }
+ }
+
+ /**
+ * Dispatches the latest route updates in {@link #mLastNotifiedRoutesToPrivilegedRouters}
+ * and {@link #mLastNotifiedRoutesToNonPrivilegedRouters} to registered {@link
+ * android.media.MediaRouter2 routers} and {@link MediaRouter2Manager managers} after a call
+ * to {@link #onProviderStateChangedOnHandler(MediaRoute2Provider)}. Ignores if no changes
+ * were made.
+ *
+ * @param hasAddedOrModifiedRoutes whether routes were added or modified.
+ * @param hasRemovedRoutes whether routes were removed.
+ * @param isSystemProvider whether the latest update was caused by a system provider.
+ * @param defaultRoute the current default route in {@link #mSystemProvider}.
+ */
+ private void dispatchUpdates(
+ boolean hasAddedOrModifiedRoutes,
+ boolean hasRemovedRoutes,
+ boolean isSystemProvider,
+ MediaRoute2Info defaultRoute) {
+
+ // Ignore if no changes.
+ if (!hasAddedOrModifiedRoutes && !hasRemovedRoutes) {
+ return;
+ }
+
List<IMediaRouter2> routersWithModifyAudioRoutingPermission = getRouters(true);
List<IMediaRouter2> routersWithoutModifyAudioRoutingPermission = getRouters(false);
List<IMediaRouter2Manager> managers = getManagers();
- List<MediaRoute2Info> defaultRoute = new ArrayList<>();
- defaultRoute.add(mSystemProvider.getDefaultRoute());
-
- if (addedRoutes.size() > 0) {
- notifyRoutesAddedToRouters(routersWithModifyAudioRoutingPermission, addedRoutes);
- if (!provider.mIsSystemRouteProvider) {
- notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission,
- addedRoutes);
- } else if (prevInfo == null) {
- notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission,
- defaultRoute);
- } // 'else' is handled as changed routes
- notifyRoutesAddedToManagers(managers, addedRoutes);
- }
- if (removedRoutes.size() > 0) {
- notifyRoutesRemovedToRouters(routersWithModifyAudioRoutingPermission,
- removedRoutes);
- if (!provider.mIsSystemRouteProvider) {
- notifyRoutesRemovedToRouters(routersWithoutModifyAudioRoutingPermission,
- removedRoutes);
- }
- notifyRoutesRemovedToManagers(managers, removedRoutes);
- }
- if (changedRoutes.size() > 0) {
- notifyRoutesChangedToRouters(routersWithModifyAudioRoutingPermission,
- changedRoutes);
- if (!provider.mIsSystemRouteProvider) {
- notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission,
- changedRoutes);
- } else if (prevInfo != null) {
- notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission,
- defaultRoute);
- } // 'else' is handled as added routes
- notifyRoutesChangedToManagers(managers, changedRoutes);
- }
- }
-
- private int getLastProviderInfoIndex(@NonNull String providerId) {
- for (int i = 0; i < mLastProviderInfos.size(); i++) {
- MediaRoute2ProviderInfo providerInfo = mLastProviderInfos.get(i);
- if (TextUtils.equals(providerInfo.getUniqueId(), providerId)) {
+
+ // Managers receive all provider updates with all routes.
+ notifyRoutesUpdatedToManagers(
+ managers, new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
+
+ // Routers with modify audio permission (usually system routers) receive all provider
+ // updates with all routes.
+ notifyRoutesUpdatedToRouters(
+ routersWithModifyAudioRoutingPermission,
+ new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
+
+ if (!isSystemProvider) {
+ // Regular routers receive updates from all non-system providers with all non-system
+ // routes.
+ notifyRoutesUpdatedToRouters(
+ routersWithoutModifyAudioRoutingPermission,
+ new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
+ } else if (hasAddedOrModifiedRoutes) {
+ // On system provider updates, regular routers receive the updated default route.
+ // This is the only system route they should receive.
+ mLastNotifiedRoutesToNonPrivilegedRouters.put(defaultRoute.getId(), defaultRoute);
+ notifyRoutesUpdatedToRouters(
+ routersWithoutModifyAudioRoutingPermission,
+ new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
+ }
+ }
+
+ /**
+ * Returns the index of the first element in {@code lastProviderInfos} that matches the
+ * specified unique id.
+ *
+ * @param uniqueId unique id of {@link MediaRoute2ProviderInfo} to be found.
+ * @param lastProviderInfos list of {@link MediaRoute2ProviderInfo}.
+ * @return index of found element, or -1 if not found.
+ */
+ private static int indexOfRouteProviderInfoByUniqueId(
+ @NonNull String uniqueId,
+ @NonNull List<MediaRoute2ProviderInfo> lastProviderInfos) {
+ for (int i = 0; i < lastProviderInfos.size(); i++) {
+ MediaRoute2ProviderInfo providerInfo = lastProviderInfos.get(i);
+ if (TextUtils.equals(providerInfo.getUniqueId(), uniqueId)) {
return i;
}
}
@@ -1989,41 +2111,19 @@ class MediaRouter2ServiceImpl {
}
}
- private void notifyRoutesAddedToRouters(@NonNull List<IMediaRouter2> routers,
- @NonNull List<MediaRoute2Info> routes) {
- for (IMediaRouter2 router : routers) {
- try {
- router.notifyRoutesAdded(routes);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify routes added. Router probably died.", ex);
- }
- }
- }
-
- private void notifyRoutesRemovedToRouters(@NonNull List<IMediaRouter2> routers,
- @NonNull List<MediaRoute2Info> routes) {
- for (IMediaRouter2 router : routers) {
- try {
- router.notifyRoutesRemoved(routes);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify routes removed. Router probably died.", ex);
- }
- }
- }
-
- private void notifyRoutesChangedToRouters(@NonNull List<IMediaRouter2> routers,
- @NonNull List<MediaRoute2Info> routes) {
+ private void notifyRoutesUpdatedToRouters(
+ @NonNull List<IMediaRouter2> routers, @NonNull List<MediaRoute2Info> routes) {
for (IMediaRouter2 router : routers) {
try {
- router.notifyRoutesChanged(routes);
+ router.notifyRoutesUpdated(routes);
} catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify routes changed. Router probably died.", ex);
+ Slog.w(TAG, "Failed to notify routes updated. Router probably died.", ex);
}
}
}
- private void notifySessionInfoChangedToRouters(@NonNull List<IMediaRouter2> routers,
- @NonNull RoutingSessionInfo sessionInfo) {
+ private void notifySessionInfoChangedToRouters(
+ @NonNull List<IMediaRouter2> routers, @NonNull RoutingSessionInfo sessionInfo) {
for (IMediaRouter2 router : routers) {
try {
router.notifySessionInfoChanged(sessionInfo);
@@ -2033,48 +2133,31 @@ class MediaRouter2ServiceImpl {
}
}
- private void notifyRoutesToManager(@NonNull IMediaRouter2Manager manager) {
- List<MediaRoute2Info> routes = new ArrayList<>();
- for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
- routes.addAll(providerInfo.getRoutes());
- }
- if (routes.size() == 0) {
+ /**
+ * Notifies {@code manager} with all known routes. This only happens once after {@code
+ * manager} is registered through {@link #registerManager(IMediaRouter2Manager, String)
+ * registerManager()}.
+ *
+ * @param manager {@link IMediaRouter2Manager} to be notified.
+ */
+ private void notifyInitialRoutesToManager(@NonNull IMediaRouter2Manager manager) {
+ if (mLastNotifiedRoutesToPrivilegedRouters.isEmpty()) {
return;
}
try {
- manager.notifyRoutesAdded(routes);
+ manager.notifyRoutesUpdated(
+ new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify all routes. Manager probably died.", ex);
}
}
- private void notifyRoutesAddedToManagers(@NonNull List<IMediaRouter2Manager> managers,
- @NonNull List<MediaRoute2Info> routes) {
- for (IMediaRouter2Manager manager : managers) {
- try {
- manager.notifyRoutesAdded(routes);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify routes added. Manager probably died.", ex);
- }
- }
- }
-
- private void notifyRoutesRemovedToManagers(@NonNull List<IMediaRouter2Manager> managers,
- @NonNull List<MediaRoute2Info> routes) {
- for (IMediaRouter2Manager manager : managers) {
- try {
- manager.notifyRoutesRemoved(routes);
- } catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify routes removed. Manager probably died.", ex);
- }
- }
- }
-
- private void notifyRoutesChangedToManagers(@NonNull List<IMediaRouter2Manager> managers,
+ private void notifyRoutesUpdatedToManagers(
+ @NonNull List<IMediaRouter2Manager> managers,
@NonNull List<MediaRoute2Info> routes) {
for (IMediaRouter2Manager manager : managers) {
try {
- manager.notifyRoutesChanged(routes);
+ manager.notifyRoutesUpdated(routes);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify routes changed. Manager probably died.", ex);
}