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