1 /*
2  * Copyright (C) 2013 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;
18 
19 import android.app.AlertDialog;
20 import android.app.AppGlobals;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.pm.ActivityInfo;
25 import android.content.pm.ApplicationInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.content.res.TypedArray;
29 import android.graphics.drawable.Drawable;
30 import android.os.Parcel;
31 import android.os.Parcelable;
32 import android.os.RemoteException;
33 import android.os.UserHandle;
34 import android.os.UserManager;
35 import android.util.AttributeSet;
36 import android.view.LayoutInflater;
37 import android.view.View;
38 import android.view.ViewGroup;
39 import android.widget.ArrayAdapter;
40 import android.widget.ImageView;
41 import android.widget.ListAdapter;
42 import android.widget.TextView;
43 
44 import java.util.ArrayList;
45 import java.util.List;
46 
47 /**
48  * Extends ListPreference to allow us to show the icons for a given list of applications. We do this
49  * because the names of applications are very similar and the user may not be able to determine what
50  * app they are selecting without an icon.
51  */
52 public class AppListPreference extends CustomListPreference {
53 
54     public static final String ITEM_NONE_VALUE = "";
55 
56     protected final boolean mForWork;
57     protected final int mUserId;
58 
59     private Drawable[] mEntryDrawables;
60     private boolean mShowItemNone = false;
61     private CharSequence[] mSummaries;
62     private int mSystemAppIndex = -1;
63 
64     public class AppArrayAdapter extends ArrayAdapter<CharSequence> {
65         private Drawable[] mImageDrawables = null;
66         private int mSelectedIndex = 0;
67 
AppArrayAdapter(Context context, int textViewResourceId, CharSequence[] objects, Drawable[] imageDrawables, int selectedIndex)68         public AppArrayAdapter(Context context, int textViewResourceId,
69                 CharSequence[] objects, Drawable[] imageDrawables, int selectedIndex) {
70             super(context, textViewResourceId, objects);
71             mSelectedIndex = selectedIndex;
72             mImageDrawables = imageDrawables;
73         }
74 
75         @Override
isEnabled(int position)76         public boolean isEnabled(int position) {
77             return mSummaries == null || mSummaries[position] == null;
78         }
79 
80         @Override
getView(int position, View convertView, ViewGroup parent)81         public View getView(int position, View convertView, ViewGroup parent) {
82             LayoutInflater inflater = LayoutInflater.from(getContext());
83             View view = inflater.inflate(R.layout.app_preference_item, parent, false);
84             TextView textView = (TextView) view.findViewById(android.R.id.title);
85             textView.setText(getItem(position));
86             if (position == mSelectedIndex && position == mSystemAppIndex) {
87                 view.findViewById(R.id.system_default_label).setVisibility(View.VISIBLE);
88             } else if (position == mSelectedIndex) {
89                 view.findViewById(R.id.default_label).setVisibility(View.VISIBLE);
90             } else if (position == mSystemAppIndex) {
91                 view.findViewById(R.id.system_label).setVisibility(View.VISIBLE);
92             }
93             ImageView imageView = (ImageView) view.findViewById(android.R.id.icon);
94             imageView.setImageDrawable(mImageDrawables[position]);
95             // Summaries are describing why a item is disabled, so anything with a summary
96             // is not enabled.
97             boolean enabled = mSummaries == null || mSummaries[position] == null;
98             view.setEnabled(enabled);
99             if (!enabled) {
100                 TextView summary = (TextView) view.findViewById(android.R.id.summary);
101                 summary.setText(mSummaries[position]);
102                 summary.setVisibility(View.VISIBLE);
103             }
104             return view;
105         }
106     }
107 
AppListPreference(Context context, AttributeSet attrs, int defStyle, int defAttrs)108     public AppListPreference(Context context, AttributeSet attrs, int defStyle, int defAttrs) {
109         super(context, attrs, defStyle, defAttrs);
110 
111         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WorkPreference, 0, 0);
112         mForWork = a.getBoolean(R.styleable.WorkPreference_forWork, false);
113         final UserHandle managedProfile = Utils.getManagedProfile(UserManager.get(context));
114         mUserId = mForWork && managedProfile != null ? managedProfile.getIdentifier()
115                 : UserHandle.myUserId();
116     }
117 
AppListPreference(Context context, AttributeSet attrs)118     public AppListPreference(Context context, AttributeSet attrs) {
119         super(context, attrs);
120 
121         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WorkPreference, 0, 0);
122         mForWork = a.getBoolean(R.styleable.WorkPreference_forWork, false);
123         final UserHandle managedProfile = Utils.getManagedProfile(UserManager.get(context));
124         mUserId = mForWork && managedProfile != null ? managedProfile.getIdentifier()
125                 : UserHandle.myUserId();
126     }
127 
setShowItemNone(boolean showItemNone)128     public void setShowItemNone(boolean showItemNone) {
129         mShowItemNone = showItemNone;
130     }
131 
setPackageNames(CharSequence[] packageNames, CharSequence defaultPackageName)132     public void setPackageNames(CharSequence[] packageNames, CharSequence defaultPackageName) {
133         setPackageNames(packageNames, defaultPackageName, null);
134     }
135 
setPackageNames(CharSequence[] packageNames, CharSequence defaultPackageName, CharSequence systemPackageName)136     public void setPackageNames(CharSequence[] packageNames, CharSequence defaultPackageName,
137             CharSequence systemPackageName) {
138         // Look up all package names in PackageManager. Skip ones we can't find.
139         PackageManager pm = getContext().getPackageManager();
140         final int entryCount = packageNames.length + (mShowItemNone ? 1 : 0);
141         List<CharSequence> applicationNames = new ArrayList<>(entryCount);
142         List<CharSequence> validatedPackageNames = new ArrayList<>(entryCount);
143         List<Drawable> entryDrawables = new ArrayList<>(entryCount);
144         int selectedIndex = -1;
145         mSystemAppIndex = -1;
146         for (int i = 0; i < packageNames.length; i++) {
147             try {
148                 ApplicationInfo appInfo = pm.getApplicationInfoAsUser(packageNames[i].toString(), 0,
149                         mUserId);
150                 applicationNames.add(appInfo.loadLabel(pm));
151                 validatedPackageNames.add(appInfo.packageName);
152                 entryDrawables.add(appInfo.loadIcon(pm));
153                 if (defaultPackageName != null &&
154                         appInfo.packageName.contentEquals(defaultPackageName)) {
155                     selectedIndex = i;
156                 }
157                 if (appInfo.packageName != null && systemPackageName != null &&
158                         appInfo.packageName.contentEquals(systemPackageName)) {
159                     mSystemAppIndex = i;
160                 }
161             } catch (NameNotFoundException e) {
162                 // Skip unknown packages.
163             }
164         }
165 
166         if (mShowItemNone) {
167             applicationNames.add(
168                     getContext().getResources().getText(R.string.app_list_preference_none));
169             validatedPackageNames.add(ITEM_NONE_VALUE);
170             entryDrawables.add(getContext().getDrawable(R.drawable.ic_remove_circle));
171         }
172 
173         setEntries(applicationNames.toArray(new CharSequence[applicationNames.size()]));
174         setEntryValues(
175                 validatedPackageNames.toArray(new CharSequence[validatedPackageNames.size()]));
176         mEntryDrawables = entryDrawables.toArray(new Drawable[entryDrawables.size()]);
177 
178         if (selectedIndex != -1) {
179             setValueIndex(selectedIndex);
180         } else {
181             setValue(null);
182         }
183     }
184 
setComponentNames(ComponentName[] componentNames, ComponentName defaultCN)185     public void setComponentNames(ComponentName[] componentNames, ComponentName defaultCN) {
186         setComponentNames(componentNames, defaultCN, null);
187     }
188 
setComponentNames(ComponentName[] componentNames, ComponentName defaultCN, CharSequence[] summaries)189     public void setComponentNames(ComponentName[] componentNames, ComponentName defaultCN,
190             CharSequence[] summaries) {
191         mSummaries = summaries;
192         // Look up all package names in PackageManager. Skip ones we can't find.
193         PackageManager pm = getContext().getPackageManager();
194         final int entryCount = componentNames.length + (mShowItemNone ? 1 : 0);
195         List<CharSequence> applicationNames = new ArrayList<>(entryCount);
196         List<CharSequence> validatedComponentNames = new ArrayList<>(entryCount);
197         List<Drawable> entryDrawables = new ArrayList<>(entryCount);
198         int selectedIndex = -1;
199         for (int i = 0; i < componentNames.length; i++) {
200             try {
201                 ActivityInfo activityInfo = AppGlobals.getPackageManager().getActivityInfo(
202                         componentNames[i], 0, mUserId);
203                 if (activityInfo == null) continue;
204                 applicationNames.add(activityInfo.loadLabel(pm));
205                 validatedComponentNames.add(componentNames[i].flattenToString());
206                 entryDrawables.add(activityInfo.loadIcon(pm));
207                 if (defaultCN != null && componentNames[i].equals(defaultCN)) {
208                     selectedIndex = i;
209                 }
210             } catch (RemoteException e) {
211                 // Skip unknown packages.
212             }
213         }
214 
215         if (mShowItemNone) {
216             applicationNames.add(
217                     getContext().getResources().getText(R.string.app_list_preference_none));
218             validatedComponentNames.add(ITEM_NONE_VALUE);
219             entryDrawables.add(getContext().getDrawable(R.drawable.ic_remove_circle));
220         }
221 
222         setEntries(applicationNames.toArray(new CharSequence[applicationNames.size()]));
223         setEntryValues(
224                 validatedComponentNames.toArray(new CharSequence[validatedComponentNames.size()]));
225         mEntryDrawables = entryDrawables.toArray(new Drawable[entryDrawables.size()]);
226 
227         if (selectedIndex != -1) {
228             setValueIndex(selectedIndex);
229         } else {
230             setValue(null);
231         }
232     }
233 
createListAdapter()234     protected ListAdapter createListAdapter() {
235         final String selectedValue = getValue();
236         final boolean selectedNone = selectedValue == null ||
237                 (mShowItemNone && selectedValue.contentEquals(ITEM_NONE_VALUE));
238         int selectedIndex = selectedNone ? -1 : findIndexOfValue(selectedValue);
239         return new AppArrayAdapter(getContext(),
240             R.layout.app_preference_item, getEntries(), mEntryDrawables, selectedIndex);
241     }
242 
243     @Override
onPrepareDialogBuilder(AlertDialog.Builder builder, DialogInterface.OnClickListener listener)244     protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
245             DialogInterface.OnClickListener listener) {
246         builder.setAdapter(createListAdapter(), listener);
247     }
248 
249     @Override
onSaveInstanceState()250     protected Parcelable onSaveInstanceState() {
251         Parcelable superState = super.onSaveInstanceState();
252         return new SavedState(getEntryValues(), getValue(), mSummaries, mShowItemNone, superState);
253     }
254 
255     @Override
onRestoreInstanceState(Parcelable state)256     protected void onRestoreInstanceState(Parcelable state) {
257         if (state instanceof SavedState) {
258             SavedState savedState = (SavedState) state;
259             mShowItemNone = savedState.showItemNone;
260             setPackageNames(savedState.entryValues, savedState.value);
261             mSummaries = savedState.summaries;
262             super.onRestoreInstanceState(savedState.superState);
263         } else {
264             super.onRestoreInstanceState(state);
265         }
266     }
267 
268     private static class SavedState implements Parcelable {
269 
270         public final CharSequence[] entryValues;
271         public final CharSequence value;
272         public final boolean showItemNone;
273         public final Parcelable superState;
274         public final CharSequence[] summaries;
275 
SavedState(CharSequence[] entryValues, CharSequence value, CharSequence[] summaries, boolean showItemNone, Parcelable superState)276         public SavedState(CharSequence[] entryValues, CharSequence value, CharSequence[] summaries,
277                 boolean showItemNone, Parcelable superState) {
278             this.entryValues = entryValues;
279             this.value = value;
280             this.showItemNone = showItemNone;
281             this.superState = superState;
282             this.summaries = summaries;
283         }
284 
285         @Override
describeContents()286         public int describeContents() {
287             return 0;
288         }
289 
290         @Override
writeToParcel(Parcel dest, int flags)291         public void writeToParcel(Parcel dest, int flags) {
292             dest.writeCharSequenceArray(entryValues);
293             dest.writeCharSequence(value);
294             dest.writeInt(showItemNone ? 1 : 0);
295             dest.writeParcelable(superState, flags);
296             dest.writeCharSequenceArray(summaries);
297         }
298 
299         public static Creator<SavedState> CREATOR = new Creator<SavedState>() {
300             @Override
301             public SavedState createFromParcel(Parcel source) {
302                 CharSequence[] entryValues = source.readCharSequenceArray();
303                 CharSequence value = source.readCharSequence();
304                 boolean showItemNone = source.readInt() != 0;
305                 Parcelable superState = source.readParcelable(getClass().getClassLoader());
306                 CharSequence[] summaries = source.readCharSequenceArray();
307                 return new SavedState(entryValues, value, summaries, showItemNone, superState);
308             }
309 
310             @Override
311             public SavedState[] newArray(int size) {
312                 return new SavedState[size];
313             }
314         };
315     }
316 }
317