1 /* 2 * Copyright (C) 2017 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 package com.android.wallpaper.picker; 17 18 import android.app.Activity; 19 import android.app.ProgressDialog; 20 import android.content.Context; 21 import android.content.DialogInterface; 22 import android.content.Intent; 23 import android.content.pm.PackageManager; 24 import android.graphics.Point; 25 import android.graphics.PorterDuff.Mode; 26 import android.net.Uri; 27 import android.os.Build.VERSION; 28 import android.os.Build.VERSION_CODES; 29 import android.os.Bundle; 30 import android.provider.Settings; 31 import android.util.DisplayMetrics; 32 import android.util.Log; 33 import android.view.Display; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.View.OnClickListener; 37 import android.view.ViewGroup; 38 import android.widget.Button; 39 import android.widget.FrameLayout; 40 import android.widget.ImageButton; 41 import android.widget.ImageView; 42 import android.widget.ProgressBar; 43 import android.widget.RelativeLayout; 44 import android.widget.TextView; 45 46 import androidx.annotation.Nullable; 47 import androidx.appcompat.app.AlertDialog; 48 import androidx.recyclerview.widget.GridLayoutManager; 49 import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup; 50 import androidx.recyclerview.widget.RecyclerView; 51 import androidx.recyclerview.widget.RecyclerView.ViewHolder; 52 53 import com.android.wallpaper.R; 54 import com.android.wallpaper.asset.Asset; 55 import com.android.wallpaper.config.Flags; 56 import com.android.wallpaper.model.Category; 57 import com.android.wallpaper.model.WallpaperInfo; 58 import com.android.wallpaper.module.CurrentWallpaperInfoFactory; 59 import com.android.wallpaper.module.CurrentWallpaperInfoFactory.WallpaperInfoCallback; 60 import com.android.wallpaper.module.ExploreIntentChecker; 61 import com.android.wallpaper.module.InjectorProvider; 62 import com.android.wallpaper.module.LockWallpaperStatusChecker; 63 import com.android.wallpaper.module.UserEventLogger; 64 import com.android.wallpaper.module.WallpaperPreferences; 65 import com.android.wallpaper.module.WallpaperPreferences.PresentationMode; 66 import com.android.wallpaper.module.WallpaperRotationRefresher; 67 import com.android.wallpaper.module.WallpaperRotationRefresher.Listener; 68 import com.android.wallpaper.picker.MyPhotosStarter.MyPhotosStarterProvider; 69 import com.android.wallpaper.picker.MyPhotosStarter.PermissionChangedListener; 70 import com.android.wallpaper.util.DisplayMetricsRetriever; 71 import com.android.wallpaper.util.ScreenSizeCalculator; 72 import com.android.wallpaper.util.TileSizeCalculator; 73 import com.android.wallpaper.widget.GridMarginDecoration; 74 75 import com.bumptech.glide.Glide; 76 import com.bumptech.glide.MemoryCategory; 77 78 import java.util.ArrayList; 79 import java.util.Date; 80 import java.util.List; 81 82 /** 83 * Displays the Main UI for picking a category of wallpapers to choose from. 84 */ 85 public class CategoryFragment extends ToolbarFragment { 86 87 /** 88 * Interface to be implemented by an Activity hosting a {@link CategoryFragment} 89 */ 90 public interface CategoryFragmentHost extends MyPhotosStarterProvider { 91 requestExternalStoragePermission(PermissionChangedListener listener)92 void requestExternalStoragePermission(PermissionChangedListener listener); 93 isReadExternalStoragePermissionGranted()94 boolean isReadExternalStoragePermissionGranted(); 95 showViewOnlyPreview(WallpaperInfo wallpaperInfo)96 void showViewOnlyPreview(WallpaperInfo wallpaperInfo); 97 show(String collectionId)98 void show(String collectionId); 99 } 100 newInstance(CharSequence title)101 public static CategoryFragment newInstance(CharSequence title) { 102 CategoryFragment fragment = new CategoryFragment(); 103 fragment.setArguments(ToolbarFragment.createArguments(title)); 104 return fragment; 105 } 106 107 private static final String TAG = "CategoryFragment"; 108 109 // The number of ViewHolders that don't pertain to category tiles. 110 // Currently 2: one for the metadata section and one for the "Select wallpaper" header. 111 private static final int NUM_NON_CATEGORY_VIEW_HOLDERS = 2; 112 113 /** 114 * The fixed RecyclerView.Adapter position of the ViewHolder for the initial item in the grid -- 115 * usually the wallpaper metadata, or a "permission needed" warning UI. 116 */ 117 private static final int INITIAL_HOLDER_ADAPTER_POSITION = 0; 118 119 private static final int SETTINGS_APP_INFO_REQUEST_CODE = 1; 120 121 private static final String PERMISSION_READ_WALLPAPER_INTERNAL = 122 "android.permission.READ_WALLPAPER_INTERNAL"; 123 124 private RecyclerView mImageGrid; 125 private CategoryAdapter mAdapter; 126 private ArrayList<Category> mCategories = new ArrayList<>(); 127 private Point mTileSizePx; 128 private boolean mAwaitingCategories; 129 private ProgressDialog mRefreshWallpaperProgressDialog; 130 private boolean mTestingMode; 131 CategoryFragment()132 public CategoryFragment() { 133 } 134 135 @Override onCreate(Bundle savedInstanceState)136 public void onCreate(Bundle savedInstanceState) { 137 super.onCreate(savedInstanceState); 138 mAdapter = new CategoryAdapter(mCategories); 139 } 140 141 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)142 public View onCreateView(LayoutInflater inflater, ViewGroup container, 143 Bundle savedInstanceState) { 144 View view = inflater.inflate( 145 R.layout.fragment_category_picker, container, /* attachToRoot */ false); 146 147 mImageGrid = view.findViewById(R.id.category_grid); 148 GridMarginDecoration.applyTo(mImageGrid); 149 150 mTileSizePx = TileSizeCalculator.getCategoryTileSize(getActivity()); 151 152 if (LockWallpaperStatusChecker.isLockWallpaperSet(getContext())) { 153 mAdapter.setNumMetadataCards(CategoryAdapter.METADATA_VIEW_TWO_CARDS); 154 } else { 155 mAdapter.setNumMetadataCards(CategoryAdapter.METADATA_VIEW_SINGLE_CARD); 156 } 157 mImageGrid.setAdapter(mAdapter); 158 159 GridLayoutManager gridLayoutManager = new GridLayoutManager(getActivity(), getNumColumns()); 160 gridLayoutManager.setSpanSizeLookup(new CategorySpanSizeLookup(mAdapter)); 161 mImageGrid.setLayoutManager(gridLayoutManager); 162 setUpToolbar(view); 163 return view; 164 } 165 166 @Override getDefaultTitle()167 public CharSequence getDefaultTitle() { 168 return getContext().getString(R.string.app_name); 169 } 170 171 @Override onResume()172 public void onResume() { 173 super.onResume(); 174 175 WallpaperPreferences preferences = InjectorProvider.getInjector().getPreferences(getActivity()); 176 preferences.setLastAppActiveTimestamp(new Date().getTime()); 177 178 // Reset Glide memory settings to a "normal" level of usage since it may have been lowered in 179 // PreviewFragment. 180 Glide.get(getActivity()).setMemoryCategory(MemoryCategory.NORMAL); 181 182 // Refresh metadata since it may have changed since the activity was paused. 183 ViewHolder initialViewHolder = 184 mImageGrid.findViewHolderForAdapterPosition(INITIAL_HOLDER_ADAPTER_POSITION); 185 MetadataHolder metadataHolder = null; 186 if (initialViewHolder instanceof MetadataHolder) { 187 metadataHolder = (MetadataHolder) initialViewHolder; 188 } 189 190 // The wallpaper may have been set while this fragment was paused, so force refresh the current 191 // wallpapers and presentation mode. 192 refreshCurrentWallpapers(metadataHolder, true /* forceRefresh */); 193 } 194 195 @Override onDestroy()196 public void onDestroy() { 197 super.onDestroy(); 198 if (mRefreshWallpaperProgressDialog != null) { 199 mRefreshWallpaperProgressDialog.dismiss(); 200 } 201 } 202 203 @Override onActivityResult(int requestCode, int resultCode, Intent data)204 public void onActivityResult(int requestCode, int resultCode, Intent data) { 205 if (requestCode == SETTINGS_APP_INFO_REQUEST_CODE) { 206 mAdapter.notifyDataSetChanged(); 207 } 208 } 209 210 /** 211 * Inserts the given category into the categories list in priority order. 212 */ addCategory(Category category, boolean loading)213 public void addCategory(Category category, boolean loading) { 214 // If not previously waiting for categories, enter the waiting state by showing the loading 215 // indicator. 216 if (loading && !mAwaitingCategories) { 217 mAdapter.notifyItemChanged(getNumColumns()); 218 mAdapter.notifyItemInserted(getNumColumns()); 219 mAwaitingCategories = true; 220 } 221 // Not add existing category to category list 222 if (mCategories.indexOf(category) >= 0) { 223 return; 224 } 225 226 int priority = category.getPriority(); 227 228 int index = 0; 229 while (index < mCategories.size() && priority >= mCategories.get(index).getPriority()) { 230 index++; 231 } 232 233 mCategories.add(index, category); 234 if (mAdapter != null) { 235 // Offset the index because of the static metadata element at beginning of RecyclerView. 236 mAdapter.notifyItemInserted(index + NUM_NON_CATEGORY_VIEW_HOLDERS); 237 } 238 } 239 removeCategory(Category category)240 public void removeCategory(Category category) { 241 int index = mCategories.indexOf(category); 242 if (index >= 0) { 243 mCategories.remove(index); 244 mAdapter.notifyItemRemoved(index + NUM_NON_CATEGORY_VIEW_HOLDERS); 245 } 246 } 247 updateCategory(Category category)248 public void updateCategory(Category category) { 249 int index = mCategories.indexOf(category); 250 if (index >= 0) { 251 mCategories.remove(index); 252 mCategories.add(index, category); 253 mAdapter.notifyItemChanged(index + NUM_NON_CATEGORY_VIEW_HOLDERS); 254 } 255 } 256 clearCategories()257 public void clearCategories() { 258 mCategories.clear(); 259 mAdapter.notifyDataSetChanged(); 260 } 261 262 /** 263 * Notifies the CategoryFragment that no further categories are expected so it may hide 264 * the loading indicator. 265 */ doneFetchingCategories()266 public void doneFetchingCategories() { 267 if (mAwaitingCategories) { 268 mAdapter.notifyItemRemoved(mAdapter.getItemCount() - 1); 269 mAwaitingCategories = false; 270 } 271 } 272 273 /** 274 * Enable a test mode of operation -- in which certain UI features are disabled to allow for 275 * UI tests to run correctly. Works around issue in ProgressDialog currently where the dialog 276 * constantly keeps the UI thread alive and blocks a test forever. 277 */ setTestingMode(boolean testingMode)278 void setTestingMode(boolean testingMode) { 279 mTestingMode = testingMode; 280 } 281 canShowCurrentWallpaper()282 private boolean canShowCurrentWallpaper() { 283 Activity activity = getActivity(); 284 CategoryFragmentHost host = getFragmentHost(); 285 PackageManager packageManager = activity.getPackageManager(); 286 String packageName = activity.getPackageName(); 287 288 boolean hasReadWallpaperInternal = packageManager.checkPermission( 289 PERMISSION_READ_WALLPAPER_INTERNAL, packageName) == PackageManager.PERMISSION_GRANTED; 290 return hasReadWallpaperInternal || host.isReadExternalStoragePermissionGranted(); 291 } 292 getFragmentHost()293 private CategoryFragmentHost getFragmentHost() { 294 return (CategoryFragmentHost) getActivity(); 295 } 296 297 /** 298 * Obtains the {@link WallpaperInfo} object(s) representing the wallpaper(s) currently set to the 299 * device from the {@link CurrentWallpaperInfoFactory} and binds them to the provided 300 * {@link MetadataHolder}. 301 */ refreshCurrentWallpapers(@ullable final MetadataHolder holder, boolean forceRefresh)302 private void refreshCurrentWallpapers(@Nullable final MetadataHolder holder, 303 boolean forceRefresh) { 304 CurrentWallpaperInfoFactory factory = InjectorProvider.getInjector() 305 .getCurrentWallpaperFactory(getActivity().getApplicationContext()); 306 307 factory.createCurrentWallpaperInfos(new WallpaperInfoCallback() { 308 @Override 309 public void onWallpaperInfoCreated( 310 final WallpaperInfo homeWallpaper, 311 @Nullable final WallpaperInfo lockWallpaper, 312 @PresentationMode final int presentationMode) { 313 314 // Update the metadata displayed on screen. Do this in a Handler so it is scheduled at the 315 // end of the message queue. This is necessary to ensure we do not remove or add data from 316 // the adapter while the layout is being computed. RecyclerView documentation therefore 317 // recommends performing such changes in a Handler. 318 new android.os.Handler().post(new Runnable() { 319 @Override 320 public void run() { 321 // A config change may have destroyed the activity since the refresh started, so check 322 // for that. 323 if (getActivity() == null) { 324 return; 325 } 326 327 int numMetadataCards = (lockWallpaper == null) 328 ? CategoryAdapter.METADATA_VIEW_SINGLE_CARD 329 : CategoryAdapter.METADATA_VIEW_TWO_CARDS; 330 mAdapter.setNumMetadataCards(numMetadataCards); 331 332 // The MetadataHolder may be null if the RecyclerView has not yet created the view 333 // holder. 334 if (holder != null) { 335 holder.bindWallpapers(homeWallpaper, lockWallpaper, presentationMode); 336 } 337 } 338 }); 339 } 340 }, forceRefresh); 341 } 342 getNumColumns()343 private int getNumColumns() { 344 return TileSizeCalculator.getNumCategoryColumns(getActivity()); 345 } 346 347 /** 348 * Returns the width to use for the home screen wallpaper in the "single metadata" configuration. 349 */ getSingleWallpaperImageWidth()350 private int getSingleWallpaperImageWidth() { 351 Point screenSize = ScreenSizeCalculator.getInstance() 352 .getScreenSize(getActivity().getWindowManager().getDefaultDisplay()); 353 354 int height = getResources().getDimensionPixelSize(R.dimen.single_metadata_card_layout_height); 355 return height * screenSize.x / screenSize.y; 356 } 357 358 /** 359 * Refreshes the current wallpaper in a daily wallpaper rotation. 360 */ refreshDailyWallpaper()361 private void refreshDailyWallpaper() { 362 // ProgressDialog endlessly updates the UI thread, keeping it from going idle which therefore 363 // causes Espresso to hang once the dialog is shown. 364 if (!mTestingMode) { 365 int themeResId; 366 if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) { 367 themeResId = R.style.ProgressDialogThemePreL; 368 } else { 369 themeResId = R.style.LightDialogTheme; 370 } 371 mRefreshWallpaperProgressDialog = new ProgressDialog(getActivity(), themeResId); 372 mRefreshWallpaperProgressDialog.setTitle(null); 373 mRefreshWallpaperProgressDialog.setMessage( 374 getResources().getString(R.string.refreshing_daily_wallpaper_dialog_message)); 375 mRefreshWallpaperProgressDialog.setIndeterminate(true); 376 mRefreshWallpaperProgressDialog.setCancelable(false); 377 mRefreshWallpaperProgressDialog.show(); 378 } 379 380 WallpaperRotationRefresher wallpaperRotationRefresher = 381 InjectorProvider.getInjector().getWallpaperRotationRefresher(); 382 wallpaperRotationRefresher.refreshWallpaper(getContext(), new Listener() { 383 @Override 384 public void onRefreshed() { 385 // If the fragment is detached from the activity there's nothing to do here and the UI will 386 // update when the fragment is resumed. 387 if (getActivity() == null) { 388 return; 389 } 390 391 if (mRefreshWallpaperProgressDialog != null) { 392 mRefreshWallpaperProgressDialog.dismiss(); 393 } 394 395 ViewHolder initialViewHolder = 396 mImageGrid.findViewHolderForAdapterPosition(INITIAL_HOLDER_ADAPTER_POSITION); 397 if (initialViewHolder instanceof MetadataHolder) { 398 MetadataHolder metadataHolder = (MetadataHolder) initialViewHolder; 399 // Update the metadata pane since we know now the UI there is stale. 400 refreshCurrentWallpapers(metadataHolder, true /* forceRefresh */); 401 } 402 } 403 404 @Override 405 public void onError() { 406 if (getActivity() == null) { 407 return; 408 } 409 410 if (mRefreshWallpaperProgressDialog != null) { 411 mRefreshWallpaperProgressDialog.dismiss(); 412 } 413 414 AlertDialog errorDialog = new AlertDialog.Builder(getActivity(), R.style.LightDialogTheme) 415 .setMessage(R.string.refresh_daily_wallpaper_failed_message) 416 .setPositiveButton(android.R.string.ok, null /* onClickListener */) 417 .create(); 418 errorDialog.show(); 419 } 420 }); 421 } 422 423 /** 424 * Returns the width to use for the home and lock screen wallpapers in the "both metadata" 425 * configuration. 426 */ getBothWallpaperImageWidth()427 private int getBothWallpaperImageWidth() { 428 DisplayMetrics metrics = DisplayMetricsRetriever.getInstance().getDisplayMetrics(getResources(), 429 getActivity().getWindowManager().getDefaultDisplay()); 430 431 // In the "both metadata" configuration, wallpaper images minus the gutters account for the full 432 // width of the device's screen. 433 return metrics.widthPixels - (3 * getResources().getDimensionPixelSize(R.dimen.grid_padding)); 434 } 435 436 private interface MetadataHolder { 437 /** 438 * Binds {@link WallpaperInfo} objects representing the currently-set wallpapers to the 439 * ViewHolder layout. 440 */ bindWallpapers(WallpaperInfo homeWallpaper, WallpaperInfo lockWallpaper, @PresentationMode int presentationMode)441 void bindWallpapers(WallpaperInfo homeWallpaper, WallpaperInfo lockWallpaper, 442 @PresentationMode int presentationMode); 443 } 444 445 private static class SelectWallpaperHeaderHolder extends RecyclerView.ViewHolder { SelectWallpaperHeaderHolder(View headerView)446 public SelectWallpaperHeaderHolder(View headerView) { 447 super(headerView); 448 } 449 } 450 451 /** 452 * SpanSizeLookup subclass which provides that the item in the first position spans the number of 453 * columns in the RecyclerView and all other items only take up a single span. 454 */ 455 private class CategorySpanSizeLookup extends SpanSizeLookup { 456 CategoryAdapter mAdapter; 457 CategorySpanSizeLookup(CategoryAdapter adapter)458 public CategorySpanSizeLookup(CategoryAdapter adapter) { 459 mAdapter = adapter; 460 } 461 462 @Override getSpanSize(int position)463 public int getSpanSize(int position) { 464 if (position < NUM_NON_CATEGORY_VIEW_HOLDERS 465 || mAdapter.getItemViewType(position) 466 == CategoryAdapter.ITEM_VIEW_TYPE_LOADING_INDICATOR) { 467 return getNumColumns(); 468 } 469 470 return 1; 471 } 472 } 473 474 /** 475 * ViewHolder subclass for a metadata "card" at the beginning of the RecyclerView. 476 */ 477 private class SingleWallpaperMetadataHolder extends RecyclerView.ViewHolder 478 implements MetadataHolder { 479 private WallpaperInfo mWallpaperInfo; 480 private ImageView mWallpaperImage; 481 private TextView mWallpaperPresentationModeSubtitle; 482 private TextView mWallpaperTitle; 483 private TextView mWallpaperSubtitle; 484 private TextView mWallpaperSubtitle2; 485 private ImageButton mWallpaperExploreButtonNoText; 486 private ImageButton mSkipWallpaperButton; 487 SingleWallpaperMetadataHolder(View metadataView)488 public SingleWallpaperMetadataHolder(View metadataView) { 489 super(metadataView); 490 491 mWallpaperImage = metadataView.findViewById(R.id.wallpaper_image); 492 mWallpaperImage.getLayoutParams().width = getSingleWallpaperImageWidth(); 493 494 mWallpaperPresentationModeSubtitle = 495 metadataView.findViewById(R.id.wallpaper_presentation_mode_subtitle); 496 mWallpaperTitle = metadataView.findViewById(R.id.wallpaper_title); 497 mWallpaperSubtitle = metadataView.findViewById(R.id.wallpaper_subtitle); 498 mWallpaperSubtitle2 = metadataView.findViewById(R.id.wallpaper_subtitle2); 499 500 mWallpaperExploreButtonNoText = 501 metadataView.findViewById(R.id.wallpaper_explore_button_notext); 502 503 mSkipWallpaperButton = metadataView.findViewById(R.id.skip_wallpaper_button); 504 } 505 506 /** 507 * Binds home screen wallpaper to the ViewHolder layout. 508 */ 509 @Override bindWallpapers(WallpaperInfo homeWallpaper, WallpaperInfo lockWallpaper, @PresentationMode int presentationMode)510 public void bindWallpapers(WallpaperInfo homeWallpaper, WallpaperInfo lockWallpaper, 511 @PresentationMode int presentationMode) { 512 mWallpaperInfo = homeWallpaper; 513 514 bindWallpaperAsset(); 515 bindWallpaperText(presentationMode); 516 bindWallpaperActionButtons(presentationMode); 517 } 518 bindWallpaperAsset()519 private void bindWallpaperAsset() { 520 final UserEventLogger eventLogger = 521 InjectorProvider.getInjector().getUserEventLogger(getActivity()); 522 523 mWallpaperInfo.getThumbAsset(getActivity().getApplicationContext()).loadDrawable( 524 getActivity(), mWallpaperImage, getResources().getColor(R.color.secondary_color)); 525 526 mWallpaperImage.setOnClickListener(new OnClickListener() { 527 @Override 528 public void onClick(View v) { 529 getFragmentHost().showViewOnlyPreview(mWallpaperInfo); 530 eventLogger.logCurrentWallpaperPreviewed(); 531 } 532 }); 533 } 534 bindWallpaperText(@resentationMode int presentationMode)535 private void bindWallpaperText(@PresentationMode int presentationMode) { 536 Context appContext = getActivity().getApplicationContext(); 537 538 mWallpaperPresentationModeSubtitle.setText( 539 AttributionFormatter.getHumanReadableWallpaperPresentationMode( 540 appContext, presentationMode)); 541 542 List<String> attributions = mWallpaperInfo.getAttributions(appContext); 543 if (!attributions.isEmpty()) { 544 mWallpaperTitle.setText(attributions.get(0)); 545 } 546 if (attributions.size() > 1) { 547 mWallpaperSubtitle.setText(attributions.get(1)); 548 } else { 549 mWallpaperSubtitle.setVisibility(View.INVISIBLE); 550 } 551 if (attributions.size() > 2) { 552 mWallpaperSubtitle2.setText(attributions.get(2)); 553 } else { 554 mWallpaperSubtitle2.setVisibility(View.INVISIBLE); 555 } 556 } 557 bindWallpaperActionButtons(@resentationMode int presentationMode)558 private void bindWallpaperActionButtons(@PresentationMode int presentationMode) { 559 final Context appContext = getActivity().getApplicationContext(); 560 561 final String actionUrl = mWallpaperInfo.getActionUrl(appContext); 562 if (actionUrl != null && !actionUrl.isEmpty()) { 563 564 Uri exploreUri = Uri.parse(actionUrl); 565 566 ExploreIntentChecker intentChecker = 567 InjectorProvider.getInjector().getExploreIntentChecker(appContext); 568 intentChecker.fetchValidActionViewIntent(exploreUri, (@Nullable Intent exploreIntent) -> { 569 if (getActivity() == null) { 570 return; 571 } 572 573 updateExploreSectionVisibility(presentationMode, exploreIntent); 574 }); 575 } else { 576 updateExploreSectionVisibility(presentationMode, null /* exploreIntent */); 577 } 578 } 579 580 /** 581 * Shows or hides appropriate elements in the "Explore section" (containing the Explore button 582 * and the Next Wallpaper button) depending on the current wallpaper. 583 * 584 * @param presentationMode The presentation mode of the current wallpaper. 585 * @param exploreIntent An optional explore intent for the current wallpaper. 586 */ updateExploreSectionVisibility( @resentationMode int presentationMode, @Nullable Intent exploreIntent)587 private void updateExploreSectionVisibility( 588 @PresentationMode int presentationMode, @Nullable Intent exploreIntent) { 589 590 final Context appContext = getActivity().getApplicationContext(); 591 final UserEventLogger eventLogger = 592 InjectorProvider.getInjector().getUserEventLogger(appContext); 593 594 boolean showSkipWallpaperButton = Flags.skipDailyWallpaperButtonEnabled 595 && presentationMode == WallpaperPreferences.PRESENTATION_MODE_ROTATING; 596 597 if (exploreIntent != null) { 598 mWallpaperExploreButtonNoText.setImageDrawable(getContext().getDrawable( 599 mWallpaperInfo.getActionIconRes(appContext))); 600 mWallpaperExploreButtonNoText.setContentDescription( 601 getString(mWallpaperInfo.getActionLabelRes(appContext))); 602 mWallpaperExploreButtonNoText.setColorFilter( 603 getResources().getColor(R.color.currently_set_explore_button_color, 604 getContext().getTheme()), 605 Mode.SRC_IN); 606 mWallpaperExploreButtonNoText.setVisibility(View.VISIBLE); 607 mWallpaperExploreButtonNoText.setOnClickListener((View view) -> { 608 eventLogger.logActionClicked(mWallpaperInfo.getCollectionId(appContext), 609 mWallpaperInfo.getActionLabelRes(appContext)); 610 startActivity(exploreIntent); 611 }); 612 } 613 614 if (showSkipWallpaperButton) { 615 mSkipWallpaperButton.setVisibility(View.VISIBLE); 616 mSkipWallpaperButton.setOnClickListener((View view) -> refreshDailyWallpaper()); 617 } 618 } 619 } 620 621 /** 622 * ViewHolder subclass for a metadata "card" at the beginning of the RecyclerView that shows 623 * both home screen and lock screen wallpapers. 624 */ 625 private class TwoWallpapersMetadataHolder extends RecyclerView.ViewHolder 626 implements MetadataHolder { 627 private WallpaperInfo mHomeWallpaperInfo; 628 private ImageView mHomeWallpaperImage; 629 private TextView mHomeWallpaperPresentationMode; 630 private TextView mHomeWallpaperTitle; 631 private TextView mHomeWallpaperSubtitle1; 632 private TextView mHomeWallpaperSubtitle2; 633 634 private ImageButton mHomeWallpaperExploreButton; 635 private ImageButton mSkipWallpaperButton; 636 private ViewGroup mHomeWallpaperPresentationSection; 637 638 private WallpaperInfo mLockWallpaperInfo; 639 private ImageView mLockWallpaperImage; 640 private TextView mLockWallpaperTitle; 641 private TextView mLockWallpaperSubtitle1; 642 private TextView mLockWallpaperSubtitle2; 643 644 private ImageButton mLockWallpaperExploreButton; 645 TwoWallpapersMetadataHolder(View metadataView)646 public TwoWallpapersMetadataHolder(View metadataView) { 647 super(metadataView); 648 649 // Set the min width of the metadata panel to be the screen width minus space for the 650 // 2 gutters on the sides. This ensures the RecyclerView's GridLayoutManager gives it 651 // a wide-enough initial width to fill up the width of the grid prior to the view being 652 // fully populated. 653 final Display display = getActivity().getWindowManager().getDefaultDisplay(); 654 Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(display); 655 metadataView.setMinimumWidth( 656 screenSize.x - 2 * getResources().getDimensionPixelSize(R.dimen.grid_padding)); 657 658 int bothWallpaperImageWidth = getBothWallpaperImageWidth(); 659 660 FrameLayout homeWallpaperSection = metadataView.findViewById( 661 R.id.home_wallpaper_section); 662 homeWallpaperSection.setMinimumWidth(bothWallpaperImageWidth); 663 mHomeWallpaperImage = metadataView.findViewById(R.id.home_wallpaper_image); 664 665 mHomeWallpaperPresentationMode = 666 metadataView.findViewById(R.id.home_wallpaper_presentation_mode); 667 mHomeWallpaperTitle = metadataView.findViewById(R.id.home_wallpaper_title); 668 mHomeWallpaperSubtitle1 = metadataView.findViewById(R.id.home_wallpaper_subtitle1); 669 mHomeWallpaperSubtitle2 = metadataView.findViewById(R.id.home_wallpaper_subtitle2); 670 mHomeWallpaperPresentationSection = metadataView.findViewById( 671 R.id.home_wallpaper_presentation_section); 672 mHomeWallpaperExploreButton = 673 metadataView.findViewById(R.id.home_wallpaper_explore_button); 674 mSkipWallpaperButton = metadataView.findViewById(R.id.skip_home_wallpaper); 675 676 FrameLayout lockWallpaperSection = metadataView.findViewById( 677 R.id.lock_wallpaper_section); 678 lockWallpaperSection.setMinimumWidth(bothWallpaperImageWidth); 679 mLockWallpaperImage = metadataView.findViewById(R.id.lock_wallpaper_image); 680 681 mLockWallpaperTitle = metadataView.findViewById(R.id.lock_wallpaper_title); 682 mLockWallpaperSubtitle1 = metadataView.findViewById(R.id.lock_wallpaper_subtitle1); 683 mLockWallpaperSubtitle2 = metadataView.findViewById(R.id.lock_wallpaper_subtitle2); 684 mLockWallpaperExploreButton = 685 metadataView.findViewById(R.id.lock_wallpaper_explore_button); 686 } 687 688 @Override bindWallpapers(WallpaperInfo homeWallpaper, WallpaperInfo lockWallpaper, @PresentationMode int presentationMode)689 public void bindWallpapers(WallpaperInfo homeWallpaper, WallpaperInfo lockWallpaper, 690 @PresentationMode int presentationMode) { 691 bindHomeWallpaper(homeWallpaper, presentationMode); 692 bindLockWallpaper(lockWallpaper); 693 } 694 bindHomeWallpaper(WallpaperInfo homeWallpaper, @PresentationMode int presentationMode)695 private void bindHomeWallpaper(WallpaperInfo homeWallpaper, 696 @PresentationMode int presentationMode) { 697 final Context appContext = getActivity().getApplicationContext(); 698 final UserEventLogger eventLogger = 699 InjectorProvider.getInjector().getUserEventLogger(appContext); 700 701 mHomeWallpaperInfo = homeWallpaper; 702 703 homeWallpaper.getThumbAsset(appContext).loadDrawable( 704 getActivity(), mHomeWallpaperImage, 705 getResources().getColor(R.color.secondary_color, getContext().getTheme())); 706 707 mHomeWallpaperPresentationMode.setText( 708 AttributionFormatter.getHumanReadableWallpaperPresentationMode( 709 appContext, presentationMode)); 710 711 List<String> attributions = homeWallpaper.getAttributions(appContext); 712 if (!attributions.isEmpty()) { 713 mHomeWallpaperTitle.setText(attributions.get(0)); 714 } 715 if (attributions.size() > 1) { 716 mHomeWallpaperSubtitle1.setText(attributions.get(1)); 717 } 718 if (attributions.size() > 2) { 719 mHomeWallpaperSubtitle2.setText(attributions.get(2)); 720 } 721 722 final String homeActionUrl = homeWallpaper.getActionUrl(appContext); 723 724 if (homeActionUrl != null && !homeActionUrl.isEmpty()) { 725 Uri homeExploreUri = Uri.parse(homeActionUrl); 726 727 ExploreIntentChecker intentChecker = 728 InjectorProvider.getInjector().getExploreIntentChecker(appContext); 729 730 intentChecker.fetchValidActionViewIntent( 731 homeExploreUri, (@Nullable Intent exploreIntent) -> { 732 if (exploreIntent == null || getActivity() == null) { 733 return; 734 } 735 736 mHomeWallpaperExploreButton.setVisibility(View.VISIBLE); 737 mHomeWallpaperExploreButton.setImageDrawable(getContext().getDrawable( 738 homeWallpaper.getActionIconRes(appContext))); 739 mHomeWallpaperExploreButton.setContentDescription(getString(homeWallpaper 740 .getActionLabelRes(appContext))); 741 mHomeWallpaperExploreButton.setColorFilter( 742 getResources().getColor(R.color.currently_set_explore_button_color, 743 getContext().getTheme()), 744 Mode.SRC_IN); 745 mHomeWallpaperExploreButton.setOnClickListener(v -> { 746 eventLogger.logActionClicked( 747 mHomeWallpaperInfo.getCollectionId(appContext), 748 mHomeWallpaperInfo.getActionLabelRes(appContext)); 749 startActivity(exploreIntent); 750 }); 751 }); 752 } else { 753 mHomeWallpaperExploreButton.setVisibility(View.GONE); 754 } 755 756 if (presentationMode == WallpaperPreferences.PRESENTATION_MODE_ROTATING) { 757 mHomeWallpaperPresentationSection.setVisibility(View.VISIBLE); 758 if (Flags.skipDailyWallpaperButtonEnabled) { 759 mSkipWallpaperButton.setVisibility(View.VISIBLE); 760 mSkipWallpaperButton.setColorFilter( 761 getResources().getColor(R.color.currently_set_explore_button_color, 762 getContext().getTheme()), Mode.SRC_IN); 763 mSkipWallpaperButton.setOnClickListener(view -> refreshDailyWallpaper()); 764 } else { 765 mSkipWallpaperButton.setVisibility(View.GONE); 766 } 767 } else { 768 mHomeWallpaperPresentationSection.setVisibility(View.GONE); 769 } 770 771 mHomeWallpaperImage.setOnClickListener(v -> { 772 eventLogger.logCurrentWallpaperPreviewed(); 773 getFragmentHost().showViewOnlyPreview(mHomeWallpaperInfo); 774 }); 775 } 776 bindLockWallpaper(WallpaperInfo lockWallpaper)777 private void bindLockWallpaper(WallpaperInfo lockWallpaper) { 778 if (lockWallpaper == null) { 779 Log.e(TAG, "TwoWallpapersMetadataHolder bound without a lock screen wallpaper."); 780 return; 781 } 782 783 final Context appContext = getActivity().getApplicationContext(); 784 final UserEventLogger eventLogger = 785 InjectorProvider.getInjector().getUserEventLogger(getActivity()); 786 787 mLockWallpaperInfo = lockWallpaper; 788 789 lockWallpaper.getThumbAsset(appContext).loadDrawable( 790 getActivity(), mLockWallpaperImage, getResources().getColor(R.color.secondary_color)); 791 792 List<String> lockAttributions = lockWallpaper.getAttributions(appContext); 793 if (!lockAttributions.isEmpty()) { 794 mLockWallpaperTitle.setText(lockAttributions.get(0)); 795 } 796 if (lockAttributions.size() > 1) { 797 mLockWallpaperSubtitle1.setText(lockAttributions.get(1)); 798 } 799 if (lockAttributions.size() > 2) { 800 mLockWallpaperSubtitle2.setText(lockAttributions.get(2)); 801 } 802 803 final String lockActionUrl = lockWallpaper.getActionUrl(appContext); 804 805 if (lockActionUrl != null && !lockActionUrl.isEmpty()) { 806 Uri lockExploreUri = Uri.parse(lockActionUrl); 807 808 ExploreIntentChecker intentChecker = 809 InjectorProvider.getInjector().getExploreIntentChecker(appContext); 810 intentChecker.fetchValidActionViewIntent( 811 lockExploreUri, (@Nullable Intent exploreIntent) -> { 812 if (exploreIntent == null || getActivity() == null) { 813 return; 814 } 815 mLockWallpaperExploreButton.setImageDrawable(getContext().getDrawable( 816 lockWallpaper.getActionIconRes(appContext))); 817 mLockWallpaperExploreButton.setContentDescription(getString( 818 lockWallpaper.getActionLabelRes(appContext))); 819 mLockWallpaperExploreButton.setVisibility(View.VISIBLE); 820 mLockWallpaperExploreButton.setColorFilter( 821 getResources().getColor( 822 R.color.currently_set_explore_button_color), 823 Mode.SRC_IN); 824 mLockWallpaperExploreButton.setOnClickListener(new OnClickListener() { 825 @Override 826 public void onClick(View v) { 827 eventLogger.logActionClicked( 828 mLockWallpaperInfo.getCollectionId(appContext), 829 mLockWallpaperInfo.getActionLabelRes(appContext)); 830 startActivity(exploreIntent); 831 } 832 }); 833 }); 834 } else { 835 mLockWallpaperExploreButton.setVisibility(View.GONE); 836 } 837 838 mLockWallpaperImage.setOnClickListener(new OnClickListener() { 839 @Override 840 public void onClick(View v) { 841 eventLogger.logCurrentWallpaperPreviewed(); 842 getFragmentHost().showViewOnlyPreview(mLockWallpaperInfo); 843 } 844 }); 845 } 846 } 847 848 /** 849 * ViewHolder subclass for a category tile in the RecyclerView. 850 */ 851 private class CategoryHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 852 private Category mCategory; 853 private RelativeLayout mTileLayout; 854 private ImageView mImageView; 855 private ImageView mOverlayIconView; 856 private TextView mTitleView; 857 CategoryHolder(View itemView)858 public CategoryHolder(View itemView) { 859 super(itemView); 860 itemView.setOnClickListener(this); 861 862 mTileLayout = itemView.findViewById(R.id.tile); 863 mImageView = itemView.findViewById(R.id.image); 864 mOverlayIconView = itemView.findViewById(R.id.overlay_icon); 865 mTitleView = itemView.findViewById(R.id.category_title); 866 867 mTileLayout.getLayoutParams().height = mTileSizePx.y; 868 } 869 870 @Override onClick(View view)871 public void onClick(View view) { 872 final UserEventLogger eventLogger = 873 InjectorProvider.getInjector().getUserEventLogger(getActivity()); 874 eventLogger.logCategorySelected(mCategory.getCollectionId()); 875 876 if (mCategory.supportsCustomPhotos()) { 877 getFragmentHost().getMyPhotosStarter().requestCustomPhotoPicker( 878 new PermissionChangedListener() { 879 @Override 880 public void onPermissionsGranted() { 881 drawThumbnailAndOverlayIcon(); 882 } 883 884 @Override 885 public void onPermissionsDenied(boolean dontAskAgain) { 886 // No-op 887 } 888 }); 889 return; 890 } 891 892 getFragmentHost().show(mCategory.getCollectionId()); 893 } 894 895 /** 896 * Binds the given category to this CategoryHolder. 897 */ bindCategory(Category category)898 public void bindCategory(Category category) { 899 mCategory = category; 900 mTitleView.setText(category.getTitle()); 901 drawThumbnailAndOverlayIcon(); 902 } 903 904 /** 905 * Draws the CategoryHolder's thumbnail and overlay icon. 906 */ drawThumbnailAndOverlayIcon()907 public void drawThumbnailAndOverlayIcon() { 908 mOverlayIconView.setImageDrawable(mCategory.getOverlayIcon( 909 getActivity().getApplicationContext())); 910 911 // Size the overlay icon according to the category. 912 int overlayIconDimenDp = mCategory.getOverlayIconSizeDp(); 913 DisplayMetrics metrics = DisplayMetricsRetriever.getInstance().getDisplayMetrics( 914 getResources(), getActivity().getWindowManager().getDefaultDisplay()); 915 int overlayIconDimenPx = (int) (overlayIconDimenDp * metrics.density); 916 mOverlayIconView.getLayoutParams().width = overlayIconDimenPx; 917 mOverlayIconView.getLayoutParams().height = overlayIconDimenPx; 918 919 Asset thumbnail = mCategory.getThumbnail(getActivity().getApplicationContext()); 920 if (thumbnail != null) { 921 thumbnail.loadDrawable(getActivity(), mImageView, 922 getResources().getColor(R.color.secondary_color)); 923 } else { 924 // TODO(orenb): Replace this workaround for b/62584914 with a proper way of unloading the 925 // ImageView such that no incorrect image is improperly loaded upon rapid scroll. 926 Object nullObj = null; 927 Glide.with(getActivity()) 928 .asDrawable() 929 .load(nullObj) 930 .into(mImageView); 931 932 } 933 } 934 } 935 936 /** 937 * ViewHolder subclass for the loading indicator ("spinner") shown when categories are being 938 * fetched. 939 */ 940 private class LoadingIndicatorHolder extends RecyclerView.ViewHolder { LoadingIndicatorHolder(View view)941 public LoadingIndicatorHolder(View view) { 942 super(view); 943 ProgressBar progressBar = view.findViewById(R.id.loading_indicator); 944 progressBar.getIndeterminateDrawable().setColorFilter( 945 getResources().getColor(R.color.accent_color), Mode.SRC_IN); 946 } 947 } 948 949 /** 950 * ViewHolder subclass for a "card" at the beginning of the RecyclerView showing the app needs the 951 * user to grant the storage permission to show the currently set wallpaper. 952 */ 953 private class PermissionNeededHolder extends RecyclerView.ViewHolder { 954 private Button mAllowAccessButton; 955 PermissionNeededHolder(View view)956 public PermissionNeededHolder(View view) { 957 super(view); 958 959 mAllowAccessButton = view.findViewById(R.id.permission_needed_allow_access_button); 960 mAllowAccessButton.setOnClickListener((View v) -> { 961 getFragmentHost().requestExternalStoragePermission(mAdapter); 962 }); 963 964 // Replace explanation text with text containing the Wallpapers app name which replaces the 965 // placeholder. 966 String appName = getString(R.string.app_name); 967 String explanation = getString(R.string.permission_needed_explanation, appName); 968 TextView explanationTextView = view.findViewById(R.id.permission_needed_explanation); 969 explanationTextView.setText(explanation); 970 } 971 } 972 973 /** 974 * RecyclerView Adapter subclass for the category tiles in the RecyclerView. 975 */ 976 private class CategoryAdapter extends RecyclerView.Adapter<ViewHolder> 977 implements PermissionChangedListener { 978 public static final int METADATA_VIEW_SINGLE_CARD = 1; 979 public static final int METADATA_VIEW_TWO_CARDS = 2; 980 private static final int ITEM_VIEW_TYPE_METADATA = 1; 981 private static final int ITEM_VIEW_TYPE_SELECT_WALLPAPER_HEADER = 2; 982 private static final int ITEM_VIEW_TYPE_CATEGORY = 3; 983 private static final int ITEM_VIEW_TYPE_LOADING_INDICATOR = 4; 984 private static final int ITEM_VIEW_TYPE_PERMISSION_NEEDED = 5; 985 private List<Category> mCategories; 986 private int mNumMetadataCards; 987 CategoryAdapter(List<Category> categories)988 public CategoryAdapter(List<Category> categories) { 989 mCategories = categories; 990 mNumMetadataCards = METADATA_VIEW_SINGLE_CARD; 991 } 992 993 /** 994 * Sets the number of metadata cards to be shown in the metadata view holder. Updates the UI 995 * to reflect any changes in that number (e.g., a lock screen wallpaper has been set so we now 996 * need to show two cards). 997 */ setNumMetadataCards(int numMetadataCards)998 public void setNumMetadataCards(int numMetadataCards) { 999 if (numMetadataCards != mNumMetadataCards && getItemCount() > 0) { 1000 notifyItemChanged(0); 1001 } 1002 1003 mNumMetadataCards = numMetadataCards; 1004 } 1005 1006 @Override getItemViewType(int position)1007 public int getItemViewType(int position) { 1008 if (position == 0) { 1009 if (canShowCurrentWallpaper()) { 1010 return ITEM_VIEW_TYPE_METADATA; 1011 } else { 1012 return ITEM_VIEW_TYPE_PERMISSION_NEEDED; 1013 } 1014 } else if (position == 1) { 1015 return ITEM_VIEW_TYPE_SELECT_WALLPAPER_HEADER; 1016 } else if (mAwaitingCategories && position == getItemCount() - 1) { 1017 return ITEM_VIEW_TYPE_LOADING_INDICATOR; 1018 } 1019 1020 return ITEM_VIEW_TYPE_CATEGORY; 1021 } 1022 1023 @Override onCreateViewHolder(ViewGroup parent, int viewType)1024 public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 1025 LayoutInflater layoutInflater = LayoutInflater.from(getActivity()); 1026 View view; 1027 1028 switch (viewType) { 1029 case ITEM_VIEW_TYPE_METADATA: 1030 if (mNumMetadataCards == METADATA_VIEW_SINGLE_CARD) { 1031 view = layoutInflater.inflate( 1032 R.layout.grid_item_single_metadata, parent, /* attachToRoot */ false); 1033 return new SingleWallpaperMetadataHolder(view); 1034 } else { // TWO_CARDS 1035 view = layoutInflater.inflate( 1036 R.layout.grid_item_both_metadata, parent, /* attachToRoot */ false); 1037 return new TwoWallpapersMetadataHolder(view); 1038 } 1039 case ITEM_VIEW_TYPE_SELECT_WALLPAPER_HEADER: 1040 view = layoutInflater.inflate( 1041 R.layout.grid_item_select_wallpaper_header, parent, /* attachToRoot */ false); 1042 return new SelectWallpaperHeaderHolder(view); 1043 case ITEM_VIEW_TYPE_LOADING_INDICATOR: 1044 view = layoutInflater.inflate( 1045 R.layout.grid_item_loading_indicator, parent, /* attachToRoot */ false); 1046 return new LoadingIndicatorHolder(view); 1047 case ITEM_VIEW_TYPE_CATEGORY: 1048 view = layoutInflater.inflate( 1049 R.layout.grid_item_category, parent, /* attachToRoot */ false); 1050 return new CategoryHolder(view); 1051 case ITEM_VIEW_TYPE_PERMISSION_NEEDED: 1052 view = layoutInflater.inflate( 1053 R.layout.grid_item_permission_needed, parent, /* attachToRoot */ false); 1054 return new PermissionNeededHolder(view); 1055 default: 1056 Log.e(TAG, "Unsupported viewType " + viewType + " in CategoryAdapter"); 1057 return null; 1058 } 1059 } 1060 1061 @Override onBindViewHolder(ViewHolder holder, int position)1062 public void onBindViewHolder(ViewHolder holder, int position) { 1063 int viewType = getItemViewType(position); 1064 1065 switch (viewType) { 1066 case ITEM_VIEW_TYPE_METADATA: 1067 refreshCurrentWallpapers((MetadataHolder) holder, false /* forceRefresh */); 1068 break; 1069 case ITEM_VIEW_TYPE_SELECT_WALLPAPER_HEADER: 1070 // No op. 1071 break; 1072 case ITEM_VIEW_TYPE_CATEGORY: 1073 // Offset position to get category index to account for the non-category view holders. 1074 Category category = mCategories.get(position - NUM_NON_CATEGORY_VIEW_HOLDERS); 1075 ((CategoryHolder) holder).bindCategory(category); 1076 break; 1077 case ITEM_VIEW_TYPE_LOADING_INDICATOR: 1078 case ITEM_VIEW_TYPE_PERMISSION_NEEDED: 1079 // No op. 1080 break; 1081 default: 1082 Log.e(TAG, "Unsupported viewType " + viewType + " in CategoryAdapter"); 1083 } 1084 } 1085 1086 @Override getItemCount()1087 public int getItemCount() { 1088 // Add to size of categories to account for the metadata related views. 1089 // Add 1 more for the loading indicator if not yet done loading. 1090 int size = mCategories.size() + NUM_NON_CATEGORY_VIEW_HOLDERS; 1091 if (mAwaitingCategories) { 1092 size += 1; 1093 } 1094 1095 return size; 1096 } 1097 1098 @Override onPermissionsGranted()1099 public void onPermissionsGranted() { 1100 notifyDataSetChanged(); 1101 } 1102 1103 @Override onPermissionsDenied(boolean dontAskAgain)1104 public void onPermissionsDenied(boolean dontAskAgain) { 1105 if (!dontAskAgain) { 1106 return; 1107 } 1108 1109 String permissionNeededMessage = 1110 getString(R.string.permission_needed_explanation_go_to_settings); 1111 AlertDialog dialog = new AlertDialog.Builder(getActivity(), R.style.LightDialogTheme) 1112 .setMessage(permissionNeededMessage) 1113 .setPositiveButton(android.R.string.ok, null /* onClickListener */) 1114 .setNegativeButton( 1115 R.string.settings_button_label, 1116 new DialogInterface.OnClickListener() { 1117 @Override 1118 public void onClick(DialogInterface dialogInterface, int i) { 1119 Intent appInfoIntent = new Intent(); 1120 appInfoIntent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 1121 Uri uri = Uri.fromParts( 1122 "package", getActivity().getPackageName(), null /* fragment */); 1123 appInfoIntent.setData(uri); 1124 startActivityForResult(appInfoIntent, SETTINGS_APP_INFO_REQUEST_CODE); 1125 } 1126 }) 1127 .create(); 1128 dialog.show(); 1129 } 1130 } 1131 } 1132