1 /* 2 * Copyright (C) 2021 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.internal.net.ipsec.ike.utils; 18 19 import android.app.AlarmManager; 20 import android.app.PendingIntent; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.os.Bundle; 24 import android.os.Message; 25 import android.os.Process; 26 import android.os.SystemClock; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.internal.util.WakeupMessage; 30 31 /** IkeAlarm provides interfaces to use AlarmManager for scheduling system alarm. */ 32 // TODO: b/191056695 Improve test coverage for scheduling system alarms. 33 public abstract class IkeAlarm { 34 private static final Dependencies sDeps = new Dependencies(); 35 36 protected final AlarmManager mAlarmManager; 37 protected final String mTag; 38 protected final long mDelayMs; 39 IkeAlarm(IkeAlarmConfig alarmConfig)40 private IkeAlarm(IkeAlarmConfig alarmConfig) { 41 mAlarmManager = alarmConfig.context.getSystemService(AlarmManager.class); 42 mTag = alarmConfig.tag; 43 mDelayMs = alarmConfig.delayMs; 44 } 45 46 /** Creates an alarm to be delivered precisely at the stated time. */ newExactAlarm(IkeAlarmConfig alarmConfig)47 public static IkeAlarm newExactAlarm(IkeAlarmConfig alarmConfig) { 48 return new IkeAlarmWithListener(alarmConfig, sDeps); 49 } 50 51 /** Creates an alarm with a Dependencies instance for testing */ 52 @VisibleForTesting newExactAlarm(IkeAlarmConfig alarmConfig, Dependencies deps)53 static IkeAlarm newExactAlarm(IkeAlarmConfig alarmConfig, Dependencies deps) { 54 return new IkeAlarmWithListener(alarmConfig, deps); 55 } 56 57 /** 58 * Creates an alarm to be delivered precisely at the stated time, even when the system is in 59 * low-power idle (a.k.a. doze) modes. 60 */ newExactAndAllowWhileIdleAlarm(IkeAlarmConfig alarmConfig)61 public static IkeAlarm newExactAndAllowWhileIdleAlarm(IkeAlarmConfig alarmConfig) { 62 return newExactAndAllowWhileIdleAlarm(alarmConfig, sDeps); 63 } 64 65 /** Creates an alarm with a Dependencies instance for testing */ 66 @VisibleForTesting newExactAndAllowWhileIdleAlarm(IkeAlarmConfig alarmConfig, Dependencies deps)67 static IkeAlarm newExactAndAllowWhileIdleAlarm(IkeAlarmConfig alarmConfig, Dependencies deps) { 68 if (deps.getMyUid() == Process.SYSTEM_UID) { 69 // By using listener instead of PendingIntent, the system service does not need to 70 // declare the PendingIntent broadcast as protected in the AndroidManifest. 71 return new IkeAlarmWithListener(alarmConfig, deps); 72 } else { 73 return new IkeAlarmWithPendingIntent(alarmConfig); 74 } 75 } 76 77 /** 78 * Build an alarm intent for an action, an intent ID and a message to send to the state machine. 79 * 80 * @param context The context for the target package. 81 * @param intentAction The action to use in the alarm intent. 82 * @param intentId The identifier to use in the intent, see {@link Intent#setIdentifier(String)} 83 * @param ikeSmMsg The message that should be sent to the state machine when the alarm fires 84 * @return A constructed PendingIntent for the passed arguments. 85 */ buildIkeAlarmIntent( Context context, String intentAction, String intentId, Message ikeSmMsg)86 public static PendingIntent buildIkeAlarmIntent( 87 Context context, String intentAction, String intentId, Message ikeSmMsg) { 88 Intent intent = new Intent(intentAction); 89 intent.setIdentifier(intentId); 90 intent.setPackage(context.getPackageName()); 91 92 Bundle bundle = new Bundle(); 93 bundle.putParcelable(IkeAlarmReceiver.PARCELABLE_NAME_IKE_SESSION_MSG, ikeSmMsg); 94 intent.putExtras(bundle); 95 96 return PendingIntent.getBroadcast( 97 context, 0 /* requestCode; unused */, intent, PendingIntent.FLAG_IMMUTABLE); 98 } 99 100 /** Cancel the alarm */ cancel()101 public abstract void cancel(); 102 103 /** Schedule/re-schedule the alarm */ schedule()104 public abstract void schedule(); 105 106 /** External dependencies, for injection in tests */ 107 @VisibleForTesting 108 static class Dependencies { 109 /** Get the UID of the current process */ getMyUid()110 public int getMyUid() { 111 return Process.myUid(); 112 } 113 114 /** Construct a WakeupMessage */ newWakeMessage(IkeAlarmConfig alarmConfig)115 public WakeupMessage newWakeMessage(IkeAlarmConfig alarmConfig) { 116 Message alarmMessage = alarmConfig.message; 117 return new WakeupMessage( 118 alarmConfig.context, 119 alarmMessage.getTarget(), 120 alarmConfig.tag, 121 alarmMessage.what, 122 alarmMessage.arg1, 123 alarmMessage.arg2, 124 alarmMessage.obj); 125 } 126 } 127 128 /** Alarm that will be using a PendingIntent and will be set with setExactAndAllowWhileIdle */ 129 @VisibleForTesting 130 static class IkeAlarmWithPendingIntent extends IkeAlarm { 131 private final PendingIntent mPendingIntent; 132 IkeAlarmWithPendingIntent(IkeAlarmConfig alarmConfig)133 IkeAlarmWithPendingIntent(IkeAlarmConfig alarmConfig) { 134 super(alarmConfig); 135 android.util.Log.d("IKE", "new IkeAlarmWithPendingIntent for " + mTag); 136 137 mPendingIntent = alarmConfig.pendingIntent; 138 } 139 140 @Override cancel()141 public void cancel() { 142 mAlarmManager.cancel(mPendingIntent); 143 mPendingIntent.cancel(); 144 } 145 146 @Override schedule()147 public void schedule() { 148 mAlarmManager.setExactAndAllowWhileIdle( 149 AlarmManager.ELAPSED_REALTIME_WAKEUP, 150 SystemClock.elapsedRealtime() + mDelayMs, 151 mPendingIntent); 152 } 153 } 154 155 /** 156 * Alarm that will be using a OnAlarmListener and will be set with setExact 157 * 158 * <p>If the caller is a system service, the alarm can still be fired in doze mode. 159 */ 160 @VisibleForTesting 161 static class IkeAlarmWithListener extends IkeAlarm { 162 private final WakeupMessage mWakeupMsg; 163 IkeAlarmWithListener(IkeAlarmConfig alarmConfig, Dependencies deps)164 IkeAlarmWithListener(IkeAlarmConfig alarmConfig, Dependencies deps) { 165 super(alarmConfig); 166 android.util.Log.d("IKE", "new IkeAlarmWithListener for " + mTag); 167 168 mWakeupMsg = deps.newWakeMessage(alarmConfig); 169 } 170 171 @Override cancel()172 public void cancel() { 173 mWakeupMsg.cancel(); 174 } 175 176 @Override schedule()177 public void schedule() { 178 mWakeupMsg.schedule(SystemClock.elapsedRealtime() + mDelayMs); 179 } 180 } 181 182 public static class IkeAlarmConfig { 183 public final Context context; 184 public final String tag; 185 public final long delayMs; 186 public final Message message; 187 public final PendingIntent pendingIntent; 188 IkeAlarmConfig( Context context, String tag, long delayMs, PendingIntent pendingIntent, Message message)189 public IkeAlarmConfig( 190 Context context, 191 String tag, 192 long delayMs, 193 PendingIntent pendingIntent, 194 Message message) { 195 this.context = context; 196 this.tag = tag; 197 this.delayMs = delayMs; 198 this.message = message; 199 this.pendingIntent = pendingIntent; 200 } 201 202 /** Create a copy with a different delay */ buildCopyWithDelayMs(long updatedDelayMs)203 public IkeAlarmConfig buildCopyWithDelayMs(long updatedDelayMs) { 204 return new IkeAlarmConfig(context, tag, updatedDelayMs, pendingIntent, message); 205 } 206 } 207 } 208