• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.backup;
17 
18 import android.annotation.SuppressLint;
19 import android.app.WallpaperManager;
20 import android.app.job.JobInfo;
21 import android.app.job.JobParameters;
22 import android.app.job.JobScheduler;
23 import android.app.job.JobService;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.graphics.Bitmap;
27 import android.graphics.BitmapFactory;
28 import android.graphics.drawable.BitmapDrawable;
29 import android.graphics.drawable.Drawable;
30 import android.os.ParcelFileDescriptor;
31 import android.util.Log;
32 
33 import androidx.annotation.Nullable;
34 import androidx.annotation.VisibleForTesting;
35 
36 import com.android.wallpaper.asset.BitmapUtils;
37 import com.android.wallpaper.module.Injector;
38 import com.android.wallpaper.module.InjectorProvider;
39 import com.android.wallpaper.module.JobSchedulerJobIds;
40 import com.android.wallpaper.module.WallpaperPreferences;
41 import com.android.wallpaper.util.DiskBasedLogger;
42 
43 import java.io.FileInputStream;
44 import java.io.IOException;
45 import 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")
52 public class MissingHashCodeGeneratorJobService extends JobService {
53 
54     private static final String TAG = "MissingHashCodeGenerato"; // max 23 characters
55 
56     private Thread mWorkerThread;
57 
schedule(Context context)58     public static void schedule(Context context) {
59         JobScheduler scheduler = context.getSystemService(JobScheduler.class);
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
onStartJob(JobParameters jobParameters)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
79         // long-running 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                 WallpaperPreferences wallpaperPreferences = injector.getPreferences(context);
85 
86                 boolean isLiveWallpaperSet = wallpaperManager.getWallpaperInfo() != null;
87 
88                 // Generate and set a home wallpaper hash code if there's no live wallpaper set
89                 // and no hash code stored already for the home wallpaper.
90                 if (!isLiveWallpaperSet && wallpaperPreferences.getHomeWallpaperHashCode() == 0) {
91                     wallpaperManager.forgetLoadedWallpaper();
92 
93                     Drawable wallpaperDrawable = wallpaperManager.getDrawable();
94                     // No work to do if the drawable returned is null due to an underlying
95                     // platform issue -- being extra defensive with this check due to instability
96                     // and variability of underlying platform.
97                     if (wallpaperDrawable == null) {
98                         DiskBasedLogger.e(
99                                 TAG,
100                                 "WallpaperManager#getDrawable returned null and there's no live "
101                                         + "wallpaper set",
102                                 context
103                         );
104                         jobFinished(jobParameters, false /* needsReschedule */);
105                         return;
106                     }
107 
108                     Bitmap bitmap = ((BitmapDrawable) wallpaperDrawable).getBitmap();
109                     long homeBitmapHash = BitmapUtils.generateHashCode(bitmap);
110 
111                     wallpaperPreferences.setHomeWallpaperHashCode(homeBitmapHash);
112                 }
113 
114                 // Generate and set a lock wallpaper hash code if there's none saved.
115                 if (wallpaperPreferences.getLockWallpaperHashCode() == 0) {
116                     ParcelFileDescriptor parcelFd = wallpaperManager.getWallpaperFile(
117                             WallpaperManager.FLAG_LOCK);
118                     boolean isLockWallpaperSet = parcelFd != null;
119 
120                     // Copy the home wallpaper's hash code to lock if there's no distinct lock
121                     // wallpaper set.
122                     if (!isLockWallpaperSet) {
123                         wallpaperPreferences.setLockWallpaperHashCode(
124                                 wallpaperPreferences.getHomeWallpaperHashCode());
125                         mWorkerThread = null;
126                         jobFinished(jobParameters, false /* needsReschedule */);
127                         return;
128                     }
129 
130                     // Otherwise, generate and set the distinct lock wallpaper image's hash code.
131                     Bitmap lockBitmap = null;
132                     InputStream fileStream = null;
133                     try {
134                         fileStream = new FileInputStream(parcelFd.getFileDescriptor());
135                         lockBitmap = BitmapFactory.decodeStream(fileStream);
136                         parcelFd.close();
137                     } catch (IOException e) {
138                         Log.e(TAG, "IO exception when closing the file descriptor.", e);
139                     } finally {
140                         if (fileStream != null) {
141                             try {
142                                 fileStream.close();
143                             } catch (IOException e) {
144                                 Log.e(TAG,
145                                         "IO exception when closing input stream for lock screen "
146                                                 + "wallpaper.",
147                                         e);
148                             }
149                         }
150                     }
151 
152                     if (lockBitmap != null) {
153                         wallpaperPreferences.setLockWallpaperHashCode(
154                                 BitmapUtils.generateHashCode(lockBitmap));
155                     }
156                     mWorkerThread = null;
157 
158                     jobFinished(jobParameters, false /* needsReschedule */);
159                 }
160             }
161         });
162 
163         mWorkerThread.start();
164 
165         // Return true to indicate that this JobService needs to process work on a separate thread.
166         return true;
167     }
168 
169     @Override
onStopJob(JobParameters jobParameters)170     public boolean onStopJob(JobParameters jobParameters) {
171         // This job has no special execution parameters (i.e., network capability, device idle or
172         // charging), so Android should never call this method to stop the execution of this job
173         // early. Return "false" to indicate that this job should not be rescheduled when it's
174         // stopped because we have to provide an implementation of this method.
175         return false;
176     }
177 
178     @Nullable
179     @VisibleForTesting
getWorkerThread()180         /* package */ Thread getWorkerThread() {
181         return mWorkerThread;
182     }
183 }
184