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 
21 import android.content.Context;
22 import android.content.Intent;
23 import android.hardware.SensorPrivacyManager;
24 import android.os.Bundle;
25 import android.util.Log;
26 
27 import androidx.annotation.Keep;
28 import androidx.preference.Preference;
29 import androidx.preference.PreferenceCategory;
30 import androidx.preference.PreferenceScreen;
31 import androidx.preference.SwitchPreference;
32 
33 import com.android.tv.settings.R;
34 import com.android.tv.settings.SettingsPreferenceFragment;
35 import com.android.tv.settings.device.apps.AppManagementFragment;
36 import com.android.tv.settings.overlay.FlavorUtils;
37 
38 import java.util.ArrayList;
39 import java.util.List;
40 
41 /**
42  * The microphone/camera settings screen in TV settings.
43  * Allows the user to turn of the respective sensor.
44  */
45 @Keep
46 public class SensorFragment extends SettingsPreferenceFragment {
47 
48     private static final String TAG = "SensorFragment";
49     private static final boolean DEBUG = true;
50 
51     public static final String TOGGLE_EXTRA = "toggle";
52     /** How many recent apps should be shown when the list is collapsed. */
53     private static final int MAX_RECENT_APPS_COLLAPSED = 2;
54     private List<Preference> mAllRecentAppPrefs;
55 
56     private static final String SENSOR_TOGGLE_KEY = "sensor_toggle";
57     private PrivacyToggle mToggle;
58     private SwitchPreference mSensorToggle;
59 
60     private SensorPrivacyManager mSensorPrivacyManager;
61     private final SensorPrivacyManager.OnSensorPrivacyChangedListener mPrivacyChangedListener =
62             (sensor, enabled) -> {
63                 if (mSensorToggle != null) {
64                     mSensorToggle.setChecked(!enabled);
65                 }
66             };
67 
68     @Override
onCreate(Bundle savedInstanceState)69     public void onCreate(Bundle savedInstanceState) {
70         mSensorPrivacyManager = (SensorPrivacyManager)
71                 getContext().getSystemService(Context.SENSOR_PRIVACY_SERVICE);
72 
73         mToggle = (PrivacyToggle) getArguments().get(TOGGLE_EXTRA);
74         if (mToggle == null) {
75             throw new IllegalArgumentException("PrivacyToggle extra missing");
76         }
77 
78         // Calling super at the end, otherwise mSensorPrivacyManager and mToggle are not initialized
79         // during onCreatePreferences.
80         super.onCreate(savedInstanceState);
81     }
82 
83     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)84     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
85         Context themedContext = getPreferenceManager().getContext();
86         PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(themedContext);
87 
88         screen.setTitle(mToggle.screenTitle);
89 
90         addSensorToggleWithInfo(screen, themedContext);
91         addRecentAppsGroup(screen, themedContext);
92         addPermissionControllerPreference(screen, themedContext);
93 
94         setPreferenceScreen(screen);
95     }
96 
97     /**
98      * Adds the sensor toggle with an InfoFragment (in two-panel mode) or an info text below (in
99      * one-panel mode).
100      */
addSensorToggleWithInfo(PreferenceScreen screen, Context themedContext)101     private void addSensorToggleWithInfo(PreferenceScreen screen, Context themedContext) {
102         mSensorToggle = new SwitchPreference(themedContext);
103         screen.addPreference(mSensorToggle);
104         mSensorToggle.setKey(SENSOR_TOGGLE_KEY);
105         mSensorToggle.setTitle(mToggle.toggleTitle);
106         mSensorToggle.setSummary(R.string.sensor_toggle_description);
107         mSensorToggle.setFragment(SensorToggleInfoFragment.class.getName());
108         mSensorToggle.getExtras().putObject(SensorToggleInfoFragment.TOGGLE_EXTRA, mToggle);
109 
110         // If privacy is enabled, the sensor access is turned off
111         mSensorToggle.setChecked(
112                 !mSensorPrivacyManager.isSensorPrivacyEnabled(mToggle.sensor));
113         mSensorPrivacyManager.addSensorPrivacyListener(mToggle.sensor,
114                 mPrivacyChangedListener);
115 
116         if (!FlavorUtils.isTwoPanel(themedContext)) {
117             // Show the toggle info text beneath instead.
118             Preference toggleInfo = new Preference(themedContext);
119             toggleInfo.setLayoutResource(R.xml.sensor_toggle_info);
120             toggleInfo.setSummary(mToggle.toggleInfoText);
121             toggleInfo.setSelectable(false);
122             screen.addPreference(toggleInfo);
123         }
124     }
125 
126     /**
127      * Adds section that shows an expandable list of apps that have recently accessed the sensor.
128      */
addRecentAppsGroup(PreferenceScreen screen, Context themedContext)129     private void addRecentAppsGroup(PreferenceScreen screen, Context themedContext) {
130         // Create the Recently Accessed By section.
131         PreferenceCategory recentRequests = new PreferenceCategory(themedContext);
132         recentRequests.setTitle(R.string.recently_accessed_by_category);
133         screen.addPreference(recentRequests);
134 
135         // Get recent accesses.
136         List<RecentlyAccessedByUtils.App> recentApps = RecentlyAccessedByUtils.getAppList(
137                 themedContext, mToggle.appOps);
138         if (DEBUG) Log.v(TAG, "recently accessed by " + recentApps.size() + " apps");
139 
140         // Create a preference for each access.
141         mAllRecentAppPrefs = new ArrayList<>(recentApps.size());
142         for (RecentlyAccessedByUtils.App app : recentApps) {
143             if (DEBUG) Log.v(TAG, "last access: " + app.mLastAccess);
144             Preference pref = new Preference(themedContext);
145             pref.setTitle(app.mLabel);
146             pref.setIcon(app.mIcon);
147             pref.setFragment(AppManagementFragment.class.getName());
148             AppManagementFragment.prepareArgs(pref.getExtras(), app.mPackageName);
149             mAllRecentAppPrefs.add(pref);
150         }
151 
152         for (int i = 0; i < MAX_RECENT_APPS_COLLAPSED; i++) {
153             if (mAllRecentAppPrefs.size() > i) {
154                 recentRequests.addPreference(mAllRecentAppPrefs.get(i));
155             }
156         }
157         if (mAllRecentAppPrefs.size() > MAX_RECENT_APPS_COLLAPSED) {
158             Preference showAllRecent = new Preference(themedContext);
159             showAllRecent.setTitle(R.string.recently_accessed_show_all);
160             showAllRecent.setOnPreferenceClickListener(preference -> {
161                 preference.setVisible(false);
162                 for (int i = MAX_RECENT_APPS_COLLAPSED; i < mAllRecentAppPrefs.size(); i++) {
163                     recentRequests.addPreference(mAllRecentAppPrefs.get(i));
164                 }
165                 return false;
166             });
167             recentRequests.addPreference(showAllRecent);
168         }
169 
170         if (mAllRecentAppPrefs.size() == 0) {
171             Preference banner = new Preference(themedContext);
172             banner.setSummary(R.string.no_recent_sensor_accesses);
173             banner.setSelectable(false);
174             recentRequests.addPreference(banner);
175         }
176     }
177 
178     /**
179      * Adds a preference that opens the overview of the PermissionGroup pertaining to the sensor.
180      */
addPermissionControllerPreference(PreferenceScreen screen, Context themedContext)181     private void addPermissionControllerPreference(PreferenceScreen screen, Context themedContext) {
182         Preference openPermissionController = new Preference(themedContext);
183         openPermissionController.setTitle(mToggle.appPermissionsTitle);
184         Intent showSensorPermissions = new Intent(Intent.ACTION_MANAGE_PERMISSION_APPS);
185         showSensorPermissions.putExtra(Intent.EXTRA_PERMISSION_NAME,
186                 mToggle.permissionsGroupName);
187         openPermissionController.setIntent(showSensorPermissions);
188         screen.addPreference(openPermissionController);
189     }
190 
191     @Override
onDestroy()192     public void onDestroy() {
193         mSensorPrivacyManager.removeSensorPrivacyListener(mToggle.sensor, mPrivacyChangedListener);
194         super.onDestroy();
195     }
196 
197     @Override
onPreferenceTreeClick(Preference preference)198     public boolean onPreferenceTreeClick(Preference preference) {
199         if (SENSOR_TOGGLE_KEY.equals(preference.getKey())) {
200             mSensorPrivacyManager.setSensorPrivacy(SETTINGS, mToggle.sensor,
201                     !mSensorToggle.isChecked());
202             return true;
203         }
204         return super.onPreferenceTreeClick(preference);
205     }
206 }
207