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