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.Activity; 20 import android.app.ActivityManager; 21 import android.content.BroadcastReceiver; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.content.SharedPreferences; 27 import android.content.pm.ActivityInfo; 28 import android.content.pm.ApplicationInfo; 29 import android.content.pm.PackageInfo; 30 import android.content.pm.PackageManager; 31 import android.content.pm.ResolveInfo; 32 import android.content.pm.UserInfo; 33 import android.content.res.Resources; 34 import android.graphics.ColorFilter; 35 import android.graphics.ColorMatrix; 36 import android.graphics.ColorMatrixColorFilter; 37 import android.graphics.drawable.Drawable; 38 import android.net.Uri; 39 import android.os.Build; 40 import android.os.Bundle; 41 import android.os.Handler; 42 import android.os.UserManager; 43 import android.support.v7.preference.Preference; 44 import android.support.v7.preference.PreferenceGroup; 45 import android.support.v7.preference.PreferenceViewHolder; 46 import android.text.TextUtils; 47 import android.util.Log; 48 import android.view.View; 49 import android.view.View.OnClickListener; 50 import android.widget.ImageView; 51 import android.widget.RadioButton; 52 53 import com.android.internal.logging.MetricsProto.MetricsEvent; 54 import com.android.settings.search.BaseSearchIndexProvider; 55 import com.android.settings.search.Index; 56 import com.android.settings.search.Indexable; 57 import com.android.settings.search.SearchIndexableRaw; 58 59 import java.util.ArrayList; 60 import java.util.List; 61 62 public class HomeSettings extends SettingsPreferenceFragment implements Indexable { 63 static final String TAG = "HomeSettings"; 64 65 // Boolean extra, indicates only launchers that support managed profiles should be shown. 66 // Note: must match the constant defined in ManagedProvisioning 67 private static final String EXTRA_SUPPORT_MANAGED_PROFILES = "support_managed_profiles"; 68 69 static final int REQUESTING_UNINSTALL = 10; 70 71 public static final String HOME_PREFS = "home_prefs"; 72 public static final String HOME_PREFS_DO_SHOW = "do_show"; 73 74 public static final String HOME_SHOW_NOTICE = "show"; 75 76 private class HomePackageReceiver extends BroadcastReceiver { 77 @Override onReceive(Context context, Intent intent)78 public void onReceive(Context context, Intent intent) { 79 buildHomeActivitiesList(); 80 Index.getInstance(context).updateFromClassNameResource( 81 HomeSettings.class.getName(), true, true); 82 } 83 } 84 85 private PreferenceGroup mPrefGroup; 86 private PackageManager mPm; 87 private ComponentName[] mHomeComponentSet; 88 private ArrayList<HomeAppPreference> mPrefs; 89 private HomeAppPreference mCurrentHome = null; 90 private final IntentFilter mHomeFilter; 91 private boolean mShowNotice; 92 private HomePackageReceiver mHomePackageReceiver = new HomePackageReceiver(); 93 HomeSettings()94 public HomeSettings() { 95 mHomeFilter = new IntentFilter(Intent.ACTION_MAIN); 96 mHomeFilter.addCategory(Intent.CATEGORY_HOME); 97 mHomeFilter.addCategory(Intent.CATEGORY_DEFAULT); 98 } 99 100 OnClickListener mHomeClickListener = new OnClickListener() { 101 @Override 102 public void onClick(View v) { 103 int index = (Integer)v.getTag(); 104 HomeAppPreference pref = mPrefs.get(index); 105 if (!pref.isChecked) { 106 makeCurrentHome(pref); 107 } 108 } 109 }; 110 111 OnClickListener mDeleteClickListener = new OnClickListener() { 112 @Override 113 public void onClick(View v) { 114 int index = (Integer)v.getTag(); 115 uninstallApp(mPrefs.get(index)); 116 } 117 }; 118 makeCurrentHome(HomeAppPreference newHome)119 void makeCurrentHome(HomeAppPreference newHome) { 120 if (mCurrentHome != null) { 121 mCurrentHome.setChecked(false); 122 } 123 newHome.setChecked(true); 124 mCurrentHome = newHome; 125 126 mPm.replacePreferredActivity(mHomeFilter, IntentFilter.MATCH_CATEGORY_EMPTY, 127 mHomeComponentSet, newHome.activityName); 128 129 getActivity().setResult(Activity.RESULT_OK); 130 } 131 uninstallApp(HomeAppPreference pref)132 void uninstallApp(HomeAppPreference pref) { 133 // Uninstallation is done by asking the OS to do it 134 Uri packageURI = Uri.parse("package:" + pref.uninstallTarget); 135 Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI); 136 uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false); 137 int requestCode = REQUESTING_UNINSTALL + (pref.isChecked ? 1 : 0); 138 startActivityForResult(uninstallIntent, requestCode); 139 } 140 141 @Override onActivityResult(int requestCode, int resultCode, Intent data)142 public void onActivityResult(int requestCode, int resultCode, Intent data) { 143 super.onActivityResult(requestCode, resultCode, data); 144 145 // Rebuild the list now that we might have nuked something 146 buildHomeActivitiesList(); 147 148 // if the previous home app is now gone, fall back to the system one 149 if (requestCode > REQUESTING_UNINSTALL) { 150 // if mCurrentHome has gone null, it means we didn't find the previously- 151 // default home app when rebuilding the list, i.e. it was the one we 152 // just uninstalled. When that happens we make the system-bundled 153 // home app the active default. 154 if (mCurrentHome == null) { 155 for (int i = 0; i < mPrefs.size(); i++) { 156 HomeAppPreference pref = mPrefs.get(i); 157 if (pref.isSystem) { 158 makeCurrentHome(pref); 159 break; 160 } 161 } 162 } 163 } 164 } 165 buildHomeActivitiesList()166 private void buildHomeActivitiesList() { 167 ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>(); 168 ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities); 169 170 Context context = getPrefContext(); 171 mCurrentHome = null; 172 mPrefGroup.removeAll(); 173 mPrefs = new ArrayList<HomeAppPreference>(); 174 mHomeComponentSet = new ComponentName[homeActivities.size()]; 175 int prefIndex = 0; 176 boolean supportManagedProfilesExtra = 177 getActivity().getIntent().getBooleanExtra(EXTRA_SUPPORT_MANAGED_PROFILES, false); 178 boolean mustSupportManagedProfile = hasManagedProfile() 179 || supportManagedProfilesExtra; 180 for (int i = 0; i < homeActivities.size(); i++) { 181 final ResolveInfo candidate = homeActivities.get(i); 182 final ActivityInfo info = candidate.activityInfo; 183 ComponentName activityName = new ComponentName(info.packageName, info.name); 184 mHomeComponentSet[i] = activityName; 185 try { 186 Drawable icon = info.loadIcon(mPm); 187 CharSequence name = info.loadLabel(mPm); 188 HomeAppPreference pref; 189 190 if (mustSupportManagedProfile && !launcherHasManagedProfilesFeature(candidate)) { 191 pref = new HomeAppPreference(context, activityName, prefIndex, 192 icon, name, this, info, false /* not enabled */, 193 getResources().getString(R.string.home_work_profile_not_supported)); 194 } else { 195 pref = new HomeAppPreference(context, activityName, prefIndex, 196 icon, name, this, info, true /* enabled */, null); 197 } 198 199 mPrefs.add(pref); 200 mPrefGroup.addPreference(pref); 201 if (activityName.equals(currentDefaultHome)) { 202 mCurrentHome = pref; 203 } 204 prefIndex++; 205 } catch (Exception e) { 206 Log.v(TAG, "Problem dealing with activity " + activityName, e); 207 } 208 } 209 210 if (mCurrentHome != null) { 211 if (mCurrentHome.isEnabled()) { 212 getActivity().setResult(Activity.RESULT_OK); 213 } 214 215 new Handler().post(new Runnable() { 216 public void run() { 217 mCurrentHome.setChecked(true); 218 } 219 }); 220 } 221 } 222 hasManagedProfile()223 private boolean hasManagedProfile() { 224 Context context = getActivity(); 225 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); 226 List<UserInfo> profiles = userManager.getProfiles(context.getUserId()); 227 for (UserInfo userInfo : profiles) { 228 if (userInfo.isManagedProfile()) return true; 229 } 230 return false; 231 } 232 launcherHasManagedProfilesFeature(ResolveInfo resolveInfo)233 private boolean launcherHasManagedProfilesFeature(ResolveInfo resolveInfo) { 234 try { 235 ApplicationInfo appInfo = getPackageManager().getApplicationInfo( 236 resolveInfo.activityInfo.packageName, 0 /* default flags */); 237 return versionNumberAtLeastL(appInfo.targetSdkVersion); 238 } catch (PackageManager.NameNotFoundException e) { 239 return false; 240 } 241 } 242 versionNumberAtLeastL(int versionNumber)243 private boolean versionNumberAtLeastL(int versionNumber) { 244 return versionNumber >= Build.VERSION_CODES.LOLLIPOP; 245 } 246 247 @Override getMetricsCategory()248 protected int getMetricsCategory() { 249 return MetricsEvent.HOME; 250 } 251 252 @Override onCreate(Bundle savedInstanceState)253 public void onCreate(Bundle savedInstanceState) { 254 super.onCreate(savedInstanceState); 255 addPreferencesFromResource(R.xml.home_selection); 256 257 mPm = getPackageManager(); 258 mPrefGroup = (PreferenceGroup) findPreference("home"); 259 260 Bundle args = getArguments(); 261 mShowNotice = (args != null) && args.getBoolean(HOME_SHOW_NOTICE, false); 262 } 263 264 @Override onResume()265 public void onResume() { 266 super.onResume(); 267 268 final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 269 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 270 filter.addAction(Intent.ACTION_PACKAGE_CHANGED); 271 filter.addAction(Intent.ACTION_PACKAGE_REPLACED); 272 filter.addDataScheme("package"); 273 getActivity().registerReceiver(mHomePackageReceiver, filter); 274 275 buildHomeActivitiesList(); 276 } 277 278 @Override onPause()279 public void onPause() { 280 super.onPause(); 281 getActivity().unregisterReceiver(mHomePackageReceiver); 282 } 283 284 private class HomeAppPreference extends Preference { 285 ComponentName activityName; 286 int index; 287 HomeSettings fragment; 288 final ColorFilter grayscaleFilter; 289 boolean isChecked; 290 291 boolean isSystem; 292 String uninstallTarget; 293 HomeAppPreference(Context context, ComponentName activity, int i, Drawable icon, CharSequence title, HomeSettings parent, ActivityInfo info, boolean enabled, CharSequence summary)294 public HomeAppPreference(Context context, ComponentName activity, 295 int i, Drawable icon, CharSequence title, HomeSettings parent, ActivityInfo info, 296 boolean enabled, CharSequence summary) { 297 super(context); 298 setLayoutResource(R.layout.preference_home_app); 299 setIcon(icon); 300 setTitle(title); 301 setEnabled(enabled); 302 setSummary(summary); 303 activityName = activity; 304 fragment = parent; 305 index = i; 306 307 ColorMatrix colorMatrix = new ColorMatrix(); 308 colorMatrix.setSaturation(0f); 309 float[] matrix = colorMatrix.getArray(); 310 matrix[18] = 0.5f; 311 grayscaleFilter = new ColorMatrixColorFilter(colorMatrix); 312 313 determineTargets(info); 314 } 315 316 // Check whether this activity is bundled on the system, with awareness 317 // of the META_HOME_ALTERNATE mechanism. determineTargets(ActivityInfo info)318 private void determineTargets(ActivityInfo info) { 319 final Bundle meta = info.metaData; 320 if (meta != null) { 321 final String altHomePackage = meta.getString(ActivityManager.META_HOME_ALTERNATE); 322 if (altHomePackage != null) { 323 try { 324 final int match = mPm.checkSignatures(info.packageName, altHomePackage); 325 if (match >= PackageManager.SIGNATURE_MATCH) { 326 PackageInfo altInfo = mPm.getPackageInfo(altHomePackage, 0); 327 final int altFlags = altInfo.applicationInfo.flags; 328 isSystem = (altFlags & ApplicationInfo.FLAG_SYSTEM) != 0; 329 uninstallTarget = altInfo.packageName; 330 return; 331 } 332 } catch (Exception e) { 333 // e.g. named alternate package not found during lookup 334 Log.w(TAG, "Unable to compare/resolve alternate", e); 335 } 336 } 337 } 338 // No suitable metadata redirect, so use the package's own info 339 isSystem = (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 340 uninstallTarget = info.packageName; 341 } 342 343 @Override onBindViewHolder(PreferenceViewHolder view)344 public void onBindViewHolder(PreferenceViewHolder view) { 345 super.onBindViewHolder(view); 346 347 RadioButton radio = (RadioButton) view.findViewById(R.id.home_radio); 348 radio.setChecked(isChecked); 349 350 Integer indexObj = new Integer(index); 351 352 ImageView icon = (ImageView) view.findViewById(R.id.home_app_uninstall); 353 if (isSystem) { 354 icon.setEnabled(false); 355 icon.setColorFilter(grayscaleFilter); 356 } else { 357 icon.setEnabled(true); 358 icon.setOnClickListener(mDeleteClickListener); 359 icon.setTag(indexObj); 360 } 361 362 View v = view.findViewById(R.id.home_app_pref); 363 v.setTag(indexObj); 364 365 v.setOnClickListener(mHomeClickListener); 366 } 367 setChecked(boolean state)368 void setChecked(boolean state) { 369 if (state != isChecked) { 370 isChecked = state; 371 notifyChanged(); 372 } 373 } 374 } 375 376 /** 377 * For search 378 */ 379 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 380 new BaseSearchIndexProvider() { 381 @Override 382 public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { 383 final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>(); 384 385 final PackageManager pm = context.getPackageManager(); 386 final ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>(); 387 pm.getHomeActivities(homeActivities); 388 389 final SharedPreferences sp = context.getSharedPreferences( 390 HomeSettings.HOME_PREFS, Context.MODE_PRIVATE); 391 final boolean doShowHome = sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false); 392 393 // We index Home Launchers only if there are more than one or if we are showing the 394 // Home tile into the Dashboard 395 if (homeActivities.size() > 1 || doShowHome) { 396 final Resources res = context.getResources(); 397 398 // Add fragment title 399 SearchIndexableRaw data = new SearchIndexableRaw(context); 400 data.title = res.getString(R.string.home_settings); 401 data.screenTitle = res.getString(R.string.home_settings); 402 data.keywords = res.getString(R.string.keywords_home); 403 result.add(data); 404 405 for (int i = 0; i < homeActivities.size(); i++) { 406 final ResolveInfo resolveInfo = homeActivities.get(i); 407 final ActivityInfo activityInfo = resolveInfo.activityInfo; 408 409 CharSequence name; 410 try { 411 name = activityInfo.loadLabel(pm); 412 if (TextUtils.isEmpty(name)) { 413 continue; 414 } 415 } catch (Exception e) { 416 Log.v(TAG, "Problem dealing with Home " + activityInfo.name, e); 417 continue; 418 } 419 420 data = new SearchIndexableRaw(context); 421 data.title = name.toString(); 422 data.screenTitle = res.getString(R.string.home_settings); 423 result.add(data); 424 } 425 } 426 427 return result; 428 } 429 }; 430 } 431