blob: 07cddea408a0ce9e7c0d25e5a9ef8bc97b21efd2 [file] [log] [blame]
Jon Miranda16ea1b12017-12-12 14:52:48 -08001/*
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 */
16package com.android.wallpaper.backup;
17
18import android.annotation.SuppressLint;
19import android.app.WallpaperManager;
20import android.app.job.JobInfo;
21import android.app.job.JobParameters;
22import android.app.job.JobScheduler;
23import android.app.job.JobService;
24import android.content.ComponentName;
25import android.content.Context;
26import android.graphics.Bitmap;
27import android.graphics.BitmapFactory;
28import android.graphics.drawable.BitmapDrawable;
29import android.graphics.drawable.Drawable;
30import android.os.ParcelFileDescriptor;
31import android.support.annotation.Nullable;
32import android.support.annotation.VisibleForTesting;
33import android.util.Log;
34
35import com.android.wallpaper.asset.BitmapUtils;
36import com.android.wallpaper.compat.WallpaperManagerCompat;
37import com.android.wallpaper.module.Injector;
38import com.android.wallpaper.module.InjectorProvider;
39import com.android.wallpaper.module.JobSchedulerJobIds;
40import com.android.wallpaper.module.WallpaperPreferences;
41import com.android.wallpaper.util.DiskBasedLogger;
42
43import java.io.FileInputStream;
44import java.io.IOException;
45import java.io.InputStream;
46
47/**
48 * {@link android.app.job.JobScheduler} job for generating missing hash codes for static wallpapers
49 * on N+ devices.
50 */
51@SuppressLint("ServiceCast")
52public class MissingHashCodeGeneratorJobService extends JobService {
53
54 private static final String TAG = "MissingHashCodeGenerato"; // max 23 characters
55
56 private Thread mWorkerThread;
57
58 public static void schedule(Context context) {
59 JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
60 JobInfo newJob = new JobInfo.Builder(
61 JobSchedulerJobIds.JOB_ID_GENERATE_MISSING_HASH_CODES,
62 new ComponentName(context, MissingHashCodeGeneratorJobService.class))
63 .setMinimumLatency(0)
64 .setPersisted(true)
65 .build();
66 scheduler.schedule(newJob);
67 }
68
69 @Override
70 public boolean onStartJob(JobParameters jobParameters) {
71 Context context = getApplicationContext();
72
73 // Retrieve WallpaperManager using Context#getSystemService instead of
74 // WallpaperManager#getInstance so it can be mocked out in test.
75 final WallpaperManager wallpaperManager = (WallpaperManager) context.getSystemService(
76 Context.WALLPAPER_SERVICE);
77
78 // Generate missing hash codes on a plain worker thread because we need to do some long-running
79 // disk I/O and can call #jobFinished from a background thread.
80 mWorkerThread = new Thread(new Runnable() {
81 @Override
82 public void run() {
83 Injector injector = InjectorProvider.getInjector();
84 WallpaperManagerCompat wallpaperManagerCompat = injector.getWallpaperManagerCompat(context);
85 WallpaperPreferences wallpaperPreferences = injector.getPreferences(context);
86
87 boolean isLiveWallpaperSet = wallpaperManager.getWallpaperInfo() != null;
88
89 // Generate and set a home wallpaper hash code if there's no live wallpaper set and no hash
90 // code stored already for the home wallpaper.
91 if (!isLiveWallpaperSet && wallpaperPreferences.getHomeWallpaperHashCode() == 0) {
92 wallpaperManager.forgetLoadedWallpaper();
93
94 Drawable wallpaperDrawable = wallpaperManagerCompat.getDrawable();
95 // No work to do if the drawable returned is null due to an underlying platform issue --
96 // being extra defensive with this check due to instability and variability of underlying
97 // platform.
98 if (wallpaperDrawable == null) {
99 DiskBasedLogger.e(TAG, "WallpaperManager#getDrawable returned null and there's no live "
100 + "wallpaper set", context);
101 jobFinished(jobParameters, false /* needsReschedule */);
102 return;
103 }
104
105 Bitmap bitmap = ((BitmapDrawable) wallpaperDrawable).getBitmap();
106 long homeBitmapHash = BitmapUtils.generateHashCode(bitmap);
107
108 wallpaperPreferences.setHomeWallpaperHashCode(homeBitmapHash);
109 }
110
111 // Generate and set a lock wallpaper hash code if there's none saved.
112 if (wallpaperPreferences.getLockWallpaperHashCode() == 0) {
113 ParcelFileDescriptor parcelFd =
114 wallpaperManagerCompat.getWallpaperFile(WallpaperManagerCompat.FLAG_LOCK);
115 boolean isLockWallpaperSet = parcelFd != null;
116
117 // Copy the home wallpaper's hash code to lock if there's no distinct lock wallpaper set.
118 if (!isLockWallpaperSet) {
119 wallpaperPreferences.setLockWallpaperHashCode(
120 wallpaperPreferences.getHomeWallpaperHashCode());
121 mWorkerThread = null;
122 jobFinished(jobParameters, false /* needsReschedule */);
123 return;
124 }
125
126 // Otherwise, generate and set the distinct lock wallpaper image's hash code.
127 Bitmap lockBitmap = null;
128 InputStream fileStream = null;
129 try {
130 fileStream = new FileInputStream(parcelFd.getFileDescriptor());
131 lockBitmap = BitmapFactory.decodeStream(fileStream);
132 parcelFd.close();
133 } catch (IOException e) {
134 Log.e(TAG, "IO exception when closing the file descriptor.", e);
135 } finally {
136 if (fileStream != null) {
137 try {
138 fileStream.close();
139 } catch (IOException e) {
140 Log.e(TAG, "IO exception when closing input stream for lock screen wallpaper.", e);
141 }
142 }
143 }
144
145 if (lockBitmap != null) {
146 wallpaperPreferences.setLockWallpaperHashCode(BitmapUtils.generateHashCode(lockBitmap));
147 }
148 mWorkerThread = null;
149
150 jobFinished(jobParameters, false /* needsReschedule */);
151 }
152 }
153 });
154
155 mWorkerThread.start();
156
157 // Return true to indicate that this JobService needs to process work on a separate thread.
158 return true;
159 }
160
161 @Override
162 public boolean onStopJob(JobParameters jobParameters) {
163 // This job has no special execution parameters (i.e., network capability, device idle or
164 // charging), so Android should never call this method to stop the execution of this job early.
165 // Return "false" to indicate that this job should not be rescheduled when it's stopped because
166 // we have to provide an implementation of this method.
167 return false;
168 }
169
170 @Nullable
171 @VisibleForTesting
172 /* package */ Thread getWorkerThread() {
173 return mWorkerThread;
174 }
175}