NextAlarmConditionProvider.java revision 6b0623a1129a7e1bc4949b7dca4fa133ff322c4a
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 17package com.android.server.notification; 18 19import android.app.AlarmManager; 20import android.app.AlarmManager.AlarmClockInfo; 21import android.content.ComponentName; 22import android.content.Context; 23import android.net.Uri; 24import android.service.notification.Condition; 25import android.service.notification.ConditionProviderService; 26import android.service.notification.IConditionProvider; 27import android.service.notification.ZenModeConfig; 28import android.util.Log; 29import android.util.Slog; 30import android.util.TimeUtils; 31 32import com.android.internal.R; 33import com.android.server.notification.NotificationManagerService.DumpFilter; 34 35import java.io.PrintWriter; 36 37/** 38 * Built-in zen condition provider for alarm-clock-based conditions. 39 * 40 * <p>If the user's next alarm is within a lookahead threshold (config, default 12hrs), advertise 41 * it as an exit condition for zen mode (unless the built-in downtime condition is also available). 42 * 43 * <p>When this next alarm is selected as the active exit condition, follow subsequent changes 44 * to the user's next alarm, assuming it remains within the 12-hr window. 45 * 46 * <p>The next alarm is defined as {@link AlarmManager#getNextAlarmClock(int)}, which does not 47 * survive a reboot. Maintain the illusion of a consistent next alarm value by holding on to 48 * a persisted condition until we receive the first value after reboot, or timeout with no value. 49 */ 50public class NextAlarmConditionProvider extends ConditionProviderService { 51 private static final String TAG = "NextAlarmConditions"; 52 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 53 54 private static final long SECONDS = 1000; 55 private static final long MINUTES = 60 * SECONDS; 56 private static final long HOURS = 60 * MINUTES; 57 58 private static final String NEXT_ALARM_PATH = "next_alarm"; 59 public static final ComponentName COMPONENT = 60 new ComponentName("android", NextAlarmConditionProvider.class.getName()); 61 62 private final Context mContext = this; 63 64 private NextAlarmTracker mTracker; 65 private boolean mConnected; 66 private long mLookaheadThreshold; 67 private Callback mCallback; 68 private Uri mCurrentSubscription; 69 70 public NextAlarmConditionProvider() { 71 if (DEBUG) Slog.d(TAG, "new NextAlarmConditionProvider()"); 72 } 73 74 public void dump(PrintWriter pw, DumpFilter filter) { 75 pw.println(" NextAlarmConditionProvider:"); 76 pw.print(" mConnected="); pw.println(mConnected); 77 pw.print(" mLookaheadThreshold="); pw.print(mLookaheadThreshold); 78 pw.print(" ("); TimeUtils.formatDuration(mLookaheadThreshold, pw); pw.println(")"); 79 pw.print(" mCurrentSubscription="); pw.println(mCurrentSubscription); 80 } 81 82 public void setCallback(Callback callback) { 83 mCallback = callback; 84 } 85 86 @Override 87 public void onConnected() { 88 if (DEBUG) Slog.d(TAG, "onConnected"); 89 mLookaheadThreshold = mContext.getResources() 90 .getInteger(R.integer.config_next_alarm_condition_lookahead_threshold_hrs) * HOURS; 91 mConnected = true; 92 mTracker = mCallback.getNextAlarmTracker(); 93 mTracker.addCallback(mTrackerCallback); 94 } 95 96 @Override 97 public void onDestroy() { 98 super.onDestroy(); 99 if (DEBUG) Slog.d(TAG, "onDestroy"); 100 mTracker.removeCallback(mTrackerCallback); 101 mConnected = false; 102 } 103 104 @Override 105 public void onRequestConditions(int relevance) { 106 if (!mConnected || (relevance & Condition.FLAG_RELEVANT_NOW) == 0) return; 107 108 final AlarmClockInfo nextAlarm = mTracker.getNextAlarm(); 109 if (nextAlarm == null) return; // no next alarm 110 if (mCallback != null && mCallback.isInDowntime()) return; // prefer downtime condition 111 if (!isWithinLookaheadThreshold(nextAlarm)) return; // alarm not within window 112 113 // next alarm exists, and is within the configured lookahead threshold 114 notifyCondition(newConditionId(), nextAlarm, Condition.STATE_TRUE, "request"); 115 } 116 117 @Override 118 public void onSubscribe(Uri conditionId) { 119 if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId); 120 if (!isNextAlarmCondition(conditionId)) { 121 notifyCondition(conditionId, null, Condition.STATE_FALSE, "badCondition"); 122 return; 123 } 124 mCurrentSubscription = conditionId; 125 mTracker.evaluate(); 126 } 127 128 @Override 129 public void onUnsubscribe(Uri conditionId) { 130 if (DEBUG) Slog.d(TAG, "onUnsubscribe " + conditionId); 131 if (conditionId != null && conditionId.equals(mCurrentSubscription)) { 132 mCurrentSubscription = null; 133 } 134 } 135 136 public void attachBase(Context base) { 137 attachBaseContext(base); 138 } 139 140 public IConditionProvider asInterface() { 141 return (IConditionProvider) onBind(null); 142 } 143 144 private boolean isWithinLookaheadThreshold(AlarmClockInfo alarm) { 145 if (alarm == null) return false; 146 final long delta = NextAlarmTracker.getEarlyTriggerTime(alarm) - System.currentTimeMillis(); 147 return delta > 0 && (mLookaheadThreshold <= 0 || delta < mLookaheadThreshold); 148 } 149 150 private void notifyCondition(Uri id, AlarmClockInfo alarm, int state, String reason) { 151 final String formattedAlarm = alarm == null ? "" : mTracker.formatAlarm(alarm); 152 if (DEBUG) Slog.d(TAG, "notifyCondition " + Condition.stateToString(state) 153 + " alarm=" + formattedAlarm + " reason=" + reason); 154 notifyCondition(new Condition(id, 155 mContext.getString(R.string.zen_mode_next_alarm_summary, formattedAlarm), 156 mContext.getString(R.string.zen_mode_next_alarm_line_one), 157 formattedAlarm, 0, state, Condition.FLAG_RELEVANT_NOW)); 158 } 159 160 private Uri newConditionId() { 161 return new Uri.Builder().scheme(Condition.SCHEME) 162 .authority(ZenModeConfig.SYSTEM_AUTHORITY) 163 .appendPath(NEXT_ALARM_PATH) 164 .appendPath(Integer.toString(mTracker.getCurrentUserId())) 165 .build(); 166 } 167 168 private boolean isNextAlarmCondition(Uri conditionId) { 169 return conditionId != null && conditionId.getScheme().equals(Condition.SCHEME) 170 && conditionId.getAuthority().equals(ZenModeConfig.SYSTEM_AUTHORITY) 171 && conditionId.getPathSegments().size() == 2 172 && conditionId.getPathSegments().get(0).equals(NEXT_ALARM_PATH) 173 && conditionId.getPathSegments().get(1) 174 .equals(Integer.toString(mTracker.getCurrentUserId())); 175 } 176 177 private void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) { 178 final boolean withinThreshold = isWithinLookaheadThreshold(nextAlarm); 179 if (DEBUG) Slog.d(TAG, "onEvaluate mCurrentSubscription=" + mCurrentSubscription 180 + " nextAlarmWakeup=" + mTracker.formatAlarmDebug(wakeupTime) 181 + " withinThreshold=" + withinThreshold 182 + " booted=" + booted); 183 if (mCurrentSubscription == null) return; // no one cares 184 if (!booted) { 185 // we don't know yet 186 notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_UNKNOWN, "!booted"); 187 return; 188 } 189 if (!withinThreshold) { 190 // next alarm outside threshold or in the past, condition = false 191 notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_FALSE, "!within"); 192 mCurrentSubscription = null; 193 return; 194 } 195 // next alarm in the future and within threshold, condition = true 196 notifyCondition(mCurrentSubscription, nextAlarm, Condition.STATE_TRUE, "within"); 197 } 198 199 private final NextAlarmTracker.Callback mTrackerCallback = new NextAlarmTracker.Callback() { 200 @Override 201 public void onEvaluate(AlarmClockInfo nextAlarm, long wakeupTime, boolean booted) { 202 NextAlarmConditionProvider.this.onEvaluate(nextAlarm, wakeupTime, booted); 203 } 204 }; 205 206 public interface Callback { 207 boolean isInDowntime(); 208 NextAlarmTracker getNextAlarmTracker(); 209 } 210} 211