diff options
21 files changed, 571 insertions, 199 deletions
diff --git a/media/java/android/media/IMediaRoute2ProviderClient.aidl b/media/java/android/media/IMediaRoute2ProviderClient.aidl index 6f44d45c0a12..f4fb7f450fb8 100644 --- a/media/java/android/media/IMediaRoute2ProviderClient.aidl +++ b/media/java/android/media/IMediaRoute2ProviderClient.aidl @@ -17,10 +17,13 @@ package android.media; import android.media.MediaRoute2ProviderInfo; +import android.media.MediaRoute2Info; +import android.os.Bundle; /** * @hide */ oneway interface IMediaRoute2ProviderClient { void updateProviderInfo(in MediaRoute2ProviderInfo info); + void notifyRouteSelected(String packageName, String routeId, in Bundle controlHints, int seq); } diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index 386d2dc54a88..1b6183e361c8 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -18,14 +18,19 @@ package android.media; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Service; import android.content.Intent; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.util.Log; +import java.util.Objects; + /** * @hide */ @@ -44,7 +49,7 @@ public abstract class MediaRoute2ProviderService extends Service { } @Override - public IBinder onBind(Intent intent) { + public IBinder onBind(@NonNull Intent intent) { //TODO: Allow binding from media router service only? if (SERVICE_INTERFACE.equals(intent.getAction())) { if (mStub == null) { @@ -57,11 +62,17 @@ public abstract class MediaRoute2ProviderService extends Service { /** * Called when selectRoute is called on a route of the provider. + * Once the route is ready to be used , call {@link #notifyRouteSelected(SelectToken, Bundle)} + * to notify that. * * @param packageName the package name of the application that selected the route * @param routeId the id of the route being selected + * @param token token that contains select info + * + * @see #notifyRouteSelected */ - public abstract void onSelectRoute(String packageName, String routeId); + public abstract void onSelectRoute(@NonNull String packageName, @NonNull String routeId, + @NonNull SelectToken token); /** * Called when unselectRoute is called on a route of the provider. @@ -69,7 +80,7 @@ public abstract class MediaRoute2ProviderService extends Service { * @param packageName the package name of the application that has selected the route. * @param routeId the id of the route being unselected */ - public abstract void onUnselectRoute(String packageName, String routeId); + public abstract void onUnselectRoute(@NonNull String packageName, @NonNull String routeId); /** * Called when sendControlRequest is called on a route of the provider @@ -78,21 +89,21 @@ public abstract class MediaRoute2ProviderService extends Service { * @param request the media control request intent */ //TODO: Discuss what to use for request (e.g., Intent? Request class?) - public abstract void onControlRequest(String routeId, Intent request); + public abstract void onControlRequest(@NonNull String routeId, @NonNull Intent request); /** * Called when requestSetVolume is called on a route of the provider * @param routeId the id of the route * @param volume the target volume */ - public abstract void onSetVolume(String routeId, int volume); + public abstract void onSetVolume(@NonNull String routeId, int volume); /** * Called when requestUpdateVolume is called on a route of the provider * @param routeId id of the route * @param delta the delta to add to the current volume */ - public abstract void onUpdateVolume(String routeId, int delta); + public abstract void onUpdateVolume(@NonNull String routeId, int delta); /** * Updates provider info and publishes routes @@ -102,6 +113,29 @@ public abstract class MediaRoute2ProviderService extends Service { publishState(); } + /** + * Notifies the client of that the selected route is ready for use. If the selected route can be + * controlled, pass a {@link Bundle} that contains how to control it. + * + * @param token token passed in {@link #onSelectRoute} + * @param controlHints a {@link Bundle} that contains how to control the given route. + * Pass {@code null} if the route is not available. + */ + public final void notifyRouteSelected(@NonNull SelectToken token, + @Nullable Bundle controlHints) { + Objects.requireNonNull(token, "token must not be null"); + + if (mClient == null) { + return; + } + try { + mClient.notifyRouteSelected(token.mPackageName, token.mRouteId, + controlHints, token.mSeq); + } catch (RemoteException ex) { + Log.w(TAG, "Failed to notify route selected"); + } + } + void setClient(IMediaRoute2ProviderClient client) { mClient = client; publishState(); @@ -118,6 +152,23 @@ public abstract class MediaRoute2ProviderService extends Service { } } + /** + * Route selection information. + * + * @see #notifyRouteSelected + */ + public final class SelectToken { + final String mPackageName; + final String mRouteId; + final int mSeq; + + SelectToken(String packageName, String routeId, int seq) { + mPackageName = packageName; + mRouteId = routeId; + mSeq = seq; + } + } + final class ProviderStub extends IMediaRoute2Provider.Stub { ProviderStub() { } @@ -129,10 +180,10 @@ public abstract class MediaRoute2ProviderService extends Service { @Override public void requestSelectRoute(String packageName, String id, int seq) { - // TODO: When introducing MediaRoute2ProviderService#sendConnectionHints(), - // use the sequence number here properly. mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute, - MediaRoute2ProviderService.this, packageName, id)); + MediaRoute2ProviderService.this, packageName, id, + new SelectToken(packageName, id, seq))); + } @Override diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index 9cb78696f19b..7b15d9554534 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -1318,6 +1318,7 @@ public class MediaRouter { sStatic.rebindAsUser(userId); } + //TODO: remove this and Client1Record in MediaRouter2ServiceImpl. /** * Sets the control categories of the application. * Routes that support at least one of the given control categories only exists and are handled diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 94ac77af3ac3..35cb066f9790 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -57,7 +57,8 @@ public class MediaRouter2 { @IntDef(value = { SELECT_REASON_UNKNOWN, SELECT_REASON_USER_SELECTED, - SELECT_REASON_FALLBACK}) + SELECT_REASON_FALLBACK, + SELECT_REASON_SYSTEM_SELECTED}) public @interface SelectReason {} /** @@ -80,6 +81,13 @@ public class MediaRouter2 { */ public static final int SELECT_REASON_FALLBACK = 2; + /** + * This is passed from {@link com.android.server.media.MediaRouterService} when the route + * is selected in response to a request from other apps (e.g. System UI). + * @hide + */ + public static final int SELECT_REASON_SYSTEM_SELECTED = 3; + private static final String TAG = "MR2"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final Object sLock = new Object(); @@ -485,6 +493,9 @@ public class MediaRouter2 { } mSelectingRoute = null; } + if (reason == SELECT_REASON_SYSTEM_SELECTED) { + reason = SELECT_REASON_USER_SELECTED; + } mSelectedRoute = route; notifyRouteSelected(route, reason, controlHints); } diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java index f4f8d0b73658..6650f9618638 100644 --- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java +++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java @@ -20,6 +20,7 @@ import android.content.Intent; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2ProviderService; +import android.os.Bundle; import android.os.IBinder; import java.util.HashMap; @@ -95,7 +96,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } @Override - public void onSelectRoute(String packageName, String routeId) { + public void onSelectRoute(String packageName, String routeId, SelectToken token) { MediaRoute2Info route = mRoutes.get(routeId); if (route == null) { return; @@ -104,6 +105,7 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService .setClientPackageName(packageName) .build()); publishRoutes(); + notifyRouteSelected(token, Bundle.EMPTY); } @Override diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java index d0f7c780ceb2..c70ad8d8755c 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -23,23 +23,19 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.verify; import android.content.Context; import android.content.Intent; import android.media.MediaRoute2Info; import android.media.MediaRouter2; import android.media.MediaRouter2Manager; +import android.os.Bundle; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.text.TextUtils; -import org.junit.Assert; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -95,9 +91,14 @@ public class MediaRouterManagerTest { private Executor mExecutor; private String mPackageName; + private final List<MediaRouter2Manager.Callback> mManagerCallbacks = new ArrayList<>(); + private final List<MediaRouter2.Callback> mRouterCallbacks = new ArrayList<>(); + private Map<String, MediaRoute2Info> mRoutes; + private static final List<String> CATEGORIES_ALL = new ArrayList(); private static final List<String> CATEGORIES_SPECIAL = new ArrayList(); private static final List<String> CATEGORIES_LIVE_AUDIO = new ArrayList<>(); + static { CATEGORIES_ALL.add(CATEGORY_SAMPLE); CATEGORIES_ALL.add(CATEGORY_SPECIAL); @@ -108,6 +109,7 @@ public class MediaRouterManagerTest { CATEGORIES_LIVE_AUDIO.add(CATEGORY_LIVE_AUDIO); } + @Before public void setUp() throws Exception { mContext = InstrumentationRegistry.getTargetContext(); @@ -116,6 +118,16 @@ public class MediaRouterManagerTest { //TODO: If we need to support thread pool executors, change this to thread pool executor. mExecutor = Executors.newSingleThreadExecutor(); mPackageName = mContext.getPackageName(); + + // ensure media router 2 client + addRouterCallback(new MediaRouter2.Callback()); + mRoutes = waitAndGetRoutesWithManager(CATEGORIES_ALL); + } + + @After + public void tearDown() { + // unregister callbacks + clearCallbacks(); } //TODO: Move to a separate file @@ -132,10 +144,13 @@ public class MediaRouterManagerTest { assertNotEquals(routeInfo1, routeInfo3); } + /** + * Tests if routes are added correctly when a new callback is registered. + */ @Test public void testOnRoutesAdded() throws Exception { CountDownLatch latch = new CountDownLatch(1); - MediaRouter2Manager.Callback callback = new MediaRouter2Manager.Callback() { + addManagerCallback(new MediaRouter2Manager.Callback() { @Override public void onRoutesAdded(List<MediaRoute2Info> routes) { assertTrue(routes.size() > 0); @@ -145,27 +160,15 @@ public class MediaRouterManagerTest { } } } - }; - mManager.registerCallback(mExecutor, callback); + }); assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); - - mManager.unregisterCallback(callback); } @Test public void testOnRoutesRemoved() throws Exception { - MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class); - mManager.registerCallback(mExecutor, mockCallback); - - MediaRouter2.Callback routerCallback = new MediaRouter2.Callback(); - mRouter2.registerCallback(mExecutor, routerCallback); - - Map<String, MediaRoute2Info> routes = - waitAndGetRoutesWithManager(CATEGORIES_ALL); - CountDownLatch latch = new CountDownLatch(1); - MediaRouter2Manager.Callback callback = new MediaRouter2Manager.Callback() { + addManagerCallback(new MediaRouter2Manager.Callback() { @Override public void onRoutesRemoved(List<MediaRoute2Info> routes) { assertTrue(routes.size() > 0); @@ -175,16 +178,12 @@ public class MediaRouterManagerTest { } } } - }; - mManager.registerCallback(mExecutor, callback); + }); //TODO: Figure out a more proper way to test. // (Control requests shouldn't be used in this way.) - mRouter2.sendControlRequest(routes.get(ROUTE_ID2), new Intent(ACTION_REMOVE_ROUTE)); + mRouter2.sendControlRequest(mRoutes.get(ROUTE_ID2), new Intent(ACTION_REMOVE_ROUTE)); assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); - - mRouter2.unregisterCallback(routerCallback); - mManager.unregisterCallback(mockCallback); } /** @@ -192,16 +191,10 @@ public class MediaRouterManagerTest { */ @Test public void testControlCategory() throws Exception { - MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class); - mManager.registerCallback(mExecutor, mockCallback); - - Map<String, MediaRoute2Info> routes = - waitAndGetRoutesWithManager(CATEGORIES_SPECIAL); - - Assert.assertEquals(1, routes.size()); - Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY)); + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_SPECIAL); - mManager.unregisterCallback(mockCallback); + assertEquals(1, routes.size()); + assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY)); } /** @@ -209,57 +202,74 @@ public class MediaRouterManagerTest { */ @Test public void testGetRoutes() throws Exception { - MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class); - mRouter2.registerCallback(mExecutor, mockCallback); - Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_SPECIAL); - Assert.assertEquals(1, routes.size()); - Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY)); - - mRouter2.unregisterCallback(mockCallback); + assertEquals(1, routes.size()); + assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY)); } + /** + * Tests if MR2.Callback.onRouteSelected is called when a route is selected from MR2Manager. + */ @Test - public void testOnRouteSelected() throws Exception { - MediaRouter2.Callback routerCallback = new MediaRouter2.Callback(); - MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class); - - mManager.registerCallback(mExecutor, managerCallback); - mRouter2.registerCallback(mExecutor, routerCallback); + public void testRouterOnRouteSelected() throws Exception { + CountDownLatch latch = new CountDownLatch(1); - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL); + addRouterCallback(new MediaRouter2.Callback() { + @Override + public void onRouteSelected(MediaRoute2Info route, int reason, Bundle controlHints) { + if (route != null && TextUtils.equals(route.getId(), ROUTE_ID1)) { + latch.countDown(); + } + } + }); - MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1); + MediaRoute2Info routeToSelect = mRoutes.get(ROUTE_ID1); assertNotNull(routeToSelect); mManager.selectRoute(mPackageName, routeToSelect); - verify(managerCallback, timeout(TIMEOUT_MS)) - .onRouteSelected(eq(mPackageName), - argThat(route -> route != null && route.equals(routeToSelect))); - mRouter2.unregisterCallback(routerCallback); - mManager.unregisterCallback(managerCallback); + assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); } /** - * Tests selecting and unselecting routes of a single provider. + * Tests if MR2Manager.Callback.onRouteSelected is called + * when a route is selected by MR2Manager. */ @Test - public void testSingleProviderSelect() throws Exception { - MediaRouter2.Callback routerCallback = mock(MediaRouter2.Callback.class); + public void testManagerOnRouteSelected() throws Exception { + CountDownLatch latch = new CountDownLatch(1); - mRouter2.registerCallback(mExecutor, routerCallback); + addManagerCallback(new MediaRouter2Manager.Callback() { + @Override + public void onRouteSelected(String packageName, MediaRoute2Info route) { + if (TextUtils.equals(mPackageName, packageName) + && route != null && TextUtils.equals(route.getId(), ROUTE_ID1)) { + latch.countDown(); + } + } + }); + + MediaRoute2Info routeToSelect = mRoutes.get(ROUTE_ID1); + assertNotNull(routeToSelect); + + mManager.selectRoute(mPackageName, routeToSelect); - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL); + assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } + /** + * Tests selecting and unselecting routes of a single provider. + */ + @Test + public void testSingleProviderSelect() throws Exception { awaitOnRouteChangedManager( - () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1)), + () -> mManager.selectRoute(mPackageName, mRoutes.get(ROUTE_ID1)), ROUTE_ID1, route -> TextUtils.equals(route.getClientPackageName(), mPackageName)); awaitOnRouteChangedManager( - () -> mManager.selectRoute(mPackageName, routes.get(ROUTE_ID2)), + () -> mManager.selectRoute(mPackageName, mRoutes.get(ROUTE_ID2)), ROUTE_ID2, route -> TextUtils.equals(route.getClientPackageName(), mPackageName)); @@ -267,8 +277,6 @@ public class MediaRouterManagerTest { () -> mManager.unselectRoute(mPackageName), ROUTE_ID2, route -> TextUtils.equals(route.getClientPackageName(), null)); - - mRouter2.unregisterCallback(routerCallback); } @Test @@ -292,12 +300,7 @@ public class MediaRouterManagerTest { @Test public void testControlVolumeWithManager() throws Exception { - MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class); - - mRouter2.registerCallback(mExecutor, mockCallback); - Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL); - - MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME); + MediaRoute2Info volRoute = mRoutes.get(ROUTE_ID_VARIABLE_VOLUME); int originalVolume = volRoute.getVolume(); int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1); @@ -310,24 +313,16 @@ public class MediaRouterManagerTest { () -> mManager.requestSetVolume(volRoute, originalVolume), ROUTE_ID_VARIABLE_VOLUME, (route -> route.getVolume() == originalVolume)); - - mRouter2.unregisterCallback(mockCallback); } @Test public void testVolumeHandling() throws Exception { - MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class); - mRouter2.registerCallback(mExecutor, mockCallback); - Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_ALL); - - MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME); - MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME); + MediaRoute2Info fixedVolumeRoute = mRoutes.get(ROUTE_ID_FIXED_VOLUME); + MediaRoute2Info variableVolumeRoute = mRoutes.get(ROUTE_ID_VARIABLE_VOLUME); assertEquals(PLAYBACK_VOLUME_FIXED, fixedVolumeRoute.getVolumeHandling()); assertEquals(PLAYBACK_VOLUME_VARIABLE, variableVolumeRoute.getVolumeHandling()); assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax()); - - mRouter2.unregisterCallback(mockCallback); } @Test @@ -368,6 +363,7 @@ public class MediaRouterManagerTest { latch.countDown(); } } + @Override public void onControlCategoriesChanged(String packageName) { if (TextUtils.equals(mPackageName, packageName)) { @@ -401,7 +397,7 @@ public class MediaRouterManagerTest { }; mRouter2.registerCallback(mExecutor, callback); try { - new Thread(task).start(); + task.run(); assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); } finally { mRouter2.unregisterCallback(callback); @@ -422,7 +418,7 @@ public class MediaRouterManagerTest { }; mManager.registerCallback(mExecutor, callback); try { - new Thread(task).start(); + task.run(); assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); } finally { mManager.unregisterCallback(callback); @@ -433,9 +429,31 @@ public class MediaRouterManagerTest { static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) { Map<String, MediaRoute2Info> routeMap = new HashMap<>(); for (MediaRoute2Info route : routes) { - // intentionally not route.getUniqueId() for convenience. + // intentionally not using route.getUniqueId() for convenience. routeMap.put(route.getId(), route); } return routeMap; } + + private void addManagerCallback(MediaRouter2Manager.Callback callback) { + mManagerCallbacks.add(callback); + mManager.registerCallback(mExecutor, callback); + } + + private void addRouterCallback(MediaRouter2.Callback callback) { + mRouterCallbacks.add(callback); + mRouter2.registerCallback(mExecutor, callback); + } + + private void clearCallbacks() { + for (MediaRouter2Manager.Callback callback : mManagerCallbacks) { + mManager.unregisterCallback(callback); + } + mManagerCallbacks.clear(); + + for (MediaRouter2.Callback callback : mRouterCallbacks) { + mRouter2.unregisterCallback(callback); + } + mRouterCallbacks.clear(); + } } diff --git a/packages/Tethering/apex/Android.bp b/packages/Tethering/apex/Android.bp new file mode 100644 index 000000000000..bca01ebdf819 --- /dev/null +++ b/packages/Tethering/apex/Android.bp @@ -0,0 +1,35 @@ +// +// 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. +// + +apex { + name: "com.android.tethering.apex", + apps: ["Tethering"], + manifest: "manifest.json", + key: "com.android.tethering.apex.key", + + androidManifest: "AndroidManifest.xml", +} + +apex_key { + name: "com.android.tethering.apex.key", + public_key: "com.android.tethering.apex.avbpubkey", + private_key: "com.android.tethering.apex.pem", +} + +android_app_certificate { + name: "com.android.tethering.apex.certificate", + certificate: "com.android.tethering.apex", +} diff --git a/packages/Tethering/apex/AndroidManifest.xml b/packages/Tethering/apex/AndroidManifest.xml new file mode 100644 index 000000000000..7769b799b6f9 --- /dev/null +++ b/packages/Tethering/apex/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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. + --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.tethering.apex"> + <!-- APEX does not have classes.dex --> + <application android:hasCode="false" /> + <!-- b/145383354: Current minSdk is locked to Q for development cycle, lock it to next version + before ship. --> + <uses-sdk + android:minSdkVersion="29" + android:targetSdkVersion="29" + /> +</manifest> diff --git a/packages/Tethering/apex/com.android.tethering.apex.avbpubkey b/packages/Tethering/apex/com.android.tethering.apex.avbpubkey Binary files differnew file mode 100644 index 000000000000..9c8711161547 --- /dev/null +++ b/packages/Tethering/apex/com.android.tethering.apex.avbpubkey diff --git a/packages/Tethering/apex/com.android.tethering.apex.pem b/packages/Tethering/apex/com.android.tethering.apex.pem new file mode 100644 index 000000000000..a8cd12e92e8d --- /dev/null +++ b/packages/Tethering/apex/com.android.tethering.apex.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/packages/Tethering/apex/com.android.tethering.apex.pk8 b/packages/Tethering/apex/com.android.tethering.apex.pk8 Binary files differnew file mode 100644 index 000000000000..56632462941f --- /dev/null +++ b/packages/Tethering/apex/com.android.tethering.apex.pk8 diff --git a/packages/Tethering/apex/com.android.tethering.apex.x509.pem b/packages/Tethering/apex/com.android.tethering.apex.x509.pem new file mode 100644 index 000000000000..a5e94011f7c5 --- /dev/null +++ b/packages/Tethering/apex/com.android.tethering.apex.x509.pem @@ -0,0 +1,36 @@ +-----BEGIN CERTIFICATE----- +MIIGMzCCBBugAwIBAgIUXVtoDaXanhs7ma8VIICambMkj5UwDQYJKoZIhvcNAQEL +BQAwgacxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH +DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy +b2lkMSMwIQYDVQQDDBpjb20uYW5kcm9pZC50ZXRoZXJpbmcuYXBleDEiMCAGCSqG +SIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAgFw0xOTExMjgwNjU4MTRaGA80 +NzU3MTAyNDA2NTgxNFowgacxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9y +bmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAw +DgYDVQQLDAdBbmRyb2lkMSMwIQYDVQQDDBpjb20uYW5kcm9pZC50ZXRoZXJpbmcu +YXBleDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBANwzufMBdOj9XlNwiX+bXl/94G0DklWW +nzob0jPlubCFfRqYkjCf2eOd28Mu/O1pOBcvobnrs9OTpGzcHkz2h58L5/0UMVTS +tBugwCE49XF5FHawqVHNZE+s5tDmnp2cufhNc5HXHY4oZKh80/WVdbcKxiLjSY2T +PgRAfB6E6XByKD3t1cSsc3liRVKADoJOVDvmF+xnyvSV/SN38bvTQk9aVs95mj0W +yov6gzXBnqN7iQlvkhcijZBnFWxvoNbJ5KFy1abYOrm+ueXje4BcNhVOeRMb4E9N +eo7+9k1GEI7TYG7laNNcp7UJ1IXCJzv/wBFKRg3f1HB3unKfx2rtKerDnVsr3o7V +KProkgRNKNhhQ6opNguiH1YMzKpWMaC988n4AQPryPdIOmVIxIC5jJrixdxgzDXT +qeiwFiXis291uyls08B03PQFlY9oWaY9P8s+4hIUjB6rLl+XZXsLDtDFxXeJ97NB +8XZN1gBJoBoLknFs0C4LKpmJZB/EBao9tXV9dL/5lydRo6HzQDpjW8QX06CTUM6z +Lr3LVelhqbsuZsV42yBKl+/LfrvNjBLEPdSevt2oMrlJW7m4iSNaMtDtJ2Oy8fA5 +WSIgLWuMbkaFDza3JzwiMzxbtbJHYiy6rY7aVywo3Vqwr1+KO3cq4eLQq62zUjRY +e6KJwvgE2YmpAgMBAAGjUzBRMB0GA1UdDgQWBBQ8h1oF5JfKFmJCN8nfimbUK+IR +wjAfBgNVHSMEGDAWgBQ8h1oF5JfKFmJCN8nfimbUK+IRwjAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAP5hIkAxSyt9hRafMKiFlmXcL277bgoxNd +qGZYbdcCFjfvM2r0QQcM/K7x2ZslFe7mJSzcyMifpm4YQTEo2yYbzKItXQV+eV1K +9RNksRGFG9umsdWaSHhfQmcmxtr2nu9rGAgxy5OQFtyXmJPUPEM2cb/YeILwYhuQ +Ux3kaj/fxGltX1JBag7HnMzCTZK++fRo5nqFVOJQgJH8ZpuzGeM9kZvP1+b55046 +PhSnlqmZoKhG4i5POPvvZvaakh/lM3x/N7lIlSaQpCGf7jmldni4L0/GenULVKzH +iN73aBfh4GEvE0HRcOoH3L7V6kc3WMMLve0chZBHpoVYbzUJEJOUL4yrmwEehqtf +xm4vlYg3vqtcE3UnU/UGdMb16t77Nz88LlpBY5ierIt0jZMU0M81ppRhr1uiD2Lj +091sEA0Bxcw/6Q8QNF2eR7SG7Qwipnms+lw6Vcxve+7DdTrdEA0k3XgpdXp8Ya+2 +PAp9SLVp1UHiGq3qD9Jvm34QmlUWAIUTHZs3DSgs1y3K5eyw/cnzTvUUOljc/n2y +VF0FFZtJ1dVLrzQ80Ik7apEXpBqkgBGV04/L3QYk4C0/sP+1yk6zjeeeAvDtUcHS +gLtjAfacQl/kwfVQWfrF7VByLcivApC6EUdvT3cURM5DfZRQ4RcKr1D61VYPnNRH ++/NVbMObwQ== +-----END CERTIFICATE----- diff --git a/packages/Tethering/apex/manifest.json b/packages/Tethering/apex/manifest.json new file mode 100644 index 000000000000..3fb62f3405a3 --- /dev/null +++ b/packages/Tethering/apex/manifest.json @@ -0,0 +1,4 @@ +{ + "name": "com.android.tethering.apex", + "version": 290000000 +} diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 652dd40297b9..a3a61720ac3d 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -925,6 +925,10 @@ public class ConnectivityService extends IConnectivityManager.Stub return IIpConnectivityMetrics.Stub.asInterface( ServiceManager.getService(IpConnectivityLog.SERVICE_NAME)); } + + public IBatteryStats getBatteryStatsService() { + return BatteryStatsService.getService(); + } } public ConnectivityService(Context context, INetworkManagementService netManager, @@ -2144,7 +2148,7 @@ public class ConnectivityService extends IConnectivityManager.Stub opts.setMaxManifestReceiverApiLevel(Build.VERSION_CODES.M); options = opts.toBundle(); } - final IBatteryStats bs = BatteryStatsService.getService(); + final IBatteryStats bs = mDeps.getBatteryStatsService(); try { bs.noteConnectivityChanged(intent.getIntExtra( ConnectivityManager.EXTRA_NETWORK_TYPE, ConnectivityManager.TYPE_NONE), @@ -5628,7 +5632,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // are accurate. networkAgent.clatd.fixupLinkProperties(oldLp, newLp); - updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities); + updateInterfaces(newLp, oldLp, netId, networkAgent.networkCapabilities, + networkAgent.networkInfo.getType()); // update filtering rules, need to happen after the interface update so netd knows about the // new interface (the interface name -> index map becomes initialized) @@ -5707,21 +5712,26 @@ public class ConnectivityService extends IConnectivityManager.Stub } - private void updateInterfaces(LinkProperties newLp, LinkProperties oldLp, int netId, - NetworkCapabilities caps) { - CompareResult<String> interfaceDiff = new CompareResult<>( + private void updateInterfaces(final @Nullable LinkProperties newLp, + final @Nullable LinkProperties oldLp, final int netId, + final @Nullable NetworkCapabilities caps, final int legacyType) { + final CompareResult<String> interfaceDiff = new CompareResult<>( oldLp != null ? oldLp.getAllInterfaceNames() : null, newLp != null ? newLp.getAllInterfaceNames() : null); - for (String iface : interfaceDiff.added) { - try { - if (DBG) log("Adding iface " + iface + " to network " + netId); - mNMS.addInterfaceToNetwork(iface, netId); - wakeupModifyInterface(iface, caps, true); - } catch (Exception e) { - loge("Exception adding interface: " + e); + if (!interfaceDiff.added.isEmpty()) { + final IBatteryStats bs = mDeps.getBatteryStatsService(); + for (final String iface : interfaceDiff.added) { + try { + if (DBG) log("Adding iface " + iface + " to network " + netId); + mNMS.addInterfaceToNetwork(iface, netId); + wakeupModifyInterface(iface, caps, true); + bs.noteNetworkInterfaceType(iface, legacyType); + } catch (Exception e) { + loge("Exception adding interface: " + e); + } } } - for (String iface : interfaceDiff.removed) { + for (final String iface : interfaceDiff.removed) { try { if (DBG) log("Removing iface " + iface + " from network " + netId); wakeupModifyInterface(iface, caps, false); @@ -6457,77 +6467,6 @@ public class ConnectivityService extends IConnectivityManager.Stub // do this after the default net is switched, but // before LegacyTypeTracker sends legacy broadcasts for (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri); - - // Linger any networks that are no longer needed. This should be done after sending the - // available callback for newNetwork. - for (NetworkAgentInfo nai : removedRequests) { - updateLingerState(nai, now); - } - // Possibly unlinger newNetwork. Unlingering a network does not send any callbacks so it - // does not need to be done in any particular order. - updateLingerState(newNetwork, now); - - if (isNewDefault) { - // Maintain the illusion: since the legacy API only - // understands one network at a time, we must pretend - // that the current default network disconnected before - // the new one connected. - if (oldDefaultNetwork != null) { - mLegacyTypeTracker.remove(oldDefaultNetwork.networkInfo.getType(), - oldDefaultNetwork, true); - } - mDefaultInetConditionPublished = newNetwork.lastValidated ? 100 : 0; - mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork); - notifyLockdownVpn(newNetwork); - } - - if (reassignedRequests.containsValue(newNetwork) || newNetwork.isVPN()) { - // Notify battery stats service about this network, both the normal - // interface and any stacked links. - // TODO: Avoid redoing this; this must only be done once when a network comes online. - try { - final IBatteryStats bs = BatteryStatsService.getService(); - final int type = newNetwork.networkInfo.getType(); - - final String baseIface = newNetwork.linkProperties.getInterfaceName(); - bs.noteNetworkInterfaceType(baseIface, type); - for (LinkProperties stacked : newNetwork.linkProperties.getStackedLinks()) { - final String stackedIface = stacked.getInterfaceName(); - bs.noteNetworkInterfaceType(stackedIface, type); - } - } catch (RemoteException ignored) { - } - - // This has to happen after the notifyNetworkCallbacks as that tickles each - // ConnectivityManager instance so that legacy requests correctly bind dns - // requests to this network. The legacy users are listening for this broadcast - // and will generally do a dns request so they can ensureRouteToHost and if - // they do that before the callbacks happen they'll use the default network. - // - // TODO: Is there still a race here? We send the broadcast - // after sending the callback, but if the app can receive the - // broadcast before the callback, it might still break. - // - // This *does* introduce a race where if the user uses the new api - // (notification callbacks) and then uses the old api (getNetworkInfo(type)) - // they may get old info. Reverse this after the old startUsing api is removed. - // This is on top of the multiple intent sequencing referenced in the todo above. - for (int i = 0; i < newNetwork.numNetworkRequests(); i++) { - NetworkRequest nr = newNetwork.requestAt(i); - if (nr.legacyType != TYPE_NONE && nr.isRequest()) { - // legacy type tracker filters out repeat adds - mLegacyTypeTracker.add(nr.legacyType, newNetwork); - } - } - - // A VPN generally won't get added to the legacy tracker in the "for (nri)" loop above, - // because usually there are no NetworkRequests it satisfies (e.g., mDefaultRequest - // wants the NOT_VPN capability, so it will never be satisfied by a VPN). So, add the - // newNetwork to the tracker explicitly (it's a no-op if it has already been added). - if (newNetwork.isVPN()) { - mLegacyTypeTracker.add(TYPE_VPN, newNetwork); - } - } } /** @@ -6541,15 +6480,32 @@ public class ConnectivityService extends IConnectivityManager.Stub // requests. Once the code has switched to a request-major iteration style, this can // be optimized to only do the processing needed. final long now = SystemClock.elapsedRealtime(); + final NetworkAgentInfo oldDefaultNetwork = getDefaultNetwork(); + final NetworkAgentInfo[] nais = mNetworkAgentInfos.values().toArray( new NetworkAgentInfo[mNetworkAgentInfos.size()]); // Rematch higher scoring networks first to prevent requests first matching a lower // scoring network and then a higher scoring network, which could produce multiple - // callbacks and inadvertently unlinger networks. + // callbacks. Arrays.sort(nais); - for (NetworkAgentInfo nai : nais) { + for (final NetworkAgentInfo nai : nais) { rematchNetworkAndRequests(nai, now); } + + final NetworkAgentInfo newDefaultNetwork = getDefaultNetwork(); + + for (final NetworkAgentInfo nai : nais) { + // Rematching may have altered the linger state of some networks, so update all linger + // timers. updateLingerState reads the state from the network agent and does nothing + // if the state has not changed : the source of truth is controlled with + // NetworkAgentInfo#lingerRequest and NetworkAgentInfo#unlingerRequest, which have been + // called while rematching the individual networks above. + updateLingerState(nai, now); + } + + updateLegacyTypeTrackerAndVpnLockdownForRematch(oldDefaultNetwork, newDefaultNetwork, nais); + + // Tear down all unneeded networks. for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { if (unneeded(nai, UnneededFor.TEARDOWN)) { if (nai.getLingerExpiry() > 0) { @@ -6569,6 +6525,70 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + private void updateLegacyTypeTrackerAndVpnLockdownForRematch( + @Nullable final NetworkAgentInfo oldDefaultNetwork, + @Nullable final NetworkAgentInfo newDefaultNetwork, + @NonNull final NetworkAgentInfo[] nais) { + if (oldDefaultNetwork != newDefaultNetwork) { + // Maintain the illusion : since the legacy API only understands one network at a time, + // if the default network changed, apps should see a disconnected broadcast for the + // old default network before they see a connected broadcast for the new one. + if (oldDefaultNetwork != null) { + mLegacyTypeTracker.remove(oldDefaultNetwork.networkInfo.getType(), + oldDefaultNetwork, true); + } + if (newDefaultNetwork != null) { + // The new default network can be newly null if and only if the old default + // network doesn't satisfy the default request any more because it lost a + // capability. + mDefaultInetConditionPublished = newDefaultNetwork.lastValidated ? 100 : 0; + mLegacyTypeTracker.add(newDefaultNetwork.networkInfo.getType(), newDefaultNetwork); + // If the legacy VPN is connected, notifyLockdownVpn may end up sending a broadcast + // to reflect the NetworkInfo of this new network. This broadcast has to be sent + // after the disconnect broadcasts above, but before the broadcasts sent by the + // legacy type tracker below. + // TODO : refactor this, it's too complex + notifyLockdownVpn(newDefaultNetwork); + } + } + + // Now that all the callbacks have been sent, send the legacy network broadcasts + // as needed. This is necessary so that legacy requests correctly bind dns + // requests to this network. The legacy users are listening for this broadcast + // and will generally do a dns request so they can ensureRouteToHost and if + // they do that before the callbacks happen they'll use the default network. + // + // TODO: Is there still a race here? The legacy broadcast will be sent after sending + // callbacks, but if apps can receive the broadcast before the callback, they still might + // have an inconsistent view of networking. + // + // This *does* introduce a race where if the user uses the new api + // (notification callbacks) and then uses the old api (getNetworkInfo(type)) + // they may get old info. Reverse this after the old startUsing api is removed. + // This is on top of the multiple intent sequencing referenced in the todo above. + for (NetworkAgentInfo nai : nais) { + addNetworkToLegacyTypeTracker(nai); + } + } + + private void addNetworkToLegacyTypeTracker(@NonNull final NetworkAgentInfo nai) { + for (int i = 0; i < nai.numNetworkRequests(); i++) { + NetworkRequest nr = nai.requestAt(i); + if (nr.legacyType != TYPE_NONE && nr.isRequest()) { + // legacy type tracker filters out repeat adds + mLegacyTypeTracker.add(nr.legacyType, nai); + } + } + + // A VPN generally won't get added to the legacy tracker in the "for (nri)" loop above, + // because usually there are no NetworkRequests it satisfies (e.g., mDefaultRequest + // wants the NOT_VPN capability, so it will never be satisfied by a VPN). So, add the + // newNetwork to the tracker explicitly (it's a no-op if it has already been added). + if (nai.isVPN()) { + mLegacyTypeTracker.add(TYPE_VPN, nai); + } + } + private void updateInetCondition(NetworkAgentInfo nai) { // Don't bother updating until we've graduated to validated at least once. if (!nai.everValidated) return; diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 24a5b7fa1489..bb7f86233a40 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -580,7 +580,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // semantics of WakeupMessage guarantee that if cancel is called then the alarm will // never call its callback (handleLingerComplete), even if it has already fired. // WakeupMessage makes no such guarantees about rescheduling a message, so if mLingerMessage - // has already been dispatched, rescheduling to some time in the future it won't stop it + // has already been dispatched, rescheduling to some time in the future won't stop it // from calling its callback immediately. if (mLingerMessage != null) { mLingerMessage.cancel(); diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 91c9253269a3..9a49c166e2b2 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.Intent; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; +import android.os.Bundle; import java.util.Objects; @@ -29,7 +30,7 @@ abstract class MediaRoute2Provider { final ComponentName mComponentName; final String mUniqueId; - private Callback mCallback; + Callback mCallback; private MediaRoute2ProviderInfo mProviderInfo; MediaRoute2Provider(@NonNull ComponentName componentName) { @@ -77,6 +78,9 @@ abstract class MediaRoute2Provider { } public interface Callback { - void onProviderStateChanged(MediaRoute2Provider provider); + void onProviderStateChanged(@Nullable MediaRoute2Provider provider); + void onRouteSelected(@NonNull MediaRoute2ProviderProxy provider, + @NonNull String clientPackageName, @NonNull MediaRoute2Info route, + @Nullable Bundle controlHints, int seq); } } diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java index 3b6580ad7357..a5abb1835e7b 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -26,6 +26,7 @@ import android.media.IMediaRoute2ProviderClient; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; import android.media.MediaRoute2ProviderService; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.IBinder.DeathRecipient; @@ -253,6 +254,20 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv setAndNotifyProviderInfo(info); } + private void onRouteSelected(Connection connection, + String packageName, String routeId, Bundle controlHints, int seq) { + if (mActiveConnection != connection) { + return; + } + MediaRoute2ProviderInfo providerInfo = getProviderInfo(); + MediaRoute2Info route = (providerInfo == null) ? null : providerInfo.getRoute(routeId); + if (route == null) { + Slog.w(TAG, this + ": Unknown route " + routeId + " is selected from remove provider"); + return; + } + mCallback.onRouteSelected(this, packageName, route, controlHints, seq); + } + private void disconnect() { if (mActiveConnection != null) { mConnectionReady = false; @@ -341,6 +356,11 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv void postProviderInfoUpdated(MediaRoute2ProviderInfo info) { mHandler.post(() -> onProviderInfoUpdated(Connection.this, info)); } + + void postRouteSelected(String packageName, String routeId, Bundle controlHints, int seq) { + mHandler.post(() -> onRouteSelected(Connection.this, + packageName, routeId, controlHints, seq)); + } } private static final class ProviderClient extends IMediaRoute2ProviderClient.Stub { @@ -361,5 +381,15 @@ final class MediaRoute2ProviderProxy extends MediaRoute2Provider implements Serv connection.postProviderInfoUpdated(info); } } + + @Override + public void notifyRouteSelected(String packageName, String routeId, + Bundle controlHints, int seq) { + Connection connection = mConnectionRef.get(); + if (connection != null) { + connection.postRouteSelected(packageName, routeId, controlHints, seq); + } + } + } } diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 2cf920d1aba4..2c478dff4679 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -77,7 +77,7 @@ class MediaRouter2ServiceImpl { @GuardedBy("mLock") private int mCurrentUserId = -1; @GuardedBy("mLock") - private int mSelectRouteRequestSequenceNumber = 0; + private int mSelectRouteRequestSequenceNumber = 1; MediaRouter2ServiceImpl(Context context) { mContext = context; @@ -218,7 +218,7 @@ class MediaRouter2ServiceImpl { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - requestSelectRoute2Locked(mAllClientRecords.get(client.asBinder()), route); + requestSelectRoute2Locked(mAllClientRecords.get(client.asBinder()), false, route); } } finally { Binder.restoreCallingIdentity(token); @@ -399,10 +399,12 @@ class MediaRouter2ServiceImpl { } } - private void requestSelectRoute2Locked(ClientRecord clientRecord, MediaRoute2Info route) { + private void requestSelectRoute2Locked(ClientRecord clientRecord, boolean selectedByManager, + MediaRoute2Info route) { if (clientRecord != null) { MediaRoute2Info oldRoute = clientRecord.mSelectedRoute; clientRecord.mSelectingRoute = route; + clientRecord.mIsManagerSelecting = selectedByManager; UserHandler handler = clientRecord.mUserRecord.mHandler; //TODO: Handle transfer instead of unselect and select @@ -417,7 +419,6 @@ class MediaRouter2ServiceImpl { handler.sendMessage(obtainMessage( UserHandler::requestSelectRoute, handler, clientRecord.mPackageName, route, seq)); - // Remove all previous timeout messages for (int previousSeq : clientRecord.mSelectRouteSequenceNumbers) { clientRecord.mUserRecord.mHandler.removeMessages(previousSeq); @@ -543,7 +544,7 @@ class MediaRouter2ServiceImpl { Slog.w(TAG, "Ignoring route selection for unknown client."); } if (clientRecord != null && managerRecord.mTrusted) { - requestSelectRoute2Locked(clientRecord, route); + requestSelectRoute2Locked(clientRecord, true, route); } } } @@ -656,7 +657,9 @@ class MediaRouter2ServiceImpl { public final UserRecord mUserRecord; public final String mPackageName; public final List<Integer> mSelectRouteSequenceNumbers; + public List<String> mControlCategories; + public boolean mIsManagerSelecting; public MediaRoute2Info mSelectingRoute; public MediaRoute2Info mSelectedRoute; @@ -802,9 +805,8 @@ class MediaRouter2ServiceImpl { sendMessage(PooledLambda.obtainMessage(UserHandler::updateProvider, this, provider)); } - // TODO: When introducing MediaRoute2ProviderService#sendControlHints(), - // Make this method to be called. - public void onRouteSelectionRequestHandled(@NonNull MediaRoute2ProviderProxy provider, + @Override + public void onRouteSelected(@NonNull MediaRoute2ProviderProxy provider, String clientPackageName, MediaRoute2Info route, Bundle controlHints, int seq) { sendMessage(PooledLambda.obtainMessage( UserHandler::updateSelectedRoute, this, provider, clientPackageName, route, @@ -917,6 +919,8 @@ class MediaRouter2ServiceImpl { return; } + //TODO: handle a case such that controlHints is null. (How should we notify MR2?) + if (clientRecord.mSelectingRoute == null || !TextUtils.equals( clientRecord.mSelectingRoute.getUniqueId(), selectedRoute.getUniqueId())) { Log.w(TAG, "Ignoring invalid updateSelectedRoute call. selectingRoute=" @@ -929,7 +933,9 @@ class MediaRouter2ServiceImpl { notifyRouteSelectedToClient(((Client2Record) clientRecord).mClient, selectedRoute, - MediaRouter2.SELECT_REASON_USER_SELECTED, + clientRecord.mIsManagerSelecting + ? MediaRouter2.SELECT_REASON_SYSTEM_SELECTED : + MediaRouter2.SELECT_REASON_USER_SELECTED, controlHints); updateClientUsage(clientRecord); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 0f912f1a4956..7960842daf50 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -149,7 +149,8 @@ class ActivityStarter { private final ActivityStartController mController; // Share state variable among methods when starting an activity. - private ActivityRecord mStartActivity; + @VisibleForTesting + ActivityRecord mStartActivity; private Intent mIntent; private int mCallingUid; private ActivityOptions mOptions; @@ -173,7 +174,8 @@ class ActivityStarter { private int mPreferredDisplayId; private Task mInTask; - private boolean mAddingToTask; + @VisibleForTesting + boolean mAddingToTask; private Task mReuseTask; private ActivityInfo mNewTaskInfo; @@ -1665,7 +1667,16 @@ class ActivityStarter { * - Comply to the specified activity launch flags * - Determine whether need to add a new activity on top or just brought the task to front. */ - private int recycleTask(Task targetTask, ActivityRecord targetTaskTop, Task reusedTask) { + @VisibleForTesting + int recycleTask(Task targetTask, ActivityRecord targetTaskTop, Task reusedTask) { + // Should not recycle task which is from a different user, just adding the starting + // activity to the task. + if (targetTask.mUserId != mStartActivity.mUserId) { + mTargetStack = targetTask.getStack(); + mAddingToTask = true; + return START_SUCCESS; + } + // True if we are clearing top and resetting of a standard (default) launch mode // ({@code LAUNCH_MULTIPLE}) activity. The existing activity will be finished. final boolean clearTopAndResetStandardLaunchMode = diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index ae1bb8e57e56..c712d6dedb27 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -962,4 +962,19 @@ public class ActivityStarterTests extends ActivityTestsBase { } assertThat(exceptionCaught).isTrue(); } + + @Test + public void testRecycleTaskFromAnotherUser() { + final ActivityStarter starter = prepareStarter(0 /* flags */); + starter.mStartActivity = new ActivityBuilder(mService).build(); + final Task task = new TaskBuilder(mService.mStackSupervisor) + .setStack(mService.mRootActivityContainer.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */)) + .setUserId(10) + .build(); + + final int result = starter.recycleTask(task, null, null); + assertThat(result == START_SUCCESS).isTrue(); + assertThat(starter.mAddingToTask).isTrue(); + } } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 2a09f647a46b..c4e353bdca55 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -96,6 +96,7 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.startsWith; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.any; import static org.mockito.Mockito.atLeastOnce; @@ -198,6 +199,7 @@ import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.app.IBatteryStats; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnInfo; import com.android.internal.util.ArrayUtils; @@ -305,6 +307,7 @@ public class ConnectivityServiceTest { @Mock DefaultNetworkMetrics mDefaultNetworkMetrics; @Mock INetworkManagementService mNetworkManagementService; @Mock INetworkStatsService mStatsService; + @Mock IBatteryStats mBatteryStatsService; @Mock INetworkPolicyManager mNpm; @Mock IDnsResolver mMockDnsResolver; @Mock INetd mMockNetd; @@ -1135,6 +1138,7 @@ public class ConnectivityServiceTest { doReturn(mMetricsService).when(deps).getMetricsLogger(); doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt()); doReturn(mIpConnectivityMetrics).when(deps).getIpConnectivityMetrics(); + doReturn(mBatteryStatsService).when(deps).getBatteryStatsService(); doReturn(true).when(deps).hasService(Context.ETHERNET_SERVICE); doAnswer(inv -> { mPolicyTracker = new WrappedMultinetworkPolicyTracker( @@ -5640,6 +5644,36 @@ public class ConnectivityServiceTest { mCm.unregisterNetworkCallback(defaultCallback); } + @Test + public final void testBatteryStatsNetworkType() throws Exception { + final LinkProperties cellLp = new LinkProperties(); + cellLp.setInterfaceName("cell0"); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); + mCellNetworkAgent.connect(true); + waitForIdle(); + verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(), + TYPE_MOBILE); + reset(mBatteryStatsService); + + final LinkProperties wifiLp = new LinkProperties(); + wifiLp.setInterfaceName("wifi0"); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp); + mWiFiNetworkAgent.connect(true); + waitForIdle(); + verify(mBatteryStatsService).noteNetworkInterfaceType(wifiLp.getInterfaceName(), + TYPE_WIFI); + reset(mBatteryStatsService); + + mCellNetworkAgent.disconnect(); + + cellLp.setInterfaceName("wifi0"); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); + mCellNetworkAgent.connect(true); + waitForIdle(); + verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(), + TYPE_MOBILE); + } + /** * Make simulated InterfaceConfig for Nat464Xlat to query clat lower layer info. */ @@ -5680,25 +5714,28 @@ public class ConnectivityServiceTest { mCm.registerNetworkCallback(networkRequest, networkCallback); // Prepare ipv6 only link properties. - mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); - final int cellNetId = mCellNetworkAgent.getNetwork().netId; final LinkProperties cellLp = new LinkProperties(); cellLp.setInterfaceName(MOBILE_IFNAME); cellLp.addLinkAddress(myIpv6); cellLp.addRoute(new RouteInfo((IpPrefix) null, myIpv6.getAddress(), MOBILE_IFNAME)); cellLp.addRoute(new RouteInfo(myIpv6, null, MOBILE_IFNAME)); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); reset(mNetworkManagementService); reset(mMockDnsResolver); reset(mMockNetd); + reset(mBatteryStatsService); when(mNetworkManagementService.getInterfaceConfig(CLAT_PREFIX + MOBILE_IFNAME)) .thenReturn(getClatInterfaceConfig(myIpv4)); // Connect with ipv6 link properties. Expect prefix discovery to be started. - mCellNetworkAgent.sendLinkProperties(cellLp); mCellNetworkAgent.connect(true); + final int cellNetId = mCellNetworkAgent.getNetwork().netId; + waitForIdle(); verify(mMockNetd, times(1)).networkCreatePhysical(eq(cellNetId), anyInt()); verify(mMockDnsResolver, times(1)).createNetworkCache(eq(cellNetId)); + verify(mBatteryStatsService).noteNetworkInterfaceType(cellLp.getInterfaceName(), + TYPE_MOBILE); networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); @@ -5714,6 +5751,11 @@ public class ConnectivityServiceTest { verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId); verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any()); + // Make sure BatteryStats was not told about any v4- interfaces, as none should have + // come online yet. + waitForIdle(); + verify(mBatteryStatsService, never()).noteNetworkInterfaceType(startsWith("v4-"), anyInt()); + verifyNoMoreInteractions(mMockNetd); verifyNoMoreInteractions(mMockDnsResolver); reset(mMockNetd); @@ -5760,6 +5802,11 @@ public class ConnectivityServiceTest { assertEquals(1, resolvrParams.servers.length); assertTrue(ArrayUtils.contains(resolvrParams.servers, "8.8.8.8")); + for (final LinkProperties stackedLp : stackedLpsAfterChange) { + verify(mBatteryStatsService).noteNetworkInterfaceType(stackedLp.getInterfaceName(), + TYPE_MOBILE); + } + // Add ipv4 address, expect that clatd and prefix discovery are stopped and stacked // linkproperties are cleaned up. cellLp.addLinkAddress(myIpv4); |