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