SystemOutputSwitcherDialogController.java
/*
* 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.os.Build;
import android.provider.Settings;
import androidx.annotation.NonNull;
import java.util.List;
/**
* Provides an utility method to show the system's output switcher dialog.
*
* @see <a href="https://developer.android.com/guide/topics/media/media-routing">Media Routing</a>
*/
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.S) {
result = showDialogForAndroidSAndAbove(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 showDialogForAndroidSAndAbove(@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);
}
}