1 /*
2  * Copyright (C) 2014 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.server.notification;
18 
19 import android.app.AlarmManager;
20 import android.app.PendingIntent;
21 import android.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.net.Uri;
27 import android.service.notification.Condition;
28 import android.service.notification.IConditionProvider;
29 import android.service.notification.ZenModeConfig;
30 import android.text.format.DateUtils;
31 import android.util.Log;
32 import android.util.Slog;
33 
34 import com.android.server.notification.NotificationManagerService.DumpFilter;
35 import com.android.server.pm.PackageManagerService;
36 
37 import java.io.PrintWriter;
38 
39 /** Built-in zen condition provider for simple time-based conditions */
40 public class CountdownConditionProvider extends SystemConditionProviderService {
41     private static final String TAG = "ConditionProviders.CCP";
42     private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG);
43 
44     public static final ComponentName COMPONENT =
45             new ComponentName("android", CountdownConditionProvider.class.getName());
46 
47     private static final String ACTION = CountdownConditionProvider.class.getName();
48     private static final int REQUEST_CODE = 100;
49     private static final String EXTRA_CONDITION_ID = "condition_id";
50 
51     private final Context mContext = this;
52     private final Receiver mReceiver = new Receiver();
53 
54     private boolean mConnected;
55     private long mTime;
56     private boolean mIsAlarm;
57 
CountdownConditionProvider()58     public CountdownConditionProvider() {
59         if (DEBUG) Slog.d(TAG, "new CountdownConditionProvider()");
60     }
61 
62     @Override
getComponent()63     public ComponentName getComponent() {
64         return COMPONENT;
65     }
66 
67     @Override
isValidConditionId(Uri id)68     public boolean isValidConditionId(Uri id) {
69         return ZenModeConfig.isValidCountdownConditionId(id);
70     }
71 
72     @Override
attachBase(Context base)73     public void attachBase(Context base) {
74         attachBaseContext(base);
75     }
76 
77     @Override
onBootComplete()78     public void onBootComplete() {
79         // noop
80     }
81 
82     @Override
asInterface()83     public IConditionProvider asInterface() {
84         return (IConditionProvider) onBind(null);
85     }
86 
87     @Override
dump(PrintWriter pw, DumpFilter filter)88     public void dump(PrintWriter pw, DumpFilter filter) {
89         pw.println("    CountdownConditionProvider:");
90         pw.print("      mConnected="); pw.println(mConnected);
91         pw.print("      mTime="); pw.println(mTime);
92     }
93 
94     @Override
onConnected()95     public void onConnected() {
96         if (DEBUG) Slog.d(TAG, "onConnected");
97         mContext.registerReceiver(mReceiver, new IntentFilter(ACTION),
98                 Context.RECEIVER_EXPORTED_UNAUDITED);
99         mConnected = true;
100     }
101 
102     @Override
onDestroy()103     public void onDestroy() {
104         super.onDestroy();
105         if (DEBUG) Slog.d(TAG, "onDestroy");
106         if (mConnected) {
107             mContext.unregisterReceiver(mReceiver);
108         }
109         mConnected = false;
110     }
111 
112     @Override
onSubscribe(Uri conditionId)113     public void onSubscribe(Uri conditionId) {
114         if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
115         mTime = ZenModeConfig.tryParseCountdownConditionId(conditionId);
116         mIsAlarm = ZenModeConfig.isValidCountdownToAlarmConditionId(conditionId);
117         final AlarmManager alarms = (AlarmManager)
118                 mContext.getSystemService(Context.ALARM_SERVICE);
119         final PendingIntent pendingIntent = getPendingIntent(conditionId);
120         alarms.cancel(pendingIntent);
121         if (mTime > 0) {
122             final long now = System.currentTimeMillis();
123             final CharSequence span =
124                     DateUtils.getRelativeTimeSpanString(mTime, now, DateUtils.MINUTE_IN_MILLIS);
125             if (mTime <= now) {
126                 // in the past, already false
127                 notifyCondition(newCondition(mTime, mIsAlarm, Condition.STATE_FALSE));
128             } else {
129                 // in the future, set an alarm
130                 alarms.setExact(AlarmManager.RTC_WAKEUP, mTime, pendingIntent);
131             }
132             if (DEBUG) Slog.d(TAG, String.format(
133                     "%s %s for %s, %s in the future (%s), now=%s",
134                     (mTime <= now ? "Not scheduling" : "Scheduling"),
135                     ACTION, ts(mTime), mTime - now, span, ts(now)));
136         }
137     }
138 
getPendingIntent(Uri conditionId)139     PendingIntent getPendingIntent(Uri conditionId) {
140         final Intent intent = new Intent(ACTION)
141                 .setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
142                 .putExtra(EXTRA_CONDITION_ID, conditionId)
143                 .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
144         final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, REQUEST_CODE,
145                 intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
146         return pendingIntent;
147     }
148 
149     @Override
onUnsubscribe(Uri conditionId)150     public void onUnsubscribe(Uri conditionId) {
151         // noop
152     }
153 
154     private final class Receiver extends BroadcastReceiver {
155         @Override
onReceive(Context context, Intent intent)156         public void onReceive(Context context, Intent intent) {
157             if (ACTION.equals(intent.getAction())) {
158                 final Uri conditionId = intent.getParcelableExtra(EXTRA_CONDITION_ID, android.net.Uri.class);
159                 final boolean alarm = ZenModeConfig.isValidCountdownToAlarmConditionId(conditionId);
160                 final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
161                 if (DEBUG) Slog.d(TAG, "Countdown condition fired: " + conditionId);
162                 if (time > 0) {
163                     notifyCondition(newCondition(time, alarm, Condition.STATE_FALSE));
164                 }
165             }
166         }
167     }
168 
newCondition(long time, boolean alarm, int state)169     private static final Condition newCondition(long time, boolean alarm, int state) {
170         return new Condition(ZenModeConfig.toCountdownConditionId(time, alarm),
171                 "", "", "", 0, state,Condition.FLAG_RELEVANT_NOW);
172     }
173 
tryParseDescription(Uri conditionUri)174     public static String tryParseDescription(Uri conditionUri) {
175         final long time = ZenModeConfig.tryParseCountdownConditionId(conditionUri);
176         if (time == 0) return null;
177         final long now = System.currentTimeMillis();
178         final CharSequence span =
179                 DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS);
180         return String.format("Scheduled for %s, %s in the future (%s), now=%s",
181                 ts(time), time - now, span, ts(now));
182     }
183 
184 }
185