Merge "MediaRouter2: Remove sendControlRequest"
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 47ffd3e..5566e0e 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1643,8 +1643,9 @@
     }
 
     private void updateScreenMatrixForEmbeddedHierarchy() {
+        getBoundsOnScreen(mTmpRect, true);
         mTmpMatrix.reset();
-        mTmpMatrix.setTranslate(mScreenRect.left, mScreenRect.top);
+        mTmpMatrix.setTranslate(mTmpRect.left, mTmpRect.top);
         mTmpMatrix.postScale(mScreenRect.width() / (float) mSurfaceWidth,
                 mScreenRect.height() / (float) mSurfaceHeight);
 
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 9837e1c..12fc3a6 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -64,7 +64,12 @@
  *
  * <p>The media router API is not thread-safe; all interactions with it must be
  * done from the main thread of the process.</p>
+ *
+ * <p>
+ * We recommend using {@link android.media.MediaRouter2} APIs for new applications.
+ * </p>
  */
+//TODO: Link androidx.media2.MediaRouter when we are ready.
 @SystemService(Context.MEDIA_ROUTER_SERVICE)
 public class MediaRouter {
     private static final String TAG = "MediaRouter";
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 4db1109..bd00201 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -34,6 +34,7 @@
 import android.media.tv.tuner.filter.Filter.Type;
 import android.media.tv.tuner.filter.FilterCallback;
 import android.media.tv.tuner.filter.TimeFilter;
+import android.media.tv.tuner.frontend.Atsc3PlpInfo;
 import android.media.tv.tuner.frontend.FrontendInfo;
 import android.media.tv.tuner.frontend.FrontendSettings;
 import android.media.tv.tuner.frontend.FrontendStatus;
@@ -511,6 +512,90 @@
         }
     }
 
+    private void onLocked() {
+        if (mScanCallbackExecutor != null && mScanCallback != null) {
+            mScanCallbackExecutor.execute(() -> mScanCallback.onLocked());
+        }
+    }
+
+    private void onScanStopped() {
+        if (mScanCallbackExecutor != null && mScanCallback != null) {
+            mScanCallbackExecutor.execute(() -> mScanCallback.onScanStopped());
+        }
+    }
+
+    private void onProgress(int percent) {
+        if (mScanCallbackExecutor != null && mScanCallback != null) {
+            mScanCallbackExecutor.execute(() -> mScanCallback.onProgress(percent));
+        }
+    }
+
+    private void onFrequenciesReport(int[] frequency) {
+        if (mScanCallbackExecutor != null && mScanCallback != null) {
+            mScanCallbackExecutor.execute(() -> mScanCallback.onFrequenciesReport(frequency));
+        }
+    }
+
+    private void onSymbolRates(int[] rate) {
+        if (mScanCallbackExecutor != null && mScanCallback != null) {
+            mScanCallbackExecutor.execute(() -> mScanCallback.onSymbolRates(rate));
+        }
+    }
+
+    private void onHierarchy(int hierarchy) {
+        if (mScanCallbackExecutor != null && mScanCallback != null) {
+            mScanCallbackExecutor.execute(() -> mScanCallback.onHierarchy(hierarchy));
+        }
+    }
+
+    private void onSignalType(int signalType) {
+        if (mScanCallbackExecutor != null && mScanCallback != null) {
+            mScanCallbackExecutor.execute(() -> mScanCallback.onSignalType(signalType));
+        }
+    }
+
+    private void onPlpIds(int[] plpIds) {
+        if (mScanCallbackExecutor != null && mScanCallback != null) {
+            mScanCallbackExecutor.execute(() -> mScanCallback.onPlpIds(plpIds));
+        }
+    }
+
+    private void onGroupIds(int[] groupIds) {
+        if (mScanCallbackExecutor != null && mScanCallback != null) {
+            mScanCallbackExecutor.execute(() -> mScanCallback.onGroupIds(groupIds));
+        }
+    }
+
+    private void onInputStreamIds(int[] inputStreamIds) {
+        if (mScanCallbackExecutor != null && mScanCallback != null) {
+            mScanCallbackExecutor.execute(() -> mScanCallback.onInputStreamIds(inputStreamIds));
+        }
+    }
+
+    private void onDvbsStandard(int dvbsStandandard) {
+        if (mScanCallbackExecutor != null && mScanCallback != null) {
+            mScanCallbackExecutor.execute(() -> mScanCallback.onDvbsStandard(dvbsStandandard));
+        }
+    }
+
+    private void onDvbtStandard(int dvbtStandard) {
+        if (mScanCallbackExecutor != null && mScanCallback != null) {
+            mScanCallbackExecutor.execute(() -> mScanCallback.onDvbtStandard(dvbtStandard));
+        }
+    }
+
+    private void onAnalogSifStandard(int sif) {
+        if (mScanCallbackExecutor != null && mScanCallback != null) {
+            mScanCallbackExecutor.execute(() -> mScanCallback.onAnalogSifStandard(sif));
+        }
+    }
+
+    private void onAtsc3PlpInfos(Atsc3PlpInfo[] atsc3PlpInfos) {
+        if (mScanCallbackExecutor != null && mScanCallback != null) {
+            mScanCallbackExecutor.execute(() -> mScanCallback.onAtsc3PlpInfos(atsc3PlpInfos));
+        }
+    }
+
     /**
      * Opens a filter object based on the given types and buffer size.
      *
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index ac59003..f4d2d03 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -108,6 +108,7 @@
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtMode;
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtModulation;
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtSettings;
+using ::android::hardware::tv::tuner::V1_0::FrontendScanAtsc3PlpInfo;
 using ::android::hardware::tv::tuner::V1_0::FrontendType;
 using ::android::hardware::tv::tuner::V1_0::ITuner;
 using ::android::hardware::tv::tuner::V1_0::PlaybackSettings;
@@ -249,14 +250,152 @@
             (jint)frontendEventType);
     return Void();
 }
-Return<void> FrontendCallback::onDiseqcMessage(const hidl_vec<uint8_t>& /*diseqcMessage*/) {
-    ALOGD("FrontendCallback::onDiseqcMessage");
-    return Void();
-}
 
-Return<void> FrontendCallback::onScanMessage(
-        FrontendScanMessageType type, const FrontendScanMessage& /*message*/) {
+Return<void> FrontendCallback::onScanMessage(FrontendScanMessageType type, const FrontendScanMessage& message) {
     ALOGD("FrontendCallback::onScanMessage, type=%d", type);
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jclass clazz = env->FindClass("android/media/tv/tuner/Tuner");
+    switch(type) {
+        case FrontendScanMessageType::LOCKED: {
+            if (message.isLocked()) {
+                env->CallVoidMethod(
+                        mObject,
+                        env->GetMethodID(clazz, "onLocked", "()V"));
+            }
+            break;
+        }
+        case FrontendScanMessageType::END: {
+            if (message.isEnd()) {
+                env->CallVoidMethod(
+                        mObject,
+                        env->GetMethodID(clazz, "onScanStopped", "()V"));
+            }
+            break;
+        }
+        case FrontendScanMessageType::PROGRESS_PERCENT: {
+            env->CallVoidMethod(
+                    mObject,
+                    env->GetMethodID(clazz, "onProgress", "(I)V"),
+                    (jint) message.progressPercent());
+            break;
+        }
+        case FrontendScanMessageType::FREQUENCY: {
+            std::vector<uint32_t> v = message.frequencies();
+            jintArray freqs = env->NewIntArray(v.size());
+            env->SetIntArrayRegion(freqs, 0, v.size(), reinterpret_cast<jint*>(&v[0]));
+
+            env->CallVoidMethod(
+                    mObject,
+                    env->GetMethodID(clazz, "onFrequenciesReport", "([I)V"),
+                    freqs);
+            break;
+        }
+        case FrontendScanMessageType::SYMBOL_RATE: {
+            std::vector<uint32_t> v = message.symbolRates();
+            jintArray symbolRates = env->NewIntArray(v.size());
+            env->SetIntArrayRegion(symbolRates, 0, v.size(), reinterpret_cast<jint*>(&v[0]));
+
+            env->CallVoidMethod(
+                    mObject,
+                    env->GetMethodID(clazz, "onSymbolRates", "([I)V"),
+                    symbolRates);
+            break;
+        }
+        case FrontendScanMessageType::HIERARCHY: {
+            env->CallVoidMethod(
+                    mObject,
+                    env->GetMethodID(clazz, "onHierarchy", "(I)V"),
+                    (jint) message.hierarchy());
+            break;
+        }
+        case FrontendScanMessageType::ANALOG_TYPE: {
+            env->CallVoidMethod(
+                    mObject,
+                    env->GetMethodID(clazz, "onSignalType", "(I)V"),
+                    (jint) message.analogType());
+            break;
+        }
+        case FrontendScanMessageType::PLP_IDS: {
+            std::vector<uint8_t> v = message.plpIds();
+            std::vector<jint> jintV(v.begin(), v.end());
+            jintArray plpIds = env->NewIntArray(v.size());
+            env->SetIntArrayRegion(plpIds, 0, jintV.size(), &jintV[0]);
+
+            env->CallVoidMethod(
+                    mObject,
+                    env->GetMethodID(clazz, "onPlpIds", "([I)V"),
+                    plpIds);
+            break;
+        }
+        case FrontendScanMessageType::GROUP_IDS: {
+            std::vector<uint8_t> v = message.groupIds();
+            std::vector<jint> jintV(v.begin(), v.end());
+            jintArray groupIds = env->NewIntArray(v.size());
+            env->SetIntArrayRegion(groupIds, 0, jintV.size(), &jintV[0]);
+
+            env->CallVoidMethod(
+                    mObject,
+                    env->GetMethodID(clazz, "onGroupIds", "([I)V"),
+                    groupIds);
+            break;
+        }
+        case FrontendScanMessageType::INPUT_STREAM_IDS: {
+            std::vector<uint16_t> v = message.inputStreamIds();
+            std::vector<jint> jintV(v.begin(), v.end());
+            jintArray streamIds = env->NewIntArray(v.size());
+            env->SetIntArrayRegion(streamIds, 0, jintV.size(), &jintV[0]);
+
+            env->CallVoidMethod(
+                    mObject,
+                    env->GetMethodID(clazz, "onInputStreamIds", "([I)V"),
+                    streamIds);
+            break;
+        }
+        case FrontendScanMessageType::STANDARD: {
+            FrontendScanMessage::Standard std = message.std();
+            jint standard;
+            if (std.getDiscriminator() == FrontendScanMessage::Standard::hidl_discriminator::sStd) {
+                standard = (jint) std.sStd();
+                env->CallVoidMethod(
+                        mObject,
+                        env->GetMethodID(clazz, "onDvbsStandard", "(I)V"),
+                        standard);
+            } else if (std.getDiscriminator() == FrontendScanMessage::Standard::hidl_discriminator::tStd) {
+                standard = (jint) std.tStd();
+                env->CallVoidMethod(
+                        mObject,
+                        env->GetMethodID(clazz, "onDvbtStandard", "(I)V"),
+                        standard);
+            } else if (std.getDiscriminator() == FrontendScanMessage::Standard::hidl_discriminator::sifStd) {
+                standard = (jint) std.sifStd();
+                env->CallVoidMethod(
+                        mObject,
+                        env->GetMethodID(clazz, "onAnalogSifStandard", "(I)V"),
+                        standard);
+            }
+            break;
+        }
+        case FrontendScanMessageType::ATSC3_PLP_INFO: {
+            jclass plpClazz = env->FindClass("android/media/tv/tuner/frontend/Atsc3PlpInfo");
+            jmethodID init = env->GetMethodID(plpClazz, "<init>", "(IZ)V");
+            std::vector<FrontendScanAtsc3PlpInfo> plpInfos = message.atsc3PlpInfos();
+            jobjectArray array = env->NewObjectArray(plpInfos.size(), plpClazz, NULL);
+
+            for (int i = 0; i < plpInfos.size(); i++) {
+                auto info = plpInfos[i];
+                jint plpId = (jint) info.plpId;
+                jboolean lls = (jboolean) info.bLlsFlag;
+
+                jobject obj = env->NewObject(plpClazz, init, plpId, lls);
+                env->SetObjectArrayElement(array, i, obj);
+            }
+            env->CallVoidMethod(
+                    mObject,
+                    env->GetMethodID(clazz, "onAtsc3PlpInfos", "([Landroid/media/tv/tuner/frontend/Atsc3PlpInfo;)V"),
+                    array);
+            break;
+        }
+    }
     return Void();
 }
 
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index cfe99b3..d899bbd 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -109,7 +109,6 @@
     FrontendCallback(jweak tunerObj, FrontendId id);
 
     virtual Return<void> onEvent(FrontendEventType frontendEventType);
-    virtual Return<void> onDiseqcMessage(const hidl_vec<uint8_t>& diseqcMessage);
     virtual Return<void> onScanMessage(
             FrontendScanMessageType type, const FrontendScanMessage& message);
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 8e4a982..5ff88ac 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -33,6 +33,7 @@
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
@@ -83,10 +84,179 @@
      * Get current device that played media.
      * @return MediaDevice
      */
-    public MediaDevice getCurrentConnectedDevice() {
+    MediaDevice getCurrentConnectedDevice() {
         return mCurrentConnectedDevice;
     }
 
+    /**
+     * Transfer MediaDevice for media without package name.
+     */
+    boolean connectDeviceWithoutPackageName(MediaDevice device) {
+        boolean isConnected = false;
+        final List<RoutingSessionInfo> infos = mRouterManager.getActiveSessions();
+        if (infos.size() > 0) {
+            final RoutingSessionInfo info = infos.get(0);
+            final MediaRouter2Manager.RoutingController controller =
+                    mRouterManager.getControllerForSession(info);
+
+            controller.transferToRoute(device.mRouteInfo);
+            isConnected = true;
+        }
+        return isConnected;
+    }
+
+    /**
+     * Add a MediaDevice to let it play current media.
+     *
+     * @param device MediaDevice
+     * @return If add device successful return {@code true}, otherwise return {@code false}
+     */
+    boolean addDeviceToPlayMedia(MediaDevice device) {
+        if (TextUtils.isEmpty(mPackageName)) {
+            Log.w(TAG, "addDeviceToPlayMedia() package name is null or empty!");
+            return false;
+        }
+
+        final RoutingSessionInfo info = getRoutingSessionInfo();
+        if (info != null && info.getSelectableRoutes().contains(device.mRouteInfo.getId())) {
+            mRouterManager.getControllerForSession(info).selectRoute(device.mRouteInfo);
+            return true;
+        }
+
+        Log.w(TAG, "addDeviceToPlayMedia() Ignoring selecting a non-selectable device : "
+                + device.getName());
+
+        return false;
+    }
+
+    private RoutingSessionInfo getRoutingSessionInfo() {
+        for (RoutingSessionInfo info : mRouterManager.getRoutingSessions(mPackageName)) {
+            if (TextUtils.equals(info.getClientPackageName(), mPackageName)) {
+                return info;
+            }
+        }
+
+        Log.w(TAG, "RoutingSessionInfo() cannot found match packagename : " + mPackageName);
+        return null;
+    }
+
+    /**
+     * Remove a {@code device} from current media.
+     *
+     * @param device MediaDevice
+     * @return If device stop successful return {@code true}, otherwise return {@code false}
+     */
+    boolean removeDeviceFromPlayMedia(MediaDevice device) {
+        if (TextUtils.isEmpty(mPackageName)) {
+            Log.w(TAG, "removeDeviceFromMedia() package name is null or empty!");
+            return false;
+        }
+
+        final RoutingSessionInfo info = getRoutingSessionInfo();
+        if (info != null && info.getSelectedRoutes().contains(device.mRouteInfo.getId())) {
+            mRouterManager.getControllerForSession(info).deselectRoute(device.mRouteInfo);
+            return true;
+        }
+
+        Log.w(TAG, "removeDeviceFromMedia() Ignoring deselecting a non-deselectable device : "
+                + device.getName());
+
+        return false;
+    }
+
+    /**
+     * Get the MediaDevice list that can be added to current media.
+     *
+     * @return list of MediaDevice
+     */
+    List<MediaDevice> getSelectableMediaDevice() {
+        final List<MediaDevice> deviceList = new ArrayList<>();
+        if (TextUtils.isEmpty(mPackageName)) {
+            Log.w(TAG, "getSelectableMediaDevice() package name is null or empty!");
+            return deviceList;
+        }
+
+        final RoutingSessionInfo info = getRoutingSessionInfo();
+        if (info != null) {
+            for (MediaRoute2Info route : mRouterManager.getControllerForSession(info)
+                    .getSelectableRoutes()) {
+                deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
+                        route, mPackageName));
+            }
+            return deviceList;
+        }
+
+        Log.w(TAG, "getSelectableMediaDevice() cannot found selectable MediaDevice from : "
+                + mPackageName);
+
+        return deviceList;
+    }
+
+    /**
+     * Adjust the volume of {@link android.media.RoutingSessionInfo}.
+     *
+     * @param volume the value of volume
+     */
+    void adjustSessionVolume(int volume) {
+        if (TextUtils.isEmpty(mPackageName)) {
+            Log.w(TAG, "adjustSessionVolume() package name is null or empty!");
+            return;
+        }
+
+        final RoutingSessionInfo info = getRoutingSessionInfo();
+        if (info != null) {
+            Log.d(TAG, "adjustSessionVolume() adjust volume : " + volume + ", with : "
+                    + mPackageName);
+            mRouterManager.setSessionVolume(info, volume);
+            return;
+        }
+
+        Log.w(TAG, "adjustSessionVolume() can't found corresponding RoutingSession with : "
+                + mPackageName);
+    }
+
+    /**
+     * Gets the maximum volume of the {@link android.media.RoutingSessionInfo}.
+     *
+     * @return  maximum volume of the session, and return -1 if not found.
+     */
+    public int getSessionVolumeMax() {
+        if (TextUtils.isEmpty(mPackageName)) {
+            Log.w(TAG, "getSessionVolumeMax() package name is null or empty!");
+            return -1;
+        }
+
+        final RoutingSessionInfo info = getRoutingSessionInfo();
+        if (info != null) {
+            return info.getVolumeMax();
+        }
+
+        Log.w(TAG, "getSessionVolumeMax() can't found corresponding RoutingSession with : "
+                + mPackageName);
+        return -1;
+    }
+
+    /**
+     * Gets the current volume of the {@link android.media.RoutingSessionInfo}.
+     *
+     * @return current volume of the session, and return -1 if not found.
+     */
+    public int getSessionVolume() {
+        if (TextUtils.isEmpty(mPackageName)) {
+            Log.w(TAG, "getSessionVolume() package name is null or empty!");
+            return -1;
+        }
+
+        final RoutingSessionInfo info = getRoutingSessionInfo();
+        if (info != null) {
+            return info.getVolume();
+        }
+
+        Log.w(TAG, "getSessionVolume() can't found corresponding RoutingSession with : "
+                + mPackageName);
+        return -1;
+    }
+
     private void refreshDevices() {
         mMediaDevices.clear();
         mCurrentConnectedDevice = null;
@@ -150,23 +320,6 @@
         }
     }
 
-    /**
-     * Transfer MediaDevice for media without package name.
-     */
-    public boolean connectDeviceWithoutPackageName(MediaDevice device) {
-        boolean isConnected = false;
-        final List<RoutingSessionInfo> infos = mRouterManager.getActiveSessions();
-        if (infos.size() > 0) {
-            final RoutingSessionInfo info = infos.get(0);
-            final MediaRouter2Manager.RoutingController controller =
-                    mRouterManager.getControllerForSession(info);
-
-            controller.transferToRoute(device.mRouteInfo);
-            isConnected = true;
-        }
-        return isConnected;
-    }
-
     class RouterManagerCallback extends MediaRouter2Manager.Callback {
 
         @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index f8db70a..fc373a5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -252,6 +252,62 @@
         return devices;
     }
 
+    /**
+     * Add a MediaDevice to let it play current media.
+     *
+     * @param device MediaDevice
+     * @return If add device successful return {@code true}, otherwise return {@code false}
+     */
+    public boolean addDeviceToPlayMedia(MediaDevice device) {
+        return mInfoMediaManager.addDeviceToPlayMedia(device);
+    }
+
+    /**
+     * Remove a {@code device} from current media.
+     *
+     * @param device MediaDevice
+     * @return If device stop successful return {@code true}, otherwise return {@code false}
+     */
+    public boolean removeDeviceFromPlayMedia(MediaDevice device) {
+        return mInfoMediaManager.removeDeviceFromPlayMedia(device);
+    }
+
+    /**
+     * Get the MediaDevice list that can be added to current media.
+     *
+     * @return list of MediaDevice
+     */
+    public List<MediaDevice> getSelectableMediaDevice() {
+        return mInfoMediaManager.getSelectableMediaDevice();
+    }
+
+    /**
+     * Adjust the volume of session.
+     *
+     * @param volume the value of volume
+     */
+    public void adjustSessionVolume(int volume) {
+        mInfoMediaManager.adjustSessionVolume(volume);
+    }
+
+    /**
+     * Gets the maximum volume of the {@link android.media.RoutingSessionInfo}.
+     *
+     * @return  maximum volume of the session, and return -1 if not found.
+     */
+    public int getSessionVolumeMax() {
+        return mInfoMediaManager.getSessionVolumeMax();
+    }
+
+    /**
+     * Gets the current volume of the {@link android.media.RoutingSessionInfo}.
+     *
+     * @return current volume of the session, and return -1 if not found.
+     */
+    public int getSessionVolume() {
+        return mInfoMediaManager.getSessionVolume();
+    }
+
     private MediaDevice updateCurrentConnectedDevice() {
         MediaDevice phoneMediaDevice = null;
         for (MediaDevice device : mMediaDevices) {
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 8b815bf..9668629 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
@@ -28,6 +28,7 @@
 import android.media.RoutingSessionInfo;
 
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.testutils.shadow.ShadowRouter2Manager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -36,15 +37,18 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
 
 import java.util.ArrayList;
 import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowRouter2Manager.class})
 public class InfoMediaManagerTest {
 
     private static final String TEST_PACKAGE_NAME = "com.test.packagename";
     private static final String TEST_ID = "test_id";
+    private static final String TEST_NAME = "test_name";
 
     @Mock
     private MediaRouter2Manager mRouterManager;
@@ -53,6 +57,7 @@
 
     private InfoMediaManager mInfoMediaManager;
     private Context mContext;
+    private ShadowRouter2Manager mShadowRouter2Manager;
 
     @Before
     public void setUp() {
@@ -61,22 +66,8 @@
 
         mInfoMediaManager =
                 new InfoMediaManager(mContext, TEST_PACKAGE_NAME, null, mLocalBluetoothManager);
-        mInfoMediaManager.mRouterManager = mRouterManager;
-    }
-
-    @Test
-    public void stopScan_shouldRemoveCallback() {
-        mInfoMediaManager.stopScan();
-
-        verify(mRouterManager).unregisterCallback(mInfoMediaManager.mMediaRouterCallback);
-    }
-
-    @Test
-    public void startScan_shouldAddCallback() {
-        mInfoMediaManager.startScan();
-
-        verify(mRouterManager).registerCallback(mInfoMediaManager.mExecutor,
-                mInfoMediaManager.mMediaRouterCallback);
+        mShadowRouter2Manager = ShadowRouter2Manager.getShadow();
+        mInfoMediaManager.mRouterManager = MediaRouter2Manager.getInstance(mContext);
     }
 
     @Test
@@ -87,7 +78,7 @@
 
         final List<MediaRoute2Info> routes = new ArrayList<>();
         routes.add(info);
-        when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(routes);
+        mShadowRouter2Manager.setAvailableRoutes(routes);
 
         final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
         assertThat(mediaDevice).isNull();
@@ -108,7 +99,7 @@
 
         final List<MediaRoute2Info> routes = new ArrayList<>();
         routes.add(info);
-        when(mRouterManager.getAllRoutes()).thenReturn(routes);
+        mShadowRouter2Manager.setAllRoutes(routes);
 
         final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
         assertThat(mediaDevice).isNull();
@@ -129,7 +120,7 @@
 
         final List<MediaRoute2Info> routes = new ArrayList<>();
         routes.add(info);
-        when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(routes);
+        mShadowRouter2Manager.setAvailableRoutes(routes);
 
         final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
         assertThat(mediaDevice).isNull();
@@ -157,7 +148,7 @@
 
         final List<MediaRoute2Info> routes = new ArrayList<>();
         routes.add(info);
-        when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(routes);
+        mShadowRouter2Manager.setAvailableRoutes(routes);
 
         final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
         assertThat(mediaDevice).isNull();
@@ -178,7 +169,7 @@
 
         final List<MediaRoute2Info> routes = new ArrayList<>();
         routes.add(info);
-        when(mRouterManager.getAllRoutes()).thenReturn(routes);
+        mShadowRouter2Manager.setAllRoutes(routes);
 
         final MediaDevice mediaDevice = mInfoMediaManager.findMediaDevice(TEST_ID);
         assertThat(mediaDevice).isNull();
@@ -194,12 +185,12 @@
     @Test
     public void connectDeviceWithoutPackageName_noSession_returnFalse() {
         final MediaRoute2Info info = mock(MediaRoute2Info.class);
-        final MediaDevice device = new InfoMediaDevice(mContext, mRouterManager, info,
-                TEST_PACKAGE_NAME);
+        final MediaDevice device = new InfoMediaDevice(mContext, mInfoMediaManager.mRouterManager,
+                info, TEST_PACKAGE_NAME);
 
         final List<RoutingSessionInfo> infos = new ArrayList<>();
 
-        when(mRouterManager.getActiveSessions()).thenReturn(infos);
+        mShadowRouter2Manager.setActiveSessions(infos);
 
         assertThat(mInfoMediaManager.connectDeviceWithoutPackageName(device)).isFalse();
     }
@@ -239,10 +230,212 @@
         assertThat(mediaDevice).isNull();
 
         mInfoMediaManager.mPackageName = "";
-        mInfoMediaManager.mMediaRouterCallback.onRoutesChanged(routes);
+        mInfoMediaManager.mMediaRouterCallback.onRoutesRemoved(routes);
 
         final MediaDevice infoDevice = mInfoMediaManager.mMediaDevices.get(0);
         assertThat(infoDevice.getId()).isEqualTo(TEST_ID);
         assertThat(mInfoMediaManager.mMediaDevices).hasSize(routes.size());
     }
+
+    @Test
+    public void addDeviceToPlayMedia_packageNameIsNull_returnFalse() {
+        mInfoMediaManager.mPackageName = null;
+        final MediaDevice device = mock(MediaDevice.class);
+
+        assertThat(mInfoMediaManager.addDeviceToPlayMedia(device)).isFalse();
+    }
+
+    @Test
+    public void addDeviceToPlayMedia_containSelectableRoutes_returnTrue() {
+        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+        final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
+        routingSessionInfos.add(info);
+
+        final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
+        final MediaDevice device =
+                new InfoMediaDevice(mContext, mInfoMediaManager.mRouterManager, route2Info,
+                        TEST_PACKAGE_NAME);
+
+        final List<String> list = new ArrayList<>();
+        list.add(TEST_ID);
+
+        mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
+        when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(info.getSelectableRoutes()).thenReturn(list);
+        when(route2Info.getId()).thenReturn(TEST_ID);
+        when(route2Info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        assertThat(mInfoMediaManager.addDeviceToPlayMedia(device)).isTrue();
+    }
+
+    @Test
+    public void addDeviceToPlayMedia_notContainSelectableRoutes_returnFalse() {
+        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+        final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
+        routingSessionInfos.add(info);
+
+        final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
+        final MediaDevice device =
+                new InfoMediaDevice(mContext, mInfoMediaManager.mRouterManager, route2Info,
+                        TEST_PACKAGE_NAME);
+
+        final List<String> list = new ArrayList<>();
+        list.add("fake_id");
+
+        mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
+        when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(info.getSelectableRoutes()).thenReturn(list);
+        when(route2Info.getId()).thenReturn(TEST_ID);
+        when(route2Info.getName()).thenReturn(TEST_NAME);
+        when(route2Info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        assertThat(mInfoMediaManager.addDeviceToPlayMedia(device)).isFalse();
+    }
+
+    @Test
+    public void removeDeviceFromMedia_packageNameIsNull_returnFalse() {
+        mInfoMediaManager.mPackageName = null;
+        final MediaDevice device = mock(MediaDevice.class);
+
+        assertThat(mInfoMediaManager.removeDeviceFromPlayMedia(device)).isFalse();
+    }
+
+    @Test
+    public void removeDeviceFromMedia_containSelectedRoutes_returnTrue() {
+        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+        final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
+        routingSessionInfos.add(info);
+
+        final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
+        final MediaDevice device =
+                new InfoMediaDevice(mContext, mInfoMediaManager.mRouterManager, route2Info,
+                        TEST_PACKAGE_NAME);
+
+        final List<String> list = new ArrayList<>();
+        list.add(TEST_ID);
+
+        mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
+        when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(info.getSelectedRoutes()).thenReturn(list);
+        when(route2Info.getId()).thenReturn(TEST_ID);
+        when(route2Info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        assertThat(mInfoMediaManager.removeDeviceFromPlayMedia(device)).isTrue();
+    }
+
+    @Test
+    public void removeDeviceFromMedia_notContainSelectedRoutes_returnFalse() {
+        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+        final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
+        routingSessionInfos.add(info);
+
+        final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
+        final MediaDevice device =
+                new InfoMediaDevice(mContext, mInfoMediaManager.mRouterManager, route2Info,
+                        TEST_PACKAGE_NAME);
+
+        final List<String> list = new ArrayList<>();
+        list.add("fake_id");
+
+        mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
+        when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(info.getSelectedRoutes()).thenReturn(list);
+        when(route2Info.getId()).thenReturn(TEST_ID);
+        when(route2Info.getName()).thenReturn(TEST_NAME);
+        when(route2Info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        assertThat(mInfoMediaManager.removeDeviceFromPlayMedia(device)).isFalse();
+    }
+
+    @Test
+    public void getSelectableMediaDevice_packageNameIsNull_returnFalse() {
+        mInfoMediaManager.mPackageName = null;
+
+        assertThat(mInfoMediaManager.getSelectableMediaDevice()).isEmpty();
+    }
+
+    @Test
+    public void getSelectableMediaDevice_notContainPackageName_returnEmpty() {
+        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+        final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
+        routingSessionInfos.add(info);
+
+        mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
+        when(info.getClientPackageName()).thenReturn("com.fake.packagename");
+
+        assertThat(mInfoMediaManager.getSelectableMediaDevice()).isEmpty();
+    }
+
+    @Test
+    public void adjustSessionVolume_packageNameIsNull_noCrash() {
+        mInfoMediaManager.mPackageName = null;
+
+        mInfoMediaManager.adjustSessionVolume(10);
+    }
+
+    @Test
+    public void getSessionVolumeMax_packageNameIsNull_returnNotFound() {
+        mInfoMediaManager.mPackageName = null;
+
+        assertThat(mInfoMediaManager.getSessionVolumeMax()).isEqualTo(-1);
+    }
+
+    @Test
+    public void getSessionVolumeMax_containPackageName_returnMaxVolume() {
+        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+        final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
+        routingSessionInfos.add(info);
+
+        mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
+        when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        mInfoMediaManager.getSessionVolumeMax();
+
+        verify(info).getVolumeMax();
+    }
+
+    @Test
+    public void getSessionVolumeMax_notContainPackageName_returnNotFound() {
+        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+        final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
+        routingSessionInfos.add(info);
+
+        mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
+        when(info.getClientPackageName()).thenReturn("com.fake.packagename");
+
+        assertThat(mInfoMediaManager.getSessionVolumeMax()).isEqualTo(-1);
+    }
+
+    @Test
+    public void getSessionVolume_packageNameIsNull_returnNotFound() {
+        mInfoMediaManager.mPackageName = null;
+
+        assertThat(mInfoMediaManager.getSessionVolume()).isEqualTo(-1);
+    }
+
+    @Test
+    public void getSessionVolume_containPackageName_returnMaxVolume() {
+        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+        final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
+        routingSessionInfos.add(info);
+
+        mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
+        when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        mInfoMediaManager.getSessionVolume();
+
+        verify(info).getVolume();
+    }
+
+    @Test
+    public void getSessionVolume_notContainPackageName_returnNotFound() {
+        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+        final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
+        routingSessionInfos.add(info);
+
+        mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
+        when(info.getClientPackageName()).thenReturn("com.fake.packagename");
+
+        assertThat(mInfoMediaManager.getSessionVolume()).isEqualTo(-1);
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
new file mode 100644
index 0000000..db0cb06
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.testutils.shadow;
+
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2Manager;
+import android.media.RoutingSessionInfo;
+
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.List;
+
+@Implements(MediaRouter2Manager.class)
+public class ShadowRouter2Manager {
+
+    private List<MediaRoute2Info> mAvailableRoutes;
+    private List<MediaRoute2Info> mAllRoutes;
+    private List<RoutingSessionInfo> mActiveSessions;
+    private List<RoutingSessionInfo> mRoutingSessions;
+
+    @Implementation
+    protected List<MediaRoute2Info> getAvailableRoutes(String packageName) {
+        return mAvailableRoutes;
+    }
+
+    public void setAvailableRoutes(List<MediaRoute2Info> infos) {
+        mAvailableRoutes = infos;
+    }
+
+    @Implementation
+    protected List<MediaRoute2Info> getAllRoutes() {
+        return mAllRoutes;
+    }
+
+    public void setAllRoutes(List<MediaRoute2Info> infos) {
+        mAllRoutes = infos;
+    }
+
+    @Implementation
+    protected List<RoutingSessionInfo> getActiveSessions() {
+        return mActiveSessions;
+    }
+
+    public void setActiveSessions(List<RoutingSessionInfo> infos) {
+        mActiveSessions = infos;
+    }
+
+    @Implementation
+    protected List<RoutingSessionInfo> getRoutingSessions(String packageName) {
+        return mRoutingSessions;
+    }
+
+    public void setRoutingSessions(List<RoutingSessionInfo> infos) {
+        mRoutingSessions = infos;
+    }
+
+    public static ShadowRouter2Manager getShadow() {
+        return (ShadowRouter2Manager) Shadow.extract(
+                MediaRouter2Manager.getInstance(RuntimeEnvironment.application));
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index e77d8ae..2a2ab4b 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1813,12 +1813,12 @@
      */
     private int deliverToCurrentTopIfNeeded(ActivityStack topStack) {
         final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
-        final boolean dontStart = top != null && mStartActivity.resultTo == null
+        final boolean dontStart = top != null
                 && top.mActivityComponent.equals(mStartActivity.mActivityComponent)
                 && top.mUserId == mStartActivity.mUserId
                 && top.attachedToProcess()
                 && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
-                || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK))
+                    || isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK))
                 // This allows home activity to automatically launch on secondary display when
                 // display added, if home was the top activity on default display, instead of
                 // sending new intent to the home activity on default display.
@@ -2049,8 +2049,6 @@
                 && !isLaunchModeOneOf(LAUNCH_SINGLE_TASK, LAUNCH_SINGLE_INSTANCE)
                 && (mLaunchFlags & FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
 
-        sendNewTaskResultRequestIfNeeded();
-
         if ((mLaunchFlags & FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && r.resultTo == null) {
             mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
         }
@@ -2232,6 +2230,8 @@
                 mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
             }
         }
+
+        sendNewTaskResultRequestIfNeeded();
     }
 
     private void computeSourceStack() {