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