1 /*
2  * Copyright (C) 2018 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 package com.android.settings.bluetooth;
17 
18 import static android.app.slice.Slice.EXTRA_TOGGLE_STATE;
19 
20 import android.annotation.ColorInt;
21 import android.app.PendingIntent;
22 import android.app.settings.SettingsEnums;
23 import android.bluetooth.BluetoothAdapter;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.net.Uri;
28 import android.provider.SettingsSlicesContract;
29 
30 import androidx.core.graphics.drawable.IconCompat;
31 import androidx.slice.Slice;
32 import androidx.slice.builders.ListBuilder;
33 import androidx.slice.builders.ListBuilder.RowBuilder;
34 import androidx.slice.builders.SliceAction;
35 
36 import com.android.settings.R;
37 import com.android.settings.SubSettings;
38 import com.android.settings.connecteddevice.BluetoothDashboardFragment;
39 import com.android.settings.slices.CustomSliceRegistry;
40 import com.android.settings.slices.SliceBroadcastReceiver;
41 import com.android.settings.slices.SliceBuilderUtils;
42 
43 /**
44  * Utility class to build a Bluetooth Slice, and handle all associated actions.
45  */
46 public class BluetoothSliceBuilder {
47 
48     private static final String TAG = "BluetoothSliceBuilder";
49 
50     /**
51      * Action notifying a change on the BluetoothSlice.
52      */
53     public static final String ACTION_BLUETOOTH_SLICE_CHANGED =
54             "com.android.settings.bluetooth.action.BLUETOOTH_MODE_CHANGED";
55 
56     public static final IntentFilter INTENT_FILTER = new IntentFilter();
57 
58     static {
59         INTENT_FILTER.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
60         INTENT_FILTER.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
61     }
62 
BluetoothSliceBuilder()63     private BluetoothSliceBuilder() {
64     }
65 
66     /**
67      * Return a Bluetooth Slice bound to {@link CustomSliceRegistry#BLUETOOTH_URI}.
68      * <p>
69      * Note that you should register a listener for {@link #INTENT_FILTER} to get changes for
70      * Bluetooth.
71      */
getSlice(Context context)72     public static Slice getSlice(Context context) {
73         final boolean isBluetoothEnabled = isBluetoothEnabled();
74         final CharSequence title = context.getText(R.string.bluetooth_settings);
75         final IconCompat icon = IconCompat.createWithResource(context,
76                 com.android.internal.R.drawable.ic_settings_bluetooth);
77         @ColorInt final int color = com.android.settings.Utils.getColorAccent(
78                 context).getDefaultColor();
79         final PendingIntent toggleAction = getBroadcastIntent(context);
80         final PendingIntent primaryAction = getPrimaryAction(context);
81         final SliceAction primarySliceAction = SliceAction.createDeeplink(primaryAction, icon,
82                 ListBuilder.ICON_IMAGE, title);
83         final SliceAction toggleSliceAction = SliceAction.createToggle(toggleAction,
84                 null /* actionTitle */, isBluetoothEnabled);
85 
86         return new ListBuilder(context, CustomSliceRegistry.BLUETOOTH_URI, ListBuilder.INFINITY)
87                 .setAccentColor(color)
88                 .addRow(new RowBuilder()
89                         .setTitle(title)
90                         .addEndItem(toggleSliceAction)
91                         .setPrimaryAction(primarySliceAction))
92                 .build();
93     }
94 
getIntent(Context context)95     public static Intent getIntent(Context context) {
96         final String screenTitle = context.getText(R.string.bluetooth_settings_title).toString();
97         final Uri contentUri = new Uri.Builder().appendPath(
98                 SettingsSlicesContract.KEY_BLUETOOTH).build();
99         return SliceBuilderUtils.buildSearchResultPageIntent(context,
100                 BluetoothDashboardFragment.class.getName(), null /* key */, screenTitle,
101                 SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY,
102                 R.string.menu_key_connected_devices)
103                 .setClassName(context.getPackageName(), SubSettings.class.getName())
104                 .setData(contentUri);
105     }
106 
107     /**
108      * Update the current Bluetooth status to the boolean value keyed by
109      * {@link android.app.slice.Slice#EXTRA_TOGGLE_STATE} on {@param intent}.
110      */
handleUriChange(Context context, Intent intent)111     public static void handleUriChange(Context context, Intent intent) {
112         final boolean newBluetoothState = intent.getBooleanExtra(EXTRA_TOGGLE_STATE, false);
113         final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
114 
115         if (newBluetoothState) {
116             adapter.enable();
117         } else {
118             adapter.disable();
119         }
120         // Do not notifyChange on Uri. The service takes longer to update the current value than it
121         // does for the Slice to check the current value again. Let {@link SliceBroadcastRelay}
122         // handle it.
123     }
124 
isBluetoothEnabled()125     private static boolean isBluetoothEnabled() {
126         final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
127         return adapter.getState() == BluetoothAdapter.STATE_ON
128                 || adapter.getState() == BluetoothAdapter.STATE_TURNING_ON;
129     }
130 
getPrimaryAction(Context context)131     private static PendingIntent getPrimaryAction(Context context) {
132         final Intent intent = getIntent(context);
133         return PendingIntent.getActivity(context, 0 /* requestCode */,
134                 intent, PendingIntent.FLAG_IMMUTABLE);
135     }
136 
getBroadcastIntent(Context context)137     private static PendingIntent getBroadcastIntent(Context context) {
138         final Intent intent = new Intent(ACTION_BLUETOOTH_SLICE_CHANGED)
139                 .setClass(context, SliceBroadcastReceiver.class);
140         return PendingIntent.getBroadcast(context, 0 /* requestCode */, intent,
141                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
142     }
143 }
144