• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 Google LLC
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.google.android.libraries.mobiledatadownload.foreground;
17 
18 import android.annotation.SuppressLint;
19 import android.app.NotificationChannel;
20 import android.app.PendingIntent;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.os.Build.VERSION;
24 import android.os.Build.VERSION_CODES;
25 
26 import androidx.annotation.RequiresApi;
27 import androidx.core.app.NotificationCompat;
28 import androidx.core.app.NotificationManagerCompat;
29 import androidx.core.content.ContextCompat;
30 
31 import com.google.common.base.Preconditions;
32 
33 import javax.annotation.Nullable;
34 
35 /** Utilities for creating and managing notifications. */
36 // TODO(b/148401016): Add UI test for NotificationUtil.
37 public final class NotificationUtil {
38     public static final String CANCEL_ACTION_EXTRA = "cancel-action";
39     public static final String KEY_EXTRA = "key";
40     public static final String STOP_SERVICE_EXTRA = "stop-service";
41 
NotificationUtil()42     private NotificationUtil() {
43     }
44 
45     public static final String NOTIFICATION_CHANNEL_ID = "download-notification-channel-id";
46 
47     /** Create the NotificationBuilder for the Foreground Download Service */
createForegroundServiceNotificationBuilder( Context context)48     public static NotificationCompat.Builder createForegroundServiceNotificationBuilder(
49             Context context) {
50         return getNotificationBuilder(context)
51                 .setContentTitle("Downloading")
52                 .setSmallIcon(android.R.drawable.stat_notify_sync_noanim);
53     }
54 
55     /** Create a Notification.Builder. */
createNotificationBuilder( Context context, int size, String contentTitle, String contentText)56     public static NotificationCompat.Builder createNotificationBuilder(
57             Context context, int size, String contentTitle, String contentText) {
58         return getNotificationBuilder(context)
59                 .setContentTitle(contentTitle)
60                 .setContentText(contentText)
61                 .setSmallIcon(android.R.drawable.stat_sys_download)
62                 .setOngoing(true)
63                 .setProgress(size, 0, false);
64     }
65 
getNotificationBuilder(Context context)66     private static NotificationCompat.Builder getNotificationBuilder(Context context) {
67         return new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
68                 .setCategory(NotificationCompat.CATEGORY_SERVICE)
69                 .setOnlyAlertOnce(true);
70     }
71 
72     /**
73      * Create a Notification for a key.
74      *
75      * @param key Key to identify the download this notification is created for.
76      */
cancelNotificationForKey(Context context, String key)77     public static void cancelNotificationForKey(Context context, String key) {
78         NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
79         notificationManager.cancel(notificationKeyForKey(key));
80     }
81 
82     /** Create the Cancel Menu Action which will be attach to the download notification. */
83     // FLAG_IMMUTABLE is only for api >= 23, however framework still recommends to use this:
84     // <internal>
85     @SuppressLint("InlinedApi")
createCancelAction( Context context, Class<?> foregroundDownloadServiceClass, String key, NotificationCompat.Builder notification, int notificationKey)86     public static void createCancelAction(
87             Context context,
88             Class<?> foregroundDownloadServiceClass,
89             String key,
90             NotificationCompat.Builder notification,
91             int notificationKey) {
92         SaferIntentUtils intentUtils = new SaferIntentUtils() {
93         };
94 
95         Intent cancelIntent = new Intent(context, foregroundDownloadServiceClass);
96         cancelIntent.setPackage(context.getPackageName());
97         cancelIntent.putExtra(CANCEL_ACTION_EXTRA, notificationKey);
98         cancelIntent.putExtra(KEY_EXTRA, key);
99 
100         // It should be safe since we are using SaferPendingIntent, setting Package and
101         // Component, and
102         // use PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE.
103         PendingIntent pendingCancelIntent;
104         if (VERSION.SDK_INT >= VERSION_CODES.O) {
105             pendingCancelIntent =
106                     intentUtils.getForegroundService(
107                             context,
108                             notificationKey,
109                             cancelIntent,
110                             PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
111         } else {
112             pendingCancelIntent =
113                     intentUtils.getService(
114                             context,
115                             notificationKey,
116                             cancelIntent,
117                             PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
118         }
119         NotificationCompat.Action action =
120                 new NotificationCompat.Action.Builder(
121                         android.R.drawable.stat_sys_warning,
122                         "Cancel",
123                         Preconditions.checkNotNull(pendingCancelIntent))
124                         .build();
125         notification.addAction(action);
126     }
127 
128     /** Generate the Notification Key for the Key */
notificationKeyForKey(String key)129     public static int notificationKeyForKey(String key) {
130         // Consider if we could have collision.
131         // Heavier alternative is Hashing.goodFastHash(32).hashUnencodedChars(key).asInt();
132         return key.hashCode();
133     }
134 
135     /** Send intent to start the DownloadService in foreground. */
startForegroundDownloadService( Context context, Class<?> foregroundDownloadService, String key)136     public static void startForegroundDownloadService(
137             Context context, Class<?> foregroundDownloadService, String key) {
138         Intent intent = new Intent(context, foregroundDownloadService);
139         intent.putExtra(KEY_EXTRA, key);
140 
141         // Start ForegroundDownloadService to download in the foreground.
142         ContextCompat.startForegroundService(context, intent);
143     }
144 
145     /** Sending the intent to stop the foreground download service */
stopForegroundDownloadService( Context context, Class<?> foregroundDownloadService, String key)146     public static void stopForegroundDownloadService(
147             Context context, Class<?> foregroundDownloadService, String key) {
148         Intent intent = new Intent(context, foregroundDownloadService);
149         intent.putExtra(STOP_SERVICE_EXTRA, true);
150         intent.putExtra(KEY_EXTRA, key);
151 
152         // This will send the intent to stop the service.
153         ContextCompat.startForegroundService(context, intent);
154     }
155 
156     /**
157      * Return the String message to display in Notification when the download is paused to wait for
158      * network connection.
159      */
getDownloadPausedMessage(Context context)160     public static String getDownloadPausedMessage(Context context) {
161         return "Waiting for network connection";
162     }
163 
164     /**
165      * Return the String message to display in Notification when the download is paused due to a
166      * missing wifi connection.
167      */
getDownloadPausedWifiMessage(Context context)168     public static String getDownloadPausedWifiMessage(Context context) {
169         return "Waiting for WiFi connection";
170     }
171 
172     /** Return the String message to display in Notification when the download is failed. */
getDownloadFailedMessage(Context context)173     public static String getDownloadFailedMessage(Context context) {
174         return "Download failed";
175     }
176 
177     /** Return the String message to display in Notification when the download is success. */
getDownloadSuccessMessage(Context context)178     public static String getDownloadSuccessMessage(Context context) {
179         return "Downloaded";
180     }
181 
182     /** Create the Notification Channel for Downloading. */
createNotificationChannel(Context context)183     public static void createNotificationChannel(Context context) {
184         if (VERSION.SDK_INT >= VERSION_CODES.O) {
185             NotificationChannel notificationChannel =
186                     new NotificationChannel(
187                             NOTIFICATION_CHANNEL_ID,
188                             "Data Download Notification Channel",
189                             android.app.NotificationManager.IMPORTANCE_DEFAULT);
190 
191             android.app.NotificationManager manager =
192                     context.getSystemService(android.app.NotificationManager.class);
193             manager.createNotificationChannel(notificationChannel);
194         }
195     }
196 
197     /** Utilities for safely accessing PendingIntent APIs. */
198     private interface SaferIntentUtils {
199 
200         @Nullable
201         @RequiresApi(VERSION_CODES.O) // to match PendingIntent.getForegroundService()
getForegroundService( Context context, int requestCode, Intent intent, int flags)202         default PendingIntent getForegroundService(
203                 Context context, int requestCode, Intent intent, int flags) {
204             return PendingIntent.getForegroundService(context, requestCode, intent, flags);
205         }
206 
207         @Nullable
getService(Context context, int requestCode, Intent intent, int flags)208         default PendingIntent getService(Context context, int requestCode, Intent intent,
209                 int flags) {
210             return PendingIntent.getService(context, requestCode, intent, flags);
211         }
212     }
213 }
214