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