/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wallpaper.module; import static android.app.WallpaperManager.FLAG_LOCK; import static android.app.WallpaperManager.FLAG_SYSTEM; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.WallpaperManager; import android.content.ContentProviderClient; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Log; import com.android.wallpaper.R; import com.android.wallpaper.asset.BitmapUtils; import com.android.wallpaper.model.CreativeCategory; import com.android.wallpaper.model.LiveWallpaperMetadata; import com.android.wallpaper.model.WallpaperInfoContract; import com.android.wallpaper.model.WallpaperMetadata; import com.android.wallpaper.picker.customization.data.content.WallpaperClient; import com.android.wallpaper.picker.customization.shared.model.WallpaperDestination; import com.android.wallpaper.util.DisplayUtils; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * Default implementation of {@link WallpaperRefresher} which refreshes wallpaper metadata * asynchronously. */ @SuppressLint("ServiceCast") public class DefaultWallpaperRefresher implements WallpaperRefresher { private static final String TAG = "DefaultWPRefresher"; private final Context mAppContext; private final WallpaperPreferences mWallpaperPreferences; private final WallpaperManager mWallpaperManager; private final WallpaperStatusChecker mWallpaperStatusChecker; private final DisplayUtils mDisplayUtils; private final WallpaperClient mWallpaperClient; private final Executor mExecutor = Executors.newCachedThreadPool(); /** * @param context The application's context. */ public DefaultWallpaperRefresher(Context context) { mAppContext = context.getApplicationContext(); Injector injector = InjectorProvider.getInjector(); mWallpaperPreferences = injector.getPreferences(mAppContext); mWallpaperStatusChecker = injector.getWallpaperStatusChecker(context); mDisplayUtils = injector.getDisplayUtils(mAppContext); mWallpaperClient = injector.getWallpaperClient(mAppContext); // Retrieve WallpaperManager using Context#getSystemService instead of // WallpaperManager#getInstance so it can be mocked out in test. mWallpaperManager = (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE); } @Override public void refresh(RefreshListener listener) { GetWallpaperMetadataAsyncTask task = new GetWallpaperMetadataAsyncTask(listener); task.executeOnExecutor(mExecutor); } /** * Retrieves the current wallpaper's thumbnail and metadata off the UI thread. */ private class GetWallpaperMetadataAsyncTask extends AsyncTask> { private final RefreshListener mListener; private final WallpaperManager mWallpaperManager; private long mCurrentHomeWallpaperHashCode; private long mCurrentLockWallpaperHashCode; private String mSystemWallpaperServiceName; @SuppressLint("ServiceCast") public GetWallpaperMetadataAsyncTask(RefreshListener listener) { mListener = listener; mWallpaperManager = WallpaperManager.getInstance(mAppContext); } @Override protected List doInBackground(Void... unused) { List wallpaperMetadatas = new ArrayList<>(); boolean isHomeScreenStatic = mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM) == null; if (!isHomeScreenMetadataCurrent() || (isHomeScreenStatic && isHomeScreenAttributionsEmpty())) { mWallpaperPreferences.clearHomeWallpaperMetadata(); setFallbackHomeScreenWallpaperMetadata(); } boolean isLockScreenWallpaperCurrentlySet = mWallpaperStatusChecker.isLockWallpaperSet(); if (mWallpaperManager.getWallpaperInfo() == null) { wallpaperMetadatas.add(new WallpaperMetadata( mWallpaperPreferences.getHomeWallpaperAttributions(), mWallpaperPreferences.getHomeWallpaperActionUrl(), mWallpaperPreferences.getHomeWallpaperCollectionId(), /* wallpaperComponent= */ null, getCurrentWallpaperCropHints(FLAG_SYSTEM))); } else { android.app.WallpaperInfo info = mWallpaperManager.getWallpaperInfo(); Uri previewUri = getCreativePreviewUri(mAppContext, info, WallpaperDestination.HOME); wallpaperMetadatas.add(new LiveWallpaperMetadata(info, previewUri)); } // Return only home metadata if pre-N device or lock screen wallpaper is not explicitly // set. if (!isLockScreenWallpaperCurrentlySet) { return wallpaperMetadatas; } boolean isLockScreenStatic = mWallpaperManager.getWallpaperInfo(FLAG_LOCK) == null; if (!isLockScreenMetadataCurrent() || (isLockScreenStatic && isLockScreenAttributionsEmpty())) { mWallpaperPreferences.clearLockWallpaperMetadata(); setFallbackLockScreenWallpaperMetadata(); } if (isLockScreenStatic) { wallpaperMetadatas.add(new WallpaperMetadata( mWallpaperPreferences.getLockWallpaperAttributions(), mWallpaperPreferences.getLockWallpaperActionUrl(), mWallpaperPreferences.getLockWallpaperCollectionId(), /* wallpaperComponent= */ null, getCurrentWallpaperCropHints(FLAG_LOCK))); } else { android.app.WallpaperInfo info = mWallpaperManager.getWallpaperInfo(FLAG_LOCK); Uri previewUri = getCreativePreviewUri(mAppContext, info, WallpaperDestination.LOCK); wallpaperMetadatas.add(new LiveWallpaperMetadata(info, previewUri)); } return wallpaperMetadatas; } @Override protected void onPostExecute(List metadatas) { if (metadatas.size() > 2) { Log.e(TAG, "Got more than 2 WallpaperMetadata objects - only home and (optionally) " + "lock are permitted."); return; } mListener.onRefreshed(metadatas.get(0), metadatas.size() > 1 ? metadatas.get(1) : null, mWallpaperPreferences.getWallpaperPresentationMode()); } /** * Sets fallback wallpaper attributions to WallpaperPreferences when the saved metadata did * not match the system wallpaper. For live wallpapers, loads the label (title) but for * image wallpapers loads a generic title string. */ private void setFallbackHomeScreenWallpaperMetadata() { android.app.WallpaperInfo wallpaperComponent = mWallpaperManager.getWallpaperInfo(); if (wallpaperComponent == null) { // Image wallpaper mWallpaperPreferences.setHomeWallpaperAttributions( Arrays.asList(mAppContext.getResources() .getString(R.string.fallback_wallpaper_title))); mWallpaperPreferences.setHomeWallpaperManagerId( mWallpaperManager.getWallpaperId(FLAG_SYSTEM)); } else { // Live wallpaper mWallpaperPreferences.setHomeWallpaperAttributions(Arrays.asList( wallpaperComponent.loadLabel(mAppContext.getPackageManager()).toString())); mWallpaperPreferences.setHomeWallpaperServiceName(mSystemWallpaperServiceName); } // Disable rotation wallpaper when setting fallback home screen wallpaper // Daily rotation wallpaper only rotates the home screen wallpaper mWallpaperPreferences.setWallpaperPresentationMode( WallpaperPreferences.PRESENTATION_MODE_STATIC); mWallpaperPreferences.clearDailyRotations(); } /** * Sets fallback lock screen wallpaper attributions to WallpaperPreferences. This should be * called when the saved lock screen wallpaper metadata does not match the currently set * lock screen wallpaper. */ private void setFallbackLockScreenWallpaperMetadata() { mWallpaperPreferences.setLockWallpaperAttributions( Arrays.asList(mAppContext.getResources() .getString(R.string.fallback_wallpaper_title))); mWallpaperPreferences.setLockWallpaperManagerId(mWallpaperManager.getWallpaperId( FLAG_LOCK)); } /** * Returns whether the home screen metadata saved in WallpaperPreferences corresponds to the * current system wallpaper. */ private boolean isHomeScreenMetadataCurrent() { return (mWallpaperManager.getWallpaperInfo() == null) ? isHomeScreenImageWallpaperCurrent() : isHomeScreenLiveWallpaperCurrent(); } /** * Returns whether the home screen attributions saved in WallpaperPreferences is empty. */ private boolean isHomeScreenAttributionsEmpty() { List homeScreenAttributions = mWallpaperPreferences.getHomeWallpaperAttributions(); return homeScreenAttributions.get(0) == null && homeScreenAttributions.get(1) == null && homeScreenAttributions.get(2) == null; } private long getCurrentHomeWallpaperHashCode() { if (mCurrentHomeWallpaperHashCode == 0) { BitmapDrawable wallpaperDrawable = (BitmapDrawable) mWallpaperManager.getDrawable(); // wallpaperDrawable should always be non-null, unless if there's a error in // WallpaperManager's state, in which case we'll consider the hashcode as unset. Bitmap wallpaperBitmap = wallpaperDrawable != null ? wallpaperDrawable.getBitmap() : null; mCurrentHomeWallpaperHashCode = wallpaperBitmap != null ? BitmapUtils.generateHashCode(wallpaperBitmap) : 0; // Manually request that WallpaperManager loses its reference to the current // wallpaper bitmap, which can occupy a large memory allocation for the lifetime of // the app. mWallpaperManager.forgetLoadedWallpaper(); } return mCurrentHomeWallpaperHashCode; } private long getCurrentLockWallpaperHashCode() { if (mCurrentLockWallpaperHashCode == 0 && mWallpaperStatusChecker.isLockWallpaperSet()) { Bitmap wallpaperBitmap = getLockWallpaperBitmap(); // If isLockWallpaperSet() returned true, wallpaperBitmap should always be // non-null, unless if there's a error in WallpaperManager, in which case we'll // consider the hashcode as unset. mCurrentLockWallpaperHashCode = wallpaperBitmap != null ? BitmapUtils.generateHashCode(wallpaperBitmap) : 0; } return mCurrentLockWallpaperHashCode; } /** * Returns the lock screen wallpaper currently set on the device as a Bitmap, or null if no * lock screen wallpaper is set. */ private Bitmap getLockWallpaperBitmap() { Bitmap lockBitmap = null; ParcelFileDescriptor pfd = mWallpaperManager.getWallpaperFile(FLAG_LOCK); // getWallpaperFile returns null if the lock screen isn't explicitly set, so need this // check. if (pfd != null) { InputStream fileStream = null; try { fileStream = new FileInputStream(pfd.getFileDescriptor()); lockBitmap = BitmapFactory.decodeStream(fileStream); pfd.close(); return lockBitmap; } catch (IOException e) { Log.e(TAG, "IO exception when closing the file descriptor."); } finally { if (fileStream != null) { try { fileStream.close(); } catch (IOException e) { Log.e(TAG, "IO exception when closing input stream for lock screen WP."); } } } } return lockBitmap; } /** * Returns whether the image wallpaper set to the system matches the metadata in * WallpaperPreferences. */ private boolean isHomeScreenImageWallpaperCurrent() { return mWallpaperPreferences.getHomeWallpaperManagerId() == mWallpaperManager.getWallpaperId(FLAG_SYSTEM); } /** * Returns whether the live wallpaper set to the system's home screen matches the metadata * in WallpaperPreferences. */ private boolean isHomeScreenLiveWallpaperCurrent() { mSystemWallpaperServiceName = mWallpaperManager.getWallpaperInfo().getServiceName(); String homeWallpaperServiceName = mWallpaperPreferences.getHomeWallpaperServiceName(); return mSystemWallpaperServiceName.equals(homeWallpaperServiceName); } /** * Returns whether the lock screen metadata saved in WallpaperPreferences corresponds to the * current lock screen wallpaper. */ private boolean isLockScreenMetadataCurrent() { return (mWallpaperManager.getWallpaperInfo(FLAG_LOCK) == null) ? isLockScreenImageWallpaperCurrent() : isLockScreenLiveWallpaperCurrent(); } /** * Returns whether the image wallpaper set for the lock screen matches the metadata in * WallpaperPreferences. */ private boolean isLockScreenImageWallpaperCurrent() { // Check for lock wallpaper image same-ness only when there is no stored lock wallpaper // hash code. Otherwise if there is a lock wallpaper hash code stored in // {@link WallpaperPreferences}, then check hash codes. long savedLockWallpaperHash = mWallpaperPreferences.getLockWallpaperHashCode(); if (savedLockWallpaperHash == 0) { return mWallpaperPreferences.getLockWallpaperManagerId() == mWallpaperManager.getWallpaperId(FLAG_LOCK); } else { return savedLockWallpaperHash == getCurrentLockWallpaperHashCode(); } } /** * Returns whether the live wallpaper for the home screen matches the metadata in * WallpaperPreferences. */ private boolean isLockScreenLiveWallpaperCurrent() { String currentServiceName = mWallpaperManager.getWallpaperInfo(FLAG_LOCK) .getServiceName(); String storedServiceName = mWallpaperPreferences.getLockWallpaperServiceName(); return currentServiceName.equals(storedServiceName); } /** * Returns whether the lock screen attributions saved in WallpaperPreferences are empty. */ private boolean isLockScreenAttributionsEmpty() { List attributions = mWallpaperPreferences.getLockWallpaperAttributions(); return attributions.get(0) == null && attributions.get(1) == null && attributions.get(2) == null; } private Map getCurrentWallpaperCropHints( @WallpaperManager.SetWallpaperFlags int which) { List displaySizes = mDisplayUtils .getInternalDisplaySizes(/* allDimensions= */ true); return mWallpaperClient.getCurrentCropHints(displaySizes, which); } } // Queries a live wallpaper for its preview Uri, and returns it if it exists. private static @Nullable Uri getCreativePreviewUri(Context context, android.app.WallpaperInfo info, WallpaperDestination destination) { Bundle metaData = info.getServiceInfo().metaData; String uri = metaData.getString( CreativeCategory.KEY_WALLPAPER_SAVE_CREATIVE_WALLPAPER_CURRENT); if (uri == null) { return null; } Uri currentAssetsUri = Uri.parse(uri); try (ContentProviderClient client = context.getContentResolver() .acquireContentProviderClient(currentAssetsUri.getAuthority())) { if (client == null) { return null; } try (Cursor cursor = client.query(currentAssetsUri, null, null, null, null)) { if (cursor == null || !cursor.moveToFirst()) { return null; } do { String dest = cursor.getString( cursor.getColumnIndex(WallpaperInfoContract.CURRENT_DESTINATION)); Uri previewUri = Uri.parse(cursor.getString( cursor.getColumnIndex( WallpaperInfoContract.CURRENT_CONFIG_PREVIEW_URI))); if ((dest.equals("home") && destination == WallpaperDestination.HOME) || (dest.equals("lock") && destination == WallpaperDestination.LOCK)) { return previewUri; } } while (cursor.moveToNext()); } catch (RemoteException e) { Log.w(TAG, "Error retrieving current creative asset id: ", e); } } return null; } }