• 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.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