1 /* 2 * Copyright 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.androidx.mediarouting.activities.systemrouting; 18 19 import static android.Manifest.permission.BLUETOOTH_CONNECT; 20 import static android.Manifest.permission.BLUETOOTH_SCAN; 21 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 22 23 import android.content.Context; 24 import android.content.Intent; 25 import android.os.Build; 26 import android.os.Bundle; 27 import android.widget.Toast; 28 29 import androidx.appcompat.app.AppCompatActivity; 30 import androidx.core.app.ActivityCompat; 31 import androidx.core.content.ContextCompat; 32 import androidx.recyclerview.widget.LinearLayoutManager; 33 import androidx.recyclerview.widget.RecyclerView; 34 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; 35 36 import com.example.androidx.mediarouting.R; 37 import com.example.androidx.mediarouting.activities.systemrouting.source.AndroidXMediaRouterSystemRoutesSource; 38 import com.example.androidx.mediarouting.activities.systemrouting.source.AudioManagerSystemRoutesSource; 39 import com.example.androidx.mediarouting.activities.systemrouting.source.BluetoothManagerSystemRoutesSource; 40 import com.example.androidx.mediarouting.activities.systemrouting.source.MediaRouter2SystemRoutesSource; 41 import com.example.androidx.mediarouting.activities.systemrouting.source.MediaRouterSystemRoutesSource; 42 import com.example.androidx.mediarouting.activities.systemrouting.source.SystemRoutesSource; 43 44 import org.jspecify.annotations.NonNull; 45 import org.jspecify.annotations.Nullable; 46 47 import java.util.ArrayList; 48 import java.util.HashMap; 49 import java.util.List; 50 import java.util.Map; 51 52 /** 53 * Shows available system routes gathered from different sources. 54 */ 55 public final class SystemRoutingActivity extends AppCompatActivity { 56 57 private static final int REQUEST_CODE_BLUETOOTH_CONNECT = 4199; 58 59 private final @NonNull SystemRoutesAdapter mSystemRoutesAdapter = 60 new SystemRoutesAdapter(this::onRouteItemClicked); 61 62 private final @NonNull Map<String, SystemRoutesSource> mSystemRoutesSources = new HashMap<>(); 63 private @NonNull SwipeRefreshLayout mSwipeRefreshLayout; 64 65 /** 66 * Creates and launches an intent to start current activity. 67 */ launch(@onNull Context context)68 public static void launch(@NonNull Context context) { 69 Intent intent = new Intent(context, SystemRoutingActivity.class); 70 context.startActivity(intent); 71 } 72 73 @Override onCreate(@ullable Bundle savedInstanceState)74 protected void onCreate(@Nullable Bundle savedInstanceState) { 75 super.onCreate(savedInstanceState); 76 setContentView(R.layout.activity_system_routing); 77 78 RecyclerView recyclerView = findViewById(R.id.recycler_view); 79 mSwipeRefreshLayout = findViewById(R.id.pull_to_refresh_layout); 80 81 recyclerView.setAdapter(mSystemRoutesAdapter); 82 recyclerView.setLayoutManager(new LinearLayoutManager(this)); 83 84 mSwipeRefreshLayout.setOnRefreshListener(this::refreshSystemRoutesList); 85 86 if (hasBluetoothPermission()) { 87 initializeSystemRoutesSources(); 88 refreshSystemRoutesList(); 89 } else { 90 requestBluetoothPermission(); 91 } 92 } 93 94 @Override onDestroy()95 protected void onDestroy() { 96 for (SystemRoutesSource source : mSystemRoutesSources.values()) { 97 source.stop(); 98 } 99 100 super.onDestroy(); 101 } 102 103 @Override onRequestPermissionsResult(int requestCode, String @NonNull [] permissions, int @NonNull [] grantResults)104 public void onRequestPermissionsResult(int requestCode, String @NonNull [] permissions, 105 int @NonNull [] grantResults) { 106 super.onRequestPermissionsResult(requestCode, permissions, grantResults); 107 108 if (requestCode == REQUEST_CODE_BLUETOOTH_CONNECT 109 && grantResults.length > 0) { 110 if (grantResults[0] == PERMISSION_GRANTED) { 111 onBluetoothPermissionGranted(); 112 } else { 113 onBluetoothPermissionDenied(); 114 } 115 } 116 } 117 refreshSystemRoutesList()118 private void refreshSystemRoutesList() { 119 List<SystemRoutesAdapterItem> systemRoutesSourceItems = new ArrayList<>(); 120 for (SystemRoutesSource source : mSystemRoutesSources.values()) { 121 systemRoutesSourceItems.add(source.getSourceItem()); 122 systemRoutesSourceItems.addAll(source.fetchSourceRouteItems()); 123 } 124 mSystemRoutesAdapter.setItems(systemRoutesSourceItems); 125 mSwipeRefreshLayout.setRefreshing(false); 126 } 127 onRouteItemClicked(SystemRouteItem item)128 private void onRouteItemClicked(SystemRouteItem item) { 129 SystemRoutesSource systemRoutesSource = mSystemRoutesSources.get(item.mSourceId); 130 if (systemRoutesSource == null) { 131 throw new IllegalStateException("Couldn't find source with id: " + item.mSourceId); 132 } 133 if (!systemRoutesSource.select(item)) { 134 Toast.makeText( 135 /* context= */ this, 136 "Something went wrong with route selection", 137 Toast.LENGTH_LONG) 138 .show(); 139 } 140 } 141 hasBluetoothPermission()142 private boolean hasBluetoothPermission() { 143 return ContextCompat.checkSelfPermission(/* context= */ this, BLUETOOTH_CONNECT) 144 == PERMISSION_GRANTED 145 && ContextCompat.checkSelfPermission(/* context= */ this, BLUETOOTH_SCAN) 146 == PERMISSION_GRANTED; 147 } 148 requestBluetoothPermission()149 private void requestBluetoothPermission() { 150 ActivityCompat.requestPermissions(this, 151 new String[]{BLUETOOTH_CONNECT, BLUETOOTH_SCAN}, REQUEST_CODE_BLUETOOTH_CONNECT); 152 } 153 onBluetoothPermissionGranted()154 private void onBluetoothPermissionGranted() { 155 initializeSystemRoutesSources(); 156 refreshSystemRoutesList(); 157 } 158 onBluetoothPermissionDenied()159 private void onBluetoothPermissionDenied() { 160 Toast.makeText(this, getString(R.string.system_routing_activity_bluetooth_denied), 161 Toast.LENGTH_LONG).show(); 162 } 163 initializeSystemRoutesSources()164 private void initializeSystemRoutesSources() { 165 ArrayList<SystemRoutesSource> sources = new ArrayList<>(); 166 sources.add(MediaRouterSystemRoutesSource.create(/* context= */ this)); 167 168 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 169 sources.add(MediaRouter2SystemRoutesSource.create(/* context= */ this)); 170 } 171 172 sources.add(AndroidXMediaRouterSystemRoutesSource.create(/* context= */ this)); 173 174 if (hasBluetoothPermission()) { 175 sources.add(BluetoothManagerSystemRoutesSource.create(/* context= */ this)); 176 } 177 178 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 179 sources.add(AudioManagerSystemRoutesSource.create(/* context= */ this)); 180 } 181 182 mSystemRoutesSources.clear(); 183 for (SystemRoutesSource source : sources) { 184 source.setOnRoutesChangedListener(this::refreshSystemRoutesList); 185 mSystemRoutesSources.put(source.getSourceId(), source); 186 source.start(); 187 } 188 } 189 } 190