ScheduleConditionProvider.java revision 1998ee56285419ff7fea0ec9a247e31d1d27a0ab
1/*
2 * Copyright (C) 2015 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
17package com.android.server.notification;
18
19import android.app.ActivityManager;
20import android.app.AlarmManager;
21import android.app.PendingIntent;
22import android.content.BroadcastReceiver;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.net.Uri;
28import android.service.notification.Condition;
29import android.service.notification.IConditionProvider;
30import android.service.notification.ZenModeConfig;
31import android.service.notification.ZenModeConfig.ScheduleInfo;
32import android.util.ArrayMap;
33import android.util.Log;
34import android.util.Slog;
35
36import com.android.server.notification.NotificationManagerService.DumpFilter;
37
38import java.io.PrintWriter;
39import java.util.TimeZone;
40
41/**
42 * Built-in zen condition provider for daily scheduled time-based conditions.
43 */
44public class ScheduleConditionProvider extends SystemConditionProviderService {
45    private static final String TAG = "ConditionProviders.SCP";
46    private static final boolean DEBUG = true || Log.isLoggable("ConditionProviders", Log.DEBUG);
47
48    public static final ComponentName COMPONENT =
49            new ComponentName("android", ScheduleConditionProvider.class.getName());
50    private static final String NOT_SHOWN = "...";
51    private static final String SIMPLE_NAME = ScheduleConditionProvider.class.getSimpleName();
52    private static final String ACTION_EVALUATE =  SIMPLE_NAME + ".EVALUATE";
53    private static final int REQUEST_CODE_EVALUATE = 1;
54    private static final String EXTRA_TIME = "time";
55
56    private final Context mContext = this;
57    private final ArrayMap<Uri, ScheduleCalendar> mSubscriptions = new ArrayMap<>();
58
59    private AlarmManager mAlarmManager;
60    private boolean mConnected;
61    private boolean mRegistered;
62    private long mNextAlarmTime;
63
64    public ScheduleConditionProvider() {
65        if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()");
66    }
67
68    @Override
69    public ComponentName getComponent() {
70        return COMPONENT;
71    }
72
73    @Override
74    public boolean isValidConditionId(Uri id) {
75        return ZenModeConfig.isValidScheduleConditionId(id);
76    }
77
78    @Override
79    public void dump(PrintWriter pw, DumpFilter filter) {
80        pw.print("    "); pw.print(SIMPLE_NAME); pw.println(":");
81        pw.print("      mConnected="); pw.println(mConnected);
82        pw.print("      mRegistered="); pw.println(mRegistered);
83        pw.println("      mSubscriptions=");
84        final long now = System.currentTimeMillis();
85        for (Uri conditionId : mSubscriptions.keySet()) {
86            pw.print("        ");
87            pw.print(meetsSchedule(mSubscriptions.get(conditionId), now) ? "* " : "  ");
88            pw.println(conditionId);
89        }
90        dumpUpcomingTime(pw, "mNextAlarmTime", mNextAlarmTime, now);
91    }
92
93    @Override
94    public void onConnected() {
95        if (DEBUG) Slog.d(TAG, "onConnected");
96        mConnected = true;
97    }
98
99    @Override
100    public void onBootComplete() {
101        // noop
102    }
103
104    @Override
105    public void onDestroy() {
106        super.onDestroy();
107        if (DEBUG) Slog.d(TAG, "onDestroy");
108        mConnected = false;
109    }
110
111    @Override
112    public void onSubscribe(Uri conditionId) {
113        if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
114        if (!ZenModeConfig.isValidScheduleConditionId(conditionId)) {
115            notifyCondition(conditionId, Condition.STATE_FALSE, "badCondition");
116            return;
117        }
118        mSubscriptions.put(conditionId, toScheduleCalendar(conditionId));
119        evaluateSubscriptions();
120    }
121
122    @Override
123    public void onUnsubscribe(Uri conditionId) {
124        if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
125        mSubscriptions.remove(conditionId);
126        evaluateSubscriptions();
127    }
128
129    @Override
130    public void attachBase(Context base) {
131        attachBaseContext(base);
132    }
133
134    @Override
135    public IConditionProvider asInterface() {
136        return (IConditionProvider) onBind(null);
137    }
138
139    private void evaluateSubscriptions() {
140        if (mAlarmManager == null) {
141            mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
142        }
143        setRegistered(!mSubscriptions.isEmpty());
144        final long now = System.currentTimeMillis();
145        mNextAlarmTime = 0;
146        long nextUserAlarmTime = getNextAlarm();
147        for (Uri conditionId : mSubscriptions.keySet()) {
148            final ScheduleCalendar cal = mSubscriptions.get(conditionId);
149            if (cal != null && cal.isInSchedule(now)) {
150                notifyCondition(conditionId, Condition.STATE_TRUE, "meetsSchedule");
151                cal.maybeSetNextAlarm(now, nextUserAlarmTime);
152            } else {
153                notifyCondition(conditionId, Condition.STATE_FALSE, "!meetsSchedule");
154            }
155            if (cal != null) {
156                final long nextChangeTime = cal.getNextChangeTime(now);
157                if (nextChangeTime > 0 && nextChangeTime > now) {
158                    if (mNextAlarmTime == 0 || nextChangeTime < mNextAlarmTime) {
159                        mNextAlarmTime = nextChangeTime;
160                    }
161                }
162            }
163        }
164        updateAlarm(now, mNextAlarmTime);
165    }
166
167    private void updateAlarm(long now, long time) {
168        final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
169        final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
170                REQUEST_CODE_EVALUATE,
171                new Intent(ACTION_EVALUATE)
172                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
173                        .putExtra(EXTRA_TIME, time),
174                PendingIntent.FLAG_UPDATE_CURRENT);
175        alarms.cancel(pendingIntent);
176        if (time > now) {
177            if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s",
178                    ts(time), formatDuration(time - now), ts(now)));
179            alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
180        } else {
181            if (DEBUG) Slog.d(TAG, "Not scheduling evaluate");
182        }
183    }
184
185    public long getNextAlarm() {
186        final AlarmManager.AlarmClockInfo info = mAlarmManager.getNextAlarmClock(
187                ActivityManager.getCurrentUser());
188        return info != null ? info.getTriggerTime() : 0;
189    }
190
191    private static boolean meetsSchedule(ScheduleCalendar cal, long time) {
192        return cal != null && cal.isInSchedule(time);
193    }
194
195    private static ScheduleCalendar toScheduleCalendar(Uri conditionId) {
196        final ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId(conditionId);
197        if (schedule == null || schedule.days == null || schedule.days.length == 0) return null;
198        final ScheduleCalendar sc = new ScheduleCalendar();
199        sc.setSchedule(schedule);
200        sc.setTimeZone(TimeZone.getDefault());
201        return sc;
202    }
203
204    private void setRegistered(boolean registered) {
205        if (mRegistered == registered) return;
206        if (DEBUG) Slog.d(TAG, "setRegistered " + registered);
207        mRegistered = registered;
208        if (mRegistered) {
209            final IntentFilter filter = new IntentFilter();
210            filter.addAction(Intent.ACTION_TIME_CHANGED);
211            filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
212            filter.addAction(ACTION_EVALUATE);
213            filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
214            registerReceiver(mReceiver, filter);
215        } else {
216            unregisterReceiver(mReceiver);
217        }
218    }
219
220    private void notifyCondition(Uri conditionId, int state, String reason) {
221        if (DEBUG) Slog.d(TAG, "notifyCondition " + conditionId
222                + " " + Condition.stateToString(state)
223                + " reason=" + reason);
224        notifyCondition(createCondition(conditionId, state));
225    }
226
227    private Condition createCondition(Uri id, int state) {
228        final String summary = NOT_SHOWN;
229        final String line1 = NOT_SHOWN;
230        final String line2 = NOT_SHOWN;
231        return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS);
232    }
233
234    private BroadcastReceiver mReceiver = new BroadcastReceiver() {
235        @Override
236        public void onReceive(Context context, Intent intent) {
237            if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction());
238            evaluateSubscriptions();
239        }
240    };
241
242}
243