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