1 /*
2  * Copyright (C) 2016 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 com.android.deskclock.data;
18 
19 import android.app.Notification;
20 import android.app.NotificationChannel;
21 import android.app.PendingIntent;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.res.Resources;
25 import android.os.SystemClock;
26 import androidx.annotation.DrawableRes;
27 import androidx.annotation.StringRes;
28 import androidx.core.app.NotificationCompat;
29 import androidx.core.app.NotificationCompat.Action;
30 import androidx.core.app.NotificationCompat.Builder;
31 import androidx.core.app.NotificationManagerCompat;
32 import androidx.core.content.ContextCompat;
33 import android.widget.RemoteViews;
34 
35 import com.android.deskclock.R;
36 import com.android.deskclock.Utils;
37 import com.android.deskclock.events.Events;
38 import com.android.deskclock.stopwatch.StopwatchService;
39 
40 import java.util.ArrayList;
41 import java.util.List;
42 
43 import static android.view.View.GONE;
44 import static android.view.View.VISIBLE;
45 
46 /**
47  * Builds notification to reflect the latest state of the stopwatch and recorded laps.
48  */
49 class StopwatchNotificationBuilder {
50 
51     /**
52      * Notification channel containing all stopwatch notifications.
53      */
54     private static final String STOPWATCH_NOTIFICATION_CHANNEL_ID = "StopwatchNotification";
55 
buildChannel(Context context, NotificationManagerCompat notificationManager)56     public void buildChannel(Context context, NotificationManagerCompat notificationManager) {
57         if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
58             NotificationChannel channel = new NotificationChannel(
59                     STOPWATCH_NOTIFICATION_CHANNEL_ID,
60                     context.getString(R.string.default_label),
61                     NotificationManagerCompat.IMPORTANCE_DEFAULT);
62             notificationManager.createNotificationChannel(channel);
63         }
64     }
65 
build(Context context, NotificationModel nm, Stopwatch stopwatch)66     public Notification build(Context context, NotificationModel nm, Stopwatch stopwatch) {
67         @StringRes final int eventLabel = R.string.label_notification;
68 
69         // Intent to load the app when the notification is tapped.
70         final Intent showApp = new Intent(context, StopwatchService.class)
71                 .setAction(StopwatchService.ACTION_SHOW_STOPWATCH)
72                 .putExtra(Events.EXTRA_EVENT_LABEL, eventLabel);
73 
74         final PendingIntent pendingShowApp = PendingIntent.getService(context, 0, showApp,
75                 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
76 
77         // Compute some values required below.
78         final boolean running = stopwatch.isRunning();
79         final String pname = context.getPackageName();
80         final Resources res = context.getResources();
81         final long base = SystemClock.elapsedRealtime() - stopwatch.getTotalTime();
82 
83         final RemoteViews content = new RemoteViews(pname, R.layout.chronometer_notif_content);
84         content.setChronometer(R.id.chronometer, base, null, running);
85 
86         final List<Action> actions = new ArrayList<>(2);
87 
88         if (running) {
89             // Left button: Pause
90             final Intent pause = new Intent(context, StopwatchService.class)
91                     .setAction(StopwatchService.ACTION_PAUSE_STOPWATCH)
92                     .putExtra(Events.EXTRA_EVENT_LABEL, eventLabel);
93 
94             @DrawableRes final int icon1 = R.drawable.ic_pause_24dp;
95             final CharSequence title1 = res.getText(R.string.sw_pause_button);
96             final PendingIntent intent1 = Utils.pendingServiceIntent(context, pause);
97             actions.add(new Action.Builder(icon1, title1, intent1).build());
98 
99             // Right button: Add Lap
100             if (DataModel.getDataModel().canAddMoreLaps()) {
101                 final Intent lap = new Intent(context, StopwatchService.class)
102                         .setAction(StopwatchService.ACTION_LAP_STOPWATCH)
103                         .putExtra(Events.EXTRA_EVENT_LABEL, eventLabel);
104 
105                 @DrawableRes final int icon2 = R.drawable.ic_sw_lap_24dp;
106                 final CharSequence title2 = res.getText(R.string.sw_lap_button);
107                 final PendingIntent intent2 = Utils.pendingServiceIntent(context, lap);
108                 actions.add(new Action.Builder(icon2, title2, intent2).build());
109             }
110 
111             // Show the current lap number if any laps have been recorded.
112             final int lapCount = DataModel.getDataModel().getLaps().size();
113             if (lapCount > 0) {
114                 final int lapNumber = lapCount + 1;
115                 final String lap = res.getString(R.string.sw_notification_lap_number, lapNumber);
116                 content.setTextViewText(R.id.state, lap);
117                 content.setViewVisibility(R.id.state, VISIBLE);
118             } else {
119                 content.setViewVisibility(R.id.state, GONE);
120             }
121         } else {
122             // Left button: Start
123             final Intent start = new Intent(context, StopwatchService.class)
124                     .setAction(StopwatchService.ACTION_START_STOPWATCH)
125                     .putExtra(Events.EXTRA_EVENT_LABEL, eventLabel);
126 
127             @DrawableRes final int icon1 = R.drawable.ic_start_24dp;
128             final CharSequence title1 = res.getText(R.string.sw_start_button);
129             final PendingIntent intent1 = Utils.pendingServiceIntent(context, start);
130             actions.add(new Action.Builder(icon1, title1, intent1).build());
131 
132             // Right button: Reset (dismisses notification and resets stopwatch)
133             final Intent reset = new Intent(context, StopwatchService.class)
134                     .setAction(StopwatchService.ACTION_RESET_STOPWATCH)
135                     .putExtra(Events.EXTRA_EVENT_LABEL, eventLabel);
136 
137             @DrawableRes final int icon2 = R.drawable.ic_reset_24dp;
138             final CharSequence title2 = res.getText(R.string.sw_reset_button);
139             final PendingIntent intent2 = Utils.pendingServiceIntent(context, reset);
140             actions.add(new Action.Builder(icon2, title2, intent2).build());
141 
142             // Indicate the stopwatch is paused.
143             content.setTextViewText(R.id.state, res.getString(R.string.swn_paused));
144             content.setViewVisibility(R.id.state, VISIBLE);
145         }
146 
147         final Builder notification = new NotificationCompat.Builder(
148                 context, STOPWATCH_NOTIFICATION_CHANNEL_ID)
149                         .setLocalOnly(true)
150                         .setOngoing(running)
151                         .setCustomContentView(content)
152                         .setContentIntent(pendingShowApp)
153                         .setAutoCancel(stopwatch.isPaused())
154                         .setPriority(Notification.PRIORITY_MAX)
155                         .setSmallIcon(R.drawable.stat_notify_stopwatch)
156                         .setStyle(new NotificationCompat.DecoratedCustomViewStyle())
157                         .setColor(ContextCompat.getColor(context, R.color.default_background));
158 
159         if (Utils.isNOrLater()) {
160             notification.setGroup(nm.getStopwatchNotificationGroupKey());
161         }
162 
163         for (Action action : actions) {
164             notification.addAction(action);
165         }
166 
167         return notification.build();
168     }
169 }
170