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