• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.device.apps;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.pm.ApplicationInfo;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.os.SystemClock;
25 import android.support.annotation.NonNull;
26 import android.support.v17.preference.LeanbackPreferenceFragment;
27 import android.support.v7.preference.Preference;
28 import android.support.v7.preference.PreferenceCategory;
29 import android.support.v7.preference.PreferenceGroup;
30 import android.support.v7.preference.PreferenceScreen;
31 import android.text.TextUtils;
32 import android.util.ArrayMap;
33 import android.util.ArraySet;
34 import android.util.Log;
35 
36 import com.android.settingslib.applications.ApplicationsState;
37 import com.android.tv.settings.R;
38 
39 import java.util.ArrayList;
40 import java.util.Map;
41 import java.util.Set;
42 
43 public class AppsFragment extends LeanbackPreferenceFragment {
44 
45     private static final String TAG = "AppsFragment";
46 
47     private ApplicationsState mApplicationsState;
48     private ApplicationsState.Session mSessionSystem;
49     private ApplicationsState.AppFilter mFilterSystem;
50     private ApplicationsState.Session mSessionDownloaded;
51     private ApplicationsState.AppFilter mFilterDownloaded;
52 
53     private PreferenceGroup mSystemPreferenceGroup;
54     private PreferenceGroup mDownloadedPreferenceGroup;
55 
56     private final Handler mHandler = new Handler();
57     private final Map<PreferenceGroup,
58             ArrayList<ApplicationsState.AppEntry>> mUpdateMap = new ArrayMap<>(3);
59     private long mRunAt = Long.MIN_VALUE;
60     private final Runnable mUpdateRunnable = new Runnable() {
61         @Override
62         public void run() {
63             for (final PreferenceGroup group : mUpdateMap.keySet()) {
64                 final ArrayList<ApplicationsState.AppEntry> entries = mUpdateMap.get(group);
65                 updateAppListInternal(group, entries);
66             }
67             mUpdateMap.clear();
68             mRunAt = 0;
69         }
70     };
71 
prepareArgs(Bundle b, String volumeUuid, String volumeName)72     public static void prepareArgs(Bundle b, String volumeUuid, String volumeName) {
73         b.putString(AppsActivity.EXTRA_VOLUME_UUID, volumeUuid);
74         b.putString(AppsActivity.EXTRA_VOLUME_NAME, volumeName);
75     }
76 
newInstance(String volumeUuid, String volumeName)77     public static AppsFragment newInstance(String volumeUuid, String volumeName) {
78         final Bundle b = new Bundle(2);
79         prepareArgs(b, volumeUuid, volumeName);
80         final AppsFragment f = new AppsFragment();
81         f.setArguments(b);
82         return f;
83     }
84 
85     @Override
onActivityCreated(Bundle savedInstanceState)86     public void onActivityCreated(Bundle savedInstanceState) {
87         super.onActivityCreated(savedInstanceState);
88         mApplicationsState = ApplicationsState.getInstance(getActivity().getApplication());
89 
90         final String volumeUuid = getArguments().getString(AppsActivity.EXTRA_VOLUME_UUID);
91         final String volumeName = getArguments().getString(AppsActivity.EXTRA_VOLUME_NAME);
92 
93         // The UUID of internal storage is null, so we check if there's a volume name to see if we
94         // should only be showing the apps on the internal storage or all apps.
95         if (!TextUtils.isEmpty(volumeUuid) || !TextUtils.isEmpty(volumeName)) {
96             ApplicationsState.AppFilter volumeFilter =
97                     new ApplicationsState.VolumeFilter(volumeUuid);
98 
99             mFilterSystem =
100                     new ApplicationsState.CompoundFilter(FILTER_SYSTEM, volumeFilter);
101             mFilterDownloaded =
102                     new ApplicationsState.CompoundFilter(FILTER_DOWNLOADED, volumeFilter);
103         } else {
104             mFilterSystem = FILTER_SYSTEM;
105             mFilterDownloaded = FILTER_DOWNLOADED;
106         }
107 
108         mSessionSystem = mApplicationsState.newSession(new RowUpdateCallbacks() {
109             @Override
110             protected void doRebuild() {
111                 rebuildSystem();
112             }
113 
114             @Override
115             public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
116                 updateAppList(mSystemPreferenceGroup, apps);
117             }
118         });
119         rebuildSystem();
120 
121         mSessionDownloaded = mApplicationsState.newSession(new RowUpdateCallbacks() {
122             @Override
123             protected void doRebuild() {
124                 rebuildDownloaded();
125             }
126 
127             @Override
128             public void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps) {
129                 updateAppList(mDownloadedPreferenceGroup, apps);
130             }
131         });
132         rebuildDownloaded();
133 
134     }
135 
136     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)137     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
138         final Context context = getPreferenceManager().getContext();
139 
140         final PreferenceScreen screen =
141                 getPreferenceManager().createPreferenceScreen(context);
142         screen.setTitle(R.string.device_apps);
143         // TODO: show volume name somewhere?
144 
145         final String volumeUuid = getArguments().getString(AppsActivity.EXTRA_VOLUME_UUID);
146 
147         if (TextUtils.isEmpty(volumeUuid)) {
148             final Preference permissionsPreference = new Preference(context);
149             permissionsPreference.setKey("Permissions");
150             permissionsPreference.setTitle(R.string.device_apps_permissions);
151             permissionsPreference.setIntent(new Intent(Intent.ACTION_MANAGE_PERMISSIONS));
152             screen.addPreference(permissionsPreference);
153         }
154         mDownloadedPreferenceGroup = new PreferenceCategory(context);
155         mDownloadedPreferenceGroup.setKey("DownloadedPreferenceGroup");
156         mDownloadedPreferenceGroup.setTitle(R.string.apps_downloaded);
157         mDownloadedPreferenceGroup.setOrderingAsAdded(false);
158         screen.addPreference(mDownloadedPreferenceGroup);
159 
160         if (TextUtils.isEmpty(volumeUuid)) {
161             mSystemPreferenceGroup = new PreferenceCategory(context);
162             mSystemPreferenceGroup.setKey("SystemPreferenceGroup");
163             mSystemPreferenceGroup.setTitle(R.string.apps_system);
164             mSystemPreferenceGroup.setOrderingAsAdded(false);
165             screen.addPreference(mSystemPreferenceGroup);
166         }
167 
168         setPreferenceScreen(screen);
169     }
170 
171     @Override
onResume()172     public void onResume() {
173         super.onResume();
174         mSessionSystem.resume();
175         mSessionDownloaded.resume();
176     }
177 
178     @Override
onPause()179     public void onPause() {
180         super.onPause();
181         mSessionSystem.pause();
182         mSessionDownloaded.pause();
183     }
184 
rebuildSystem()185     private void rebuildSystem() {
186         ArrayList<ApplicationsState.AppEntry> apps =
187                 mSessionSystem.rebuild(mFilterSystem, ApplicationsState.ALPHA_COMPARATOR);
188         if (apps != null) {
189             updateAppList(mSystemPreferenceGroup, apps);
190         }
191     }
192 
rebuildDownloaded()193     private void rebuildDownloaded() {
194         ArrayList<ApplicationsState.AppEntry> apps =
195                 mSessionDownloaded.rebuild(mFilterDownloaded, ApplicationsState.ALPHA_COMPARATOR);
196         if (apps != null) {
197             updateAppList(mDownloadedPreferenceGroup, apps);
198         }
199     }
200 
updateAppList(PreferenceGroup group, ArrayList<ApplicationsState.AppEntry> entries)201     private void updateAppList(PreferenceGroup group,
202             ArrayList<ApplicationsState.AppEntry> entries) {
203         if (group == null) {
204             Log.d(TAG, "Not updating list for null group");
205             return;
206         }
207         mUpdateMap.put(group, entries);
208 
209         // We can get spammed with updates, so coalesce them to reduce jank and flicker
210         if (mRunAt == Long.MIN_VALUE) {
211             // First run, no delay
212             mHandler.removeCallbacks(mUpdateRunnable);
213             mHandler.post(mUpdateRunnable);
214         } else {
215             if (mRunAt == 0) {
216                 mRunAt = SystemClock.uptimeMillis() + 1000;
217             }
218             int delay = (int) (mRunAt - SystemClock.uptimeMillis());
219             delay = delay < 0 ? 0 : delay;
220 
221             mHandler.removeCallbacks(mUpdateRunnable);
222             mHandler.postDelayed(mUpdateRunnable, delay);
223         }
224     }
225 
226     private void updateAppListInternal(PreferenceGroup group,
227             ArrayList<ApplicationsState.AppEntry> entries) {
228         if (entries != null) {
229             final Set<String> touched = new ArraySet<>(entries.size());
230             for (final ApplicationsState.AppEntry entry : entries) {
231                 final String packageName = entry.info.packageName;
232                 Preference recycle = group.findPreference(packageName);
233                 if (recycle == null) {
234                     recycle = new Preference(getPreferenceManager().getContext());
235                 }
236                 final Preference newPref = bindPreference(recycle, entry);
237                 group.addPreference(newPref);
238                 touched.add(packageName);
239             }
240             for (int i = 0; i < group.getPreferenceCount();) {
241                 final Preference pref = group.getPreference(i);
242                 if (touched.contains(pref.getKey())) {
243                     i++;
244                 } else {
245                     group.removePreference(pref);
246                 }
247             }
248         }
249     }
250 
251     /**
252      * Creates or updates a preference according to an {@link ApplicationsState.AppEntry} object
253      * @param preference If non-null, updates this preference object, otherwise creates a new one
254      * @param entry Info to populate preference
255      * @return Updated preference entry
256      */
257     private Preference bindPreference(@NonNull Preference preference,
258             ApplicationsState.AppEntry entry) {
259         preference.setKey(entry.info.packageName);
260         entry.ensureLabel(getContext());
261         preference.setTitle(entry.label);
262         preference.setSummary(entry.sizeStr);
263         preference.setFragment(AppManagementFragment.class.getName());
264         AppManagementFragment.prepareArgs(preference.getExtras(), entry.info.packageName);
265         preference.setIcon(entry.icon);
266         return preference;
267     }
268 
269     private abstract class RowUpdateCallbacks implements ApplicationsState.Callbacks {
270 
271         protected abstract void doRebuild();
272 
273         @Override
274         public void onRunningStateChanged(boolean running) {
275             doRebuild();
276         }
277 
278         @Override
279         public void onPackageListChanged() {
280             doRebuild();
281         }
282 
283         @Override
284         public void onPackageIconChanged() {
285             doRebuild();
286         }
287 
288         @Override
289         public void onPackageSizeChanged(String packageName) {
290             doRebuild();
291         }
292 
293         @Override
294         public void onAllSizesComputed() {
295             doRebuild();
296         }
297 
298         @Override
299         public void onLauncherInfoChanged() {
300             doRebuild();
301         }
302 
303         @Override
304         public void onLoadEntriesCompleted() {
305             doRebuild();
306         }
307     }
308 
309     private static final ApplicationsState.AppFilter FILTER_SYSTEM =
310             new ApplicationsState.AppFilter() {
311 
312                 @Override
313                 public void init() {}
314 
315                 @Override
316                 public boolean filterApp(ApplicationsState.AppEntry info) {
317                     return (info.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
318                 }
319             };
320 
321     private static final ApplicationsState.AppFilter FILTER_DOWNLOADED =
322             new ApplicationsState.AppFilter() {
323 
324                 @Override
325                 public void init() {}
326 
327                 @Override
328                 public boolean filterApp(ApplicationsState.AppEntry info) {
329                     return (info.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0;
330                 }
331             };
332 
333 }
334