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