diff options
Diffstat (limited to 'unbundle/java/com/android/tv/settings/unbundle/sdklib/RecentLocationAppsCompat.java')
-rw-r--r-- | unbundle/java/com/android/tv/settings/unbundle/sdklib/RecentLocationAppsCompat.java | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/unbundle/java/com/android/tv/settings/unbundle/sdklib/RecentLocationAppsCompat.java b/unbundle/java/com/android/tv/settings/unbundle/sdklib/RecentLocationAppsCompat.java new file mode 100644 index 000000000..9e1d66289 --- /dev/null +++ b/unbundle/java/com/android/tv/settings/unbundle/sdklib/RecentLocationAppsCompat.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tv.settings.unbundle.sdklib; + +import android.app.AppOpsManager; +import android.content.Context; +import android.content.PermissionChecker; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.UserHandle; +import android.os.UserManager; +import android.text.format.DateUtils; +import android.util.IconDrawableFactory; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class RecentLocationAppsCompat { + private static final String TAG = RecentLocationAppsCompat.class.getSimpleName(); + static final String ANDROID_SYSTEM_PACKAGE_NAME = "android"; + + // Keep last 24 hours of location app information. + private static final long RECENT_TIME_INTERVAL_MILLIS = DateUtils.DAY_IN_MILLIS; + + static final int[] LOCATION_REQUEST_OPS = new int[]{ + AppOpsManager.OP_MONITOR_LOCATION, + AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION, + }; + static final int[] LOCATION_PERMISSION_OPS = new int[]{ + AppOpsManager.OP_FINE_LOCATION, + AppOpsManager.OP_COARSE_LOCATION, + }; + + private final PackageManager mPackageManager; + private final Context mContext; + private final IconDrawableFactory mDrawableFactory; + + public RecentLocationAppsCompat(Context context) { + mContext = context; + mPackageManager = context.getPackageManager(); + mDrawableFactory = IconDrawableFactory.newInstance(context); + } + + /** + * Fills a list of applications which queried location recently within specified time. + * Apps are sorted by recency. Apps with more recent location requests are in the front. + */ + public List<Request> getAppList(boolean showSystemApps) { + // Retrieve a location usage list from AppOps + PackageManager pm = mContext.getPackageManager(); + // Retrieve a location usage list from AppOps + AppOpsManager aoManager = + (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(LOCATION_REQUEST_OPS); + + final int appOpsCount = appOps != null ? appOps.size() : 0; + + // Process the AppOps list and generate a preference list. + ArrayList<Request> requests = new ArrayList<>(appOpsCount); + final long now = System.currentTimeMillis(); + final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + final List<UserHandle> profiles = um.getUserProfiles(); + + for (int i = 0; i < appOpsCount; ++i) { + AppOpsManager.PackageOps ops = appOps.get(i); + // Don't show the Android System in the list - it's not actionable for the user. + // Also don't show apps belonging to background users except managed users. + String packageName = ops.getPackageName(); + int uid = ops.getUid(); + final UserHandle user = UserHandle.getUserHandleForUid(uid); + + boolean isAndroidOs = + (uid == android.os.Process.SYSTEM_UID) && ANDROID_SYSTEM_PACKAGE_NAME.equals( + packageName); + if (isAndroidOs || !profiles.contains(user)) { + continue; + } + + // Don't show apps that do not have user sensitive location permissions + boolean showApp = true; + if (!showSystemApps) { + for (int op : LOCATION_PERMISSION_OPS) { + final String permission = AppOpsManager.opToPermission(op); + final int permissionFlags = pm.getPermissionFlags(permission, packageName, + user); + if (PermissionChecker.checkPermissionForPreflight(mContext, permission, + PermissionChecker.PID_UNKNOWN, uid, packageName) + == PermissionChecker.PERMISSION_GRANTED) { + if ((permissionFlags + & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) + == 0) { + showApp = false; + break; + } + } else { + if ((permissionFlags + & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED) == 0) { + showApp = false; + break; + } + } + } + } + if (showApp) { + Request request = getRequestFromOps(now, ops); + if (request != null) { + requests.add(request); + } + } + } + return requests; + } + + /** + * Gets a list of apps that requested for location recently, sorting by recency. + * + * @param showSystemApps whether includes system apps in the list. + * @return the list of apps that recently requested for location. + */ + public List<Request> getAppListSorted(boolean showSystemApps) { + List<Request> requests = getAppList(showSystemApps); + // Sort the list of Requests by recency. Most recent request first. + Collections.sort(requests, Collections.reverseOrder(new Comparator<Request>() { + @Override + public int compare(Request request1, Request request2) { + return Long.compare(request1.requestFinishTime, request2.requestFinishTime); + } + })); + return requests; + } + + /** + * Creates a Request entry for the given PackageOps. + * + * This method examines the time interval of the PackageOps first. If the PackageOps is older + * than the designated interval, this method ignores the PackageOps object and returns null. + * When the PackageOps is fresh enough, this method returns a Request object for the package + */ + private Request getRequestFromOps(long now, + AppOpsManager.PackageOps ops) { + String packageName = ops.getPackageName(); + List<AppOpsManager.OpEntry> entries = ops.getOps(); + boolean highBattery = false; + boolean normalBattery = false; + long locationRequestFinishTime = 0L; + // Earliest time for a location request to end and still be shown in list. + long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS; + for (AppOpsManager.OpEntry entry : entries) { + if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) { + locationRequestFinishTime = entry.getTime() + entry.getDuration(); + switch (entry.getOp()) { + case AppOpsManager.OP_MONITOR_LOCATION: + normalBattery = true; + break; + case AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION: + highBattery = true; + break; + default: + break; + } + } + } + + if (!highBattery && !normalBattery) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, packageName + " hadn't used location within the time interval."); + } + return null; + } + + // The package is fresh enough, continue. + int uid = ops.getUid(); + int userId = UserHandle.getUserId(uid); + + Request request = null; + try { + ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser( + packageName, PackageManager.GET_META_DATA, userId); + if (appInfo == null) { + Log.w(TAG, "Null application info retrieved for package " + packageName + + ", userId " + userId); + return null; + } + + final UserHandle userHandle = new UserHandle(userId); + Drawable icon = mDrawableFactory.getBadgedIcon(appInfo, userId); + CharSequence appLabel = mPackageManager.getApplicationLabel(appInfo); + CharSequence badgedAppLabel = mPackageManager.getUserBadgedLabel(appLabel, userHandle); + if (appLabel.toString().contentEquals(badgedAppLabel)) { + // If badged label is not different from original then no need for it as + // a separate content description. + badgedAppLabel = null; + } + request = new Request(packageName, userHandle, icon, appLabel, highBattery, + badgedAppLabel, locationRequestFinishTime); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "package name not found for " + packageName + ", userId " + userId); + } + return request; + } + + public static class Request { + public final String packageName; + public final UserHandle userHandle; + public final Drawable icon; + public final CharSequence label; + public final boolean isHighBattery; + public final CharSequence contentDescription; + public final long requestFinishTime; + + public Request(String packageName, UserHandle userHandle, Drawable icon, + CharSequence label, boolean isHighBattery, CharSequence contentDescription, + long requestFinishTime) { + this.packageName = packageName; + this.userHandle = userHandle; + this.icon = icon; + this.label = label; + this.isHighBattery = isHighBattery; + this.contentDescription = contentDescription; + this.requestFinishTime = requestFinishTime; + } + } +} |