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.Sources.QS_TILE; 20 import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE; 21 22 import android.content.Context; 23 import android.hardware.SensorPrivacyManager; 24 import android.view.View; 25 26 import androidx.annotation.IdRes; 27 import androidx.annotation.NonNull; 28 29 import com.android.systemui.car.privacy.PrivacyChip; 30 import com.android.systemui.car.privacy.SensorInfoUpdateListener; 31 import com.android.systemui.car.privacy.SensorQcPanel; 32 import com.android.systemui.privacy.OngoingPrivacyChip; 33 import com.android.systemui.privacy.PrivacyItem; 34 import com.android.systemui.privacy.PrivacyItemController; 35 import com.android.systemui.privacy.PrivacyType; 36 import com.android.systemui.settings.UserTracker; 37 38 import java.util.List; 39 import java.util.Optional; 40 41 /** Controls a Privacy Chip view in system icons. */ 42 public abstract class PrivacyChipViewController implements SensorQcPanel.SensorInfoProvider { 43 44 private final PrivacyItemController mPrivacyItemController; 45 private final SensorPrivacyManager mSensorPrivacyManager; 46 private final UserTracker mUserTracker; 47 48 private Context mContext; 49 private PrivacyChip mPrivacyChip; 50 private Runnable mQsTileNotifyUpdateRunnable; 51 private SensorInfoUpdateListener mSensorInfoUpdateListener; 52 private final SensorPrivacyManager.OnSensorPrivacyChangedListener 53 mOnSensorPrivacyChangedListener = (sensor, sensorPrivacyEnabled) -> { 54 if (mContext == null) { 55 return; 56 } 57 // Since this is launched using a callback thread, its UI based elements need 58 // to execute on main executor. 59 mContext.getMainExecutor().execute(() -> { 60 // We need to negate enabled since when it is {@code true} it means 61 // the sensor (such as microphone or camera) has been toggled off. 62 mPrivacyChip.setSensorEnabled(/* enabled= */ !sensorPrivacyEnabled); 63 mQsTileNotifyUpdateRunnable.run(); 64 if (mSensorInfoUpdateListener != null) { 65 mSensorInfoUpdateListener.onSensorPrivacyChanged(); 66 } 67 }); 68 }; 69 70 private final UserTracker.Callback mUserSwitchCallback = new UserTracker.Callback() { 71 @Override 72 public void onUserChanged(int newUser, Context userContext) { 73 mPrivacyChip.setSensorEnabled(isSensorEnabled()); 74 } 75 }; 76 77 private boolean mAllIndicatorsEnabled; 78 private boolean mMicCameraIndicatorsEnabled; 79 private boolean mIsPrivacyChipVisible; 80 private final PrivacyItemController.Callback mPicCallback = 81 new PrivacyItemController.Callback() { 82 @Override 83 public void onPrivacyItemsChanged(@NonNull List<PrivacyItem> privacyItems) { 84 if (mPrivacyChip == null) { 85 return; 86 } 87 88 // Call QS Tile notify update runnable here so that QS tile can update when app 89 // usage is added/removed/updated 90 mQsTileNotifyUpdateRunnable.run(); 91 92 boolean shouldShowPrivacyChip = isSensorPartOfPrivacyItems(privacyItems); 93 if (mIsPrivacyChipVisible == shouldShowPrivacyChip) { 94 return; 95 } 96 97 mIsPrivacyChipVisible = shouldShowPrivacyChip; 98 setChipVisibility(shouldShowPrivacyChip); 99 100 if (mSensorInfoUpdateListener != null) { 101 mSensorInfoUpdateListener.onPrivacyItemsChanged(); 102 } 103 } 104 105 @Override 106 public void onFlagAllChanged(boolean enabled) { 107 onAllIndicatorsToggled(enabled); 108 } 109 110 @Override 111 public void onFlagMicCameraChanged(boolean enabled) { 112 onMicCameraToggled(enabled); 113 } 114 115 private void onMicCameraToggled(boolean enabled) { 116 if (mMicCameraIndicatorsEnabled != enabled) { 117 mMicCameraIndicatorsEnabled = enabled; 118 } 119 } 120 121 private void onAllIndicatorsToggled(boolean enabled) { 122 if (mAllIndicatorsEnabled != enabled) { 123 mAllIndicatorsEnabled = enabled; 124 } 125 } 126 }; 127 PrivacyChipViewController(Context context, PrivacyItemController privacyItemController, SensorPrivacyManager sensorPrivacyManager, UserTracker userTracker)128 public PrivacyChipViewController(Context context, PrivacyItemController privacyItemController, 129 SensorPrivacyManager sensorPrivacyManager, UserTracker userTracker) { 130 mContext = context; 131 mPrivacyItemController = privacyItemController; 132 mSensorPrivacyManager = sensorPrivacyManager; 133 mUserTracker = userTracker; 134 135 mQsTileNotifyUpdateRunnable = () -> { 136 }; 137 mIsPrivacyChipVisible = false; 138 } 139 140 @Override isSensorEnabled()141 public boolean isSensorEnabled() { 142 // We need to negate return of isSensorPrivacyEnabled since when it is {@code true} it 143 // means the sensor (microphone/camera) has been toggled off 144 return !mSensorPrivacyManager.isSensorPrivacyEnabled(/* toggleType= */ TOGGLE_TYPE_SOFTWARE, 145 /* sensor= */ getChipSensor()); 146 } 147 148 @Override toggleSensor()149 public void toggleSensor() { 150 mSensorPrivacyManager.setSensorPrivacy(/* source= */ QS_TILE, /* sensor= */ getChipSensor(), 151 /* enable= */ isSensorEnabled()); 152 } 153 154 @Override setNotifyUpdateRunnable(Runnable runnable)155 public void setNotifyUpdateRunnable(Runnable runnable) { 156 mQsTileNotifyUpdateRunnable = runnable; 157 } 158 159 @Override setSensorInfoUpdateListener(SensorInfoUpdateListener listener)160 public void setSensorInfoUpdateListener(SensorInfoUpdateListener listener) { 161 mSensorInfoUpdateListener = listener; 162 } 163 getChipSensor()164 protected abstract @SensorPrivacyManager.Sensors.Sensor int getChipSensor(); 165 getChipPrivacyType()166 protected abstract PrivacyType getChipPrivacyType(); 167 getChipResourceId()168 protected abstract @IdRes int getChipResourceId(); 169 isSensorPartOfPrivacyItems(@onNull List<PrivacyItem> privacyItems)170 private boolean isSensorPartOfPrivacyItems(@NonNull List<PrivacyItem> privacyItems) { 171 Optional<PrivacyItem> optionalSensorPrivacyItem = privacyItems.stream() 172 .filter(privacyItem -> privacyItem.getPrivacyType() 173 .equals(getChipPrivacyType())) 174 .findAny(); 175 return optionalSensorPrivacyItem.isPresent(); 176 } 177 178 /** 179 * Finds the {@link OngoingPrivacyChip} and sets relevant callbacks. 180 */ addPrivacyChipView(View view)181 public void addPrivacyChipView(View view) { 182 if (mPrivacyChip != null) { 183 return; 184 } 185 186 mPrivacyChip = view.findViewById(getChipResourceId()); 187 if (mPrivacyChip == null) return; 188 189 mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable(); 190 mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable(); 191 mPrivacyItemController.addCallback(mPicCallback); 192 193 mSensorPrivacyManager.removeSensorPrivacyListener(getChipSensor(), 194 mOnSensorPrivacyChangedListener); 195 mSensorPrivacyManager.addSensorPrivacyListener(getChipSensor(), 196 mOnSensorPrivacyChangedListener); 197 198 // Since this can be launched using a callback thread, its UI based elements need 199 // to execute on main executor. 200 mContext.getMainExecutor().execute(() -> { 201 mPrivacyChip.setSensorEnabled(isSensorEnabled()); 202 }); 203 mUserTracker.removeCallback(mUserSwitchCallback); 204 mUserTracker.addCallback(mUserSwitchCallback, mContext.getMainExecutor()); 205 } 206 207 /** 208 * Cleans up the controller and removes callbacks. 209 */ removeAll()210 public void removeAll() { 211 if (mPrivacyChip != null) { 212 mPrivacyChip.setOnClickListener(null); 213 } 214 215 mIsPrivacyChipVisible = false; 216 mPrivacyItemController.removeCallback(mPicCallback); 217 mSensorPrivacyManager.removeSensorPrivacyListener(getChipSensor(), 218 mOnSensorPrivacyChangedListener); 219 mUserTracker.removeCallback(mUserSwitchCallback); 220 mPrivacyChip = null; 221 mSensorInfoUpdateListener = null; 222 } 223 setChipVisibility(boolean chipVisible)224 private void setChipVisibility(boolean chipVisible) { 225 if (mPrivacyChip == null) { 226 return; 227 } 228 229 // Since this is launched using a callback thread, its UI based elements need 230 // to execute on main executor. 231 mContext.getMainExecutor().execute(() -> { 232 if (chipVisible && getChipEnabled()) { 233 mPrivacyChip.animateIn(); 234 } else { 235 mPrivacyChip.animateOut(); 236 } 237 }); 238 } 239 getChipEnabled()240 private boolean getChipEnabled() { 241 return mMicCameraIndicatorsEnabled || mAllIndicatorsEnabled; 242 } 243 } 244