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
onReceive(Context context, Intent intent)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 
HomeSettings()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 
makeCurrentHome(HomeAppPreference newHome)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 
uninstallApp(HomeAppPreference pref)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
onActivityResult(int requestCode, int resultCode, Intent data)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 
buildHomeActivitiesList()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 
hasManagedProfile()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 
launcherHasManagedProfilesFeature(ResolveInfo resolveInfo)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 
versionNumberAtLeastL(int versionNumber)243     private boolean versionNumberAtLeastL(int versionNumber) {
244         return versionNumber >= Build.VERSION_CODES.LOLLIPOP;
245     }
246 
247     @Override
getMetricsCategory()248     protected int getMetricsCategory() {
249         return MetricsEvent.HOME;
250     }
251 
252     @Override
onCreate(Bundle savedInstanceState)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
onResume()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
onPause()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 
HomeAppPreference(Context context, ComponentName activity, int i, Drawable icon, CharSequence title, HomeSettings parent, ActivityInfo info, boolean enabled, CharSequence summary)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.
determineTargets(ActivityInfo info)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
onBindViewHolder(PreferenceViewHolder view)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 
setChecked(boolean state)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