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.module;
17 
18 import android.content.BroadcastReceiver;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.os.PowerManager;
22 import android.os.PowerManager.WakeLock;
23 import android.util.Log;
24 
25 import com.android.wallpaper.model.WallpaperMetadata;
26 import com.android.wallpaper.module.WallpaperPreferences.PresentationMode;
27 import com.android.wallpaper.module.WallpaperRefresher.RefreshListener;
28 import com.android.wallpaper.util.DiskBasedLogger;
29 
30 import java.util.Calendar;
31 
32 import androidx.annotation.Nullable;
33 
34 /**
35  * Performs daily logging operations when alarm is received.
36  */
37 public class DailyLoggingAlarmReceiver extends BroadcastReceiver {
38 
39     private static final String TAG = "DailyLoggingAlarm";
40 
41     /**
42      * Releases the provided WakeLock if and only if it's currently held as to avoid throwing a
43      * "WakeLock under-locked" RuntimeException.
44      */
releaseWakeLock(WakeLock wakeLock)45     private static void releaseWakeLock(WakeLock wakeLock) {
46         if (wakeLock.isHeld()) {
47             wakeLock.release();
48         }
49     }
50 
51     @Override
onReceive(Context context, Intent intent)52     public void onReceive(Context context, Intent intent) {
53         Context appContext = context.getApplicationContext();
54         Injector injector = InjectorProvider.getInjector();
55         UserEventLogger logger = injector.getUserEventLogger(appContext);
56         WallpaperPreferences preferences = injector.getPreferences(appContext);
57 
58         logger.logNumDailyWallpaperRotationsInLastWeek();
59         logger.logNumDailyWallpaperRotationsPreviousDay();
60         logger.logWallpaperPresentationMode();
61         logger.logSnapshot();
62 
63         preferences.setLastDailyLogTimestamp(System.currentTimeMillis());
64 
65         logDailyWallpaperRotationStatus(appContext);
66 
67         // Clear disk-based logs older than 7 days if they exist.
68         DiskBasedLogger.clearOldLogs(appContext);
69     }
70 
71     /**
72      * If daily wallpapers are currently in effect and were enabled more than 24 hours ago, then log
73      * the last-known rotation status as reported by the periodic background rotation components
74      * (BackdropAlarmReceiver and BackdropRotationTask), or if there wasn't any status update in the
75      * last 24 hours then log a "not attempted" status to the UserEventLogger.
76      */
logDailyWallpaperRotationStatus(Context appContext)77     private void logDailyWallpaperRotationStatus(Context appContext) {
78         // Acquire a partial wakelock because logging the daily rotation requires doing some work on
79         // another thread (via AsyncTask) after #onReceive returns, after which the kernel may power
80         // down and prevent our daily rotation log from being sent.
81         PowerManager powerManager = (PowerManager) appContext.getSystemService(Context.POWER_SERVICE);
82         final WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
83         wakeLock.acquire(10000 /* timeout */);
84 
85         final Injector injector = InjectorProvider.getInjector();
86 
87         // First check if rotation is still in effect.
88         injector.getWallpaperRefresher(appContext).refresh(new RefreshListener() {
89             @Override
90             public void onRefreshed(WallpaperMetadata homeWallpaperMetadata,
91                                     @Nullable WallpaperMetadata lockWallpaperMetadata,
92                                     @PresentationMode int presentationMode) {
93                 // Don't log or do anything else if presentation mode is not rotating.
94                 if (presentationMode != WallpaperPreferences.PRESENTATION_MODE_ROTATING) {
95                     releaseWakeLock(wakeLock);
96                     return;
97                 }
98 
99                 WallpaperPreferences preferences = injector.getPreferences(appContext);
100 
101                 long dailyWallpaperEnabledTimestamp = preferences.getDailyWallpaperEnabledTimestamp();
102                 // Validate the daily wallpaper enabled timestamp.
103                 if (dailyWallpaperEnabledTimestamp < 0) {
104                     Log.e(TAG, "There's no valid daily wallpaper enabled timestamp");
105                     releaseWakeLock(wakeLock);
106                     return;
107                 }
108 
109                 Calendar midnightYesterday = Calendar.getInstance();
110                 midnightYesterday.add(Calendar.DAY_OF_MONTH, -1);
111                 midnightYesterday.set(Calendar.HOUR_OF_DAY, 0);
112                 midnightYesterday.set(Calendar.MINUTE, 0);
113 
114                 // Exclude rotations that were put into affect later than midnight yesterday because the
115                 // background task may not have had a chance to execute yet.
116                 if (dailyWallpaperEnabledTimestamp > midnightYesterday.getTimeInMillis()) {
117                     releaseWakeLock(wakeLock);
118                     return;
119                 }
120 
121                 try {
122                     long lastRotationStatusTimestamp =
123                             preferences.getDailyWallpaperLastRotationStatusTimestamp();
124 
125                     UserEventLogger logger = injector.getUserEventLogger(appContext);
126 
127                     // If a rotation status was reported more recently than midnight yesterday, then log it.
128                     // Otherwise, log a "not attempted" rotation status.
129                     if (lastRotationStatusTimestamp > midnightYesterday.getTimeInMillis()) {
130                         int lastDailyWallpaperRotationStatus =
131                                 preferences.getDailyWallpaperLastRotationStatus();
132 
133                         logger.logDailyWallpaperRotationStatus(lastDailyWallpaperRotationStatus);
134 
135                         // If the daily rotation status is "failed", increment the num days failed in
136                         // SharedPreferences and log it, otherwise reset the counter in SharedPreferences to 0.
137                         if (UserEventLogger.ROTATION_STATUS_FAILED == lastDailyWallpaperRotationStatus) {
138                             preferences.incrementNumDaysDailyRotationFailed();
139                             logger.logNumDaysDailyRotationFailed(preferences.getNumDaysDailyRotationFailed());
140                         } else {
141                             preferences.resetNumDaysDailyRotationFailed();
142                         }
143 
144                         // If there was a valid rotation status reported since midnight yesterday, then reset
145                         // the counter for consecutive days of "not attempted".
146                         preferences.resetNumDaysDailyRotationNotAttempted();
147                     } else {
148                         logger.logDailyWallpaperRotationStatus(UserEventLogger.ROTATION_STATUS_NOT_ATTEMPTED);
149 
150                         // Increment and log the consecutive # days in a row that daily rotation was not
151                         // attempted.
152                         preferences.incrementNumDaysDailyRotationNotAttempted();
153                         logger.logNumDaysDailyRotationNotAttempted(
154                                 preferences.getNumDaysDailyRotationNotAttempted());
155 
156                         // Reset the disk-based counter for number of consecutive days daily rotation failed
157                         // because if rotation was not attempted but restarts tomorrow after a boot and fails
158                         // then, we want to report that as 1 day of failure instead of 3 consecutive days.
159                         preferences.resetNumDaysDailyRotationFailed();
160                     }
161                 } finally {
162                     releaseWakeLock(wakeLock);
163                 }
164             }
165         });
166     }
167 }
168