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 android.app.AppOpsManager;
20 import android.content.Context;
21 import android.content.pm.ApplicationInfo;
22 import android.content.pm.PackageManager;
23 import android.graphics.drawable.Drawable;
24 import android.os.Process;
25 import android.os.UserHandle;
26 import android.os.UserManager;
27 import android.text.format.DateUtils;
28 import android.util.IconDrawableFactory;
29 import android.util.Log;
30 
31 import java.util.ArrayList;
32 import java.util.Comparator;
33 import java.util.List;
34 
35 /**
36  * Helper class for the 'Recently Accessed By' sections.
37  */
38 public class RecentlyAccessedByUtils {
39 
40     // Get the last 24 hours of app ops access information.
41     private static final long RECENT_TIME_INTERVAL_MILLIS = DateUtils.DAY_IN_MILLIS;
42     private static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
43 
44     private static final String TAG = RecentlyAccessedByUtils.class.getSimpleName();
45     private static final boolean DEBUG = false;
46 
47     /**
48      * Gets list of apps that recently used the given AppOps sorted by the most recent access.
49      */
getAppList(Context context, int[] appOps)50     public static List<App> getAppList(Context context, int[] appOps) {
51         PackageManager packageManager = context.getPackageManager();
52         IconDrawableFactory iconDrawableFactory = IconDrawableFactory.newInstance(context);
53         long currentTime = System.currentTimeMillis();
54 
55         List<AppOpsManager.PackageOps> packageOps = context.getSystemService(
56                 AppOpsManager.class).getPackagesForOps(appOps);
57         int packageOpsCount = packageOps != null ? packageOps.size() : 0;
58         ArrayList<App> recentApps = new ArrayList<>(packageOpsCount);
59 
60         List<UserHandle> currentUserProfiles = context.getSystemService(
61                 UserManager.class).getUserProfiles();
62 
63         for (int i = 0; i < packageOpsCount; i++) {
64             AppOpsManager.PackageOps ops = packageOps.get(i);
65 
66             // Don't include app ops usages from the system or other users and restricted profiles
67             boolean isSystem = UserHandle.isSameApp(ops.getUid(), Process.SYSTEM_UID)
68                     && ANDROID_SYSTEM_PACKAGE_NAME.equals(ops.getPackageName());
69             boolean isAppOpByCurrentUser = currentUserProfiles.contains(
70                     UserHandle.getUserHandleForUid(ops.getUid()));
71             if (isSystem || !isAppOpByCurrentUser) {
72                 continue;
73             }
74 
75             App recentApp = getRecentRequestFromOps(packageManager, currentTime, ops,
76                     iconDrawableFactory);
77             if (recentApp != null) {
78                 recentApps.add(recentApp);
79             }
80         }
81 
82         // Sort by most recent access.
83         recentApps.sort(Comparator.comparingLong(app -> ((App) app).mLastAccess).reversed());
84 
85         return recentApps;
86     }
87 
getRecentRequestFromOps(PackageManager packageManager, long currentTime, AppOpsManager.PackageOps ops, IconDrawableFactory iconDrawableFactory)88     private static App getRecentRequestFromOps(PackageManager packageManager, long currentTime,
89             AppOpsManager.PackageOps ops, IconDrawableFactory iconDrawableFactory) {
90         String packageName = ops.getPackageName();
91         List<AppOpsManager.OpEntry> entries = ops.getOps();
92         long recentAccessCutoffTime = currentTime - RECENT_TIME_INTERVAL_MILLIS;
93 
94         if (DEBUG) Log.v(TAG, "package: " + packageName + ", entries: " + entries.size());
95         long mostRecentAccessEnd = 0;
96         for (AppOpsManager.OpEntry entry : entries) {
97             long accessEnd = entry.getLastAccessTime(AppOpsManager.OP_FLAGS_ALL)
98                     + entry.getLastDuration(AppOpsManager.OP_FLAGS_ALL);
99 
100             if (entry.isRunning()) {
101                 mostRecentAccessEnd = currentTime;
102             } else if (accessEnd >= recentAccessCutoffTime && accessEnd >= mostRecentAccessEnd) {
103                 mostRecentAccessEnd = accessEnd;
104             }
105         }
106 
107         if (mostRecentAccessEnd <= 0) {
108             return null;
109         }
110 
111         ApplicationInfo appInfo;
112         try {
113             appInfo = packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
114         } catch (PackageManager.NameNotFoundException e) {
115             Log.w(TAG, "package name could not be found for " + packageName);
116             return null;
117         }
118 
119         Drawable icon = iconDrawableFactory.getBadgedIcon(appInfo);
120         CharSequence appLabel = packageManager.getApplicationLabel(appInfo);
121         return new App(packageName, icon, appLabel, mostRecentAccessEnd);
122     }
123 
124     public static class App {
125         public final String mPackageName;
126         public final Drawable mIcon;
127         public final CharSequence mLabel;
128         public final long mLastAccess;
129 
App(String packageName, Drawable icon, CharSequence label, long lastAccess)130         private App(String packageName, Drawable icon, CharSequence label, long lastAccess) {
131             this.mPackageName = packageName;
132             this.mIcon = icon;
133             this.mLabel = label;
134             this.mLastAccess = lastAccess;
135         }
136     }
137 }
138