1 /* 2 * Copyright (C) 2021 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.systemui.car.systembar; 18 19 import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE; 20 import static android.hardware.SensorPrivacyManager.Sources.QS_TILE; 21 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.hardware.SensorPrivacyManager; 27 import android.os.UserHandle; 28 import android.util.Log; 29 import android.view.View; 30 31 import androidx.annotation.AnyThread; 32 import androidx.annotation.NonNull; 33 34 import com.android.systemui.R; 35 import com.android.systemui.broadcast.BroadcastDispatcher; 36 import com.android.systemui.car.CarDeviceProvisionedController; 37 import com.android.systemui.car.CarServiceProvider; 38 import com.android.systemui.car.privacy.MicPrivacyChip; 39 import com.android.systemui.car.privacy.MicQcPanel; 40 import com.android.systemui.dagger.SysUISingleton; 41 import com.android.systemui.privacy.OngoingPrivacyChip; 42 import com.android.systemui.privacy.PrivacyItem; 43 import com.android.systemui.privacy.PrivacyItemController; 44 import com.android.systemui.privacy.PrivacyType; 45 46 import java.util.List; 47 import java.util.Optional; 48 49 import javax.inject.Inject; 50 51 /** 52 * Controls a Privacy Chip view in system icons. 53 */ 54 @SysUISingleton 55 public class PrivacyChipViewController implements MicQcPanel.MicSensorInfoProvider { 56 private static final String TAG = "PrivacyChipViewContrllr"; 57 private static final boolean DEBUG = false; 58 59 private final PrivacyItemController mPrivacyItemController; 60 private final CarServiceProvider mCarServiceProvider; 61 private final BroadcastDispatcher mBroadcastDispatcher; 62 private final CarDeviceProvisionedController mCarDeviceProvisionedController; 63 private final SensorPrivacyManager mSensorPrivacyManager; 64 65 private Context mContext; 66 private MicPrivacyChip mPrivacyChip; 67 private Runnable mQsTileNotifyUpdateRunnable; 68 private final SensorPrivacyManager.OnSensorPrivacyChangedListener 69 mOnSensorPrivacyChangedListener = (sensor, sensorPrivacyEnabled) -> { 70 if (mContext == null) { 71 return; 72 } 73 // Since this is launched using a callback thread, its UI based elements need 74 // to execute on main executor. 75 mContext.getMainExecutor().execute(() -> { 76 // We need to negate sensorPrivacyEnabled since when it is {@code true} it means 77 // microphone has been toggled off. 78 mPrivacyChip.setMicrophoneEnabled(/* isMicrophoneEnabled= */ !sensorPrivacyEnabled); 79 mQsTileNotifyUpdateRunnable.run(); 80 }); 81 }; 82 private boolean mAllIndicatorsEnabled; 83 private boolean mMicCameraIndicatorsEnabled; 84 private boolean mIsMicPrivacyChipVisible; 85 private final PrivacyItemController.Callback mPicCallback = 86 new PrivacyItemController.Callback() { 87 @Override 88 public void onPrivacyItemsChanged(@NonNull List<PrivacyItem> privacyItems) { 89 if (mPrivacyChip == null) { 90 return; 91 } 92 93 // Call QS Tile notify update runnable here so that QS tile can update when app 94 // usage is added/removed/updated 95 mQsTileNotifyUpdateRunnable.run(); 96 97 boolean shouldShowMicPrivacyChip = isMicPartOfPrivacyItems(privacyItems); 98 if (mIsMicPrivacyChipVisible == shouldShowMicPrivacyChip) { 99 return; 100 } 101 102 mIsMicPrivacyChipVisible = shouldShowMicPrivacyChip; 103 setChipVisibility(shouldShowMicPrivacyChip); 104 } 105 106 @Override 107 public void onFlagAllChanged(boolean enabled) { 108 onAllIndicatorsToggled(enabled); 109 } 110 111 @Override 112 public void onFlagMicCameraChanged(boolean enabled) { 113 onMicCameraToggled(enabled); 114 } 115 116 private void onMicCameraToggled(boolean enabled) { 117 if (mMicCameraIndicatorsEnabled != enabled) { 118 mMicCameraIndicatorsEnabled = enabled; 119 } 120 } 121 122 private void onAllIndicatorsToggled(boolean enabled) { 123 if (mAllIndicatorsEnabled != enabled) { 124 mAllIndicatorsEnabled = enabled; 125 } 126 } 127 }; 128 private int mCurrentUserId; 129 private final BroadcastReceiver mUserUpdateReceiver = new BroadcastReceiver() { 130 @Override 131 public void onReceive(Context context, Intent intent) { 132 if (mPrivacyChip == null) { 133 return; 134 } 135 if (!Intent.ACTION_USER_INFO_CHANGED.equals(intent.getAction())) { 136 return; 137 } 138 int currentUserId = mCarDeviceProvisionedController.getCurrentUser(); 139 if (mCurrentUserId == currentUserId) { 140 return; 141 } 142 143 setUser(currentUserId); 144 } 145 }; 146 private final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() { 147 @Override 148 public void onReceive(Context context, Intent intent) { 149 if (mPrivacyChip == null) { 150 return; 151 } 152 int currentUserId = mCarDeviceProvisionedController.getCurrentUser(); 153 if (mCurrentUserId == currentUserId) { 154 return; 155 } 156 setUser(currentUserId); 157 } 158 }; 159 160 @Inject PrivacyChipViewController(Context context, PrivacyItemController privacyItemController, CarServiceProvider carServiceProvider, BroadcastDispatcher broadcastDispatcher, SensorPrivacyManager sensorPrivacyManager, CarDeviceProvisionedController carDeviceProvisionedController)161 public PrivacyChipViewController(Context context, PrivacyItemController privacyItemController, 162 CarServiceProvider carServiceProvider, BroadcastDispatcher broadcastDispatcher, 163 SensorPrivacyManager sensorPrivacyManager, 164 CarDeviceProvisionedController carDeviceProvisionedController) { 165 mContext = context; 166 mPrivacyItemController = privacyItemController; 167 mCarServiceProvider = carServiceProvider; 168 mBroadcastDispatcher = broadcastDispatcher; 169 mSensorPrivacyManager = sensorPrivacyManager; 170 mCarDeviceProvisionedController = carDeviceProvisionedController; 171 172 mQsTileNotifyUpdateRunnable = () -> { 173 }; 174 mIsMicPrivacyChipVisible = false; 175 mCurrentUserId = carDeviceProvisionedController.getCurrentUser(); 176 } 177 178 @Override isMicEnabled()179 public boolean isMicEnabled() { 180 // We need to negate return of isSensorPrivacyEnabled since when it is {@code true} it 181 // means microphone has been toggled off 182 return !mSensorPrivacyManager.isSensorPrivacyEnabled(MICROPHONE, mCurrentUserId); 183 } 184 185 @Override toggleMic()186 public void toggleMic() { 187 mSensorPrivacyManager.setSensorPrivacy(QS_TILE, MICROPHONE, isMicEnabled(), mCurrentUserId); 188 } 189 190 @Override setNotifyUpdateRunnable(Runnable runnable)191 public void setNotifyUpdateRunnable(Runnable runnable) { 192 mQsTileNotifyUpdateRunnable = runnable; 193 } 194 isMicPartOfPrivacyItems(@onNull List<PrivacyItem> privacyItems)195 private boolean isMicPartOfPrivacyItems(@NonNull List<PrivacyItem> privacyItems) { 196 Optional<PrivacyItem> optionalMicPrivacyItem = privacyItems.stream() 197 .filter(privacyItem -> privacyItem.getPrivacyType() 198 .equals(PrivacyType.TYPE_MICROPHONE)) 199 .findAny(); 200 return optionalMicPrivacyItem.isPresent(); 201 } 202 203 /** 204 * Finds the {@link OngoingPrivacyChip} and sets relevant callbacks. 205 */ addPrivacyChipView(View view)206 public void addPrivacyChipView(View view) { 207 if (mPrivacyChip != null) { 208 return; 209 } 210 211 mPrivacyChip = view.findViewById(R.id.privacy_chip); 212 if (mPrivacyChip == null) return; 213 214 mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable(); 215 mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable(); 216 mPrivacyItemController.addCallback(mPicCallback); 217 registerForUserChangeEvents(); 218 setUser(mCarDeviceProvisionedController.getCurrentUser()); 219 } 220 221 /** 222 * Cleans up the controller and removes callbacks. 223 */ removeAll()224 public void removeAll() { 225 if (mPrivacyChip != null) { 226 mPrivacyChip.setOnClickListener(null); 227 } 228 229 mPrivacyItemController.removeCallback(mPicCallback); 230 mBroadcastDispatcher.unregisterReceiver(mUserUpdateReceiver); 231 mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver); 232 mSensorPrivacyManager.removeSensorPrivacyListener(MICROPHONE, 233 mOnSensorPrivacyChangedListener); 234 mPrivacyChip = null; 235 } 236 setChipVisibility(boolean chipVisible)237 private void setChipVisibility(boolean chipVisible) { 238 if (mPrivacyChip == null) { 239 return; 240 } 241 242 // Since this is launched using a callback thread, its UI based elements need 243 // to execute on main executor. 244 mContext.getMainExecutor().execute(() -> { 245 if (chipVisible && getChipEnabled()) { 246 mPrivacyChip.animateIn(); 247 } else { 248 mPrivacyChip.animateOut(); 249 } 250 }); 251 } 252 getChipEnabled()253 private boolean getChipEnabled() { 254 return mMicCameraIndicatorsEnabled || mAllIndicatorsEnabled; 255 } 256 registerForUserChangeEvents()257 private void registerForUserChangeEvents() { 258 // Register for user switching 259 IntentFilter userChangeFilter = new IntentFilter(Intent.ACTION_USER_FOREGROUND); 260 mBroadcastDispatcher.registerReceiver(mUserChangeReceiver, userChangeFilter, 261 /* executor= */ null, UserHandle.ALL); 262 263 // Also register for user info changing 264 IntentFilter filter = new IntentFilter(); 265 filter.addAction(Intent.ACTION_USER_INFO_CHANGED); 266 mBroadcastDispatcher.registerReceiver(mUserUpdateReceiver, filter, /* executor= */ null, 267 UserHandle.ALL); 268 } 269 270 @AnyThread setUser(int userId)271 private void setUser(int userId) { 272 if (DEBUG) Log.d(TAG, "New user ID: " + userId); 273 274 mCurrentUserId = userId; 275 276 mSensorPrivacyManager.removeSensorPrivacyListener(MICROPHONE, 277 mOnSensorPrivacyChangedListener); 278 mSensorPrivacyManager.addSensorPrivacyListener(MICROPHONE, userId, 279 mOnSensorPrivacyChangedListener); 280 281 // Since this can be launched using a callback thread, its UI based elements need 282 // to execute on main executor. 283 mContext.getMainExecutor().execute(() -> { 284 mPrivacyChip.setMicrophoneEnabled(isMicEnabled()); 285 }); 286 } 287 } 288