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 onReceive(Context context, Intent intent)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 HomeSettings()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 makeCurrentHome(HomeAppPreference newHome)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 uninstallApp(HomeAppPreference pref)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 onActivityResult(int requestCode, int resultCode, Intent data)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 buildHomeActivitiesList()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 hasManagedProfile()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 launcherHasManagedProfilesFeature(ResolveInfo resolveInfo)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 versionNumberAtLeastL(int versionNumber)251 private boolean versionNumberAtLeastL(int versionNumber) { 252 return versionNumber >= Build.VERSION_CODES.LOLLIPOP; 253 } 254 255 @Override onCreate(Bundle savedInstanceState)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 onResume()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 onPause()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 HomeAppPreference(Context context, ComponentName activity, int i, Drawable icon, CharSequence title, HomeSettings parent, ActivityInfo info, boolean enabled, CharSequence summary)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. determineTargets(ActivityInfo info)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 onBindView(View view)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 setChecked(boolean state)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