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