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.car.settings.privacy;
18 
19 import android.car.drivingstate.CarUxRestrictions;
20 import android.content.Context;
21 import android.hardware.SensorPrivacyManager;
22 
23 import androidx.preference.PreferenceCategory;
24 
25 import com.android.car.settings.R;
26 import com.android.car.settings.common.FragmentController;
27 import com.android.car.settings.common.PreferenceController;
28 import com.android.car.ui.preference.CarUiPreference;
29 import com.android.internal.annotations.VisibleForTesting;
30 import com.android.settingslib.applications.RecentAppOpsAccess;
31 
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Set;
35 
36 /**
37  * This controller displays a list of apps that recently access the microphone. Only non-system apps
38  * are displayed.
39  */
40 public class MicrophoneRecentAccessesPreferenceController extends
41         PreferenceController<PreferenceCategory> {
42 
43     private final SensorPrivacyManager mSensorPrivacyManager;
44     private final SensorPrivacyManager.OnSensorPrivacyChangedListener mListener =
45             (sensor, enabled) -> refreshUi();
46     private final Set<CarUiPreference> mAddedPreferences = new HashSet<>();
47 
48     private final RecentAppOpsAccess mRecentMicrophoneAccesses;
49     private final int mRecentAppsMaxCount;
50 
MicrophoneRecentAccessesPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)51     public MicrophoneRecentAccessesPreferenceController(Context context, String preferenceKey,
52             FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
53         this(context, preferenceKey, fragmentController, uxRestrictions,
54                 RecentAppOpsAccess.createForMicrophone(context),
55                 context.getResources()
56                         .getInteger(R.integer.recent_microphone_access_apps_list_count),
57                 SensorPrivacyManager.getInstance(context));
58     }
59 
60     @VisibleForTesting
MicrophoneRecentAccessesPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions, RecentAppOpsAccess recentMicrophoneAccesses, int recentAppsMaxCount, SensorPrivacyManager sensorPrivacyManager)61     MicrophoneRecentAccessesPreferenceController(Context context, String preferenceKey,
62             FragmentController fragmentController, CarUxRestrictions uxRestrictions,
63             RecentAppOpsAccess recentMicrophoneAccesses, int recentAppsMaxCount,
64             SensorPrivacyManager sensorPrivacyManager) {
65         super(context, preferenceKey, fragmentController, uxRestrictions);
66         mRecentMicrophoneAccesses = recentMicrophoneAccesses;
67         mRecentAppsMaxCount = recentAppsMaxCount;
68         mSensorPrivacyManager = sensorPrivacyManager;
69     }
70 
71     @Override
getPreferenceType()72     protected Class<PreferenceCategory> getPreferenceType() {
73         return PreferenceCategory.class;
74     }
75 
76     @Override
onStartInternal()77     protected void onStartInternal() {
78         mSensorPrivacyManager.addSensorPrivacyListener(
79                 SensorPrivacyManager.Sensors.MICROPHONE, mListener);
80     }
81 
82     @Override
onStopInternal()83     protected void onStopInternal() {
84         mSensorPrivacyManager.removeSensorPrivacyListener(SensorPrivacyManager.Sensors.MICROPHONE,
85                 mListener);
86     }
87 
88     @Override
updateState(PreferenceCategory preference)89     public void updateState(PreferenceCategory preference) {
90         super.updateState(preference);
91         if (mSensorPrivacyManager.isSensorPrivacyEnabled(
92                 SensorPrivacyManager.Sensors.MICROPHONE)) {
93             getPreference().setVisible(false);
94             return;
95         }
96         getPreference().setVisible(true);
97         List<RecentAppOpsAccess.Access> sortedRecentMicrophoneAccesses = loadData();
98         updateUi(sortedRecentMicrophoneAccesses);
99     }
100 
loadData()101     private List<RecentAppOpsAccess.Access> loadData() {
102         return mRecentMicrophoneAccesses.getAppListSorted(/* showSystem= */ false);
103     }
104 
hasAtLeastOneRecentAppAccess()105     private boolean hasAtLeastOneRecentAppAccess() {
106         return !mRecentMicrophoneAccesses.getAppListSorted(/* showSystem= */ true).isEmpty();
107     }
108 
updateUi(List<RecentAppOpsAccess.Access> sortedRecentMicrophoneAccesses)109     private void updateUi(List<RecentAppOpsAccess.Access> sortedRecentMicrophoneAccesses) {
110         // remove any already added preferences
111         for (CarUiPreference addedPreference : mAddedPreferences) {
112             getPreference().removePreference(addedPreference);
113         }
114         mAddedPreferences.clear();
115 
116         if (sortedRecentMicrophoneAccesses.isEmpty()) {
117             CarUiPreference emptyPreference = createNoRecentAccessPreference();
118             getPreference().addPreference(emptyPreference);
119             mAddedPreferences.add(emptyPreference);
120         } else {
121             int count = Math.min(sortedRecentMicrophoneAccesses.size(), mRecentAppsMaxCount);
122             for (int i = 0; i < count; i++) {
123                 RecentAppOpsAccess.Access request = sortedRecentMicrophoneAccesses.get(i);
124                 CarUiPreference appPreference = MicrophoneRecentAccessUtil.createAppPreference(
125                         getContext(),
126                         request);
127                 getPreference().addPreference(appPreference);
128                 mAddedPreferences.add(appPreference);
129             }
130         }
131 
132         if (hasAtLeastOneRecentAppAccess()) {
133             CarUiPreference viewAllPreference = createViewAllPreference();
134             getPreference().addPreference(viewAllPreference);
135             mAddedPreferences.add(viewAllPreference);
136         }
137     }
138 
createNoRecentAccessPreference()139     private CarUiPreference createNoRecentAccessPreference() {
140         CarUiPreference preference = new CarUiPreference(getContext());
141         preference.setTitle(R.string.microphone_no_recent_access);
142         preference.setSelectable(false);
143         return preference;
144     }
145 
createViewAllPreference()146     private CarUiPreference createViewAllPreference() {
147         CarUiPreference preference = new CarUiPreference(getContext());
148         preference.setTitle(R.string.microphone_settings_recent_requests_view_all_title);
149         preference.setIcon(R.drawable.ic_apps);
150         preference.setOnPreferenceClickListener(p -> {
151             getFragmentController().launchFragment(new MicrophoneRecentAccessViewAllFragment());
152             return true;
153         });
154         return preference;
155     }
156 }
157