1 /*
2  * Copyright (C) 2017 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.android.settings.slices;
18 
19 import static com.android.settings.bluetooth.BluetoothSliceBuilder.ACTION_BLUETOOTH_SLICE_CHANGED;
20 import static com.android.settings.network.telephony.Enhanced4gLteSliceHelper.ACTION_ENHANCED_4G_LTE_CHANGED;
21 import static com.android.settings.notification.zen.ZenModeSliceBuilder.ACTION_ZEN_MODE_SLICE_CHANGED;
22 import static com.android.settings.slices.SettingsSliceProvider.ACTION_SLIDER_CHANGED;
23 import static com.android.settings.slices.SettingsSliceProvider.ACTION_TOGGLE_CHANGED;
24 import static com.android.settings.slices.SettingsSliceProvider.EXTRA_SLICE_KEY;
25 import static com.android.settings.wifi.calling.WifiCallingSliceHelper.ACTION_WIFI_CALLING_CHANGED;
26 import static com.android.settings.wifi.calling.WifiCallingSliceHelper.ACTION_WIFI_CALLING_PREFERENCE_CELLULAR_PREFERRED;
27 import static com.android.settings.wifi.calling.WifiCallingSliceHelper.ACTION_WIFI_CALLING_PREFERENCE_WIFI_ONLY;
28 import static com.android.settings.wifi.calling.WifiCallingSliceHelper.ACTION_WIFI_CALLING_PREFERENCE_WIFI_PREFERRED;
29 
30 import android.app.settings.SettingsEnums;
31 import android.app.slice.Slice;
32 import android.content.BroadcastReceiver;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.net.Uri;
36 import android.text.TextUtils;
37 import android.util.Log;
38 
39 import com.android.settings.bluetooth.BluetoothSliceBuilder;
40 import com.android.settings.core.BasePreferenceController;
41 import com.android.settings.core.SliderPreferenceController;
42 import com.android.settings.core.TogglePreferenceController;
43 import com.android.settings.notification.zen.ZenModeSliceBuilder;
44 import com.android.settings.overlay.FeatureFactory;
45 
46 /**
47  * Responds to actions performed on slices and notifies slices of updates in state changes.
48  */
49 public class SliceBroadcastReceiver extends BroadcastReceiver {
50 
51     private static String TAG = "SettSliceBroadcastRec";
52 
53     @Override
onReceive(Context context, Intent intent)54     public void onReceive(Context context, Intent intent) {
55         final String action = intent.getAction();
56         final String key = intent.getStringExtra(EXTRA_SLICE_KEY);
57 
58         if (CustomSliceRegistry.isValidAction(action)) {
59             final CustomSliceable sliceable =
60                     CustomSliceable.createInstance(context,
61                             CustomSliceRegistry.getSliceClassByUri(Uri.parse(action)));
62             sliceable.onNotifyChange(intent);
63             return;
64         }
65         final Uri sliceUri = intent.getData();
66 
67         switch (action) {
68             case ACTION_TOGGLE_CHANGED:
69                 final boolean isChecked = intent.getBooleanExtra(Slice.EXTRA_TOGGLE_STATE, false);
70                 handleToggleAction(context, sliceUri, key, isChecked);
71                 break;
72             case ACTION_SLIDER_CHANGED:
73                 final int newPosition = intent.getIntExtra(Slice.EXTRA_RANGE_VALUE, -1);
74                 handleSliderAction(context, sliceUri, key, newPosition);
75                 break;
76             case ACTION_BLUETOOTH_SLICE_CHANGED:
77                 BluetoothSliceBuilder.handleUriChange(context, intent);
78                 break;
79             case ACTION_WIFI_CALLING_CHANGED:
80                 FeatureFactory.getFeatureFactory()
81                         .getSlicesFeatureProvider()
82                         .getNewWifiCallingSliceHelper(context)
83                         .handleWifiCallingChanged(intent);
84                 break;
85             case ACTION_ZEN_MODE_SLICE_CHANGED:
86                 ZenModeSliceBuilder.handleUriChange(context, intent);
87                 break;
88             case ACTION_ENHANCED_4G_LTE_CHANGED:
89                 FeatureFactory.getFeatureFactory()
90                         .getSlicesFeatureProvider()
91                         .getNewEnhanced4gLteSliceHelper(context)
92                         .handleEnhanced4gLteChanged(intent);
93                 break;
94             case ACTION_WIFI_CALLING_PREFERENCE_WIFI_ONLY:
95             case ACTION_WIFI_CALLING_PREFERENCE_WIFI_PREFERRED:
96             case ACTION_WIFI_CALLING_PREFERENCE_CELLULAR_PREFERRED:
97                 FeatureFactory.getFeatureFactory()
98                         .getSlicesFeatureProvider()
99                         .getNewWifiCallingSliceHelper(context)
100                         .handleWifiCallingPreferenceChanged(intent);
101                 break;
102         }
103     }
104 
handleToggleAction(Context context, Uri sliceUri, String key, boolean isChecked)105     private void handleToggleAction(Context context, Uri sliceUri, String key, boolean isChecked) {
106         if (TextUtils.isEmpty(key)) {
107             throw new IllegalStateException("No key passed to Intent for toggle controller");
108         }
109 
110         final BasePreferenceController controller = getPreferenceController(context, key);
111 
112         if (!(controller instanceof TogglePreferenceController)) {
113             throw new IllegalStateException("Toggle action passed for a non-toggle key: " + key);
114         }
115 
116         if (!controller.isAvailable()) {
117             Log.w(TAG, "Can't update " + key + " since the setting is unavailable");
118             if (!controller.hasAsyncUpdate()) {
119                 context.getContentResolver().notifyChange(sliceUri, null /* observer */);
120             }
121             return;
122         }
123 
124         // TODO post context.getContentResolver().notifyChanged(uri, null) in the Toggle controller
125         // so that it's automatically broadcast to any slice.
126         final TogglePreferenceController toggleController = (TogglePreferenceController) controller;
127         toggleController.setChecked(isChecked);
128         logSliceValueChange(context, key, isChecked ? 1 : 0);
129         if (!controller.hasAsyncUpdate()) {
130             context.getContentResolver().notifyChange(sliceUri, null /* observer */);
131         }
132     }
133 
handleSliderAction(Context context, Uri sliceUri, String key, int newPosition)134     private void handleSliderAction(Context context, Uri sliceUri, String key, int newPosition) {
135         if (TextUtils.isEmpty(key)) {
136             throw new IllegalArgumentException(
137                     "No key passed to Intent for slider controller. Use extra: " + EXTRA_SLICE_KEY);
138         }
139 
140         if (newPosition == -1) {
141             throw new IllegalArgumentException("Invalid position passed to Slider controller");
142         }
143 
144         final BasePreferenceController controller = getPreferenceController(context, key);
145 
146         if (!(controller instanceof SliderPreferenceController)) {
147             throw new IllegalArgumentException("Slider action passed for a non-slider key: " + key);
148         }
149 
150         if (!controller.isAvailable()) {
151             Log.w(TAG, "Can't update " + key + " since the setting is unavailable");
152             context.getContentResolver().notifyChange(sliceUri, null /* observer */);
153             return;
154         }
155 
156         final SliderPreferenceController sliderController = (SliderPreferenceController) controller;
157         final int minValue = sliderController.getMin();
158         final int maxValue = sliderController.getMax();
159         if (newPosition < minValue || newPosition > maxValue) {
160             throw new IllegalArgumentException(
161                     "Invalid position passed to Slider controller. Expected between " + minValue
162                             + " and " + maxValue + " but found " + newPosition);
163         }
164 
165         sliderController.setSliderPosition(newPosition);
166         logSliceValueChange(context, key, newPosition);
167         context.getContentResolver().notifyChange(sliceUri, null /* observer */);
168     }
169 
170     /**
171      * Log Slice value update events into MetricsFeatureProvider. The logging schema generally
172      * follows the pattern in SharedPreferenceLogger.
173      */
logSliceValueChange(Context context, String sliceKey, int newValue)174     private void logSliceValueChange(Context context, String sliceKey, int newValue) {
175         FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
176                 .action(SettingsEnums.PAGE_UNKNOWN,
177                         SettingsEnums.ACTION_SETTINGS_SLICE_CHANGED,
178                         SettingsEnums.PAGE_UNKNOWN,
179                         sliceKey, newValue);
180     }
181 
getPreferenceController(Context context, String key)182     private BasePreferenceController getPreferenceController(Context context, String key) {
183         final SlicesDatabaseAccessor accessor = new SlicesDatabaseAccessor(context);
184         final SliceData sliceData = accessor.getSliceDataFromKey(key);
185         return SliceBuilderUtils.getPreferenceController(context, sliceData);
186     }
187 }
188