1 /*
2  * Copyright (C) 2016 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.ims.internal;
18 
19 import android.telecom.VideoProfile;
20 import android.telephony.ims.ImsVideoCallProvider;
21 import android.util.ArraySet;
22 import android.util.Log;
23 
24 import java.util.Collection;
25 import java.util.Set;
26 import java.util.stream.Collectors;
27 
28 /**
29  * Used by an {@link ImsVideoCallProviderWrapper} to track requests to pause video from various
30  * sources.
31  *
32  * Requests to pause the video stream using the {@link VideoProfile#STATE_PAUSED} bit can come
33  * from both the {@link android.telecom.InCallService}, as well as via the
34  * {@link ImsVideoCallProviderWrapper#pauseVideo(int, int)} and
35  * {@link ImsVideoCallProviderWrapper#resumeVideo(int, int)} methods.  As a result, multiple sources
36  * can potentially pause or resume the video stream.
37  *
38  * This class is responsible for tracking any active requests to pause the video.
39  */
40 public class VideoPauseTracker {
41     /** The pause or resume request originated from an InCallService. */
42     public static final int SOURCE_INCALL = 1;
43 
44     /**
45      * The pause or resume request originated from a change to the data enabled state from the
46      * {@code ImsPhoneCallTracker#onDataEnabledChanged(boolean, int)} callback.  This happens when
47      * the user reaches their data limit or enables and disables data.
48      */
49     public static final int SOURCE_DATA_ENABLED = 2;
50 
51     private static final String SOURCE_INCALL_STR = "INCALL";
52     private static final String SOURCE_DATA_ENABLED_STR = "DATA_ENABLED";
53 
54     private static final String LOG_TAG = VideoPauseTracker.class.getSimpleName();
55 
56     /**
57      * Tracks the current sources of pause requests.
58      */
59     private Set<Integer> mPauseRequests = new ArraySet<Integer>(2);
60 
61     /**
62      * Lock for the {@link #mPauseRequests} {@link ArraySet}.
63      */
64     private Object mPauseRequestsLock = new Object();
65 
66     /**
67      * Tracks a request to pause the video for a source (see {@link #SOURCE_DATA_ENABLED},
68      * {@link #SOURCE_INCALL}) and determines whether a pause request should be issued to the
69      * video provider.
70      *
71      * We want to issue a pause request to the provider when we receive the first request
72      * to pause via any source and we're not already paused.
73      *
74      * @param source The source of the pause request.
75      * @return {@code true} if a pause should be issued to the
76      *      {@link ImsVideoCallProvider}, {@code false} otherwise.
77      */
shouldPauseVideoFor(int source)78     public boolean shouldPauseVideoFor(int source) {
79         synchronized (mPauseRequestsLock) {
80             boolean wasPaused = isPaused();
81             mPauseRequests.add(source);
82 
83             if (!wasPaused) {
84                 Log.i(LOG_TAG, String.format(
85                         "shouldPauseVideoFor: source=%s, pendingRequests=%s - should pause",
86                         sourceToString(source), sourcesToString(mPauseRequests)));
87                 // There were previously no pause requests, but there is one now, so pause.
88                 return true;
89             } else {
90                 Log.i(LOG_TAG, String.format(
91                         "shouldPauseVideoFor: source=%s, pendingRequests=%s - already paused",
92                         sourceToString(source), sourcesToString(mPauseRequests)));
93                 // There were already pause requests, so no need to re-pause.
94                 return false;
95             }
96         }
97     }
98 
99     /**
100      * Tracks a request to resume the video for a source (see {@link #SOURCE_DATA_ENABLED},
101      * {@link #SOURCE_INCALL}) and determines whether a resume request should be issued to the
102      * video provider.
103      *
104      * We want to issue a resume request to the provider when we have issued a corresponding
105      * resume for each previously issued pause.
106      *
107      * @param source The source of the resume request.
108      * @return {@code true} if a resume should be issued to the
109      *      {@link ImsVideoCallProvider}, {@code false} otherwise.
110      */
shouldResumeVideoFor(int source)111     public boolean shouldResumeVideoFor(int source) {
112         synchronized (mPauseRequestsLock) {
113             boolean wasPaused = isPaused();
114             mPauseRequests.remove(source);
115             boolean isPaused = isPaused();
116 
117             if (wasPaused && !isPaused) {
118                 Log.i(LOG_TAG, String.format(
119                         "shouldResumeVideoFor: source=%s, pendingRequests=%s - should resume",
120                         sourceToString(source), sourcesToString(mPauseRequests)));
121                 // This was the last pause request, so resume video.
122                 return true;
123             } else if (wasPaused && isPaused) {
124                 Log.i(LOG_TAG, String.format(
125                         "shouldResumeVideoFor: source=%s, pendingRequests=%s - stay paused",
126                         sourceToString(source), sourcesToString(mPauseRequests)));
127                 // There are still pending pause requests, so don't resume.
128                 return false;
129             } else {
130                 Log.i(LOG_TAG, String.format(
131                         "shouldResumeVideoFor: source=%s, pendingRequests=%s - not paused",
132                         sourceToString(source), sourcesToString(mPauseRequests)));
133                 // Although there are no pending pause requests, it is possible that we cleared the
134                 // pause tracker because the video state reported we're un-paused.  In this case it
135                 // is benign to just allow the resume request to be sent since it'll have no effect.
136                 // Re-writing it to squelch the resume would end up causing it to be a pause
137                 // request, which is bad.
138                 return true;
139             }
140         }
141     }
142 
143     /**
144      * @return {@code true} if the video should be paused, {@code false} otherwise.
145      */
isPaused()146     public boolean isPaused() {
147         synchronized (mPauseRequestsLock) {
148             return !mPauseRequests.isEmpty();
149         }
150     }
151 
152     /**
153      * @param source the source of the pause.
154      * @return {@code true} if the specified source initiated a pause request and the video is
155      *      currently paused, {@code false} otherwise.
156      */
wasVideoPausedFromSource(int source)157     public boolean wasVideoPausedFromSource(int source) {
158         synchronized (mPauseRequestsLock) {
159             return mPauseRequests.contains(source);
160         }
161     }
162 
163     /**
164      * Clears pending pause requests for the tracker.
165      */
clearPauseRequests()166     public void clearPauseRequests() {
167         synchronized (mPauseRequestsLock) {
168             mPauseRequests.clear();
169         }
170     }
171 
172     /**
173      * Returns a string equivalent of a {@code SOURCE_*} constant.
174      *
175      * @param source A {@code SOURCE_*} constant.
176      * @return String equivalent of the source.
177      */
sourceToString(int source)178     private String sourceToString(int source) {
179         switch (source) {
180             case SOURCE_DATA_ENABLED:
181                 return SOURCE_DATA_ENABLED_STR;
182             case SOURCE_INCALL:
183                 return SOURCE_INCALL_STR;
184         }
185         return "unknown";
186     }
187 
188     /**
189      * Returns a comma separated list of sources.
190      *
191      * @param sources The sources.
192      * @return Comma separated list of sources.
193      */
sourcesToString(Collection<Integer> sources)194     private String sourcesToString(Collection<Integer> sources) {
195         synchronized (mPauseRequestsLock) {
196             return sources.stream()
197                     .map(source -> sourceToString(source))
198                     .collect(Collectors.joining(", "));
199         }
200     }
201 }
202