Home | History | Annotate | Download | only in notification
      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 
     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.content.pm.PackageManager.NameNotFoundException;
     27 import android.net.Uri;
     28 import android.os.Handler;
     29 import android.os.HandlerThread;
     30 import android.os.Looper;
     31 import android.os.Process;
     32 import android.os.UserHandle;
     33 import android.os.UserManager;
     34 import android.service.notification.Condition;
     35 import android.service.notification.IConditionProvider;
     36 import android.service.notification.ZenModeConfig;
     37 import android.service.notification.ZenModeConfig.EventInfo;
     38 import android.util.ArraySet;
     39 import android.util.Log;
     40 import android.util.Slog;
     41 import android.util.SparseArray;
     42 
     43 import com.android.server.notification.CalendarTracker.CheckEventResult;
     44 import com.android.server.notification.NotificationManagerService.DumpFilter;
     45 
     46 import java.io.PrintWriter;
     47 import java.util.ArrayList;
     48 import java.util.List;
     49 
     50 /**
     51  * Built-in zen condition provider for calendar event-based conditions.
     52  */
     53 public class EventConditionProvider extends SystemConditionProviderService {
     54     private static final String TAG = "ConditionProviders.ECP";
     55     private static final boolean DEBUG = Log.isLoggable("ConditionProviders", Log.DEBUG);
     56 
     57     public static final ComponentName COMPONENT =
     58             new ComponentName("android", EventConditionProvider.class.getName());
     59     private static final String NOT_SHOWN = "...";
     60     private static final String SIMPLE_NAME = EventConditionProvider.class.getSimpleName();
     61     private static final String ACTION_EVALUATE = SIMPLE_NAME + ".EVALUATE";
     62     private static final int REQUEST_CODE_EVALUATE = 1;
     63     private static final String EXTRA_TIME = "time";
     64     private static final long CHANGE_DELAY = 2 * 1000;  // coalesce chatty calendar changes
     65 
     66     private final Context mContext = this;
     67     private final ArraySet<Uri> mSubscriptions = new ArraySet<Uri>();
     68     private final SparseArray<CalendarTracker> mTrackers = new SparseArray<>();
     69     private final Handler mWorker;
     70     private final HandlerThread mThread;
     71 
     72     private boolean mConnected;
     73     private boolean mRegistered;
     74     private boolean mBootComplete;  // don't hammer the calendar provider until boot completes.
     75     private long mNextAlarmTime;
     76 
     77     public EventConditionProvider() {
     78         if (DEBUG) Slog.d(TAG, "new " + SIMPLE_NAME + "()");
     79         mThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
     80         mThread.start();
     81         mWorker = new Handler(mThread.getLooper());
     82     }
     83 
     84     @Override
     85     public ComponentName getComponent() {
     86         return COMPONENT;
     87     }
     88 
     89     @Override
     90     public boolean isValidConditionId(Uri id) {
     91         return ZenModeConfig.isValidEventConditionId(id);
     92     }
     93 
     94     @Override
     95     public void dump(PrintWriter pw, DumpFilter filter) {
     96         pw.print("    "); pw.print(SIMPLE_NAME); pw.println(":");
     97         pw.print("      mConnected="); pw.println(mConnected);
     98         pw.print("      mRegistered="); pw.println(mRegistered);
     99         pw.print("      mBootComplete="); pw.println(mBootComplete);
    100         dumpUpcomingTime(pw, "mNextAlarmTime", mNextAlarmTime, System.currentTimeMillis());
    101         synchronized (mSubscriptions) {
    102             pw.println("      mSubscriptions=");
    103             for (Uri conditionId : mSubscriptions) {
    104                 pw.print("        ");
    105                 pw.println(conditionId);
    106             }
    107         }
    108         pw.println("      mTrackers=");
    109         for (int i = 0; i < mTrackers.size(); i++) {
    110             pw.print("        user="); pw.println(mTrackers.keyAt(i));
    111             mTrackers.valueAt(i).dump("          ", pw);
    112         }
    113     }
    114 
    115     @Override
    116     public void onBootComplete() {
    117         if (DEBUG) Slog.d(TAG, "onBootComplete");
    118         if (mBootComplete) return;
    119         mBootComplete = true;
    120         final IntentFilter filter = new IntentFilter();
    121         filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
    122         filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
    123         mContext.registerReceiver(new BroadcastReceiver() {
    124             @Override
    125             public void onReceive(Context context, Intent intent) {
    126                 reloadTrackers();
    127             }
    128         }, filter);
    129         reloadTrackers();
    130     }
    131 
    132     @Override
    133     public void onConnected() {
    134         if (DEBUG) Slog.d(TAG, "onConnected");
    135         mConnected = true;
    136     }
    137 
    138     @Override
    139     public void onDestroy() {
    140         super.onDestroy();
    141         if (DEBUG) Slog.d(TAG, "onDestroy");
    142         mConnected = false;
    143     }
    144 
    145     @Override
    146     public void onSubscribe(Uri conditionId) {
    147         if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
    148         if (!ZenModeConfig.isValidEventConditionId(conditionId)) {
    149             notifyCondition(createCondition(conditionId, Condition.STATE_FALSE));
    150             return;
    151         }
    152         synchronized (mSubscriptions) {
    153             if (mSubscriptions.add(conditionId)) {
    154                 evaluateSubscriptions();
    155             }
    156         }
    157     }
    158 
    159     @Override
    160     public void onUnsubscribe(Uri conditionId) {
    161         if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId);
    162         synchronized (mSubscriptions) {
    163             if (mSubscriptions.remove(conditionId)) {
    164                 evaluateSubscriptions();
    165             }
    166         }
    167     }
    168 
    169     @Override
    170     public void attachBase(Context base) {
    171         attachBaseContext(base);
    172     }
    173 
    174     @Override
    175     public IConditionProvider asInterface() {
    176         return (IConditionProvider) onBind(null);
    177     }
    178 
    179     private void reloadTrackers() {
    180         if (DEBUG) Slog.d(TAG, "reloadTrackers");
    181         for (int i = 0; i < mTrackers.size(); i++) {
    182             mTrackers.valueAt(i).setCallback(null);
    183         }
    184         mTrackers.clear();
    185         for (UserHandle user : UserManager.get(mContext).getUserProfiles()) {
    186             final Context context = user.isSystem() ? mContext : getContextForUser(mContext, user);
    187             if (context == null) {
    188                 Slog.w(TAG, "Unable to create context for user " + user.getIdentifier());
    189                 continue;
    190             }
    191             mTrackers.put(user.getIdentifier(), new CalendarTracker(mContext, context));
    192         }
    193         evaluateSubscriptions();
    194     }
    195 
    196     private void evaluateSubscriptions() {
    197         if (!mWorker.hasCallbacks(mEvaluateSubscriptionsW)) {
    198             mWorker.post(mEvaluateSubscriptionsW);
    199         }
    200     }
    201 
    202     private void evaluateSubscriptionsW() {
    203         if (DEBUG) Slog.d(TAG, "evaluateSubscriptions");
    204         if (!mBootComplete) {
    205             if (DEBUG) Slog.d(TAG, "Skipping evaluate before boot complete");
    206             return;
    207         }
    208         final long now = System.currentTimeMillis();
    209         List<Condition> conditionsToNotify = new ArrayList<>();
    210         synchronized (mSubscriptions) {
    211             for (int i = 0; i < mTrackers.size(); i++) {
    212                 mTrackers.valueAt(i).setCallback(
    213                         mSubscriptions.isEmpty() ? null : mTrackerCallback);
    214             }
    215             setRegistered(!mSubscriptions.isEmpty());
    216             long reevaluateAt = 0;
    217             for (Uri conditionId : mSubscriptions) {
    218                 final EventInfo event = ZenModeConfig.tryParseEventConditionId(conditionId);
    219                 if (event == null) {
    220                     conditionsToNotify.add(createCondition(conditionId, Condition.STATE_FALSE));
    221                     continue;
    222                 }
    223                 CheckEventResult result = null;
    224                 if (event.calendar == null) { // any calendar
    225                     // event could exist on any tracker
    226                     for (int i = 0; i < mTrackers.size(); i++) {
    227                         final CalendarTracker tracker = mTrackers.valueAt(i);
    228                         final CheckEventResult r = tracker.checkEvent(event, now);
    229                         if (result == null) {
    230                             result = r;
    231                         } else {
    232                             result.inEvent |= r.inEvent;
    233                             result.recheckAt = Math.min(result.recheckAt, r.recheckAt);
    234                         }
    235                     }
    236                 } else {
    237                     // event should exist on one tracker
    238                     final int userId = EventInfo.resolveUserId(event.userId);
    239                     final CalendarTracker tracker = mTrackers.get(userId);
    240                     if (tracker == null) {
    241                         Slog.w(TAG, "No calendar tracker found for user " + userId);
    242                         conditionsToNotify.add(createCondition(conditionId, Condition.STATE_FALSE));
    243                         continue;
    244                     }
    245                     result = tracker.checkEvent(event, now);
    246                 }
    247                 if (result.recheckAt != 0
    248                         && (reevaluateAt == 0 || result.recheckAt < reevaluateAt)) {
    249                     reevaluateAt = result.recheckAt;
    250                 }
    251                 if (!result.inEvent) {
    252                     conditionsToNotify.add(createCondition(conditionId, Condition.STATE_FALSE));
    253                     continue;
    254                 }
    255                 conditionsToNotify.add(createCondition(conditionId, Condition.STATE_TRUE));
    256             }
    257             rescheduleAlarm(now, reevaluateAt);
    258         }
    259         for (Condition condition : conditionsToNotify) {
    260             if (condition != null) {
    261                 notifyCondition(condition);
    262             }
    263         }
    264         if (DEBUG) Slog.d(TAG, "evaluateSubscriptions took " + (System.currentTimeMillis() - now));
    265     }
    266 
    267     private void rescheduleAlarm(long now, long time) {
    268         mNextAlarmTime = time;
    269         final AlarmManager alarms = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
    270         final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
    271                 REQUEST_CODE_EVALUATE,
    272                 new Intent(ACTION_EVALUATE)
    273                         .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
    274                         .putExtra(EXTRA_TIME, time),
    275                 PendingIntent.FLAG_UPDATE_CURRENT);
    276         alarms.cancel(pendingIntent);
    277         if (time == 0 || time < now) {
    278             if (DEBUG) Slog.d(TAG, "Not scheduling evaluate: " + (time == 0 ? "no time specified"
    279                     : "specified time in the past"));
    280             return;
    281         }
    282         if (DEBUG) Slog.d(TAG, String.format("Scheduling evaluate for %s, in %s, now=%s",
    283                 ts(time), formatDuration(time - now), ts(now)));
    284         alarms.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
    285     }
    286 
    287     private Condition createCondition(Uri id, int state) {
    288         final String summary = NOT_SHOWN;
    289         final String line1 = NOT_SHOWN;
    290         final String line2 = NOT_SHOWN;
    291         return new Condition(id, summary, line1, line2, 0, state, Condition.FLAG_RELEVANT_ALWAYS);
    292     }
    293 
    294     private void setRegistered(boolean registered) {
    295         if (mRegistered == registered) return;
    296         if (DEBUG) Slog.d(TAG, "setRegistered " + registered);
    297         mRegistered = registered;
    298         if (mRegistered) {
    299             final IntentFilter filter = new IntentFilter();
    300             filter.addAction(Intent.ACTION_TIME_CHANGED);
    301             filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
    302             filter.addAction(ACTION_EVALUATE);
    303             registerReceiver(mReceiver, filter);
    304         } else {
    305             unregisterReceiver(mReceiver);
    306         }
    307     }
    308 
    309     private static Context getContextForUser(Context context, UserHandle user) {
    310         try {
    311             return context.createPackageContextAsUser(context.getPackageName(), 0, user);
    312         } catch (NameNotFoundException e) {
    313             return null;
    314         }
    315     }
    316 
    317     private final CalendarTracker.Callback mTrackerCallback = new CalendarTracker.Callback() {
    318         @Override
    319         public void onChanged() {
    320             if (DEBUG) Slog.d(TAG, "mTrackerCallback.onChanged");
    321             mWorker.removeCallbacks(mEvaluateSubscriptionsW);
    322             mWorker.postDelayed(mEvaluateSubscriptionsW, CHANGE_DELAY);
    323         }
    324     };
    325 
    326     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    327         @Override
    328         public void onReceive(Context context, Intent intent) {
    329             if (DEBUG) Slog.d(TAG, "onReceive " + intent.getAction());
    330             evaluateSubscriptions();
    331         }
    332     };
    333 
    334     private final Runnable mEvaluateSubscriptionsW = new Runnable() {
    335         @Override
    336         public void run() {
    337             evaluateSubscriptionsW();
    338         }
    339     };
    340 }
    341