1 /**
2  * Copyright (C) 2024 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 package com.android.telephony.imsmedia;
17 
18 import android.content.Context;
19 import android.os.PowerManager;
20 import android.telephony.imsmedia.RtpConfig;
21 
22 import androidx.annotation.VisibleForTesting;
23 
24 import com.android.telephony.imsmedia.util.Log;
25 
26 import java.io.PrintWriter;
27 import java.util.HashSet;
28 import java.util.Iterator;
29 
30 /**
31  * WakeLockManager makes sure that the process is not suspended when the device switches to
32  * doze mode by acquiring wake lock based on media direction of audio, video and text sessions.
33  */
34 public class WakeLockManager {
35     private static final String TAG = "WakeLockManager";
36     private static final String WAKELOCK_TAG = "imsmedia.insession_lock";
37     private static WakeLockManager sWakeLockManager;
38 
39     @VisibleForTesting
40     protected PowerManager.WakeLock mWakeLock;
41     // Sessions for which lock is acquired.
42     private HashSet<Integer> mWakeLockAcquiredSessions = new HashSet<>();
43 
WakeLockManager()44     private WakeLockManager() {
45         Context context = ImsMediaApplication.getAppContext();
46 
47         if (context != null) {
48             PowerManager powerManager = context.getSystemService(PowerManager.class);
49             mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
50 
51             // Wake locks are reference counted by default. Still we want to make sure it's true.
52             mWakeLock.setReferenceCounted(true);
53             Log.d(TAG, "WakeLockManager - initialized. Wakelock:" + mWakeLock.toString());
54         } else {
55             Log.e(TAG, "WakeLockManager not initialized. Context is null");
56         }
57     }
58 
59     /**
60      * Get the instance of WakeLockManager
61      * @return instance of WakeLockManager
62      */
getInstance()63     public static WakeLockManager getInstance() {
64         if (sWakeLockManager == null) {
65             sWakeLockManager = new WakeLockManager();
66         }
67 
68         return sWakeLockManager;
69     }
70 
71     /**
72      * Acquires wake lock by incrementing the reference counter of WakeLock.
73      * WakeLockManager should be initialized before calling.
74      */
acquireLock()75     private void acquireLock() {
76         if (mWakeLock != null) {
77             mWakeLock.acquire();
78             Log.d(TAG, "acquireLock. " + mWakeLock.toString());
79             return;
80         }
81         Log.e(TAG, "acquireLock - WakeLockManager not initialized");
82     }
83 
84     /**
85      * Releases wake lock by decrementing the reference counter of WakeLock.
86      * WakeLockManager should be initialized before calling.
87      */
releaseLock()88     private void releaseLock() {
89         if (mWakeLock != null && mWakeLock.isHeld()) {
90             mWakeLock.release();
91             Log.d(TAG, "releaseLock. " + mWakeLock.toString());
92             return;
93         }
94 
95         if (mWakeLock == null) {
96             Log.e(TAG, "releaseLock - WakeLockManager not initialized");
97         }
98     }
99 
100     /**
101      * Releases all wake locks by decrementing the reference counter to zero.
102      * WakeLockManager should be initialized before calling.
103      */
cleanup()104     public synchronized void cleanup() {
105         Log.d(TAG, "cleanup");
106         if (mWakeLockAcquiredSessions.size() > 0) {
107             Iterator<Integer> iterator = mWakeLockAcquiredSessions.iterator();
108             while (iterator.hasNext()) {
109                 Integer sessionId = iterator.next();
110                 Log.e(TAG, "LEAKED session-id: " + sessionId);
111             }
112 
113             mWakeLockAcquiredSessions.clear();
114         }
115 
116         if (mWakeLock != null && mWakeLock.isHeld()) {
117             Log.e(TAG, "LEAKED wakelock: " + mWakeLock);
118 
119             // Release leaked wakelock
120             while (mWakeLock.isHeld()) {
121                 mWakeLock.release();
122             }
123         }
124     }
125 
126     /**
127      * Helper method used by all session classes (audio, video and text) to acquire or release
128      * wake lock based on session media direction.
129      *
130      * @param sessionId Id of the session
131      * @param mediaDirection current media direction of the session.
132      */
manageWakeLockOnMediaDirectionUpdate( int sessionId, final @RtpConfig.MediaDirection int mediaDirection)133     public synchronized void manageWakeLockOnMediaDirectionUpdate(
134             int sessionId, final @RtpConfig.MediaDirection int mediaDirection) {
135         try {
136             boolean wakeLockAcquired = mWakeLockAcquiredSessions.contains(sessionId);
137             Log.dc(TAG, "manageWakeLockOnMediaDirectionUpdate - SessionId:" + sessionId
138                     + ", mediaDirection:" + mediaDirection);
139 
140             if (wakeLockAcquired && mediaDirection == RtpConfig.MEDIA_DIRECTION_NO_FLOW) {
141                 releaseLock();
142                 mWakeLockAcquiredSessions.remove(sessionId);
143 
144                 if (mWakeLock.isHeld()) {
145                     Log.d(TAG, "Wakelock still held for other sessions. SessionCount:"
146                             + mWakeLockAcquiredSessions.size()
147                             + ", Wakelock:" + mWakeLock.toString());
148 
149                     if (mWakeLockAcquiredSessions.size() <= 0) {
150                         cleanup();
151                     }
152                 }
153             } else if (!wakeLockAcquired && mediaDirection != RtpConfig.MEDIA_DIRECTION_NO_FLOW) {
154                 acquireLock();
155                 mWakeLockAcquiredSessions.add(sessionId);
156             } /* else {
157                 if (wakeLockAcquired && mediaDirection != RtpConfig.MEDIA_DIRECTION_NO_FLOW) {
158                     * No operation as Lock is already acquired.
159                     * Lock is acquired only once per session.
160                 }
161                 if (!wakeLockAcquired && mediaDirection == RtpConfig.MEDIA_DIRECTION_NO_FLOW) {
162                     * No need to acquire lock as media is not flowing.
163                     * Example: During VoLTE calls, audio stream is processed in Modem and
164                     AP can goto sleep.
165                     * Session are configured and ready in this state to handle handover from VoLTE
166                     to WiFi.
167                 }
168             }*/
169         } catch (Exception e) {
170             Log.e(TAG, "Exception in manageWakeLockOnMediaDirectionUpdate:" + e.getMessage());
171             e.printStackTrace();
172         }
173     }
174 
dump(PrintWriter writer)175     void dump(PrintWriter writer) {
176         writer.println("\n--- WakeLockManager ---\n");
177         writer.println(mWakeLock.toString());
178         writer.println("Sessions holding wakelock:\n\tCount: " + mWakeLockAcquiredSessions.size());
179         Iterator<Integer> iterator = mWakeLockAcquiredSessions.iterator();
180         while (iterator.hasNext()) {
181             writer.println("\tSession Id:" + iterator.next());
182         }
183         writer.println("\n--- END : WakeLockManager ---\n");
184     }
185 }
186