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