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.Context; 19 import android.content.Intent; 20 import android.icu.text.RelativeDateTimeFormatter; 21 import android.os.UserHandle; 22 import android.os.UserManager; 23 24 import androidx.annotation.VisibleForTesting; 25 import androidx.preference.Preference; 26 import androidx.preference.PreferenceCategory; 27 import androidx.preference.PreferenceScreen; 28 29 import com.android.settings.R; 30 import com.android.settings.dashboard.DashboardFragment; 31 import com.android.settings.dashboard.profileselector.ProfileSelectFragment; 32 import com.android.settingslib.applications.RecentAppOpsAccess; 33 import com.android.settingslib.utils.StringUtil; 34 import com.android.settingslib.widget.AppPreference; 35 36 import java.util.ArrayList; 37 import java.util.List; 38 39 /** 40 * Preference controller that handles the display of apps that access locations. 41 */ 42 public class RecentLocationAccessPreferenceController extends LocationBasePreferenceController { 43 public static final int MAX_APPS = 3; 44 @VisibleForTesting 45 RecentAppOpsAccess mRecentLocationApps; 46 private PreferenceCategory mCategoryRecentLocationRequests; 47 private int mType = ProfileSelectFragment.ProfileType.ALL; 48 49 private static class PackageEntryClickedListener implements 50 Preference.OnPreferenceClickListener { 51 private final Context mContext; 52 private final String mPackage; 53 private final UserHandle mUserHandle; 54 PackageEntryClickedListener(Context context, String packageName, UserHandle userHandle)55 PackageEntryClickedListener(Context context, String packageName, 56 UserHandle userHandle) { 57 mContext = context; 58 mPackage = packageName; 59 mUserHandle = userHandle; 60 } 61 62 @Override onPreferenceClick(Preference preference)63 public boolean onPreferenceClick(Preference preference) { 64 final Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSION); 65 intent.putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, LOCATION); 66 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mPackage); 67 intent.putExtra(Intent.EXTRA_USER, mUserHandle); 68 mContext.startActivity(intent); 69 return true; 70 } 71 } 72 RecentLocationAccessPreferenceController(Context context, String key)73 public RecentLocationAccessPreferenceController(Context context, String key) { 74 this(context, key, RecentAppOpsAccess.createForLocation(context)); 75 } 76 77 @VisibleForTesting RecentLocationAccessPreferenceController(Context context, String key, RecentAppOpsAccess recentLocationApps)78 public RecentLocationAccessPreferenceController(Context context, String key, 79 RecentAppOpsAccess recentLocationApps) { 80 super(context, key); 81 mRecentLocationApps = recentLocationApps; 82 } 83 84 @Override displayPreference(PreferenceScreen screen)85 public void displayPreference(PreferenceScreen screen) { 86 super.displayPreference(screen); 87 mCategoryRecentLocationRequests = screen.findPreference(getPreferenceKey()); 88 final Context prefContext = mCategoryRecentLocationRequests.getContext(); 89 final List<RecentAppOpsAccess.Access> recentLocationAccesses = new ArrayList<>(); 90 final UserManager userManager = UserManager.get(mContext); 91 for (RecentAppOpsAccess.Access access : mRecentLocationApps.getAppListSorted( 92 /* showSystemApps= */ false)) { 93 if (isRequestMatchesProfileType(userManager, access, mType)) { 94 recentLocationAccesses.add(access); 95 if (recentLocationAccesses.size() == MAX_APPS) { 96 break; 97 } 98 } 99 } 100 101 if (recentLocationAccesses.size() > 0) { 102 // Add preferences to container in original order (already sorted by recency). 103 for (RecentAppOpsAccess.Access access : recentLocationAccesses) { 104 mCategoryRecentLocationRequests.addPreference( 105 createAppPreference(prefContext, access, mFragment)); 106 } 107 } else { 108 // If there's no item to display, add a "No recent apps" item. 109 final Preference banner = new AppPreference(prefContext); 110 banner.setTitle(R.string.location_no_recent_accesses); 111 banner.setSelectable(false); 112 mCategoryRecentLocationRequests.addPreference(banner); 113 } 114 } 115 116 @Override onLocationModeChanged(int mode, boolean restricted)117 public void onLocationModeChanged(int mode, boolean restricted) { 118 boolean enabled = mLocationEnabler.isEnabled(mode); 119 mCategoryRecentLocationRequests.setVisible(enabled); 120 } 121 122 /** 123 * Initialize {@link ProfileSelectFragment.ProfileType} of the controller 124 * 125 * @param type {@link ProfileSelectFragment.ProfileType} of the controller. 126 */ setProfileType(@rofileSelectFragment.ProfileType int type)127 public void setProfileType(@ProfileSelectFragment.ProfileType int type) { 128 mType = type; 129 } 130 131 /** 132 * Create a {@link AppPreference} 133 */ createAppPreference(Context prefContext, RecentAppOpsAccess.Access access, DashboardFragment fragment)134 public static AppPreference createAppPreference(Context prefContext, 135 RecentAppOpsAccess.Access access, DashboardFragment fragment) { 136 final AppPreference pref = new AppPreference(prefContext); 137 pref.setIcon(access.icon); 138 pref.setTitle(access.label); 139 pref.setSummary(StringUtil.formatRelativeTime(prefContext, 140 System.currentTimeMillis() - access.accessFinishTime, false, 141 RelativeDateTimeFormatter.Style.SHORT)); 142 pref.setOnPreferenceClickListener(new PackageEntryClickedListener( 143 fragment.getContext(), access.packageName, access.userHandle)); 144 return pref; 145 } 146 147 /** 148 * Return if the {@link RecentAppOpsAccess.Access} matches current UI 149 * {@link ProfileSelectFragment.ProfileType} 150 */ isRequestMatchesProfileType(UserManager userManager, RecentAppOpsAccess.Access access, @ProfileSelectFragment.ProfileType int type)151 public static boolean isRequestMatchesProfileType(UserManager userManager, 152 RecentAppOpsAccess.Access access, @ProfileSelectFragment.ProfileType int type) { 153 154 final boolean isWorkProfile = userManager.isManagedProfile( 155 access.userHandle.getIdentifier()); 156 if (isWorkProfile && (type & ProfileSelectFragment.ProfileType.WORK) != 0) { 157 return true; 158 } 159 if (!isWorkProfile && (type & ProfileSelectFragment.ProfileType.PERSONAL) != 0) { 160 return true; 161 } 162 return false; 163 } 164 } 165