1 /*
2  * Copyright 2019 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 
17 package androidx.work.impl.foreground;
18 
19 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
20 
21 import android.Manifest;
22 import android.app.ForegroundServiceStartNotAllowedException;
23 import android.app.Notification;
24 import android.app.NotificationManager;
25 import android.app.Service;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.os.Build;
29 
30 import androidx.annotation.MainThread;
31 import androidx.annotation.RequiresApi;
32 import androidx.annotation.RequiresPermission;
33 import androidx.annotation.RestrictTo;
34 import androidx.lifecycle.LifecycleService;
35 import androidx.work.Logger;
36 
37 import org.jspecify.annotations.NonNull;
38 import org.jspecify.annotations.Nullable;
39 
40 /**
41  */
42 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
43 public class SystemForegroundService extends LifecycleService implements
44         SystemForegroundDispatcher.Callback {
45 
46     private static final String TAG = Logger.tagWithPrefix("SystemFgService");
47 
48     private static @Nullable SystemForegroundService sForegroundService = null;
49 
50     private boolean mIsShutdown;
51 
52     // Synthetic access
53     SystemForegroundDispatcher mDispatcher;
54     // Synthetic access
55     NotificationManager mNotificationManager;
56 
57     @Override
onCreate()58     public void onCreate() {
59         super.onCreate();
60         sForegroundService = this;
61         initializeDispatcher();
62     }
63 
64     @Override
onStartCommand(@ullable Intent intent, int flags, int startId)65     public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
66         super.onStartCommand(intent, flags, startId);
67         if (mIsShutdown) {
68             Logger.get().info(TAG,
69                     "Re-initializing SystemForegroundService after a request to shut-down.");
70 
71             // Destroy the old dispatcher to complete it's lifecycle.
72             mDispatcher.onDestroy();
73             // Create a new dispatcher to setup a new lifecycle.
74             initializeDispatcher();
75             // Set mIsShutdown to false, to correctly accept new commands.
76             mIsShutdown = false;
77         }
78 
79         if (intent != null) {
80             mDispatcher.onStartCommand(intent);
81         }
82 
83         // If the service were to crash, we want all unacknowledged Intents to get redelivered.
84         return Service.START_REDELIVER_INTENT;
85     }
86 
87     @Override
onDestroy()88     public void onDestroy() {
89         super.onDestroy();
90         mDispatcher.onDestroy();
91     }
92 
93     @MainThread
initializeDispatcher()94     private void initializeDispatcher() {
95         mNotificationManager = (NotificationManager)
96                 getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
97         mDispatcher = new SystemForegroundDispatcher(getApplicationContext());
98         mDispatcher.setCallback(this);
99     }
100 
101     @MainThread
102     @Override
stop()103     public void stop() {
104         mIsShutdown = true;
105         Logger.get().debug(TAG, "Shutting down.");
106         // No need to pass in startId; stopSelf() translates to stopSelf(-1) which is a hard stop
107         // of all startCommands. This is the behavior we want.
108         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
109             stopForeground(true);
110         }
111         sForegroundService = null;
112         stopSelf();
113     }
114 
115     @Override
onTimeout(int startId)116     public void onTimeout(int startId) {
117         // On API devices 35 both overloads of onTimeout() are invoked so we do nothing on the
118         // version introduced in API 34 since the newer version will be invoked. However, on API 34
119         // devices only this version is invoked, and thus why both versions are overridden.
120         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
121             return;
122         }
123         mDispatcher.onTimeout(startId, FOREGROUND_SERVICE_TYPE_SHORT_SERVICE);
124     }
125 
126     @Override
onTimeout(int startId, int fgsType)127     public void onTimeout(int startId, int fgsType) {
128         mDispatcher.onTimeout(startId, fgsType);
129     }
130 
131     @MainThread
132     @Override
startForeground( final int notificationId, final int notificationType, final @NonNull Notification notification)133     public void startForeground(
134             final int notificationId,
135             final int notificationType,
136             final @NonNull Notification notification) {
137         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
138             Api31Impl.startForeground(SystemForegroundService.this, notificationId,
139                     notification, notificationType);
140         } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
141             Api29Impl.startForeground(SystemForegroundService.this, notificationId,
142                     notification, notificationType);
143         } else {
144             startForeground(notificationId, notification);
145         }
146     }
147 
148     @MainThread
149     @RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)
150     @Override
notify(final int notificationId, final @NonNull Notification notification)151     public void notify(final int notificationId, final @NonNull Notification notification) {
152         mNotificationManager.notify(notificationId, notification);
153     }
154 
155     @Override
156     @MainThread
cancelNotification(final int notificationId)157     public void cancelNotification(final int notificationId) {
158         mNotificationManager.cancel(notificationId);
159     }
160 
161     /**
162      * @return The current instance of {@link SystemForegroundService}.
163      */
getInstance()164     public static @Nullable SystemForegroundService getInstance() {
165         return sForegroundService;
166     }
167 
168     @RequiresApi(29)
169     static class Api29Impl {
Api29Impl()170         private Api29Impl() {
171             // This class is not instantiable.
172         }
173 
startForeground(Service service, int id, Notification notification, int foregroundServiceType)174         static void startForeground(Service service, int id, Notification notification,
175                 int foregroundServiceType) {
176             service.startForeground(id, notification, foregroundServiceType);
177         }
178     }
179 
180     @RequiresApi(31)
181     static class Api31Impl {
Api31Impl()182         private Api31Impl() {
183             // This class is not instantiable.
184         }
185 
startForeground(Service service, int id, Notification notification, int foregroundServiceType)186         static void startForeground(Service service, int id, Notification notification,
187                 int foregroundServiceType) {
188             try {
189                 service.startForeground(id, notification, foregroundServiceType);
190             } catch (ForegroundServiceStartNotAllowedException exception) {
191                 // This should ideally never happen. But there a chance that this method
192                 // is called, and the app is no longer in a state where it's possible to start a
193                 // foreground service. WorkManager will eventually call stop() to clean up.
194                 Logger.get().warning(TAG, "Unable to start foreground service", exception);
195             } catch (SecurityException exception) {
196                 // In the case that a foreground type prerequisites permission is revoked
197                 // and the service is restarted it will not be possible to start the
198                 // foreground service.
199                 Logger.get().warning(TAG, "Unable to start foreground service", exception);
200             }
201         }
202     }
203 }
204