MediaRouter: initialize GlobalMediaRouter lazily

MediaRouter.getInstance() took 16-22ms due to instantiation
of too many objects.
This CL makss it initialize lazily for some apps to reduce
initialization time.

Bug: 150662969
Test: ./gradlew mediarouter:mediarouter:connectedCheck
&& estimate time for MediaRouter.getInstance() running
w/ support v7 demos on Pixel 3XL, it reduced roughly
from 16ms to 3ms.
It also passed global TAP presubmit.

Change-Id: Idb2438daf6306efe560c746cb5386288e9a8623d
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2Provider.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2Provider.java
index 2b98060..9bb1db4 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2Provider.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRoute2Provider.java
@@ -373,7 +373,6 @@
         @Override
         public void onTransfer(@NonNull MediaRouter2.RoutingController oldController,
                 @NonNull MediaRouter2.RoutingController newController) {
-            // TODO: Call onPrepareTransfer() when the API is added.
             mControllerMap.remove(oldController);
             if (newController == mMediaRouter2.getSystemController()) {
                 mCallback.onSelectFallbackRoute(UNSELECT_REASON_ROUTE_CHANGED);
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
index 01d7ab2..9e98d05 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouter.java
@@ -139,9 +139,9 @@
     public static final int UNSELECT_REASON_ROUTE_CHANGED = 3;
 
     // Maintains global media router state for the process.
-    // This field is initialized in MediaRouter.getInstance() before any
-    // MediaRouter objects are instantiated so it is guaranteed to be
-    // valid whenever any instance method is invoked.
+    // This field is initialized lazily when it is necessary.
+    // Access this field directly only when you don't want to initialize it.
+    // Use {@link #getGlobalRouter()} to get a valid instance.
     static GlobalMediaRouter sGlobal;
 
     // Context-bound state of the media router.
@@ -288,25 +288,41 @@
 
         if (sGlobal == null) {
             sGlobal = new GlobalMediaRouter(context.getApplicationContext());
-            sGlobal.start();
         }
+        // Use sGlobal directly to avoid initialization.
         return sGlobal.getRouter(context);
     }
 
     /**
+     * Gets the initialized global router.
+     * Please make sure this is called in the main thread.
+     */
+    @MainThread
+    static GlobalMediaRouter getGlobalRouter() {
+        if (sGlobal == null) {
+            return null;
+        }
+        sGlobal.ensureInitialized();
+        return sGlobal;
+    }
+
+    /**
      * Gets information about the {@link MediaRouter.RouteInfo routes} currently known to
      * this media router.
      */
     @NonNull
     public List<RouteInfo> getRoutes() {
         checkCallingThread();
-        return sGlobal == null ? Collections.<RouteInfo>emptyList() : sGlobal.getRoutes();
+        GlobalMediaRouter globalMediaRouter = getGlobalRouter();
+        return globalMediaRouter == null ? Collections.<RouteInfo>emptyList() :
+                globalMediaRouter.getRoutes();
     }
 
     @Nullable
     RouteInfo getRoute(String uniqueId) {
         checkCallingThread();
-        return sGlobal == null ? null : sGlobal.getRoute(uniqueId);
+        GlobalMediaRouter globalMediaRouter = getGlobalRouter();
+        return globalMediaRouter == null ? null : globalMediaRouter.getRoute(uniqueId);
     }
 
     /**
@@ -316,7 +332,9 @@
     @NonNull
     public List<ProviderInfo> getProviders() {
         checkCallingThread();
-        return sGlobal == null ? Collections.<ProviderInfo>emptyList() : sGlobal.getProviders();
+        GlobalMediaRouter globalMediaRouter = getGlobalRouter();
+        return globalMediaRouter == null ? Collections.<ProviderInfo>emptyList() :
+                globalMediaRouter.getProviders();
     }
 
     /**
@@ -330,7 +348,7 @@
     @NonNull
     public RouteInfo getDefaultRoute() {
         checkCallingThread();
-        return sGlobal.getDefaultRoute();
+        return getGlobalRouter().getDefaultRoute();
     }
 
     /**
@@ -341,7 +359,8 @@
     @Nullable
     public RouteInfo getBluetoothRoute() {
         checkCallingThread();
-        return sGlobal == null ? null : sGlobal.getBluetoothRoute();
+        GlobalMediaRouter globalMediaRouter = getGlobalRouter();
+        return globalMediaRouter == null ? null : globalMediaRouter.getBluetoothRoute();
     }
 
     /**
@@ -392,7 +411,7 @@
     @NonNull
     public RouteInfo getSelectedRoute() {
         checkCallingThread();
-        return sGlobal.getSelectedRoute();
+        return getGlobalRouter().getSelectedRoute();
     }
 
     /**
@@ -417,10 +436,11 @@
         if (DEBUG) {
             Log.d(TAG, "updateSelectedRoute: " + selector);
         }
-        RouteInfo route = sGlobal.getSelectedRoute();
+        GlobalMediaRouter globalRouter = getGlobalRouter();
+        RouteInfo route = globalRouter.getSelectedRoute();
         if (!route.isDefaultOrBluetooth() && !route.matchesSelector(selector)) {
-            route = sGlobal.chooseFallbackRoute();
-            sGlobal.selectRoute(route, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
+            route = globalRouter.chooseFallbackRoute();
+            globalRouter.selectRoute(route, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
         }
         return route;
     }
@@ -439,7 +459,7 @@
         if (DEBUG) {
             Log.d(TAG, "selectRoute: " + route);
         }
-        sGlobal.selectRoute(route, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
+        getGlobalRouter().selectRoute(route, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
     }
 
     /**
@@ -463,9 +483,11 @@
         checkCallingThread();
 
         // Choose the fallback route if it's not already selected.
-        RouteInfo fallbackRoute = sGlobal.chooseFallbackRoute();
-        if (sGlobal.getSelectedRoute() != fallbackRoute) {
-            sGlobal.selectRoute(fallbackRoute, reason);
+        // Otherwise, select the default route.
+        GlobalMediaRouter globalRouter = getGlobalRouter();
+        RouteInfo fallbackRoute = globalRouter.chooseFallbackRoute();
+        if (globalRouter.getSelectedRoute() != fallbackRoute) {
+            globalRouter.selectRoute(fallbackRoute, reason);
         }
     }
 
@@ -479,7 +501,7 @@
             throw new NullPointerException("route must not be null");
         }
         checkCallingThread();
-        sGlobal.addMemberToDynamicGroup(route);
+        getGlobalRouter().addMemberToDynamicGroup(route);
     }
 
     /**
@@ -492,7 +514,7 @@
             throw new NullPointerException("route must not be null");
         }
         checkCallingThread();
-        sGlobal.removeMemberFromDynamicGroup(route);
+        getGlobalRouter().removeMemberFromDynamicGroup(route);
     }
 
     /**
@@ -505,7 +527,7 @@
             throw new NullPointerException("route must not be null");
         }
         checkCallingThread();
-        sGlobal.transferToRoute(route);
+        getGlobalRouter().transferToRoute(route);
     }
 
     /**
@@ -535,8 +557,7 @@
             throw new IllegalArgumentException("selector must not be null");
         }
         checkCallingThread();
-
-        return sGlobal.isRouteAvailable(selector, flags);
+        return getGlobalRouter().isRouteAvailable(selector, flags);
     }
 
     /**
@@ -708,7 +729,7 @@
             updateNeeded = true;
         }
         if (updateNeeded) {
-            sGlobal.updateDiscoveryRequest();
+            getGlobalRouter().updateDiscoveryRequest();
         }
     }
 
@@ -732,7 +753,7 @@
         int index = findCallbackRecord(callback);
         if (index >= 0) {
             mCallbackRecords.remove(index);
-            sGlobal.updateDiscoveryRequest();
+            getGlobalRouter().updateDiscoveryRequest();
         }
     }
 
@@ -752,7 +773,7 @@
     @MainThread
     public void setOnPrepareTransferListener(@Nullable OnPrepareTransferListener listener) {
         checkCallingThread();
-        sGlobal.mOnPrepareTransferListener = listener;
+        getGlobalRouter().mOnPrepareTransferListener = listener;
     }
 
     /**
@@ -776,7 +797,7 @@
         if (DEBUG) {
             Log.d(TAG, "addProvider: " + providerInstance);
         }
-        sGlobal.addProvider(providerInstance);
+        getGlobalRouter().addProvider(providerInstance);
     }
 
     /**
@@ -800,7 +821,7 @@
         if (DEBUG) {
             Log.d(TAG, "removeProvider: " + providerInstance);
         }
-        sGlobal.removeProvider(providerInstance);
+        getGlobalRouter().removeProvider(providerInstance);
     }
 
     /**
@@ -823,7 +844,7 @@
         if (DEBUG) {
             Log.d(TAG, "addRemoteControlClient: " + remoteControlClient);
         }
-        sGlobal.addRemoteControlClient(remoteControlClient);
+        getGlobalRouter().addRemoteControlClient(remoteControlClient);
     }
 
     /**
@@ -836,11 +857,12 @@
         if (remoteControlClient == null) {
             throw new IllegalArgumentException("remoteControlClient must not be null");
         }
+        checkCallingThread();
 
         if (DEBUG) {
             Log.d(TAG, "removeRemoteControlClient: " + remoteControlClient);
         }
-        sGlobal.removeRemoteControlClient(remoteControlClient);
+        getGlobalRouter().removeRemoteControlClient(remoteControlClient);
     }
 
     /**
@@ -852,10 +874,11 @@
      * @param mediaSession The {@link android.media.session.MediaSession} to use.
      */
     public void setMediaSession(@Nullable Object mediaSession) {
+        checkCallingThread();
         if (DEBUG) {
             Log.d(TAG, "setMediaSession: " + mediaSession);
         }
-        sGlobal.setMediaSession(mediaSession);
+        getGlobalRouter().setMediaSession(mediaSession);
     }
 
     /**
@@ -867,15 +890,17 @@
      * @param mediaSession The {@link MediaSessionCompat} to use.
      */
     public void setMediaSessionCompat(@Nullable MediaSessionCompat mediaSession) {
+        checkCallingThread();
         if (DEBUG) {
             Log.d(TAG, "setMediaSessionCompat: " + mediaSession);
         }
-        sGlobal.setMediaSessionCompat(mediaSession);
+        getGlobalRouter().setMediaSessionCompat(mediaSession);
     }
 
     @Nullable
     public MediaSessionCompat.Token getMediaSessionToken() {
         return sGlobal == null ? null : sGlobal.getMediaSessionToken();
+        // Use sGlobal exceptionally due to unchecked thread.
     }
 
     /**
@@ -885,7 +910,8 @@
     @Nullable
     public MediaRouterParams getRouterParams() {
         checkCallingThread();
-        return sGlobal == null ? null : sGlobal.getRouterParams();
+        GlobalMediaRouter globalMediaRouter = getGlobalRouter();
+        return globalMediaRouter == null ? null : globalMediaRouter.getRouterParams();
     }
 
     /**
@@ -896,7 +922,7 @@
      */
     public void setRouterParams(@Nullable MediaRouterParams params) {
         checkCallingThread();
-        sGlobal.setRouterParams(params);
+        getGlobalRouter().setRouterParams(params);
     }
 
     /**
@@ -920,7 +946,7 @@
         if (sGlobal == null) {
             return false;
         }
-        return sGlobal.isMediaTransferEnabled();
+        return getGlobalRouter().isMediaTransferEnabled();
     }
 
     /**
@@ -931,14 +957,15 @@
         if (sGlobal == null) {
             return 0;
         }
-        return sGlobal.getCallbackCount();
+        return getGlobalRouter().getCallbackCount();
     }
 
     /**
      * Returns whether transferring media from remote to local is enabled.
      */
     static boolean isTransferToLocalEnabled() {
-        return sGlobal == null ? false : sGlobal.isTransferToLocalEnabled();
+        GlobalMediaRouter globalMediaRouter = getGlobalRouter();
+        return globalMediaRouter == null ? false : globalMediaRouter.isTransferToLocalEnabled();
     }
 
     /**
@@ -1216,7 +1243,7 @@
         //   - If this route is a selected member route of a group, it returns false.
         public boolean isSelected() {
             checkCallingThread();
-            return sGlobal.getSelectedRoute() == this;
+            return getGlobalRouter().getSelectedRoute() == this;
         }
 
         /**
@@ -1228,7 +1255,7 @@
          */
         public boolean isDefault() {
             checkCallingThread();
-            return sGlobal.getDefaultRoute() == this;
+            return getGlobalRouter().getDefaultRoute() == this;
         }
 
         /**
@@ -1240,7 +1267,7 @@
          */
         public boolean isBluetooth() {
             checkCallingThread();
-            return sGlobal.getBluetoothRoute() == this;
+            return getGlobalRouter().getBluetoothRoute() == this;
         }
 
         /**
@@ -1380,7 +1407,7 @@
             }
             checkCallingThread();
 
-            ContentResolver contentResolver = sGlobal.getContentResolver();
+            ContentResolver contentResolver = getGlobalRouter().getContentResolver();
             int count = mControlFilters.size();
             for (int i = 0; i < count; i++) {
                 if (mControlFilters.get(i).match(contentResolver, intent, true, TAG) >= 0) {
@@ -1414,7 +1441,7 @@
             }
             checkCallingThread();
 
-            sGlobal.sendControlRequest(this, intent, callback);
+            getGlobalRouter().sendControlRequest(this, intent, callback);
         }
 
         /**
@@ -1529,7 +1556,7 @@
          */
         public void requestSetVolume(int volume) {
             checkCallingThread();
-            sGlobal.requestSetVolume(this, Math.min(mVolumeMax, Math.max(0, volume)));
+            getGlobalRouter().requestSetVolume(this, Math.min(mVolumeMax, Math.max(0, volume)));
         }
 
         /**
@@ -1544,7 +1571,7 @@
         public void requestUpdateVolume(int delta) {
             checkCallingThread();
             if (delta != 0) {
-                sGlobal.requestUpdateVolume(this, delta);
+                getGlobalRouter().requestUpdateVolume(this, delta);
             }
         }
 
@@ -1581,7 +1608,7 @@
         public Display getPresentationDisplay() {
             checkCallingThread();
             if (mPresentationDisplayId >= 0 && mPresentationDisplay == null) {
-                mPresentationDisplay = sGlobal.getDisplay(mPresentationDisplayId);
+                mPresentationDisplay = getGlobalRouter().getDisplay(mPresentationDisplayId);
             }
             return mPresentationDisplay;
         }
@@ -1618,7 +1645,7 @@
          */
         public void select() {
             checkCallingThread();
-            sGlobal.selectRoute(this, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
+            getGlobalRouter().selectRoute(this, MediaRouter.UNSELECT_REASON_ROUTE_CHANGED);
         }
 
         /**
@@ -1666,8 +1693,9 @@
         @RestrictTo(LIBRARY)
         @Nullable
         public DynamicGroupRouteController getDynamicGroupController() {
+            checkCallingThread();
             //TODO: handle multiple controllers case
-            RouteController controller = sGlobal.mSelectedRouteController;
+            RouteController controller = getGlobalRouter().mSelectedRouteController;
             if (controller instanceof DynamicGroupRouteController) {
                 return (DynamicGroupRouteController) controller;
             }
@@ -1847,13 +1875,17 @@
                 if (groupMemberIds.size() != mMemberRoutes.size()) {
                     memberChanged = true;
                 }
-                for (String groupMemberId : groupMemberIds) {
-                    String uniqueId = sGlobal.getUniqueId(getProvider(), groupMemberId);
-                    RouteInfo groupMember = sGlobal.getRoute(uniqueId);
-                    if (groupMember != null) {
-                        routes.add(groupMember);
-                        if (!memberChanged && !mMemberRoutes.contains(groupMember)) {
-                            memberChanged = true;
+                //TODO: Clean this up not to reference the global router
+                if (!groupMemberIds.isEmpty()) {
+                    GlobalMediaRouter globalRouter = getGlobalRouter();
+                    for (String groupMemberId : groupMemberIds) {
+                        String uniqueId = globalRouter.getUniqueId(getProvider(), groupMemberId);
+                        RouteInfo groupMember = globalRouter.getRoute(uniqueId);
+                        if (groupMember != null) {
+                            routes.add(groupMember);
+                            if (!memberChanged && !mMemberRoutes.contains(groupMember)) {
+                                memberChanged = true;
+                            }
                         }
                     }
                 }
@@ -1896,7 +1928,7 @@
                     mMemberRoutes.add(route);
                 }
             }
-            sGlobal.mCallbackHandler.post(
+            getGlobalRouter().mCallbackHandler.post(
                     GlobalMediaRouter.CallbackHandler.MSG_ROUTE_CHANGED, this);
         }
 
@@ -1970,8 +2002,6 @@
 
         private final ProviderMetadata mMetadata;
         private MediaRouteProviderDescriptor mDescriptor;
-        private Resources mResources;
-        private boolean mResourcesNotAvailable;
 
         ProviderInfo(MediaRouteProvider provider) {
             mProviderInstance = provider;
@@ -2012,21 +2042,6 @@
             return Collections.unmodifiableList(mRoutes);
         }
 
-        Resources getResources() {
-            if (mResources == null && !mResourcesNotAvailable) {
-                String packageName = getPackageName();
-                Context context = sGlobal.getProviderContext(packageName);
-                if (context != null) {
-                    mResources = context.getResources();
-                } else {
-                    Log.w(TAG, "Unable to obtain resources for route provider package: "
-                            + packageName);
-                    mResourcesNotAvailable = true;
-                }
-            }
-            return mResources;
-        }
-
         boolean updateDescriptor(MediaRouteProviderDescriptor descriptor) {
             if (mDescriptor != descriptor) {
                 mDescriptor = descriptor;
@@ -2356,8 +2371,14 @@
             implements SystemMediaRouteProvider.SyncCallback,
             RegisteredMediaRouteProviderWatcher.Callback {
         final Context mApplicationContext;
-        final boolean mMediaTransferEnabled;
-        final MediaRoute2Provider mMr2Provider;
+        boolean mIsInitialized;
+
+        SystemMediaRouteProvider mSystemProvider;
+        @VisibleForTesting
+        RegisteredMediaRouteProviderWatcher mRegisteredProviderWatcher;
+        boolean mMediaTransferEnabled;
+        MediaRoute2Provider mMr2Provider;
+
         final ArrayList<WeakReference<MediaRouter>> mRouters = new ArrayList<>();
         private final ArrayList<RouteInfo> mRoutes = new ArrayList<>();
         private final Map<Pair<String, String>, String> mUniqueIdMap = new HashMap<>();
@@ -2368,22 +2389,12 @@
                 new RemoteControlClientCompat.PlaybackInfo();
         private final ProviderCallback mProviderCallback = new ProviderCallback();
         final CallbackHandler mCallbackHandler = new CallbackHandler();
-        private final DisplayManagerCompat mDisplayManager;
-        final SystemMediaRouteProvider mSystemProvider;
+        private DisplayManagerCompat mDisplayManager;
         private final boolean mLowRam;
-        private MediaRouterActiveScanThrottlingHelper mActiveScanThrottlingHelper =
-                new MediaRouterActiveScanThrottlingHelper(
-                        new Runnable() {
-                        @Override
-                        public void run() {
-                            updateDiscoveryRequest();
-                        }
-                });
+        private MediaRouterActiveScanThrottlingHelper mActiveScanThrottlingHelper;
 
         private MediaRouterParams mRouterParams;
-        @VisibleForTesting
-        RegisteredMediaRouteProviderWatcher mRegisteredProviderWatcher;
-        private RouteInfo mDefaultRoute;
+        RouteInfo mDefaultRoute;
         private RouteInfo mBluetoothRoute;
         RouteInfo mSelectedRoute;
         RouteController mSelectedRouteController;
@@ -2398,10 +2409,13 @@
         private int mCallbackCount;
         OnPrepareTransferListener mOnPrepareTransferListener;
         PrepareTransferNotifier mTransferNotifier;
+        RouteInfo mTransferredRoute;
+        RouteController mTransferredRouteController;
+
         private MediaSessionRecord mMediaSession;
         MediaSessionCompat mRccMediaSession;
         private MediaSessionCompat mCompatSession;
-        private MediaSessionCompat.OnActiveChangeListener mSessionActiveListener =
+        private final MediaSessionCompat.OnActiveChangeListener mSessionActiveListener =
                 new MediaSessionCompat.OnActiveChangeListener() {
             @Override
             public void onActiveChanged() {
@@ -2415,13 +2429,19 @@
             }
         };
 
-        @SuppressLint({"SyntheticAccessor", "NewApi"})
         GlobalMediaRouter(Context applicationContext) {
             mApplicationContext = applicationContext;
-            mDisplayManager = DisplayManagerCompat.getInstance(applicationContext);
             mLowRam = ActivityManagerCompat.isLowRamDevice(
                     (ActivityManager)applicationContext.getSystemService(
                             Context.ACTIVITY_SERVICE));
+        }
+
+        @SuppressLint({"NewApi", "SyntheticAccessor"})
+        void ensureInitialized() {
+            if (mIsInitialized) {
+                return;
+            }
+            mIsInitialized = true;
 
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                 mMediaTransferEnabled = MediaTransferReceiver.isDeclared(mApplicationContext);
@@ -2435,13 +2455,23 @@
             } else {
                 mMr2Provider = null;
             }
+
             // Add the system media route provider for interoperating with
             // the framework media router.  This one is special and receives
             // synchronization messages from the media router.
-            mSystemProvider = SystemMediaRouteProvider.obtain(applicationContext, this);
+            mSystemProvider = SystemMediaRouteProvider.obtain(mApplicationContext, this);
+            start();
         }
 
-        public void start() {
+        private void start() {
+            // Using lambda would break some apps.
+            mActiveScanThrottlingHelper = new MediaRouterActiveScanThrottlingHelper(
+                    new Runnable() {
+                        @Override
+                        public void run() {
+                            updateDiscoveryRequest();
+                        }
+                    });
             addProvider(mSystemProvider);
             if (mMr2Provider != null) {
                 addProvider(mMr2Provider);
@@ -2486,6 +2516,9 @@
         }
 
         public Display getDisplay(int displayId) {
+            if (mDisplayManager == null) {
+                mDisplayManager = DisplayManagerCompat.getInstance(mApplicationContext);
+            }
             return mDisplayManager.getDisplay(displayId);
         }
 
@@ -3467,7 +3500,7 @@
             }
         }
 
-        private final class Mr2ProviderCallback extends MediaRoute2Provider.Callback {
+        final class Mr2ProviderCallback extends MediaRoute2Provider.Callback {
             @Override
             public void onSelectRoute(@NonNull String routeDescriptorId,
                     @UnselectReason int reason) {
@@ -3703,7 +3736,7 @@
             }
 
             // Using Pair<RouteInfo, RouteInfo>
-            @SuppressWarnings({"unchecked", "SyntheticAccessor"})
+            @SuppressWarnings({"unchecked"})
             private void syncWithSystemProvider(int what, Object obj) {
                 switch (what) {
                     case MSG_ROUTE_ADDED:
@@ -3843,7 +3876,7 @@
             mMemberRoutes = (memberRoutes == null) ? null : new ArrayList<>(memberRoutes);
 
             // For the case it's not handled properly
-            router.mCallbackHandler.postDelayed(() -> this.finishTransfer(),
+            router.mCallbackHandler.postDelayed(this::finishTransfer,
                     TRANSFER_TIMEOUT_MS);
         }
 
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterActiveScanThrottlingHelper.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterActiveScanThrottlingHelper.java
index 4e05ca5..e1862ae 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterActiveScanThrottlingHelper.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouterActiveScanThrottlingHelper.java
@@ -25,7 +25,7 @@
  * suppressed.
  */
 class MediaRouterActiveScanThrottlingHelper {
-    // The constant is package visible for tests can set it to a shorter durationaq.
+    // The constant is package visible for tests can set it to a shorter duration.
     static final long MAX_ACTIVE_SCAN_DURATION_MS = 30000;
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());