• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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