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