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