blob: 558e518003dff8f8ff103c24adba823442fe2a99 [file] [log] [blame]
/*
* 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.backup;
import android.annotation.SuppressLint;
import android.app.WallpaperManager;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import com.android.wallpaper.asset.BitmapUtils;
import com.android.wallpaper.compat.WallpaperManagerCompat;
import com.android.wallpaper.module.Injector;
import com.android.wallpaper.module.InjectorProvider;
import com.android.wallpaper.module.JobSchedulerJobIds;
import com.android.wallpaper.module.WallpaperPreferences;
import com.android.wallpaper.util.DiskBasedLogger;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
/**
* {@link android.app.job.JobScheduler} job for generating missing hash codes for static wallpapers
* on N+ devices.
*/
@SuppressLint("ServiceCast")
public class MissingHashCodeGeneratorJobService extends JobService {
private static final String TAG = "MissingHashCodeGenerato"; // max 23 characters
private Thread mWorkerThread;
public static void schedule(Context context) {
JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo newJob = new JobInfo.Builder(
JobSchedulerJobIds.JOB_ID_GENERATE_MISSING_HASH_CODES,
new ComponentName(context, MissingHashCodeGeneratorJobService.class))
.setMinimumLatency(0)
.setPersisted(true)
.build();
scheduler.schedule(newJob);
}
@Override
public boolean onStartJob(JobParameters jobParameters) {
Context context = getApplicationContext();
// Retrieve WallpaperManager using Context#getSystemService instead of
// WallpaperManager#getInstance so it can be mocked out in test.
final WallpaperManager wallpaperManager = (WallpaperManager) context.getSystemService(
Context.WALLPAPER_SERVICE);
// Generate missing hash codes on a plain worker thread because we need to do some long-running
// disk I/O and can call #jobFinished from a background thread.
mWorkerThread = new Thread(new Runnable() {
@Override
public void run() {
Injector injector = InjectorProvider.getInjector();
WallpaperManagerCompat wallpaperManagerCompat = injector.getWallpaperManagerCompat(context);
WallpaperPreferences wallpaperPreferences = injector.getPreferences(context);
boolean isLiveWallpaperSet = wallpaperManager.getWallpaperInfo() != null;
// Generate and set a home wallpaper hash code if there's no live wallpaper set and no hash
// code stored already for the home wallpaper.
if (!isLiveWallpaperSet && wallpaperPreferences.getHomeWallpaperHashCode() == 0) {
wallpaperManager.forgetLoadedWallpaper();
Drawable wallpaperDrawable = wallpaperManagerCompat.getDrawable();
// No work to do if the drawable returned is null due to an underlying platform issue --
// being extra defensive with this check due to instability and variability of underlying
// platform.
if (wallpaperDrawable == null) {
DiskBasedLogger.e(TAG, "WallpaperManager#getDrawable returned null and there's no live "
+ "wallpaper set", context);
jobFinished(jobParameters, false /* needsReschedule */);
return;
}
Bitmap bitmap = ((BitmapDrawable) wallpaperDrawable).getBitmap();
long homeBitmapHash = BitmapUtils.generateHashCode(bitmap);
wallpaperPreferences.setHomeWallpaperHashCode(homeBitmapHash);
}
// Generate and set a lock wallpaper hash code if there's none saved.
if (wallpaperPreferences.getLockWallpaperHashCode() == 0) {
ParcelFileDescriptor parcelFd =
wallpaperManagerCompat.getWallpaperFile(WallpaperManagerCompat.FLAG_LOCK);
boolean isLockWallpaperSet = parcelFd != null;
// Copy the home wallpaper's hash code to lock if there's no distinct lock wallpaper set.
if (!isLockWallpaperSet) {
wallpaperPreferences.setLockWallpaperHashCode(
wallpaperPreferences.getHomeWallpaperHashCode());
mWorkerThread = null;
jobFinished(jobParameters, false /* needsReschedule */);
return;
}
// Otherwise, generate and set the distinct lock wallpaper image's hash code.
Bitmap lockBitmap = null;
InputStream fileStream = null;
try {
fileStream = new FileInputStream(parcelFd.getFileDescriptor());
lockBitmap = BitmapFactory.decodeStream(fileStream);
parcelFd.close();
} catch (IOException e) {
Log.e(TAG, "IO exception when closing the file descriptor.", e);
} finally {
if (fileStream != null) {
try {
fileStream.close();
} catch (IOException e) {
Log.e(TAG, "IO exception when closing input stream for lock screen wallpaper.", e);
}
}
}
if (lockBitmap != null) {
wallpaperPreferences.setLockWallpaperHashCode(BitmapUtils.generateHashCode(lockBitmap));
}
mWorkerThread = null;
jobFinished(jobParameters, false /* needsReschedule */);
}
}
});
mWorkerThread.start();
// Return true to indicate that this JobService needs to process work on a separate thread.
return true;
}
@Override
public boolean onStopJob(JobParameters jobParameters) {
// This job has no special execution parameters (i.e., network capability, device idle or
// charging), so Android should never call this method to stop the execution of this job early.
// Return "false" to indicate that this job should not be rescheduled when it's stopped because
// we have to provide an implementation of this method.
return false;
}
@Nullable
@VisibleForTesting
/* package */ Thread getWorkerThread() {
return mWorkerThread;
}
}