• 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.tv.settings.privacy;
18 
19 import static android.hardware.SensorPrivacyManager.Sources.SETTINGS;
20 import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_HARDWARE;
21 import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE;
22 
23 import static com.android.tv.settings.overlay.FlavorUtils.FLAVOR_CLASSIC;
24 
25 import android.content.Context;
26 import android.content.Intent;
27 import android.hardware.SensorPrivacyManager;
28 import android.os.Bundle;
29 import android.util.Log;
30 import android.view.View;
31 import android.view.ViewTreeObserver;
32 
33 import androidx.annotation.Keep;
34 import androidx.leanback.widget.VerticalGridView;
35 import androidx.preference.Preference;
36 import androidx.preference.PreferenceCategory;
37 import androidx.preference.PreferenceGroupAdapter;
38 import androidx.preference.PreferenceManager;
39 import androidx.preference.PreferenceScreen;
40 
41 import com.android.tv.settings.R;
42 import com.android.tv.settings.SettingsPreferenceFragment;
43 import com.android.tv.settings.device.apps.AppManagementFragment;
44 import com.android.tv.settings.overlay.FlavorUtils;
45 import com.android.tv.settings.widget.SwitchWithSoundPreference;
46 
47 import java.util.ArrayList;
48 import java.util.List;
49 
50 /**
51  * The microphone/camera settings screen in TV settings.
52  * Allows the user to turn of the respective sensor.
53  */
54 @Keep
55 public class SensorFragment extends SettingsPreferenceFragment {
56 
57     private static final String TAG = "SensorFragment";
58     private static final boolean DEBUG = true;
59 
60     public static final String TOGGLE_EXTRA = "toggle";
61     /** How many recent apps should be shown when the list is collapsed. */
62     private static final int MAX_RECENT_APPS_COLLAPSED = 2;
63     private List<Preference> mAllRecentAppPrefs;
64 
65     protected static final String SENSOR_TOGGLE_KEY = "sensor_toggle";
66     private PrivacyToggle mToggle;
67     protected SwitchWithSoundPreference mSensorToggle;
68     private Preference mPhysicalPrivacyEnabledInfo;
69 
70     private SensorPrivacyManager mSensorPrivacyManager;
71     private final SensorPrivacyManager.OnSensorPrivacyChangedListener mPrivacyChangedListener =
72             (sensor, enabled) -> updateSensorPrivacyState();
73 
74     @Override
onCreate(Bundle savedInstanceState)75     public void onCreate(Bundle savedInstanceState) {
76         mSensorPrivacyManager = (SensorPrivacyManager)
77                 getContext().getSystemService(Context.SENSOR_PRIVACY_SERVICE);
78 
79         mToggle = (PrivacyToggle) getArguments().get(TOGGLE_EXTRA);
80         if (mToggle == null) {
81             throw new IllegalArgumentException("PrivacyToggle extra missing");
82         }
83 
84         super.onCreate(savedInstanceState);
85         getPreferenceManager().setPreferenceComparisonCallback(
86                 new PreferenceManager.SimplePreferenceComparisonCallback());
87     }
88 
89     @Override
onViewCreated(View view, Bundle savedInstanceState)90     public void onViewCreated(View view, Bundle savedInstanceState) {
91         super.onViewCreated(view, savedInstanceState);
92         mSensorPrivacyManager.addSensorPrivacyListener(mToggle.sensor,
93                 mPrivacyChangedListener);
94         updateSensorPrivacyState();
95     }
96 
97     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)98     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
99         Context themedContext = getPreferenceManager().getContext();
100         PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(themedContext);
101 
102         screen.setTitle(mToggle.screenTitle);
103 
104         addPhysicalPrivacyEnabledInfo(screen, themedContext);
105         addSensorToggleWithInfo(screen, themedContext);
106         addHardwareToggle(screen, themedContext);
107         addRecentAppsGroup(screen, themedContext);
108         addPermissionControllerPreference(screen, themedContext);
109         updateSensorPrivacyState();
110 
111         setPreferenceScreen(screen);
112     }
113 
addHardwareToggle(PreferenceScreen screen, Context themedContext)114     protected void addHardwareToggle(PreferenceScreen screen, Context themedContext) {
115         // no-op
116     }
117 
updateHardwareToggle()118     protected void updateHardwareToggle() {
119         // no-nop
120     }
121 
addPhysicalPrivacyEnabledInfo(PreferenceScreen screen, Context themedContext)122     private void addPhysicalPrivacyEnabledInfo(PreferenceScreen screen, Context themedContext) {
123         mPhysicalPrivacyEnabledInfo = new Preference(themedContext);
124         mPhysicalPrivacyEnabledInfo.setLayoutResource(
125                 R.layout.sensor_physical_privacy_enabled_info);
126         mPhysicalPrivacyEnabledInfo.setSelectable(true);
127         mPhysicalPrivacyEnabledInfo.setTitle(mToggle.physicalPrivacyEnabledInfoTitle);
128         mPhysicalPrivacyEnabledInfo.setSummary(mToggle.physicalPrivacyEnabledInfoText);
129         mPhysicalPrivacyEnabledInfo.setIcon(mToggle.physicalPrivacyEnabledIcon);
130 
131         // Use InfoFragment when using 2-panel settings
132         if (FlavorUtils.getFlavor(getContext()) == FLAVOR_CLASSIC) {
133             mPhysicalPrivacyEnabledInfo.setFragment(PhysicalPrivacyUnblockFragment.class.getName());
134             mPhysicalPrivacyEnabledInfo.getExtras().putObject(
135                     PhysicalPrivacyUnblockFragment.TOGGLE_EXTRA, mToggle);
136         } else {
137             mPhysicalPrivacyEnabledInfo.setFragment(
138                     PhysicalPrivacyUnblockInfoFragment.class.getName());
139             mPhysicalPrivacyEnabledInfo.getExtras().putObject(
140                     PhysicalPrivacyUnblockInfoFragment.TOGGLE_EXTRA, mToggle);
141         }
142 
143         screen.addPreference(mPhysicalPrivacyEnabledInfo);
144     }
145 
146     /**
147      * Adds the sensor toggle with an InfoFragment (in two-panel mode) or an info text below (in
148      * one-panel mode).
149      */
addSensorToggleWithInfo(PreferenceScreen screen, Context themedContext)150     private void addSensorToggleWithInfo(PreferenceScreen screen, Context themedContext) {
151         mSensorToggle = new SwitchWithSoundPreference(themedContext);
152         screen.addPreference(mSensorToggle);
153         mSensorToggle.setKey(SENSOR_TOGGLE_KEY);
154         mSensorToggle.setTitle(mToggle.toggleTitle);
155         mSensorToggle.setSummary(R.string.sensor_toggle_description);
156         mSensorToggle.setFragment(SensorToggleInfoFragment.class.getName());
157         mSensorToggle.getExtras().putObject(SensorToggleInfoFragment.TOGGLE_EXTRA, mToggle);
158 
159         if (!FlavorUtils.isTwoPanel(themedContext)) {
160             // Show the toggle info text beneath instead.
161             Preference toggleInfo = new Preference(themedContext);
162             toggleInfo.setLayoutResource(R.layout.sensor_toggle_info);
163             toggleInfo.setSummary(mToggle.toggleInfoText);
164             toggleInfo.setSelectable(false);
165             screen.addPreference(toggleInfo);
166         }
167     }
168 
updateSensorPrivacyState()169     private void updateSensorPrivacyState() {
170         boolean softwarePrivacyEnabled = mSensorPrivacyManager.isSensorPrivacyEnabled(
171                 TOGGLE_TYPE_SOFTWARE, mToggle.sensor);
172         boolean physicalPrivacyEnabled = mSensorPrivacyManager.isSensorPrivacyEnabled(
173                 TOGGLE_TYPE_HARDWARE, mToggle.sensor);
174 
175         if (DEBUG) {
176             Log.v(TAG,
177                     "softwarePrivacyEnabled=" + softwarePrivacyEnabled + ", physicalPrivacyEnabled="
178                             + physicalPrivacyEnabled);
179         }
180         // If privacy is enabled, the sensor access is turned off
181         mSensorToggle.setChecked(!softwarePrivacyEnabled && !physicalPrivacyEnabled);
182         mSensorToggle.setEnabled(!physicalPrivacyEnabled);
183         mPhysicalPrivacyEnabledInfo.setVisible(physicalPrivacyEnabled);
184 
185         if (physicalPrivacyEnabled) {
186             selectPreference(mPhysicalPrivacyEnabledInfo);
187         }
188         updateHardwareToggle();
189     }
190 
selectPreference(Preference preference)191     private void selectPreference(Preference preference) {
192         scrollToPreference(preference);
193         if (getListView() instanceof VerticalGridView) {
194             VerticalGridView listView = (VerticalGridView) getListView();
195             PreferenceGroupAdapter adapter = (PreferenceGroupAdapter) (listView.getAdapter());
196 
197             ViewTreeObserver.OnPreDrawListener listener = new ViewTreeObserver.OnPreDrawListener() {
198                 @Override
199                 public boolean onPreDraw() {
200                     listView.post(() -> {
201                         int position = adapter.getPreferenceAdapterPosition(preference);
202                         listView.setSelectedPositionSmooth(position);
203                     });
204                     listView.getViewTreeObserver().removeOnPreDrawListener(this);
205                     return true;
206                 }
207             };
208             listView.getViewTreeObserver().addOnPreDrawListener(listener);
209         }
210     }
211 
212     /**
213      * Adds section that shows an expandable list of apps that have recently accessed the sensor.
214      */
addRecentAppsGroup(PreferenceScreen screen, Context themedContext)215     private void addRecentAppsGroup(PreferenceScreen screen, Context themedContext) {
216         // Create the Recently Accessed By section.
217         PreferenceCategory recentRequests = new PreferenceCategory(themedContext);
218         recentRequests.setTitle(R.string.recently_accessed_by_category);
219         screen.addPreference(recentRequests);
220 
221         // Get recent accesses.
222         List<RecentlyAccessedByUtils.App> recentApps = RecentlyAccessedByUtils.getAppList(
223                 themedContext, mToggle.appOps);
224         if (DEBUG) Log.v(TAG, "recently accessed by " + recentApps.size() + " apps");
225 
226         // Create a preference for each access.
227         mAllRecentAppPrefs = new ArrayList<>(recentApps.size());
228         for (RecentlyAccessedByUtils.App app : recentApps) {
229             if (DEBUG) Log.v(TAG, "last access: " + app.mLastAccess);
230             Preference pref = new Preference(themedContext);
231             pref.setTitle(app.mLabel);
232             pref.setIcon(app.mIcon);
233             pref.setFragment(AppManagementFragment.class.getName());
234             AppManagementFragment.prepareArgs(pref.getExtras(), app.mPackageName);
235             mAllRecentAppPrefs.add(pref);
236         }
237 
238         for (int i = 0; i < MAX_RECENT_APPS_COLLAPSED; i++) {
239             if (mAllRecentAppPrefs.size() > i) {
240                 recentRequests.addPreference(mAllRecentAppPrefs.get(i));
241             }
242         }
243         if (mAllRecentAppPrefs.size() > MAX_RECENT_APPS_COLLAPSED) {
244             Preference showAllRecent = new Preference(themedContext);
245             showAllRecent.setTitle(R.string.recently_accessed_show_all);
246             showAllRecent.setOnPreferenceClickListener(preference -> {
247                 preference.setVisible(false);
248                 for (int i = MAX_RECENT_APPS_COLLAPSED; i < mAllRecentAppPrefs.size(); i++) {
249                     recentRequests.addPreference(mAllRecentAppPrefs.get(i));
250                 }
251                 return false;
252             });
253             recentRequests.addPreference(showAllRecent);
254         }
255 
256         if (mAllRecentAppPrefs.size() == 0) {
257             Preference banner = new Preference(themedContext);
258             banner.setSummary(R.string.no_recent_sensor_accesses);
259             banner.setSelectable(false);
260             recentRequests.addPreference(banner);
261         }
262     }
263 
264     /**
265      * Adds a preference that opens the overview of the PermissionGroup pertaining to the sensor.
266      */
addPermissionControllerPreference(PreferenceScreen screen, Context themedContext)267     private void addPermissionControllerPreference(PreferenceScreen screen, Context themedContext) {
268         Preference openPermissionController = new Preference(themedContext);
269         openPermissionController.setTitle(mToggle.appPermissionsTitle);
270         Intent showSensorPermissions = new Intent(Intent.ACTION_MANAGE_PERMISSION_APPS);
271         showSensorPermissions.putExtra(Intent.EXTRA_PERMISSION_NAME,
272                 mToggle.permissionsGroupName);
273         openPermissionController.setIntent(showSensorPermissions);
274         screen.addPreference(openPermissionController);
275     }
276 
277     @Override
onDestroyView()278     public void onDestroyView() {
279         mSensorPrivacyManager.removeSensorPrivacyListener(mToggle.sensor, mPrivacyChangedListener);
280         super.onDestroyView();
281     }
282 
283     @Override
onPreferenceTreeClick(Preference preference)284     public boolean onPreferenceTreeClick(Preference preference) {
285         if (SENSOR_TOGGLE_KEY.equals(preference.getKey())) {
286             boolean physicalPrivacyEnabled = mSensorPrivacyManager.isSensorPrivacyEnabled(
287                     TOGGLE_TYPE_HARDWARE, mToggle.sensor);
288             if (!physicalPrivacyEnabled) {
289                 mSensorPrivacyManager.setSensorPrivacy(SETTINGS, mToggle.sensor,
290                         !mSensorToggle.isChecked());
291             }
292             updateSensorPrivacyState();
293             return true;
294         }
295         return super.onPreferenceTreeClick(preference);
296     }
297 }
298