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