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