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