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 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 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 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 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 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 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 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 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 243 private boolean versionNumberAtLeastL(int versionNumber) { 244 return versionNumber >= Build.VERSION_CODES.LOLLIPOP; 245 } 246 247 @Override 248 protected int getMetricsCategory() { 249 return MetricsEvent.HOME; 250 } 251 252 @Override 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 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 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 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. 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 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 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