/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings; import android.app.Activity; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.ColorFilter; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.UserManager; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceGroup; import android.support.v7.preference.PreferenceViewHolder; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageView; import android.widget.RadioButton; import com.android.internal.logging.MetricsProto.MetricsEvent; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Index; import com.android.settings.search.Indexable; import com.android.settings.search.SearchIndexableRaw; import java.util.ArrayList; import java.util.List; public class HomeSettings extends SettingsPreferenceFragment implements Indexable { static final String TAG = "HomeSettings"; // Boolean extra, indicates only launchers that support managed profiles should be shown. // Note: must match the constant defined in ManagedProvisioning private static final String EXTRA_SUPPORT_MANAGED_PROFILES = "support_managed_profiles"; static final int REQUESTING_UNINSTALL = 10; public static final String HOME_PREFS = "home_prefs"; public static final String HOME_PREFS_DO_SHOW = "do_show"; public static final String HOME_SHOW_NOTICE = "show"; private class HomePackageReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { buildHomeActivitiesList(); Index.getInstance(context).updateFromClassNameResource( HomeSettings.class.getName(), true, true); } } private PreferenceGroup mPrefGroup; private PackageManager mPm; private ComponentName[] mHomeComponentSet; private ArrayList<HomeAppPreference> mPrefs; private HomeAppPreference mCurrentHome = null; private final IntentFilter mHomeFilter; private boolean mShowNotice; private HomePackageReceiver mHomePackageReceiver = new HomePackageReceiver(); public HomeSettings() { mHomeFilter = new IntentFilter(Intent.ACTION_MAIN); mHomeFilter.addCategory(Intent.CATEGORY_HOME); mHomeFilter.addCategory(Intent.CATEGORY_DEFAULT); } OnClickListener mHomeClickListener = new OnClickListener() { @Override public void onClick(View v) { int index = (Integer)v.getTag(); HomeAppPreference pref = mPrefs.get(index); if (!pref.isChecked) { makeCurrentHome(pref); } } }; OnClickListener mDeleteClickListener = new OnClickListener() { @Override public void onClick(View v) { int index = (Integer)v.getTag(); uninstallApp(mPrefs.get(index)); } }; void makeCurrentHome(HomeAppPreference newHome) { if (mCurrentHome != null) { mCurrentHome.setChecked(false); } newHome.setChecked(true); mCurrentHome = newHome; mPm.replacePreferredActivity(mHomeFilter, IntentFilter.MATCH_CATEGORY_EMPTY, mHomeComponentSet, newHome.activityName); getActivity().setResult(Activity.RESULT_OK); } void uninstallApp(HomeAppPreference pref) { // Uninstallation is done by asking the OS to do it Uri packageURI = Uri.parse("package:" + pref.uninstallTarget); Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI); uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false); int requestCode = REQUESTING_UNINSTALL + (pref.isChecked ? 1 : 0); startActivityForResult(uninstallIntent, requestCode); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); // Rebuild the list now that we might have nuked something buildHomeActivitiesList(); // if the previous home app is now gone, fall back to the system one if (requestCode > REQUESTING_UNINSTALL) { // if mCurrentHome has gone null, it means we didn't find the previously- // default home app when rebuilding the list, i.e. it was the one we // just uninstalled. When that happens we make the system-bundled // home app the active default. if (mCurrentHome == null) { for (int i = 0; i < mPrefs.size(); i++) { HomeAppPreference pref = mPrefs.get(i); if (pref.isSystem) { makeCurrentHome(pref); break; } } } } } private void buildHomeActivitiesList() { ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>(); ComponentName currentDefaultHome = mPm.getHomeActivities(homeActivities); Context context = getPrefContext(); mCurrentHome = null; mPrefGroup.removeAll(); mPrefs = new ArrayList<HomeAppPreference>(); mHomeComponentSet = new ComponentName[homeActivities.size()]; int prefIndex = 0; boolean supportManagedProfilesExtra = getActivity().getIntent().getBooleanExtra(EXTRA_SUPPORT_MANAGED_PROFILES, false); boolean mustSupportManagedProfile = hasManagedProfile() || supportManagedProfilesExtra; for (int i = 0; i < homeActivities.size(); i++) { final ResolveInfo candidate = homeActivities.get(i); final ActivityInfo info = candidate.activityInfo; ComponentName activityName = new ComponentName(info.packageName, info.name); mHomeComponentSet[i] = activityName; try { Drawable icon = info.loadIcon(mPm); CharSequence name = info.loadLabel(mPm); HomeAppPreference pref; if (mustSupportManagedProfile && !launcherHasManagedProfilesFeature(candidate)) { pref = new HomeAppPreference(context, activityName, prefIndex, icon, name, this, info, false /* not enabled */, getResources().getString(R.string.home_work_profile_not_supported)); } else { pref = new HomeAppPreference(context, activityName, prefIndex, icon, name, this, info, true /* enabled */, null); } mPrefs.add(pref); mPrefGroup.addPreference(pref); if (activityName.equals(currentDefaultHome)) { mCurrentHome = pref; } prefIndex++; } catch (Exception e) { Log.v(TAG, "Problem dealing with activity " + activityName, e); } } if (mCurrentHome != null) { if (mCurrentHome.isEnabled()) { getActivity().setResult(Activity.RESULT_OK); } new Handler().post(new Runnable() { public void run() { mCurrentHome.setChecked(true); } }); } } private boolean hasManagedProfile() { Context context = getActivity(); UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); List<UserInfo> profiles = userManager.getProfiles(context.getUserId()); for (UserInfo userInfo : profiles) { if (userInfo.isManagedProfile()) return true; } return false; } private boolean launcherHasManagedProfilesFeature(ResolveInfo resolveInfo) { try { ApplicationInfo appInfo = getPackageManager().getApplicationInfo( resolveInfo.activityInfo.packageName, 0 /* default flags */); return versionNumberAtLeastL(appInfo.targetSdkVersion); } catch (PackageManager.NameNotFoundException e) { return false; } } private boolean versionNumberAtLeastL(int versionNumber) { return versionNumber >= Build.VERSION_CODES.LOLLIPOP; } @Override protected int getMetricsCategory() { return MetricsEvent.HOME; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.home_selection); mPm = getPackageManager(); mPrefGroup = (PreferenceGroup) findPreference("home"); Bundle args = getArguments(); mShowNotice = (args != null) && args.getBoolean(HOME_SHOW_NOTICE, false); } @Override public void onResume() { super.onResume(); final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addAction(Intent.ACTION_PACKAGE_REPLACED); filter.addDataScheme("package"); getActivity().registerReceiver(mHomePackageReceiver, filter); buildHomeActivitiesList(); } @Override public void onPause() { super.onPause(); getActivity().unregisterReceiver(mHomePackageReceiver); } private class HomeAppPreference extends Preference { ComponentName activityName; int index; HomeSettings fragment; final ColorFilter grayscaleFilter; boolean isChecked; boolean isSystem; String uninstallTarget; public HomeAppPreference(Context context, ComponentName activity, int i, Drawable icon, CharSequence title, HomeSettings parent, ActivityInfo info, boolean enabled, CharSequence summary) { super(context); setLayoutResource(R.layout.preference_home_app); setIcon(icon); setTitle(title); setEnabled(enabled); setSummary(summary); activityName = activity; fragment = parent; index = i; ColorMatrix colorMatrix = new ColorMatrix(); colorMatrix.setSaturation(0f); float[] matrix = colorMatrix.getArray(); matrix[18] = 0.5f; grayscaleFilter = new ColorMatrixColorFilter(colorMatrix); determineTargets(info); } // Check whether this activity is bundled on the system, with awareness // of the META_HOME_ALTERNATE mechanism. private void determineTargets(ActivityInfo info) { final Bundle meta = info.metaData; if (meta != null) { final String altHomePackage = meta.getString(ActivityManager.META_HOME_ALTERNATE); if (altHomePackage != null) { try { final int match = mPm.checkSignatures(info.packageName, altHomePackage); if (match >= PackageManager.SIGNATURE_MATCH) { PackageInfo altInfo = mPm.getPackageInfo(altHomePackage, 0); final int altFlags = altInfo.applicationInfo.flags; isSystem = (altFlags & ApplicationInfo.FLAG_SYSTEM) != 0; uninstallTarget = altInfo.packageName; return; } } catch (Exception e) { // e.g. named alternate package not found during lookup Log.w(TAG, "Unable to compare/resolve alternate", e); } } } // No suitable metadata redirect, so use the package's own info isSystem = (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; uninstallTarget = info.packageName; } @Override public void onBindViewHolder(PreferenceViewHolder view) { super.onBindViewHolder(view); RadioButton radio = (RadioButton) view.findViewById(R.id.home_radio); radio.setChecked(isChecked); Integer indexObj = new Integer(index); ImageView icon = (ImageView) view.findViewById(R.id.home_app_uninstall); if (isSystem) { icon.setEnabled(false); icon.setColorFilter(grayscaleFilter); } else { icon.setEnabled(true); icon.setOnClickListener(mDeleteClickListener); icon.setTag(indexObj); } View v = view.findViewById(R.id.home_app_pref); v.setTag(indexObj); v.setOnClickListener(mHomeClickListener); } void setChecked(boolean state) { if (state != isChecked) { isChecked = state; notifyChanged(); } } } /** * For search */ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider() { @Override public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>(); final PackageManager pm = context.getPackageManager(); final ArrayList<ResolveInfo> homeActivities = new ArrayList<ResolveInfo>(); pm.getHomeActivities(homeActivities); final SharedPreferences sp = context.getSharedPreferences( HomeSettings.HOME_PREFS, Context.MODE_PRIVATE); final boolean doShowHome = sp.getBoolean(HomeSettings.HOME_PREFS_DO_SHOW, false); // We index Home Launchers only if there are more than one or if we are showing the // Home tile into the Dashboard if (homeActivities.size() > 1 || doShowHome) { final Resources res = context.getResources(); // Add fragment title SearchIndexableRaw data = new SearchIndexableRaw(context); data.title = res.getString(R.string.home_settings); data.screenTitle = res.getString(R.string.home_settings); data.keywords = res.getString(R.string.keywords_home); result.add(data); for (int i = 0; i < homeActivities.size(); i++) { final ResolveInfo resolveInfo = homeActivities.get(i); final ActivityInfo activityInfo = resolveInfo.activityInfo; CharSequence name; try { name = activityInfo.loadLabel(pm); if (TextUtils.isEmpty(name)) { continue; } } catch (Exception e) { Log.v(TAG, "Problem dealing with Home " + activityInfo.name, e); continue; } data = new SearchIndexableRaw(context); data.title = name.toString(); data.screenTitle = res.getString(R.string.home_settings); result.add(data); } } return result; } }; }