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