• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.applications.intentpicker;
18 
19 import static android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_NONE;
20 import static android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_SELECTED;
21 import static android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_VERIFIED;
22 
23 import android.app.Activity;
24 import android.app.settings.SettingsEnums;
25 import android.appwidget.AppWidgetManager;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.PackageManager;
29 import android.content.pm.verify.domain.DomainVerificationManager;
30 import android.content.pm.verify.domain.DomainVerificationUserState;
31 import android.icu.text.MessageFormat;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.util.ArraySet;
35 import android.util.Log;
36 import android.view.LayoutInflater;
37 import android.view.View;
38 import android.widget.Switch;
39 import android.widget.TextView;
40 
41 import androidx.annotation.VisibleForTesting;
42 import androidx.appcompat.app.AlertDialog;
43 import androidx.preference.Preference;
44 import androidx.preference.PreferenceCategory;
45 
46 import com.android.settings.R;
47 import com.android.settings.Utils;
48 import com.android.settings.applications.AppInfoBase;
49 import com.android.settings.applications.ClearDefaultsPreference;
50 import com.android.settings.widget.EntityHeaderController;
51 import com.android.settingslib.applications.AppUtils;
52 import com.android.settingslib.widget.FooterPreference;
53 import com.android.settingslib.widget.MainSwitchPreference;
54 import com.android.settingslib.widget.OnMainSwitchChangeListener;
55 
56 import java.util.HashMap;
57 import java.util.List;
58 import java.util.Locale;
59 import java.util.Map;
60 import java.util.Set;
61 import java.util.UUID;
62 
63 /** The page of the Open by default */
64 public class AppLaunchSettings extends AppInfoBase implements
65         Preference.OnPreferenceChangeListener, OnMainSwitchChangeListener {
66     private static final String TAG = "AppLaunchSettings";
67     // Preference keys
68     private static final String MAIN_SWITCH_PREF_KEY = "open_by_default_supported_links";
69     private static final String VERIFIED_LINKS_PREF_KEY = "open_by_default_verified_links";
70     private static final String ADD_LINK_PREF_KEY = "open_by_default_add_link";
71     private static final String CLEAR_DEFAULTS_PREF_KEY = "app_launch_clear_defaults";
72     private static final String FOOTER_PREF_KEY = "open_by_default_footer";
73 
74     private static final String MAIN_PREF_CATEGORY_KEY = "open_by_default_main_category";
75     private static final String SELECTED_LINKS_CATEGORY_KEY =
76             "open_by_default_selected_links_category";
77     private static final String OTHER_DETAILS_PREF_CATEGORY_KEY = "app_launch_other_defaults";
78 
79     private static final String LEARN_MORE_URI =
80             "https://developer.android.com/training/app-links/verify-site-associations";
81 
82     // Dialogs id
83     private static final int DLG_VERIFIED_LINKS = DLG_BASE + 1;
84 
85     // Arguments key
86     public static final String APP_PACKAGE_KEY = "app_package";
87 
88     private ClearDefaultsPreference mClearDefaultsPreference;
89     private MainSwitchPreference mMainSwitchPreference;
90     private Preference mAddLinkPreference;
91     private PreferenceCategory mMainPreferenceCategory;
92     private PreferenceCategory mSelectedLinksPreferenceCategory;
93     private PreferenceCategory mOtherDefaultsPreferenceCategory;
94 
95     private boolean mActivityCreated;
96 
97     @VisibleForTesting
98     Context mContext;
99     @VisibleForTesting
100     DomainVerificationManager mDomainVerificationManager;
101 
102     @Override
onAttach(Context context)103     public void onAttach(Context context) {
104         super.onAttach(context);
105         mContext = context;
106         mActivityCreated = false;
107     }
108 
109     @Override
onCreate(Bundle savedInstanceState)110     public void onCreate(Bundle savedInstanceState) {
111         super.onCreate(savedInstanceState);
112         if (mAppEntry == null) {
113             Log.w(TAG, "onCreate: mAppEntry is null, please check the reason!!!");
114             getActivity().finish();
115             return;
116         }
117         addPreferencesFromResource(R.xml.installed_app_launch_settings);
118         mDomainVerificationManager = mContext.getSystemService(DomainVerificationManager.class);
119         initUIComponents();
120     }
121 
122     @Override
onViewCreated(View view, Bundle savedInstanceState)123     public void onViewCreated(View view, Bundle savedInstanceState) {
124         super.onViewCreated(view, savedInstanceState);
125         createHeaderPreference();
126     }
127 
128     @Override
getMetricsCategory()129     public int getMetricsCategory() {
130         return SettingsEnums.APPLICATIONS_APP_LAUNCH;
131     }
132 
133     @Override
createDialog(int id, int errorCode)134     protected AlertDialog createDialog(int id, int errorCode) {
135         if (id == DLG_VERIFIED_LINKS) {
136             return createVerifiedLinksDialog();
137         }
138         return null;
139     }
140 
141     @Override
refreshUi()142     protected boolean refreshUi() {
143         mClearDefaultsPreference.setPackageName(mPackageName);
144         mClearDefaultsPreference.setAppEntry(mAppEntry);
145         return true;
146     }
147 
148     @Override
onPreferenceChange(Preference preference, Object newValue)149     public boolean onPreferenceChange(Preference preference, Object newValue) {
150         final boolean isChecked = (boolean) newValue;
151         IntentPickerUtils.logd(
152                 "onPreferenceChange: " + preference.getTitle() + " isChecked: " + isChecked);
153         if ((preference instanceof LeftSideCheckBoxPreference) && !isChecked) {
154             final Set<String> domainSet = new ArraySet<>();
155             domainSet.add(preference.getTitle().toString());
156             removePreference(preference.getKey());
157             final DomainVerificationUserState userState =
158                     IntentPickerUtils.getDomainVerificationUserState(mDomainVerificationManager,
159                             mPackageName);
160             if (userState == null) {
161                 return false;
162             }
163             setDomainVerificationUserSelection(userState.getIdentifier(), domainSet, /* enabled= */
164                     false);
165             mAddLinkPreference.setEnabled(isAddLinksNotEmpty());
166         }
167         return true;
168     }
169 
170     @Override
onSwitchChanged(Switch switchView, boolean isChecked)171     public void onSwitchChanged(Switch switchView, boolean isChecked) {
172         IntentPickerUtils.logd("onSwitchChanged: isChecked=" + isChecked);
173         if (mMainSwitchPreference != null) { //mMainSwitchPreference synced with Switch
174             mMainSwitchPreference.setChecked(isChecked);
175         }
176         if (mMainPreferenceCategory != null) {
177             mMainPreferenceCategory.setVisible(isChecked);
178         }
179         if (mDomainVerificationManager != null) {
180             try {
181                 mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(mPackageName,
182                         isChecked);
183             } catch (PackageManager.NameNotFoundException e) {
184                 Log.w(TAG, "onSwitchChanged: " + e.getMessage());
185             }
186         }
187     }
188 
createHeaderPreference()189     private void createHeaderPreference() {
190         if (mActivityCreated) {
191             Log.w(TAG, "onParentActivityCreated: ignoring duplicate call.");
192             return;
193         }
194         mActivityCreated = true;
195         if (mPackageInfo == null) {
196             Log.w(TAG, "onParentActivityCreated: PakcageInfo is null.");
197             return;
198         }
199         final Activity activity = getActivity();
200         final String summary = activity.getString(R.string.app_launch_top_intro_message);
201         final Preference pref = EntityHeaderController
202                 .newInstance(activity, this, null /* header */)
203                 .setRecyclerView(getListView(), getSettingsLifecycle())
204                 .setIcon(Utils.getBadgedIcon(mContext, mPackageInfo.applicationInfo))
205                 .setLabel(mPackageInfo.applicationInfo.loadLabel(mPm))
206                 .setSummary(summary)  // add intro text
207                 .setIsInstantApp(AppUtils.isInstant(mPackageInfo.applicationInfo))
208                 .setPackageName(mPackageName)
209                 .setUid(mPackageInfo.applicationInfo.uid)
210                 .setHasAppInfoLink(true)
211                 .setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
212                         EntityHeaderController.ActionType.ACTION_NONE)
213                 .done(activity, getPrefContext());
214         getPreferenceScreen().addPreference(pref);
215     }
216 
initUIComponents()217     private void initUIComponents() {
218         initMainSwitchAndCategories();
219         if (canUpdateMainSwitchAndCategories()) {
220             initVerifiedLinksPreference();
221             initAddLinkPreference();
222             addSelectedLinksPreference();
223             initFooter();
224         }
225     }
226 
initMainSwitchAndCategories()227     private void initMainSwitchAndCategories() {
228         mMainSwitchPreference = (MainSwitchPreference) findPreference(MAIN_SWITCH_PREF_KEY);
229         mMainPreferenceCategory = findPreference(MAIN_PREF_CATEGORY_KEY);
230         mSelectedLinksPreferenceCategory = findPreference(SELECTED_LINKS_CATEGORY_KEY);
231         // Initialize the "Other Default Category" section
232         initOtherDefaultsSection();
233     }
234 
canUpdateMainSwitchAndCategories()235     private boolean canUpdateMainSwitchAndCategories() {
236         final DomainVerificationUserState userState =
237                 IntentPickerUtils.getDomainVerificationUserState(mDomainVerificationManager,
238                         mPackageName);
239         if (userState == null) {
240             disabledPreference();
241             return false;
242         }
243 
244         IntentPickerUtils.logd("isLinkHandlingAllowed() : " + userState.isLinkHandlingAllowed());
245         mMainSwitchPreference.updateStatus(userState.isLinkHandlingAllowed());
246         mMainSwitchPreference.addOnSwitchChangeListener(this);
247         mMainPreferenceCategory.setVisible(userState.isLinkHandlingAllowed());
248         return true;
249     }
250 
251     /** Initialize verified links preference */
initVerifiedLinksPreference()252     private void initVerifiedLinksPreference() {
253         final Preference verifiedLinksPreference = mMainPreferenceCategory.findPreference(
254                 VERIFIED_LINKS_PREF_KEY);
255         verifiedLinksPreference.setOnPreferenceClickListener(preference -> {
256             showVerifiedLinksDialog();
257             return true;
258         });
259         final int verifiedLinksNo = getLinksNumber(DOMAIN_STATE_VERIFIED);
260         verifiedLinksPreference.setTitle(getVerifiedLinksTitle(verifiedLinksNo));
261         verifiedLinksPreference.setEnabled(verifiedLinksNo > 0);
262     }
263 
showVerifiedLinksDialog()264     private void showVerifiedLinksDialog() {
265         final int linksNo = getLinksNumber(DOMAIN_STATE_VERIFIED);
266         if (linksNo == 0) {
267             return;
268         }
269         showDialogInner(DLG_VERIFIED_LINKS, /* moveErrorCode= */ 0);
270     }
271 
createVerifiedLinksDialog()272     private AlertDialog createVerifiedLinksDialog() {
273         final int linksNo = getLinksNumber(DOMAIN_STATE_VERIFIED);
274 
275         final View titleView = LayoutInflater.from(mContext).inflate(
276                 R.layout.app_launch_verified_links_title, /* root= */ null);
277         ((TextView) titleView.findViewById(R.id.dialog_title)).setText(
278                 getVerifiedLinksTitle(linksNo));
279         ((TextView) titleView.findViewById(R.id.dialog_message)).setText(
280                 getVerifiedLinksMessage(linksNo));
281 
282         final List<String> verifiedLinksList = IntentPickerUtils.getLinksList(
283                 mDomainVerificationManager, mPackageName, DOMAIN_STATE_VERIFIED);
284         AlertDialog dialog = new AlertDialog.Builder(mContext)
285                 .setCustomTitle(titleView)
286                 .setCancelable(true)
287                 .setItems(verifiedLinksList.toArray(new String[0]), /* listener= */ null)
288                 .setPositiveButton(R.string.app_launch_dialog_ok, /* listener= */ null)
289                 .create();
290         if (dialog.getListView() != null) {
291             dialog.getListView().setTextDirection(View.TEXT_DIRECTION_LOCALE);
292         } else {
293             Log.w(TAG, "createVerifiedLinksDialog: dialog.getListView() is null, please check it.");
294         }
295         return dialog;
296     }
297 
298     @VisibleForTesting
getVerifiedLinksTitle(int linksNo)299     String getVerifiedLinksTitle(int linksNo) {
300         MessageFormat msgFormat = new MessageFormat(
301                 getResources().getString(R.string.app_launch_verified_links_title),
302                 Locale.getDefault());
303         Map<String, Object> arguments = new HashMap<>();
304         arguments.put("count", linksNo);
305         return msgFormat.format(arguments);
306     }
307 
getVerifiedLinksMessage(int linksNo)308     private String getVerifiedLinksMessage(int linksNo) {
309         MessageFormat msgFormat = new MessageFormat(
310                 getResources().getString(R.string.app_launch_verified_links_message),
311                 Locale.getDefault());
312         Map<String, Object> arguments = new HashMap<>();
313         arguments.put("count", linksNo);
314         return msgFormat.format(arguments);
315     }
316 
317     /** Add selected links items */
addSelectedLinksPreference()318     void addSelectedLinksPreference() {
319         if (getLinksNumber(DOMAIN_STATE_SELECTED) == 0) {
320             return;
321         }
322         mSelectedLinksPreferenceCategory.removeAll();
323         final List<String> selectedLinks = IntentPickerUtils.getLinksList(
324                 mDomainVerificationManager, mPackageName, DOMAIN_STATE_SELECTED);
325         for (String host : selectedLinks) {
326             generateCheckBoxPreference(mSelectedLinksPreferenceCategory, host);
327         }
328         mAddLinkPreference.setEnabled(isAddLinksNotEmpty());
329     }
330 
331     /** Initialize add link preference */
initAddLinkPreference()332     private void initAddLinkPreference() {
333         mAddLinkPreference = findPreference(ADD_LINK_PREF_KEY);
334         mAddLinkPreference.setVisible(isAddLinksShown());
335         mAddLinkPreference.setEnabled(isAddLinksNotEmpty());
336         mAddLinkPreference.setOnPreferenceClickListener(preference -> {
337             final int stateNoneLinksNo = getLinksNumber(DOMAIN_STATE_NONE);
338             IntentPickerUtils.logd("The number of the state none links: " + stateNoneLinksNo);
339             if (stateNoneLinksNo > 0) {
340                 showProgressDialogFragment();
341             }
342             return true;
343         });
344     }
345 
isAddLinksNotEmpty()346     private boolean isAddLinksNotEmpty() {
347         return getLinksNumber(DOMAIN_STATE_NONE) > 0;
348     }
349 
isAddLinksShown()350     private boolean isAddLinksShown() {
351         return (isAddLinksNotEmpty() || getLinksNumber(DOMAIN_STATE_SELECTED) > 0);
352     }
353 
showProgressDialogFragment()354     private void showProgressDialogFragment() {
355         final Bundle args = new Bundle();
356         args.putString(APP_PACKAGE_KEY, mPackageName);
357         final ProgressDialogFragment dialogFragment = new ProgressDialogFragment();
358         dialogFragment.setArguments(args);
359         dialogFragment.showDialog(getActivity().getSupportFragmentManager());
360     }
361 
disabledPreference()362     private void disabledPreference() {
363         mMainSwitchPreference.updateStatus(false);
364         mMainSwitchPreference.setSelectable(false);
365         mMainSwitchPreference.setEnabled(false);
366         mMainPreferenceCategory.setVisible(false);
367     }
368 
369     /** Init OTHER DEFAULTS category */
initOtherDefaultsSection()370     private void initOtherDefaultsSection() {
371         mOtherDefaultsPreferenceCategory = findPreference(OTHER_DETAILS_PREF_CATEGORY_KEY);
372         mOtherDefaultsPreferenceCategory.setVisible(isClearDefaultsEnabled());
373         mClearDefaultsPreference = (ClearDefaultsPreference) findPreference(
374                 CLEAR_DEFAULTS_PREF_KEY);
375     }
376 
initFooter()377     private void initFooter() {
378         final CharSequence footerText = mContext.getText(R.string.app_launch_footer);
379         final FooterPreference footerPreference = (FooterPreference) findPreference(
380                 FOOTER_PREF_KEY);
381         footerPreference.setTitle(footerText);
382         // learn more
383         footerPreference.setLearnMoreAction(view -> {
384             final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(LEARN_MORE_URI));
385             mContext.startActivity(intent);
386         });
387         final String learnMoreText = mContext.getString(
388                 R.string.footer_learn_more_content_description, getLabelName());
389         footerPreference.setLearnMoreText(learnMoreText);
390     }
391 
getLabelName()392     private String getLabelName() {
393         return mContext.getString(R.string.launch_by_default);
394     }
395 
isClearDefaultsEnabled()396     private boolean isClearDefaultsEnabled() {
397         final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
398         final boolean hasBindAppWidgetPermission =
399                 appWidgetManager.hasBindAppWidgetPermission(mAppEntry.info.packageName);
400 
401         final boolean isAutoLaunchEnabled = AppUtils.hasPreferredActivities(mPm, mPackageName)
402                 || AppUtils.isDefaultBrowser(mContext, mPackageName)
403                 || AppUtils.hasUsbDefaults(mUsbManager, mPackageName);
404 
405         IntentPickerUtils.logd("isClearDefaultsEnabled hasBindAppWidgetPermission : "
406                 + hasBindAppWidgetPermission);
407         IntentPickerUtils.logd(
408                 "isClearDefaultsEnabled isAutoLaunchEnabled : " + isAutoLaunchEnabled);
409         return (isAutoLaunchEnabled || hasBindAppWidgetPermission);
410     }
411 
setDomainVerificationUserSelection(UUID identifier, Set<String> domainSet, boolean isEnabled)412     private void setDomainVerificationUserSelection(UUID identifier, Set<String> domainSet,
413             boolean isEnabled) {
414         try {
415             mDomainVerificationManager.setDomainVerificationUserSelection(identifier, domainSet,
416                     isEnabled);
417         } catch (PackageManager.NameNotFoundException e) {
418             Log.w(TAG, "addSelectedItems : " + e.getMessage());
419         }
420     }
421 
generateCheckBoxPreference(PreferenceCategory parent, String title)422     private void generateCheckBoxPreference(PreferenceCategory parent, String title) {
423         final LeftSideCheckBoxPreference checkBoxPreference = new LeftSideCheckBoxPreference(
424                 parent.getContext(), /* isChecked= */ true);
425         checkBoxPreference.setTitle(title);
426         checkBoxPreference.setOnPreferenceChangeListener(this);
427         checkBoxPreference.setKey(UUID.randomUUID().toString());
428         parent.addPreference(checkBoxPreference);
429     }
430 
431     /** get the number of the specify links */
getLinksNumber(@omainVerificationUserState.DomainState int state)432     private int getLinksNumber(@DomainVerificationUserState.DomainState int state) {
433         final List<String> linkList = IntentPickerUtils.getLinksList(
434                 mDomainVerificationManager, mPackageName, state);
435         if (linkList == null) {
436             return 0;
437         }
438         return linkList.size();
439     }
440 }
441