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