1 /* 2 * Copyright 2022 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; 18 19 import android.app.AlertDialog; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.ServiceConnection; 24 import android.content.pm.PackageManager; 25 import android.os.Bundle; 26 import android.os.IBinder; 27 import android.view.View; 28 import android.widget.AdapterView; 29 import android.widget.ArrayAdapter; 30 import android.widget.Button; 31 import android.widget.Spinner; 32 import android.widget.Switch; 33 import android.widget.Toast; 34 35 import androidx.appcompat.app.AppCompatActivity; 36 import androidx.appcompat.widget.AppCompatButton; 37 import androidx.mediarouter.media.MediaRouter; 38 import androidx.mediarouter.media.MediaRouterParams; 39 import androidx.recyclerview.widget.LinearLayoutManager; 40 import androidx.recyclerview.widget.RecyclerView; 41 42 import com.example.androidx.mediarouting.R; 43 import com.example.androidx.mediarouting.RoutesManager; 44 import com.example.androidx.mediarouting.activities.systemrouting.SystemRoutingActivity; 45 import com.example.androidx.mediarouting.services.SampleDynamicGroupMediaRouteProviderService; 46 import com.example.androidx.mediarouting.services.SampleMediaRouteProviderService; 47 import com.example.androidx.mediarouting.services.WrapperMediaRouteProviderService; 48 import com.example.androidx.mediarouting.ui.RoutesAdapter; 49 import com.google.android.material.floatingactionbutton.FloatingActionButton; 50 51 import org.jspecify.annotations.NonNull; 52 import org.jspecify.annotations.Nullable; 53 54 /** 55 * Allows the user to control dialog types, enabling or disabling Dynamic Groups, enabling or 56 * disabling transfer to local and customize the routes exposed by {@link 57 * SampleDynamicGroupMediaRouteProviderService}. 58 */ 59 public final class SettingsActivity extends AppCompatActivity { 60 private final ProviderServiceConnection mConnection = new ProviderServiceConnection(); 61 private PackageManager mPackageManager; 62 private MediaRouter mMediaRouter; 63 private RoutesManager mRoutesManager; 64 private RoutesAdapter mRoutesAdapter; 65 private Toast mMediaTransferRestrictedToSelfProvidersToast; 66 67 /** Returns whether the service corresponding to the provided {@link Class} is enabled. */ isServiceEnabled( @onNull Context context, @NonNull Class<?> serviceClass)68 public static boolean isServiceEnabled( 69 @NonNull Context context, @NonNull Class<?> serviceClass) { 70 ComponentName serviceComponentName = new ComponentName(context, serviceClass); 71 return context.getPackageManager().getComponentEnabledSetting(serviceComponentName) 72 != PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 73 } 74 75 @Override onCreate(@ullable Bundle savedInstanceState)76 protected void onCreate(@Nullable Bundle savedInstanceState) { 77 super.onCreate(savedInstanceState); 78 setContentView(R.layout.activity_settings); 79 mMediaTransferRestrictedToSelfProvidersToast = 80 Toast.makeText( 81 /* context= */ this, 82 "Wrapper provider enabled. Output switcher for Cast won't work.", 83 Toast.LENGTH_LONG); 84 85 mPackageManager = getPackageManager(); 86 mMediaRouter = MediaRouter.getInstance(this); 87 mRoutesManager = RoutesManager.getInstance(getApplicationContext()); 88 89 setUpViews(); 90 91 RoutesAdapter.RouteItemListener routeItemListener = new RoutesAdapter.RouteItemListener() { 92 @Override 93 public void onRouteEditClick(@NonNull String routeId) { 94 AddEditRouteActivity.launchActivity( 95 /* context= */ SettingsActivity.this, /* routeId */ routeId); 96 } 97 98 @Override 99 public void onRouteDeleteClick(@NonNull String routeId) { 100 new AlertDialog.Builder(SettingsActivity.this) 101 .setTitle(R.string.delete_route_alert_dialog_title) 102 .setMessage(R.string.delete_route_alert_dialog_message) 103 .setPositiveButton(android.R.string.cancel, null) 104 .setNegativeButton( 105 android.R.string.ok, 106 (dialogInterface, i) -> { 107 mRoutesManager.deleteRouteWithId(routeId); 108 SampleDynamicGroupMediaRouteProviderService providerService = 109 mConnection.mService; 110 if (providerService != null) { 111 providerService.reloadRoutes(); 112 } 113 mRoutesAdapter.updateRoutes( 114 mRoutesManager.getRouteItems()); 115 }) 116 .show(); 117 } 118 }; 119 120 Button goToRouteListingPreferenceButton = 121 findViewById(R.id.go_to_route_listing_preference_button); 122 goToRouteListingPreferenceButton.setOnClickListener( 123 unusedView -> { 124 startActivity(new Intent(this, RouteListingPreferenceActivity.class)); 125 }); 126 127 RecyclerView routeList = findViewById(R.id.routes_recycler_view); 128 routeList.setLayoutManager(new LinearLayoutManager(/* context= */ this)); 129 mRoutesAdapter = new RoutesAdapter(mRoutesManager.getRouteItems(), routeItemListener); 130 routeList.setAdapter(mRoutesAdapter); 131 routeList.setHasFixedSize(true); 132 } 133 134 @Override onStart()135 protected void onStart() { 136 super.onStart(); 137 bindToDynamicProviderService(); 138 } 139 140 @Override onResume()141 protected void onResume() { 142 super.onResume(); 143 mRoutesAdapter.updateRoutes(mRoutesManager.getRouteItems()); 144 } 145 146 @Override onStop()147 protected void onStop() { 148 try { 149 unbindService(mConnection); 150 } catch (RuntimeException e) { 151 // This happens when the provider is disabled, but there's no way of preventing this 152 // completely so we just ignore the exception. 153 } 154 super.onStop(); 155 } 156 setUpViews()157 private void setUpViews() { 158 setUpDynamicGroupsEnabledSwitch(); 159 setUpTransferToLocalSwitch(); 160 setUpDynamicProviderEnabledSwitch(); 161 setUpSimpleProviderEnabledSwitch(); 162 setUpWrapperProviderEnabledSwitch(); 163 setUpDialogTypeDropDownList(); 164 setUpNewRouteButton(); 165 setupSystemRoutesButton(); 166 } 167 setUpDynamicGroupsEnabledSwitch()168 private void setUpDynamicGroupsEnabledSwitch() { 169 Switch dynamicRoutingEnabled = findViewById(R.id.dynamic_routing_switch); 170 dynamicRoutingEnabled.setChecked(mRoutesManager.isDynamicRoutingEnabled()); 171 dynamicRoutingEnabled.setOnCheckedChangeListener( 172 (compoundButton, enabled) -> { 173 mRoutesManager.setDynamicRoutingEnabled(enabled); 174 SampleDynamicGroupMediaRouteProviderService providerService = 175 mConnection.mService; 176 if (providerService != null) { 177 providerService.reloadDynamicRoutesEnabled(); 178 } 179 }); 180 } 181 setUpTransferToLocalSwitch()182 private void setUpTransferToLocalSwitch() { 183 Switch showThisPhoneSwitch = findViewById(R.id.show_this_phone_switch); 184 showThisPhoneSwitch.setChecked(mMediaRouter.getRouterParams().isTransferToLocalEnabled()); 185 showThisPhoneSwitch.setOnCheckedChangeListener( 186 (compoundButton, enabled) -> { 187 MediaRouterParams.Builder builder = 188 new MediaRouterParams.Builder(mMediaRouter.getRouterParams()); 189 builder.setTransferToLocalEnabled(enabled); 190 mMediaRouter.setRouterParams(builder.build()); 191 }); 192 } 193 setUpDynamicProviderEnabledSwitch()194 private void setUpDynamicProviderEnabledSwitch() { 195 Switch dynamicProviderEnabledSwitch = findViewById(R.id.enable_dynamic_provider_switch); 196 setUpServiceEnabledSwitch( 197 dynamicProviderEnabledSwitch, 198 SampleDynamicGroupMediaRouteProviderService.class, 199 /* onEnableRunnable= */ this::bindToDynamicProviderService, 200 /* onDisabledRunnable= */ () -> {}); // Will unbind automatically. 201 } 202 setUpSimpleProviderEnabledSwitch()203 private void setUpSimpleProviderEnabledSwitch() { 204 Switch simpleProviderEnabledSwitch = findViewById(R.id.enable_simple_provider_switch); 205 setUpServiceEnabledSwitch( 206 simpleProviderEnabledSwitch, 207 SampleMediaRouteProviderService.class, 208 /* onEnableRunnable= */ () -> {}, 209 /* onDisabledRunnable= */ () -> {}); 210 } 211 setUpWrapperProviderEnabledSwitch()212 private void setUpWrapperProviderEnabledSwitch() { 213 Switch wrapperProviderEnabledSwitch = findViewById(R.id.enable_wrapper_provider_switch); 214 setUpServiceEnabledSwitch( 215 wrapperProviderEnabledSwitch, 216 WrapperMediaRouteProviderService.class, 217 /* onEnableRunnable= */ () -> { 218 updateMediaTransferRestrictedToSelfProviders(true); 219 mMediaTransferRestrictedToSelfProvidersToast.show(); 220 }, 221 /* onDisabledRunnable= */ () -> 222 updateMediaTransferRestrictedToSelfProviders(false)); 223 } 224 setUpServiceEnabledSwitch( Switch aSwitch, Class<?> service, Runnable onEnabledRunnable, Runnable onDisabledRunnable)225 private void setUpServiceEnabledSwitch( 226 Switch aSwitch, 227 Class<?> service, 228 Runnable onEnabledRunnable, 229 Runnable onDisabledRunnable) { 230 ComponentName serviceComponentName = new ComponentName(/* context= */ this, service); 231 aSwitch.setChecked(isServiceEnabled(/* context= */ this, service)); 232 aSwitch.setOnCheckedChangeListener( 233 (compoundButton, enabled) -> { 234 mPackageManager.setComponentEnabledSetting( 235 serviceComponentName, 236 enabled 237 ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED 238 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 239 /* flags= */ PackageManager.DONT_KILL_APP); 240 if (enabled) { 241 onEnabledRunnable.run(); 242 } else { 243 onDisabledRunnable.run(); 244 } 245 }); 246 } 247 setUpDialogTypeDropDownList()248 private void setUpDialogTypeDropDownList() { 249 Spinner spinner = findViewById(R.id.dialog_spinner); 250 spinner.setOnItemSelectedListener( 251 new AdapterView.OnItemSelectedListener() { 252 @Override 253 public void onItemSelected( 254 AdapterView<?> adapterView, View view, int i, long l) { 255 mRoutesManager.setDialogType(RoutesManager.DialogType.values()[i]); 256 mRoutesManager.reloadDialogType(); 257 } 258 259 @Override 260 public void onNothingSelected(AdapterView<?> adapterView) {} 261 }); 262 263 // Create an ArrayAdapter using the string array and a default spinner layout 264 ArrayAdapter<CharSequence> adapter = 265 ArrayAdapter.createFromResource( 266 this, R.array.dialog_types_array, android.R.layout.simple_spinner_item); 267 // Specify the layout to use when the list of choices appears 268 adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 269 // Apply the adapter to the spinner 270 spinner.setAdapter(adapter); 271 if (mMediaRouter.getRouterParams().isOutputSwitcherEnabled()) { 272 spinner.setSelection(2); 273 } else if (mMediaRouter.getRouterParams().getDialogType() 274 == MediaRouterParams.DIALOG_TYPE_DYNAMIC_GROUP) { 275 spinner.setSelection(1); 276 } 277 } 278 setUpNewRouteButton()279 private void setUpNewRouteButton() { 280 FloatingActionButton newRouteButton = findViewById(R.id.new_route_button); 281 newRouteButton.setOnClickListener( 282 view -> { 283 AddEditRouteActivity.launchActivity( 284 /* context= */ SettingsActivity.this, /* routeId= */ null); 285 }); 286 } 287 setupSystemRoutesButton()288 private void setupSystemRoutesButton() { 289 AppCompatButton showSystemRoutesButton = findViewById(R.id.open_system_routes); 290 showSystemRoutesButton.setOnClickListener(v -> SystemRoutingActivity.launch(this)); 291 } 292 bindToDynamicProviderService()293 private void bindToDynamicProviderService() { 294 Intent intent = new Intent(this, SampleDynamicGroupMediaRouteProviderService.class); 295 intent.setAction(SampleDynamicGroupMediaRouteProviderService.ACTION_BIND_LOCAL); 296 bindService(intent, mConnection, Context.BIND_AUTO_CREATE); 297 } 298 updateMediaTransferRestrictedToSelfProviders(boolean value)299 private void updateMediaTransferRestrictedToSelfProviders(boolean value) { 300 MediaRouter mediaRouter = MediaRouter.getInstance(/* context= */ this); 301 MediaRouterParams routerParams = mediaRouter.getRouterParams(); 302 MediaRouterParams newRouterParams = 303 new MediaRouterParams.Builder(routerParams) 304 .setMediaTransferRestrictedToSelfProviders(value) 305 .build(); 306 mediaRouter.setRouterParams(newRouterParams); 307 } 308 309 private static class ProviderServiceConnection implements ServiceConnection { 310 311 private @Nullable SampleDynamicGroupMediaRouteProviderService mService; 312 313 @Override onServiceConnected(ComponentName className, IBinder service)314 public void onServiceConnected(ComponentName className, IBinder service) { 315 SampleDynamicGroupMediaRouteProviderService.LocalBinder binder = 316 (SampleDynamicGroupMediaRouteProviderService.LocalBinder) service; 317 mService = binder.getService(); 318 mService.reloadRoutes(); 319 } 320 321 @Override onServiceDisconnected(ComponentName arg0)322 public void onServiceDisconnected(ComponentName arg0) { 323 mService = null; 324 } 325 } 326 } 327