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 static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED; 19 import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED; 20 21 import android.app.Activity; 22 import android.app.AlertDialog; 23 import android.app.WallpaperColors; 24 import android.content.Intent; 25 import android.content.pm.PackageManager; 26 import android.graphics.Rect; 27 import android.net.Uri; 28 import android.os.Bundle; 29 import android.provider.Settings; 30 import android.service.wallpaper.WallpaperService; 31 import android.text.TextUtils; 32 import android.view.LayoutInflater; 33 import android.view.MenuItem; 34 import android.view.SurfaceView; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.widget.Button; 38 import android.widget.ImageView; 39 import android.widget.TextView; 40 41 import androidx.annotation.NonNull; 42 import androidx.annotation.Nullable; 43 import androidx.cardview.widget.CardView; 44 import androidx.viewpager.widget.PagerAdapter; 45 import androidx.viewpager.widget.ViewPager; 46 47 import com.android.wallpaper.R; 48 import com.android.wallpaper.model.Category; 49 import com.android.wallpaper.model.LiveWallpaperInfo; 50 import com.android.wallpaper.model.WallpaperCategory; 51 import com.android.wallpaper.model.WallpaperInfo; 52 import com.android.wallpaper.module.CurrentWallpaperInfoFactory; 53 import com.android.wallpaper.module.CurrentWallpaperInfoFactory.WallpaperInfoCallback; 54 import com.android.wallpaper.module.InjectorProvider; 55 import com.android.wallpaper.module.UserEventLogger; 56 import com.android.wallpaper.module.WallpaperPersister; 57 import com.android.wallpaper.module.WallpaperPreferences; 58 import com.android.wallpaper.module.WallpaperPreferences.PresentationMode; 59 import com.android.wallpaper.picker.CategorySelectorFragment.CategorySelectorFragmentHost; 60 import com.android.wallpaper.picker.MyPhotosStarter.MyPhotosStarterProvider; 61 import com.android.wallpaper.picker.MyPhotosStarter.PermissionChangedListener; 62 import com.android.wallpaper.picker.individual.IndividualPickerFragment; 63 import com.android.wallpaper.picker.individual.IndividualPickerFragment.ThumbnailUpdater; 64 import com.android.wallpaper.picker.individual.IndividualPickerFragment.WallpaperDestinationCallback; 65 import com.android.wallpaper.util.DeepLinkUtils; 66 import com.android.wallpaper.util.ResourceUtils; 67 import com.android.wallpaper.util.SizeCalculator; 68 import com.android.wallpaper.util.WallpaperConnection; 69 import com.android.wallpaper.util.WallpaperConnection.WallpaperConnectionListener; 70 import com.android.wallpaper.util.WallpaperSurfaceCallback; 71 import com.android.wallpaper.widget.LockScreenPreviewer; 72 import com.android.wallpaper.widget.PreviewPager; 73 import com.android.wallpaper.widget.WallpaperColorsLoader; 74 import com.android.wallpaper.widget.WallpaperPickerRecyclerViewAccessibilityDelegate; 75 76 import com.bumptech.glide.Glide; 77 import com.bumptech.glide.MemoryCategory; 78 import com.google.android.material.bottomsheet.BottomSheetBehavior; 79 80 import java.util.ArrayList; 81 import java.util.Collections; 82 import java.util.Date; 83 import java.util.List; 84 85 /** 86 * Displays the Main UI for picking a category of wallpapers to choose from. 87 */ 88 public class CategoryFragment extends AppbarFragment 89 implements CategorySelectorFragmentHost, ThumbnailUpdater, WallpaperDestinationCallback, 90 WallpaperPickerRecyclerViewAccessibilityDelegate.BottomSheetHost, 91 IndividualPickerFragment.IndividualPickerFragmentHost { 92 93 /** 94 * Interface to be implemented by an Activity hosting a {@link CategoryFragment} 95 */ 96 public interface CategoryFragmentHost extends MyPhotosStarterProvider { 97 requestExternalStoragePermission(PermissionChangedListener listener)98 void requestExternalStoragePermission(PermissionChangedListener listener); 99 isReadExternalStoragePermissionGranted()100 boolean isReadExternalStoragePermissionGranted(); 101 showViewOnlyPreview(WallpaperInfo wallpaperInfo, boolean isViewAsHome)102 void showViewOnlyPreview(WallpaperInfo wallpaperInfo, boolean isViewAsHome); 103 show(String collectionId)104 void show(String collectionId); 105 fetchCategories()106 void fetchCategories(); 107 cleanUp()108 void cleanUp(); 109 } 110 newInstance(CharSequence title)111 public static CategoryFragment newInstance(CharSequence title) { 112 CategoryFragment fragment = new CategoryFragment(); 113 fragment.setArguments(AppbarFragment.createArguments(title)); 114 return fragment; 115 } 116 117 private static final String TAG = "CategoryFragment"; 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 SurfaceView mWorkspaceSurface; 125 private WorkspaceSurfaceHolderCallback mWorkspaceSurfaceCallback; 126 private SurfaceView mWallpaperSurface; 127 private WallpaperSurfaceCallback mWallpaperSurfaceCallback; 128 private PreviewPager mPreviewPager; 129 private List<View> mWallPaperPreviews; 130 private WallpaperConnection mWallpaperConnection; 131 private CategorySelectorFragment mCategorySelectorFragment; 132 private IndividualPickerFragment mIndividualPickerFragment; 133 private boolean mShowSelectedWallpaper; 134 private BottomSheetBehavior<View> mBottomSheetBehavior; 135 // The index of Destination#DEST_HOME_SCREEN or Destination#DEST_LOCK_SCREEN 136 private int mWallpaperIndex; 137 138 // The wallpaper information which is currently shown on the home preview. 139 private WallpaperInfo mHomePreviewWallpaperInfo; 140 // The wallpaper information which is currently shown on the lock preview. 141 private WallpaperInfo mLockPreviewWallpaperInfo; 142 143 private LockScreenPreviewer mLockScreenPreviewer; 144 private View mRootContainer; 145 146 private final Rect mPreviewLocalRect = new Rect(); 147 private final Rect mPreviewGlobalRect = new Rect(); 148 private final int[] mLivePreviewLocation = new int[2]; 149 CategoryFragment()150 public CategoryFragment() { 151 mCategorySelectorFragment = new CategorySelectorFragment(); 152 } 153 154 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)155 public View onCreateView(LayoutInflater inflater, ViewGroup container, 156 Bundle savedInstanceState) { 157 View view = inflater.inflate( 158 R.layout.fragment_category_picker, container, /* attachToRoot= */ false); 159 160 mWallPaperPreviews = new ArrayList<>(); 161 CardView homePreviewCard = (CardView) inflater.inflate( 162 R.layout.wallpaper_preview_card, null); 163 mWorkspaceSurface = homePreviewCard.findViewById(R.id.workspace_surface); 164 mWorkspaceSurfaceCallback = new WorkspaceSurfaceHolderCallback( 165 mWorkspaceSurface, getContext()); 166 mWallpaperSurface = homePreviewCard.findViewById(R.id.wallpaper_surface); 167 mWallpaperSurfaceCallback = new WallpaperSurfaceCallback(getContext(), homePreviewCard, 168 mWallpaperSurface); 169 mWallPaperPreviews.add(homePreviewCard); 170 171 CardView lockscreenPreviewCard = (CardView) inflater.inflate( 172 R.layout.wallpaper_preview_card, null); 173 lockscreenPreviewCard.findViewById(R.id.workspace_surface).setVisibility(View.GONE); 174 lockscreenPreviewCard.findViewById(R.id.wallpaper_surface).setVisibility(View.GONE); 175 ViewGroup lockPreviewContainer = lockscreenPreviewCard.findViewById( 176 R.id.lock_screen_preview_container); 177 lockPreviewContainer.setVisibility(View.VISIBLE); 178 mLockScreenPreviewer = new LockScreenPreviewer(getLifecycle(), getContext(), 179 lockPreviewContainer); 180 mWallPaperPreviews.add(lockscreenPreviewCard); 181 182 mPreviewPager = view.findViewById(R.id.wallpaper_preview_pager); 183 if (mPreviewPager.isRtl()) { 184 Collections.reverse(mWallPaperPreviews); 185 } 186 mPreviewPager.setAdapter(new PreviewPagerAdapter(mWallPaperPreviews)); 187 mPreviewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { 188 @Override 189 public void onPageScrolled(int position, float positionOffset, 190 int positionOffsetPixels) { 191 // For live wallpaper, show its thumbnail when scrolling. 192 if (mWallpaperConnection != null && mWallpaperConnection.isEngineReady() 193 && mHomePreviewWallpaperInfo instanceof LiveWallpaperInfo) { 194 if (positionOffset == 0.0f || positionOffset == 1.0f 195 || positionOffsetPixels == 0) { 196 // The page is not moved. Show live wallpaper. 197 mWallpaperSurface.setZOrderMediaOverlay(false); 198 } else { 199 // The page is moving. Show live wallpaper's thumbnail. 200 mWallpaperSurface.setZOrderMediaOverlay(true); 201 } 202 } 203 } 204 205 @Override 206 public void onPageSelected(int i) { 207 mWallpaperIndex = mPreviewPager.isRtl() 208 ? (mWallPaperPreviews.size() - 1) - i 209 : i; 210 if (mIndividualPickerFragment != null && mIndividualPickerFragment.isVisible()) { 211 mIndividualPickerFragment.highlightAppliedWallpaper(mWallpaperIndex); 212 } 213 } 214 215 @Override 216 public void onPageScrollStateChanged(int i) { 217 } 218 }); 219 setupCurrentWallpaperPreview(view); 220 221 ViewGroup fragmentContainer = view.findViewById(R.id.category_fragment_container); 222 mBottomSheetBehavior = BottomSheetBehavior.from(fragmentContainer); 223 mBottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { 224 @Override 225 public void onStateChanged(@NonNull View bottomSheet, int newState) { 226 // Update preview pager's accessibility param since it will be blocked by the 227 // bottom sheet when expanded. 228 mPreviewPager.setImportantForAccessibility(newState == STATE_EXPANDED 229 ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS 230 : View.IMPORTANT_FOR_ACCESSIBILITY_YES); 231 } 232 233 @Override 234 public void onSlide(@NonNull View bottomSheet, float slideOffset) {} 235 }); 236 mRootContainer = view.findViewById(R.id.root_container); 237 fragmentContainer.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { 238 @Override 239 public void onLayoutChange(View containerView, int left, int top, int right, 240 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 241 int minimumHeight = mRootContainer.getHeight() - mPreviewPager.getMeasuredHeight(); 242 mBottomSheetBehavior.setPeekHeight(minimumHeight); 243 containerView.setMinimumHeight(minimumHeight); 244 homePreviewCard.setRadius(SizeCalculator.getPreviewCornerRadius( 245 getActivity(), homePreviewCard.getMeasuredWidth())); 246 if (lockscreenPreviewCard != null) { 247 lockscreenPreviewCard 248 .setRadius(SizeCalculator.getPreviewCornerRadius( 249 getActivity(), lockPreviewContainer.getMeasuredWidth())); 250 } 251 }}); 252 fragmentContainer.setOnApplyWindowInsetsListener((v, windowInsets) -> { 253 v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), 254 windowInsets.getSystemWindowInsetBottom()); 255 return windowInsets; 256 }); 257 258 setUpToolbar(view); 259 260 getChildFragmentManager() 261 .beginTransaction() 262 .replace(R.id.category_fragment_container, mCategorySelectorFragment) 263 .commitNow(); 264 265 // Deep link case 266 Intent intent = getActivity().getIntent(); 267 String deepLinkCollectionId = DeepLinkUtils.getCollectionId(intent); 268 if (!TextUtils.isEmpty(deepLinkCollectionId)) { 269 mIndividualPickerFragment = InjectorProvider.getInjector() 270 .getIndividualPickerFragment(deepLinkCollectionId); 271 mIndividualPickerFragment.highlightAppliedWallpaper(mWallpaperIndex); 272 getChildFragmentManager() 273 .beginTransaction() 274 .replace(R.id.category_fragment_container, mIndividualPickerFragment) 275 .addToBackStack(null) 276 .commit(); 277 getChildFragmentManager().executePendingTransactions(); 278 intent.setData(null); 279 } 280 return view; 281 } 282 283 @Override onViewCreated(@onNull View view, @Nullable Bundle savedInstanceState)284 public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 285 super.onViewCreated(view, savedInstanceState); 286 updateWallpaperSurface(); 287 updateWorkspaceSurface(); 288 } 289 290 @Override getDefaultTitle()291 public CharSequence getDefaultTitle() { 292 return getContext().getString(R.string.app_name); 293 } 294 295 @Override onResume()296 public void onResume() { 297 super.onResume(); 298 299 WallpaperPreferences preferences = InjectorProvider.getInjector().getPreferences(getActivity()); 300 preferences.setLastAppActiveTimestamp(new Date().getTime()); 301 302 // Reset Glide memory settings to a "normal" level of usage since it may have been lowered in 303 // PreviewFragment. 304 Glide.get(getActivity()).setMemoryCategory(MemoryCategory.NORMAL); 305 306 // The wallpaper may have been set while this fragment was paused, so force refresh the current 307 // wallpapers and presentation mode. 308 if (!mShowSelectedWallpaper) { 309 refreshCurrentWallpapers(/* forceRefresh= */ true); 310 } 311 if (mWallpaperConnection != null) { 312 mWallpaperConnection.setVisibility(true); 313 } 314 } 315 316 @Override onPause()317 public void onPause() { 318 super.onPause(); 319 if (mWallpaperConnection != null) { 320 mWallpaperConnection.setVisibility(false); 321 } 322 } 323 324 @Override onStop()325 public void onStop() { 326 super.onStop(); 327 if (mWallpaperConnection != null) { 328 mWallpaperConnection.disconnect(); 329 mWallpaperConnection = null; 330 } 331 } 332 333 @Override onDestroyView()334 public void onDestroyView() { 335 super.onDestroyView(); 336 mWallpaperSurfaceCallback.cleanUp(); 337 mWorkspaceSurfaceCallback.cleanUp(); 338 if (mWallpaperConnection != null) { 339 mWallpaperConnection.disconnect(); 340 mWallpaperConnection = null; 341 } 342 mPreviewPager.setAdapter(null); 343 mWallPaperPreviews.forEach(view -> ((ViewGroup) view).removeAllViews()); 344 mWallPaperPreviews.clear(); 345 } 346 347 @Override onDestroy()348 public void onDestroy() { 349 super.onDestroy(); 350 if (mWallpaperConnection != null) { 351 mWallpaperConnection.disconnect(); 352 mWallpaperConnection = null; 353 } 354 } 355 356 @Override onActivityResult(int requestCode, int resultCode, Intent data)357 public void onActivityResult(int requestCode, int resultCode, Intent data) { 358 if (requestCode == SETTINGS_APP_INFO_REQUEST_CODE) { 359 mCategorySelectorFragment.notifyDataSetChanged(); 360 } 361 } 362 363 @Override requestCustomPhotoPicker(PermissionChangedListener listener)364 public void requestCustomPhotoPicker(PermissionChangedListener listener) { 365 getFragmentHost().getMyPhotosStarter().requestCustomPhotoPicker(listener); 366 } 367 368 @Override show(Category category)369 public void show(Category category) { 370 if (!(category instanceof WallpaperCategory)) { 371 getFragmentHost().show(category.getCollectionId()); 372 return; 373 } 374 mIndividualPickerFragment = InjectorProvider.getInjector() 375 .getIndividualPickerFragment(category.getCollectionId()); 376 mIndividualPickerFragment.highlightAppliedWallpaper(mWallpaperIndex); 377 mIndividualPickerFragment.setOnWallpaperSelectedListener(position -> { 378 // Scroll to the selected wallpaper and collapse the sheet if needed. 379 // Resize and scroll here because we want to let the RecyclerView's scrolling and 380 // BottomSheet's collapsing can be executed together instead of scrolling 381 // the RecyclerView after the BottomSheet is collapsed. 382 mIndividualPickerFragment.resizeLayout(mBottomSheetBehavior.getPeekHeight()); 383 mIndividualPickerFragment.scrollToPosition(position); 384 if (mBottomSheetBehavior.getState() != STATE_COLLAPSED) { 385 mBottomSheetBehavior.setState(STATE_COLLAPSED); 386 } 387 }); 388 getChildFragmentManager() 389 .beginTransaction() 390 .replace(R.id.category_fragment_container, mIndividualPickerFragment) 391 .addToBackStack(null) 392 .commit(); 393 getChildFragmentManager().executePendingTransactions(); 394 } 395 396 @Override isHostToolbarShown()397 public boolean isHostToolbarShown() { 398 return true; 399 } 400 401 @Override setToolbarTitle(CharSequence title)402 public void setToolbarTitle(CharSequence title) { 403 setTitle(title); 404 } 405 406 @Override setToolbarMenu(int menuResId)407 public void setToolbarMenu(int menuResId) { 408 setUpToolbarMenu(menuResId); 409 } 410 411 @Override removeToolbarMenu()412 public void removeToolbarMenu() { 413 mToolbar.getMenu().clear(); 414 } 415 416 @Override moveToPreviousFragment()417 public void moveToPreviousFragment() { 418 getChildFragmentManager().popBackStack(); 419 } 420 421 @Override fetchCategories()422 public void fetchCategories() { 423 getFragmentHost().fetchCategories(); 424 } 425 426 @Override cleanUp()427 public void cleanUp() { 428 getFragmentHost().cleanUp(); 429 } 430 431 @Override expandBottomSheet()432 public void expandBottomSheet() { 433 if (mBottomSheetBehavior.getState() != BottomSheetBehavior.STATE_EXPANDED) { 434 mBottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); 435 } 436 } 437 438 @Override getBottomSheetState()439 public int getBottomSheetState() { 440 return mBottomSheetBehavior.getState(); 441 } 442 443 @Override updateThumbnail(WallpaperInfo wallpaperInfo)444 public void updateThumbnail(WallpaperInfo wallpaperInfo) { 445 new android.os.Handler().post(() -> { 446 // A config change may have destroyed the activity since the refresh started, so check 447 // for that. 448 if (getActivity() == null) { 449 return; 450 } 451 452 mHomePreviewWallpaperInfo = wallpaperInfo; 453 mLockPreviewWallpaperInfo = wallpaperInfo; 454 updateThumbnail(mHomePreviewWallpaperInfo, 455 mWallpaperSurfaceCallback.getHomeImageWallpaper(), true); 456 mShowSelectedWallpaper = true; 457 }); 458 } 459 460 @Override restoreThumbnails()461 public void restoreThumbnails() { 462 refreshCurrentWallpapers(/* forceRefresh= */ true); 463 mShowSelectedWallpaper = false; 464 } 465 466 @Override onDestinationSet(@allpaperPersister.Destination int destination)467 public void onDestinationSet(@WallpaperPersister.Destination int destination) { 468 if (destination == WallpaperPersister.DEST_BOTH) { 469 return; 470 } 471 mPreviewPager.switchPreviewPage(destination); 472 } 473 474 @Override onMenuItemClick(MenuItem item)475 public boolean onMenuItemClick(MenuItem item) { 476 if (item.getItemId() == R.id.daily_rotation) { 477 if (mIndividualPickerFragment != null && mIndividualPickerFragment.isVisible()) { 478 mIndividualPickerFragment.showRotationDialog(); 479 } 480 return true; 481 } 482 return super.onMenuItemClick(item); 483 } 484 485 /** 486 * Gets the {@link CategorySelectorFragment} which is attached to {@link CategoryFragment}. 487 */ getCategorySelectorFragment()488 public CategorySelectorFragment getCategorySelectorFragment() { 489 return mCategorySelectorFragment; 490 } 491 492 /** 493 * Pops the child fragment from the stack if {@link CategoryFragment} is visible to the users. 494 * 495 * @return {@code true} if the child fragment is popped, {@code false} otherwise. 496 */ popChildFragment()497 public boolean popChildFragment() { 498 return isVisible() && getChildFragmentManager().popBackStackImmediate(); 499 } 500 canShowCurrentWallpaper()501 private boolean canShowCurrentWallpaper() { 502 Activity activity = getActivity(); 503 CategoryFragmentHost host = getFragmentHost(); 504 PackageManager packageManager = activity.getPackageManager(); 505 String packageName = activity.getPackageName(); 506 507 boolean hasReadWallpaperInternal = packageManager.checkPermission( 508 PERMISSION_READ_WALLPAPER_INTERNAL, packageName) == PackageManager.PERMISSION_GRANTED; 509 return hasReadWallpaperInternal || host.isReadExternalStoragePermissionGranted(); 510 } 511 showCurrentWallpaper(View rootView, boolean show)512 private void showCurrentWallpaper(View rootView, boolean show) { 513 // The category/wallpaper tiles page depends on the height of the preview pager. 514 // So if we want to hide the preview pager, we should use INVISIBLE instead of GONE. 515 rootView.findViewById(R.id.wallpaper_preview_pager) 516 .setVisibility(show ? View.VISIBLE : View.INVISIBLE); 517 rootView.findViewById(R.id.permission_needed) 518 .setVisibility(show ? View.GONE : View.VISIBLE); 519 } 520 setupCurrentWallpaperPreview(View rootView)521 private void setupCurrentWallpaperPreview(View rootView) { 522 if (canShowCurrentWallpaper()) { 523 showCurrentWallpaper(rootView, true); 524 } else { 525 showCurrentWallpaper(rootView, false); 526 527 Button mAllowAccessButton = rootView 528 .findViewById(R.id.permission_needed_allow_access_button); 529 mAllowAccessButton.setOnClickListener(view -> 530 getFragmentHost().requestExternalStoragePermission( 531 new PermissionChangedListener() { 532 533 @Override 534 public void onPermissionsGranted() { 535 showCurrentWallpaper(rootView, true); 536 mCategorySelectorFragment.notifyDataSetChanged(); 537 } 538 539 @Override 540 public void onPermissionsDenied(boolean dontAskAgain) { 541 if (!dontAskAgain) { 542 return; 543 } 544 showPermissionNeededDialog(); 545 } 546 }) 547 ); 548 549 // Replace explanation text with text containing the Wallpapers app name which replaces 550 // the placeholder. 551 String appName = getString(R.string.app_name); 552 String explanation = getString(R.string.permission_needed_explanation, appName); 553 TextView explanationView = rootView.findViewById(R.id.permission_needed_explanation); 554 explanationView.setText(explanation); 555 } 556 } 557 showPermissionNeededDialog()558 private void showPermissionNeededDialog() { 559 String permissionNeededMessage = getString( 560 R.string.permission_needed_explanation_go_to_settings); 561 AlertDialog dialog = new AlertDialog.Builder(getActivity(), R.style.LightDialogTheme) 562 .setMessage(permissionNeededMessage) 563 .setPositiveButton(android.R.string.ok, /* onClickListener= */ null) 564 .setNegativeButton( 565 R.string.settings_button_label, 566 (dialogInterface, i) -> { 567 Intent appInfoIntent = new Intent(); 568 appInfoIntent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 569 Uri uri = Uri.fromParts("package", 570 getActivity().getPackageName(), /* fragment= */ null); 571 appInfoIntent.setData(uri); 572 startActivityForResult(appInfoIntent, SETTINGS_APP_INFO_REQUEST_CODE); 573 }) 574 .create(); 575 dialog.show(); 576 } 577 getFragmentHost()578 private CategoryFragmentHost getFragmentHost() { 579 return (CategoryFragmentHost) getActivity(); 580 } 581 getWallpaperIntent(android.app.WallpaperInfo info)582 private Intent getWallpaperIntent(android.app.WallpaperInfo info) { 583 return new Intent(WallpaperService.SERVICE_INTERFACE) 584 .setClassName(info.getPackageName(), info.getServiceName()); 585 } 586 587 /** 588 * Obtains the {@link WallpaperInfo} object(s) representing the wallpaper(s) currently set to 589 * the device from the {@link CurrentWallpaperInfoFactory}. 590 */ refreshCurrentWallpapers(boolean forceRefresh)591 private void refreshCurrentWallpapers(boolean forceRefresh) { 592 CurrentWallpaperInfoFactory factory = InjectorProvider.getInjector() 593 .getCurrentWallpaperFactory(getActivity().getApplicationContext()); 594 595 factory.createCurrentWallpaperInfos(new WallpaperInfoCallback() { 596 @Override 597 public void onWallpaperInfoCreated( 598 final WallpaperInfo homeWallpaper, 599 @Nullable final WallpaperInfo lockWallpaper, 600 @PresentationMode final int presentationMode) { 601 602 // Update the metadata displayed on screen. Do this in a Handler so it is scheduled at the 603 // end of the message queue. This is necessary to ensure we do not remove or add data from 604 // the adapter while the layout is being computed. RecyclerView documentation therefore 605 // recommends performing such changes in a Handler. 606 new android.os.Handler().post(new Runnable() { 607 @Override 608 public void run() { 609 final Activity activity = getActivity(); 610 // A config change may have destroyed the activity since the refresh 611 // started, so check for that. 612 if (activity == null) { 613 return; 614 } 615 616 mHomePreviewWallpaperInfo = homeWallpaper; 617 mLockPreviewWallpaperInfo = 618 lockWallpaper == null ? homeWallpaper : lockWallpaper; 619 updateThumbnail(mHomePreviewWallpaperInfo, 620 mWallpaperSurfaceCallback.getHomeImageWallpaper(), true); 621 } 622 }); 623 } 624 }, forceRefresh); 625 } 626 setUpLiveWallpaperPreview(WallpaperInfo homeWallpaper)627 private void setUpLiveWallpaperPreview(WallpaperInfo homeWallpaper) { 628 Activity activity = getActivity(); 629 if (activity == null) { 630 return; 631 } 632 if (mWallpaperConnection != null) { 633 mWallpaperConnection.disconnect(); 634 } 635 636 if (WallpaperConnection.isPreviewAvailable()) { 637 ImageView previewView = mWallpaperSurfaceCallback.getHomeImageWallpaper(); 638 mWallpaperConnection = new WallpaperConnection( 639 getWallpaperIntent(homeWallpaper.getWallpaperComponent()), activity, 640 new WallpaperConnectionListener() { 641 @Override 642 public void onWallpaperColorsChanged(WallpaperColors colors, 643 int displayId) { 644 if (mLockPreviewWallpaperInfo instanceof LiveWallpaperInfo) { 645 mLockScreenPreviewer.setColor(colors); 646 } 647 } 648 }, mWallpaperSurface); 649 650 651 mWallpaperConnection.setVisibility(true); 652 previewView.post(() -> { 653 if (mWallpaperConnection != null && !mWallpaperConnection.connect()) { 654 mWallpaperConnection = null; 655 } 656 }); 657 } 658 } 659 updateThumbnail(WallpaperInfo wallpaperInfo, ImageView thumbnailView, boolean isHomeWallpaper)660 private void updateThumbnail(WallpaperInfo wallpaperInfo, ImageView thumbnailView, 661 boolean isHomeWallpaper) { 662 if (wallpaperInfo == null) { 663 return; 664 } 665 666 if (thumbnailView == null) { 667 return; 668 } 669 670 Activity activity = getActivity(); 671 if (activity == null) { 672 return; 673 } 674 675 UserEventLogger eventLogger = InjectorProvider.getInjector().getUserEventLogger(activity); 676 677 boolean renderInImageWallpaperSurface = 678 !(wallpaperInfo instanceof LiveWallpaperInfo) && isHomeWallpaper; 679 ImageView imageView = renderInImageWallpaperSurface 680 ? mWallpaperSurfaceCallback.getHomeImageWallpaper() : thumbnailView; 681 if (imageView != null) { 682 wallpaperInfo.getThumbAsset(activity.getApplicationContext()) 683 .loadPreviewImage(activity, imageView, 684 ResourceUtils.getColorAttr( 685 getActivity(), android.R.attr.colorSecondary)); 686 } 687 688 if (isHomeWallpaper) { 689 if (wallpaperInfo instanceof LiveWallpaperInfo) { 690 if (mWallpaperSurfaceCallback.getHomeImageWallpaper() != null) { 691 wallpaperInfo.getThumbAsset(activity.getApplicationContext()).loadPreviewImage( 692 activity, mWallpaperSurfaceCallback.getHomeImageWallpaper(), 693 ResourceUtils.getColorAttr( 694 getActivity(), android.R.attr.colorSecondary)); 695 } 696 setUpLiveWallpaperPreview(wallpaperInfo); 697 } else { 698 if (mWallpaperConnection != null) { 699 mWallpaperConnection.disconnect(); 700 mWallpaperConnection = null; 701 } 702 } 703 } else { 704 // lock screen wallpaper 705 if (!(wallpaperInfo instanceof LiveWallpaperInfo) 706 || !WallpaperConnection.isPreviewAvailable()) { 707 // Load wallpaper color from thumbnail for static wallpaper. 708 WallpaperColorsLoader.getWallpaperColors( 709 activity, 710 wallpaperInfo.getThumbAsset(activity), 711 mLockScreenPreviewer::setColor); 712 } 713 } 714 715 ((View) thumbnailView.getParent()).setOnClickListener(view -> { 716 getFragmentHost().showViewOnlyPreview(wallpaperInfo, isHomeWallpaper); 717 eventLogger.logCurrentWallpaperPreviewed(); 718 }); 719 } 720 updateWallpaperSurface()721 private void updateWallpaperSurface() { 722 mWallpaperSurface.getHolder().addCallback(mWallpaperSurfaceCallback); 723 mWallpaperSurface.setZOrderMediaOverlay(true); 724 } 725 updateWorkspaceSurface()726 private void updateWorkspaceSurface() { 727 mWorkspaceSurface.setZOrderMediaOverlay(true); 728 mWorkspaceSurface.getHolder().addCallback(mWorkspaceSurfaceCallback); 729 } 730 731 private static class PreviewPagerAdapter extends PagerAdapter { 732 733 private List<View> mPages; 734 PreviewPagerAdapter(List<View> pages)735 PreviewPagerAdapter(List<View> pages) { 736 mPages = pages; 737 } 738 739 @Override destroyItem(@onNull ViewGroup container, int position, @NonNull Object object)740 public void destroyItem(@NonNull ViewGroup container, int position, 741 @NonNull Object object) { 742 container.removeView((View) object); 743 } 744 745 @NonNull 746 @Override instantiateItem(@onNull ViewGroup container, int position)747 public Object instantiateItem(@NonNull ViewGroup container, int position) { 748 View view = mPages.get(position); 749 container.addView(view); 750 return view; 751 } 752 753 @Override getCount()754 public int getCount() { 755 return mPages.size(); 756 } 757 758 @Override isViewFromObject(@onNull View view, @NonNull Object o)759 public boolean isViewFromObject(@NonNull View view, @NonNull Object o) { 760 return view == o; 761 } 762 } 763 } 764