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