• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.util.Log;
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.contract.SettingsContractKt;
40 import com.android.settings.network.SatelliteRepository;
41 import com.android.settings.slices.CustomSliceRegistry;
42 import com.android.settings.slices.SliceBroadcastReceiver;
43 import com.android.settings.slices.SliceBuilderUtils;
44 
45 import java.util.concurrent.ExecutionException;
46 import java.util.concurrent.Executors;
47 import java.util.concurrent.TimeUnit;
48 import java.util.concurrent.TimeoutException;
49 
50 /**
51  * Utility class to build a Bluetooth Slice, and handle all associated actions.
52  */
53 public class BluetoothSliceBuilder {
54 
55     private static final String TAG = "BluetoothSliceBuilder";
56 
57     /**
58      * Action notifying a change on the BluetoothSlice.
59      */
60     public static final String ACTION_BLUETOOTH_SLICE_CHANGED =
61             "com.android.settings.bluetooth.action.BLUETOOTH_MODE_CHANGED";
62 
63     public static final IntentFilter INTENT_FILTER = new IntentFilter();
64 
65     static {
66         INTENT_FILTER.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
67         INTENT_FILTER.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
68     }
69 
BluetoothSliceBuilder()70     private BluetoothSliceBuilder() {
71     }
72 
73     /**
74      * Return a Bluetooth Slice bound to {@link CustomSliceRegistry#BLUETOOTH_URI}.
75      * <p>
76      * Note that you should register a listener for {@link #INTENT_FILTER} to get changes for
77      * Bluetooth.
78      */
getSlice(Context context)79     public static Slice getSlice(Context context) {
80         final boolean isBluetoothEnabled = isBluetoothEnabled();
81         final CharSequence title = context.getText(R.string.bluetooth_settings);
82         final IconCompat icon = IconCompat.createWithResource(context,
83                 com.android.internal.R.drawable.ic_settings_bluetooth);
84         @ColorInt final int color = com.android.settings.Utils.getColorAccent(
85                 context).getDefaultColor();
86         final PendingIntent toggleAction = getBroadcastIntent(context);
87         final PendingIntent primaryAction = getPrimaryAction(context);
88         final SliceAction primarySliceAction = SliceAction.createDeeplink(primaryAction, icon,
89                 ListBuilder.ICON_IMAGE, title);
90 
91         RowBuilder rowBuilder = new RowBuilder();
92         rowBuilder.setTitle(title);
93         rowBuilder.setPrimaryAction(primarySliceAction);
94         if (!isSatelliteOn(context)) {
95             final SliceAction toggleSliceAction = SliceAction.createToggle(toggleAction,
96                     null /* actionTitle */, isBluetoothEnabled);
97             rowBuilder.addEndItem(toggleSliceAction);
98         }
99         return new ListBuilder(context, CustomSliceRegistry.BLUETOOTH_URI, ListBuilder.INFINITY)
100                 .setAccentColor(color)
101                 .addRow(rowBuilder)
102                 .build();
103     }
104 
isSatelliteOn(Context context)105     private static boolean isSatelliteOn(Context context) {
106         boolean result = false;
107         SatelliteRepository satelliteRepository = new SatelliteRepository(context);
108         try {
109             result = satelliteRepository.requestIsSessionStarted(
110                     Executors.newSingleThreadExecutor()).get(3000, TimeUnit.MILLISECONDS);
111         } catch (InterruptedException | ExecutionException | TimeoutException e) {
112             Log.e(TAG, "Error to get satellite status : " + e);
113         }
114 
115         return result;
116     }
117 
getIntent(Context context)118     public static Intent getIntent(Context context) {
119         final String screenTitle = context.getText(R.string.bluetooth_settings_title).toString();
120         final Uri contentUri = new Uri.Builder().appendPath(
121                 SettingsContractKt.KEY_BLUETOOTH).build();
122         return SliceBuilderUtils.buildSearchResultPageIntent(context,
123                 BluetoothDashboardFragment.class.getName(), null /* key */, screenTitle,
124                 SettingsEnums.SETTINGS_CONNECTED_DEVICE_CATEGORY,
125                 R.string.menu_key_connected_devices)
126                 .setClassName(context.getPackageName(), SubSettings.class.getName())
127                 .setData(contentUri);
128     }
129 
130     /**
131      * Update the current Bluetooth status to the boolean value keyed by
132      * {@link android.app.slice.Slice#EXTRA_TOGGLE_STATE} on {@param intent}.
133      */
handleUriChange(Context context, Intent intent)134     public static void handleUriChange(Context context, Intent intent) {
135         final boolean newBluetoothState = intent.getBooleanExtra(EXTRA_TOGGLE_STATE, false);
136         final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
137 
138         if (newBluetoothState) {
139             adapter.enable();
140         } else {
141             adapter.disable();
142         }
143         // Do not notifyChange on Uri. The service takes longer to update the current value than it
144         // does for the Slice to check the current value again. Let {@link SliceBroadcastRelay}
145         // handle it.
146     }
147 
isBluetoothEnabled()148     private static boolean isBluetoothEnabled() {
149         final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
150         return adapter.getState() == BluetoothAdapter.STATE_ON
151                 || adapter.getState() == BluetoothAdapter.STATE_TURNING_ON;
152     }
153 
getPrimaryAction(Context context)154     private static PendingIntent getPrimaryAction(Context context) {
155         final Intent intent = getIntent(context);
156         return PendingIntent.getActivity(context, 0 /* requestCode */,
157                 intent, PendingIntent.FLAG_IMMUTABLE);
158     }
159 
getBroadcastIntent(Context context)160     private static PendingIntent getBroadcastIntent(Context context) {
161         final Intent intent = new Intent(ACTION_BLUETOOTH_SLICE_CHANGED)
162                 .setClass(context, SliceBroadcastReceiver.class);
163         return PendingIntent.getBroadcast(context, 0 /* requestCode */, intent,
164                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
165     }
166 }
167