• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 package com.android.settings.location;
15 
16 import static android.Manifest.permission_group.LOCATION;
17 
18 import android.content.ActivityNotFoundException;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.icu.text.RelativeDateTimeFormatter;
22 import android.location.LocationManager;
23 import android.os.UserHandle;
24 import android.os.UserManager;
25 import android.provider.DeviceConfig;
26 import android.provider.Settings;
27 import android.util.Log;
28 
29 import androidx.annotation.VisibleForTesting;
30 import androidx.preference.Preference;
31 import androidx.preference.PreferenceCategory;
32 import androidx.preference.PreferenceScreen;
33 
34 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
35 import com.android.settings.R;
36 import com.android.settings.dashboard.DashboardFragment;
37 import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
38 import com.android.settingslib.applications.RecentAppOpsAccess;
39 import com.android.settingslib.utils.StringUtil;
40 import com.android.settingslib.widget.AppPreference;
41 
42 import java.util.ArrayList;
43 import java.util.List;
44 
45 /**
46  * Preference controller that handles the display of apps that access locations.
47  */
48 public class RecentLocationAccessPreferenceController extends LocationBasePreferenceController {
49     private static final String TAG = RecentLocationAccessPreferenceController.class
50             .getSimpleName();
51     public static final int MAX_APPS = 3;
52     @VisibleForTesting
53     RecentAppOpsAccess mRecentLocationApps;
54     private PreferenceCategory mCategoryRecentLocationRequests;
55     private int mType = ProfileSelectFragment.ProfileType.ALL;
56     private boolean mShowSystem = false;
57     private boolean mSystemSettingChanged = false;
58 
59     @VisibleForTesting
60     static class PackageEntryClickedListener implements
61             Preference.OnPreferenceClickListener {
62         private final Context mContext;
63         private final String mPackage;
64         private final UserHandle mUserHandle;
65 
PackageEntryClickedListener(Context context, String packageName, UserHandle userHandle)66         PackageEntryClickedListener(Context context, String packageName,
67                 UserHandle userHandle) {
68             mContext = context;
69             mPackage = packageName;
70             mUserHandle = userHandle;
71         }
72 
73         @Override
onPreferenceClick(Preference preference)74         public boolean onPreferenceClick(Preference preference) {
75             if (mPackage.equals(mContext.getSystemService(LocationManager.class)
76                     .getExtraLocationControllerPackage())) {
77                 try {
78                     mContext.startActivityAsUser(
79                             new Intent(Settings.ACTION_LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS),
80                             mUserHandle);
81                 } catch (ActivityNotFoundException e) {
82                     // In rare cases where location controller extra package is set, but
83                     // no activity exists to handle the location controller extra package settings
84                     // intent, log an error instead of crashing.
85                     Log.e(TAG, "No activity to handle "
86                             + "android.settings.LOCATION_CONTROLLER_EXTRA_PACKAGE_SETTINGS");
87                 }
88             } else {
89                 final Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSION);
90                 intent.setPackage(mContext.getPackageManager()
91                         .getPermissionControllerPackageName());
92                 intent.putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, LOCATION);
93                 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mPackage);
94                 intent.putExtra(Intent.EXTRA_USER, mUserHandle);
95                 mContext.startActivity(intent);
96             }
97             return true;
98         }
99     }
100 
RecentLocationAccessPreferenceController(Context context, String key)101     public RecentLocationAccessPreferenceController(Context context, String key) {
102         this(context, key, RecentAppOpsAccess.createForLocation(context));
103     }
104 
105     @VisibleForTesting
RecentLocationAccessPreferenceController(Context context, String key, RecentAppOpsAccess recentLocationApps)106     public RecentLocationAccessPreferenceController(Context context, String key,
107             RecentAppOpsAccess recentLocationApps) {
108         super(context, key);
109         mRecentLocationApps = recentLocationApps;
110         mShowSystem = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
111                 SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED, false)
112                 ? Settings.Secure.getInt(mContext.getContentResolver(),
113                 Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, 0) == 1
114                 : false;
115     }
116 
117     @Override
displayPreference(PreferenceScreen screen)118     public void displayPreference(PreferenceScreen screen) {
119         super.displayPreference(screen);
120         mCategoryRecentLocationRequests = screen.findPreference(getPreferenceKey());
121         mLocationEnabler.refreshLocationMode();
122         loadRecentAccesses();
123     }
124 
125     @Override
updateState(Preference preference)126     public void updateState(Preference preference) {
127         // Only reload the recent accesses in updateState if the system setting has changed.
128         if (mSystemSettingChanged) {
129             loadRecentAccesses();
130             mSystemSettingChanged = false;
131         }
132     }
133 
loadRecentAccesses()134     private void loadRecentAccesses() {
135         mCategoryRecentLocationRequests.removeAll();
136         final Context prefContext = mCategoryRecentLocationRequests.getContext();
137         final List<RecentAppOpsAccess.Access> recentLocationAccesses = new ArrayList<>();
138         final UserManager userManager = UserManager.get(mContext);
139         for (RecentAppOpsAccess.Access access : mRecentLocationApps.getAppListSorted(mShowSystem)) {
140             if (isRequestMatchesProfileType(userManager, access, mType)) {
141                 recentLocationAccesses.add(access);
142                 if (recentLocationAccesses.size() == MAX_APPS) {
143                     break;
144                 }
145             }
146         }
147 
148         if (recentLocationAccesses.size() > 0) {
149             // Add preferences to container in original order (already sorted by recency).
150             for (RecentAppOpsAccess.Access access : recentLocationAccesses) {
151                 mCategoryRecentLocationRequests.addPreference(
152                         createAppPreference(prefContext, access, mFragment));
153             }
154         } else {
155             // If there's no item to display, add a "No recent apps" item.
156             final Preference banner = new AppPreference(prefContext);
157             banner.setTitle(R.string.location_no_recent_accesses);
158             banner.setSelectable(false);
159             mCategoryRecentLocationRequests.addPreference(banner);
160         }
161     }
162 
163     @Override
onLocationModeChanged(int mode, boolean restricted)164     public void onLocationModeChanged(int mode, boolean restricted) {
165         boolean enabled = mLocationEnabler.isEnabled(mode);
166         mCategoryRecentLocationRequests.setVisible(enabled);
167     }
168 
169     /**
170      * Clears the list of apps which recently accessed location from the screen.
171      */
clearPreferenceList()172     public void clearPreferenceList() {
173         if (mCategoryRecentLocationRequests != null) {
174             mCategoryRecentLocationRequests.removeAll();
175         }
176     }
177 
178     /**
179      * Initialize {@link ProfileSelectFragment.ProfileType} of the controller
180      *
181      * @param type {@link ProfileSelectFragment.ProfileType} of the controller.
182      */
setProfileType(@rofileSelectFragment.ProfileType int type)183     public void setProfileType(@ProfileSelectFragment.ProfileType int type) {
184         mType = type;
185     }
186 
187     /**
188      * Create a {@link AppPreference}
189      */
createAppPreference(Context prefContext, RecentAppOpsAccess.Access access, DashboardFragment fragment)190     public static AppPreference createAppPreference(Context prefContext,
191             RecentAppOpsAccess.Access access, DashboardFragment fragment) {
192         final AppPreference pref = new AppPreference(prefContext);
193         pref.setIcon(access.icon);
194         pref.setTitle(access.label);
195         pref.setSummary(StringUtil.formatRelativeTime(prefContext,
196                 System.currentTimeMillis() - access.accessFinishTime, false,
197                 RelativeDateTimeFormatter.Style.LONG));
198         pref.setOnPreferenceClickListener(new PackageEntryClickedListener(
199                 fragment.getContext(), access.packageName, access.userHandle));
200         return pref;
201     }
202 
203     /**
204      * Return if the {@link RecentAppOpsAccess.Access} matches current UI
205      * {@link ProfileSelectFragment.ProfileType}
206      */
isRequestMatchesProfileType(UserManager userManager, RecentAppOpsAccess.Access access, @ProfileSelectFragment.ProfileType int type)207     public static boolean isRequestMatchesProfileType(UserManager userManager,
208             RecentAppOpsAccess.Access access, @ProfileSelectFragment.ProfileType int type) {
209 
210         final boolean isWorkProfile = userManager.isManagedProfile(
211                 access.userHandle.getIdentifier());
212         if (isWorkProfile && (type & ProfileSelectFragment.ProfileType.WORK) != 0) {
213             return true;
214         }
215         if (!isWorkProfile && (type & ProfileSelectFragment.ProfileType.PERSONAL) != 0) {
216             return true;
217         }
218         return false;
219     }
220 
221     /**
222      * Update the state of the showSystem setting flag and load the new results.
223      */
updateShowSystem()224     void updateShowSystem() {
225         mSystemSettingChanged = true;
226         mShowSystem = !mShowSystem;
227         clearPreferenceList();
228         loadRecentAccesses();
229     }
230 }
231