1 /*
2  * Copyright (C) 2016 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.settings.display;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.pm.PackageManager;
22 import android.content.pm.ResolveInfo;
23 import android.content.res.TypedArray;
24 import android.graphics.drawable.Drawable;
25 import android.os.UserHandle;
26 import android.util.AttributeSet;
27 import android.util.IconDrawableFactory;
28 import android.util.Log;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.widget.ArrayAdapter;
32 import android.widget.GridView;
33 import android.widget.ImageView;
34 
35 import androidx.annotation.VisibleForTesting;
36 import androidx.core.util.Preconditions;
37 
38 import com.android.settings.R;
39 
40 import java.util.ArrayList;
41 import java.util.Collections;
42 import java.util.List;
43 
44 /**
45  * The grid view for displaying the application entries.
46  *
47  * <p> The attribute value {@code appCount} from XML should be more than or equal to 1, otherwise
48  * throws an {@link IllegalArgumentException}.</p>
49  */
50 public class AppGridView extends GridView {
51     private static final String TAG = "AppGridView";
52 
53     private static final int APP_COUNT_DEF_VALUE = 6;
54     private int mAppCount = APP_COUNT_DEF_VALUE;
55 
AppGridView(Context context)56     public AppGridView(Context context) {
57         super(context);
58         init(context);
59     }
60 
AppGridView(Context context, AttributeSet attrs)61     public AppGridView(Context context, AttributeSet attrs) {
62         super(context, attrs);
63         applyAttributeSet(context, attrs);
64         init(context);
65     }
66 
AppGridView(Context context, AttributeSet attrs, int defStyleAttr)67     public AppGridView(Context context, AttributeSet attrs, int defStyleAttr) {
68         super(context, attrs, defStyleAttr);
69         applyAttributeSet(context, attrs);
70         init(context);
71     }
72 
AppGridView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleResId)73     public AppGridView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleResId) {
74         super(context, attrs, defStyleAttr, defStyleResId);
75         applyAttributeSet(context, attrs);
76         init(context);
77     }
78 
init(Context context)79     private void init(Context context) {
80         setAdapter(new AppsAdapter(context, R.layout.screen_zoom_preview_app_icon,
81                 android.R.id.text1, android.R.id.icon1, mAppCount));
82     }
83 
applyAttributeSet(Context context, AttributeSet attrs)84     private void applyAttributeSet(Context context, AttributeSet attrs) {
85         final TypedArray styledAttrs =
86                 context.obtainStyledAttributes(attrs, R.styleable.AppGridView);
87         mAppCount =
88                 styledAttrs.getInteger(R.styleable.AppGridView_appCount, APP_COUNT_DEF_VALUE);
89         Preconditions.checkArgument(mAppCount >= 1,
90                 /* errorMessage= */ "App count may not be negative or zero");
91 
92         styledAttrs.recycle();
93     }
94 
95     /**
96      * Loads application labels and icons.
97      */
98     @VisibleForTesting
99     public static class AppsAdapter extends ArrayAdapter<ActivityEntry> {
100         private final PackageManager mPackageManager;
101         private final int mIconResId;
102         private final int mAppCount;
103 
AppsAdapter(Context context, int layout, int textResId, int iconResId, int appCount)104         public AppsAdapter(Context context, int layout, int textResId, int iconResId,
105                 int appCount) {
106             super(context, layout, textResId);
107 
108             mIconResId = iconResId;
109             mPackageManager = context.getPackageManager();
110             mAppCount = appCount;
111 
112             loadAllApps();
113         }
114 
115         @Override
getView(int position, View convertView, ViewGroup parent)116         public View getView(int position, View convertView, ViewGroup parent) {
117             final View view = super.getView(position, convertView, parent);
118             final ActivityEntry entry = getItem(position);
119             final ImageView iconView = view.findViewById(mIconResId);
120             iconView.setImageDrawable(entry.getIcon());
121             return view;
122         }
123 
124         @Override
hasStableIds()125         public boolean hasStableIds() {
126             return true;
127         }
128 
129         @Override
getItemId(int position)130         public long getItemId(int position) {
131             return position;
132         }
133 
134         @Override
isEnabled(int position)135         public boolean isEnabled(int position) {
136             return false;
137         }
138 
loadAllApps()139         private void loadAllApps() {
140             final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
141             mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
142 
143             final PackageManager pm = mPackageManager;
144             final ArrayList<ActivityEntry> results = new ArrayList<>();
145             final List<ResolveInfo> infos = pm.queryIntentActivities(mainIntent, 0);
146 
147             if (mAppCount > infos.size()) {
148                 Log.d(TAG, "Visible app icon count does not meet the target count.");
149             }
150 
151             final IconDrawableFactory iconFactory = IconDrawableFactory.newInstance(getContext());
152             for (ResolveInfo info : infos) {
153                 final CharSequence label = info.loadLabel(pm);
154                 if (label != null) {
155                     results.add(new ActivityEntry(info, label.toString(), iconFactory));
156                 }
157                 if (results.size() >= mAppCount) {
158                     break;
159                 }
160             }
161 
162             Collections.sort(results);
163 
164             addAll(results);
165         }
166     }
167 
168     /**
169      * Class used for caching the activity label and icon.
170      */
171     @VisibleForTesting
172     public static class ActivityEntry implements Comparable<ActivityEntry> {
173 
174         public final ResolveInfo info;
175         public final String label;
176         private final IconDrawableFactory mIconFactory;
177         private final int mUserId;
178 
ActivityEntry(ResolveInfo info, String label, IconDrawableFactory iconFactory)179         public ActivityEntry(ResolveInfo info, String label, IconDrawableFactory iconFactory) {
180             this.info = info;
181             this.label = label;
182             mIconFactory = iconFactory;
183             mUserId = UserHandle.myUserId();
184         }
185 
186         @Override
compareTo(ActivityEntry entry)187         public int compareTo(ActivityEntry entry) {
188             return label.compareToIgnoreCase(entry.label);
189         }
190 
191         @Override
toString()192         public String toString() {
193             return label;
194         }
195 
getIcon()196         public Drawable getIcon() {
197             return mIconFactory.getBadgedIcon(
198                     info.activityInfo, info.activityInfo.applicationInfo, mUserId);
199         }
200     }
201 }
202