public final class

SystemOutputSwitcherDialogController

extends java.lang.Object

 java.lang.Object

↳androidx.mediarouter.app.SystemOutputSwitcherDialogController

Gradle dependencies

compile group: 'androidx.mediarouter', name: 'mediarouter', version: '1.7.0'

  • groupId: androidx.mediarouter
  • artifactId: mediarouter
  • version: 1.7.0

Artifact androidx.mediarouter:mediarouter:1.7.0 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.mediarouter:mediarouter com.android.support:mediarouter-v7

Overview

Provides an utility method to show the system's output switcher dialog.

See the Media Routing guide for more information.

Summary

Methods
public static booleanshowDialog(Context context)

Shows the system output switcher dialog.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Methods

public static boolean showDialog(Context context)

Shows the system output switcher dialog.

The appearance and precise behaviour of the system output switcher dialog may vary across different devices, OS versions, and form factors, but the basic functionality stays the same.

See Output Switcher documentation for more details.

Parameters:

context: Android context

Returns:

true if the dialog was shown successfully and false otherwise

Source

/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.mediarouter.app;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.media.MediaRouter2;
import android.os.Build;
import android.provider.Settings;

import androidx.annotation.DoNotInline;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;

import java.util.List;

/**
 * Provides an utility method to show the system's output switcher dialog.
 *
 * <p>See <a href="https://developer.android.com/guide/topics/media/media-routing">the Media
 * Routing guide</a> for more information.
 */
public final class SystemOutputSwitcherDialogController {

    /** System ui service package name. */
    private static final String PACKAGE_NAME_SYSTEM_UI =
            "com.android.systemui";

    /** Output switcher dialog intent action in Android S. */
    private static final String OUTPUT_SWITCHER_INTENT_ACTION_ANDROID_S =
            "com.android.systemui.action.LAUNCH_MEDIA_OUTPUT_DIALOG";

    /** Output switcher dialog intent action in Android R. */
    private static final String OUTPUT_SWITCHER_INTENT_ACTION_ANDROID_R =
            "com.android.settings.panel.action.MEDIA_OUTPUT";

    /** A package name key for output switcher intent in Android S. */
    private static final String OUTPUT_SWITCHER_INTENT_KEY_PACKAGE_NAME_ANDROID_S =
            "package_name";

    /** A package name key for output switcher intent in Android R. */
    private static final String OUTPUT_SWITCHER_INTENT_KEY_PACKAGE_NAME_ANDROID_R =
            "com.android.settings.panel.extra.PACKAGE_NAME";

    private SystemOutputSwitcherDialogController() {
        // Private to prevent new instances.
    }

    /**
     * Shows the system output switcher dialog.
     *
     * <p>The appearance and precise behaviour of the system output switcher dialog
     * may vary across different devices, OS versions, and form factors,
     * but the basic functionality stays the same.
     *
     * <p>See
     * <a href="https://developer.android.com/guide/topics/media/media-routing#output-switcher">
     * Output Switcher documentation</a> for more details.
     *
     * @param context Android context
     * @return {@code true} if the dialog was shown successfully and {@code false} otherwise
     */
    public static boolean showDialog(@NonNull Context context) {
        boolean result = false;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
            result = showDialogForAndroidUAndAbove(context);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            result = showDialogForAndroidSAndT(context)
                    // The intent action and related string constants are changed in S,
                    // however they are not public API yet. Try opening the output switcher with the
                    // old constants for devices that have prior version of the constants.
                    || showDialogForAndroidR(context);
        } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
            result = showDialogForAndroidR(context);
        }

        if (result) {
            return true;
        }

        if (isRunningOnWear(context) && showBluetoothSettingsFragment(context)) {
            return true;
        }

        return false;
    }

    private static boolean showDialogForAndroidUAndAbove(@NonNull Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            MediaRouter2 mediaRouter2 = Api30Impl.getInstance(context);
            if (Build.VERSION.SDK_INT >= 34) {
                return Api34Impl.showSystemOutputSwitcher(mediaRouter2);
            }
        }

        return false;
    }

    private static boolean showDialogForAndroidSAndT(@NonNull Context context) {
        Intent intent = new Intent()
                .setAction(OUTPUT_SWITCHER_INTENT_ACTION_ANDROID_S)
                .setPackage(PACKAGE_NAME_SYSTEM_UI)
                .putExtra(OUTPUT_SWITCHER_INTENT_KEY_PACKAGE_NAME_ANDROID_S,
                        context.getPackageName());

        PackageManager packageManager = context.getPackageManager();
        List<ResolveInfo> resolveInfos = packageManager.queryBroadcastReceivers(intent,
                0 /* flags */);
        for (ResolveInfo resolveInfo : resolveInfos) {
            ActivityInfo activityInfo = resolveInfo.activityInfo;
            if (activityInfo == null || activityInfo.applicationInfo == null) {
                continue;
            }
            ApplicationInfo appInfo = activityInfo.applicationInfo;
            if (((ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)
                    & appInfo.flags) != 0) {
                context.sendBroadcast(intent);
                return true;
            }
        }

        return false;
    }

    private static boolean showDialogForAndroidR(@NonNull Context context) {
        Intent intent = new Intent()
                // Context can be either activity's or application's context,
                // therefore we need to start a new task.
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                .setAction(OUTPUT_SWITCHER_INTENT_ACTION_ANDROID_R)
                .putExtra(OUTPUT_SWITCHER_INTENT_KEY_PACKAGE_NAME_ANDROID_R,
                        context.getPackageName());

        PackageManager packageManager = context.getPackageManager();
        List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent,
                0 /* flags */);
        for (ResolveInfo resolveInfo : resolveInfos) {
            ActivityInfo activityInfo = resolveInfo.activityInfo;
            if (activityInfo == null || activityInfo.applicationInfo == null) {
                continue;
            }
            ApplicationInfo appInfo = activityInfo.applicationInfo;
            if (((ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)
                    & appInfo.flags) != 0) {
                context.startActivity(intent);
                return true;
            }
        }
        return false;
    }

    private static boolean showBluetoothSettingsFragment(@NonNull Context context) {
        // Wear OS specific intent. This is a default behaviour
        // for devices without the output switcher dialog.
        // See https://developer.android.com/training/wearables/overlays/audio.
        Intent intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
                .putExtra("EXTRA_CONNECTION_ONLY", true)
                .putExtra("android.bluetooth.devicepicker.extra.FILTER_TYPE", 1);

        PackageManager packageManager = context.getPackageManager();
        List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent,
                0 /* flags */);
        for (ResolveInfo resolveInfo : resolveInfos) {
            ActivityInfo activityInfo = resolveInfo.activityInfo;
            if (activityInfo == null || activityInfo.applicationInfo == null) {
                continue;
            }
            ApplicationInfo appInfo = activityInfo.applicationInfo;
            if (((ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)
                    & appInfo.flags) != 0) {
                context.startActivity(intent);
                return true;
            }
        }
        return false;
    }

    private static boolean isRunningOnWear(@NonNull Context context) {
        PackageManager packageManager = context.getPackageManager();
        return packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH);
    }

    @RequiresApi(30)
    static class Api30Impl {
        private Api30Impl() {
            // This class is not instantiable.
        }

        @DoNotInline
        static MediaRouter2 getInstance(Context context) {
            return MediaRouter2.getInstance(context);
        }
    }

    @RequiresApi(34)
    static class Api34Impl {
        private Api34Impl() {
            // This class is not instantiable.
        }

        @DoNotInline
        static boolean showSystemOutputSwitcher(MediaRouter2 mediaRouter2) {
            return mediaRouter2.showSystemOutputSwitcher();
        }
    }
}