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.Log;
20 import android.telecom.VideoProfile;
21 import android.telephony.ims.ImsVideoCallProvider;
22 import android.util.ArraySet;
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     /**
55      * Tracks the current sources of pause requests.
56      */
57     private Set<Integer> mPauseRequests = new ArraySet<Integer>(2);
58 
59     /**
60      * Lock for the {@link #mPauseRequests} {@link ArraySet}.
61      */
62     private Object mPauseRequestsLock = new Object();
63 
64     /**
65      * Tracks a request to pause the video for a source (see {@link #SOURCE_DATA_ENABLED},
66      * {@link #SOURCE_INCALL}) and determines whether a pause request should be issued to the
67      * video provider.
68      *
69      * We want to issue a pause request to the provider when we receive the first request
70      * to pause via any source and we're not already paused.
71      *
72      * @param source The source of the pause request.
73      * @return {@code true} if a pause should be issued to the
74      *      {@link ImsVideoCallProvider}, {@code false} otherwise.
75      */
shouldPauseVideoFor(int source)76     public boolean shouldPauseVideoFor(int source) {
77         synchronized (mPauseRequestsLock) {
78             boolean wasPaused = isPaused();
79             mPauseRequests.add(source);
80 
81             if (!wasPaused) {
82                 Log.i(this, "shouldPauseVideoFor: source=%s, pendingRequests=%s - should pause",
83                         sourceToString(source), sourcesToString(mPauseRequests));
84                 // There were previously no pause requests, but there is one now, so pause.
85                 return true;
86             } else {
87                 Log.i(this, "shouldPauseVideoFor: source=%s, pendingRequests=%s - already paused",
88                         sourceToString(source), sourcesToString(mPauseRequests));
89                 // There were already pause requests, so no need to re-pause.
90                 return false;
91             }
92         }
93     }
94 
95     /**
96      * Tracks a request to resume the video for a source (see {@link #SOURCE_DATA_ENABLED},
97      * {@link #SOURCE_INCALL}) and determines whether a resume request should be issued to the
98      * video provider.
99      *
100      * We want to issue a resume request to the provider when we have issued a corresponding
101      * resume for each previously issued pause.
102      *
103      * @param source The source of the resume request.
104      * @return {@code true} if a resume should be issued to the
105      *      {@link ImsVideoCallProvider}, {@code false} otherwise.
106      */
shouldResumeVideoFor(int source)107     public boolean shouldResumeVideoFor(int source) {
108         synchronized (mPauseRequestsLock) {
109             boolean wasPaused = isPaused();
110             mPauseRequests.remove(source);
111             boolean isPaused = isPaused();
112 
113             if (wasPaused && !isPaused) {
114                 Log.i(this, "shouldResumeVideoFor: source=%s, pendingRequests=%s - should resume",
115                         sourceToString(source), sourcesToString(mPauseRequests));
116                 // This was the last pause request, so resume video.
117                 return true;
118             } else if (wasPaused && isPaused) {
119                 Log.i(this, "shouldResumeVideoFor: source=%s, pendingRequests=%s - stay paused",
120                         sourceToString(source), sourcesToString(mPauseRequests));
121                 // There are still pending pause requests, so don't resume.
122                 return false;
123             } else {
124                 Log.i(this, "shouldResumeVideoFor: source=%s, pendingRequests=%s - not paused",
125                         sourceToString(source), sourcesToString(mPauseRequests));
126                 // Although there are no pending pause requests, it is possible that we cleared the
127                 // pause tracker because the video state reported we're un-paused.  In this case it
128                 // is benign to just allow the resume request to be sent since it'll have no effect.
129                 // Re-writing it to squelch the resume would end up causing it to be a pause
130                 // request, which is bad.
131                 return true;
132             }
133         }
134     }
135 
136     /**
137      * @return {@code true} if the video should be paused, {@code false} otherwise.
138      */
isPaused()139     public boolean isPaused() {
140         synchronized (mPauseRequestsLock) {
141             return !mPauseRequests.isEmpty();
142         }
143     }
144 
145     /**
146      * @param source the source of the pause.
147      * @return {@code true} if the specified source initiated a pause request and the video is
148      *      currently paused, {@code false} otherwise.
149      */
wasVideoPausedFromSource(int source)150     public boolean wasVideoPausedFromSource(int source) {
151         synchronized (mPauseRequestsLock) {
152             return mPauseRequests.contains(source);
153         }
154     }
155 
156     /**
157      * Clears pending pause requests for the tracker.
158      */
clearPauseRequests()159     public void clearPauseRequests() {
160         synchronized (mPauseRequestsLock) {
161             mPauseRequests.clear();
162         }
163     }
164 
165     /**
166      * Returns a string equivalent of a {@code SOURCE_*} constant.
167      *
168      * @param source A {@code SOURCE_*} constant.
169      * @return String equivalent of the source.
170      */
sourceToString(int source)171     private String sourceToString(int source) {
172         switch (source) {
173             case SOURCE_DATA_ENABLED:
174                 return SOURCE_DATA_ENABLED_STR;
175             case SOURCE_INCALL:
176                 return SOURCE_INCALL_STR;
177         }
178         return "unknown";
179     }
180 
181     /**
182      * Returns a comma separated list of sources.
183      *
184      * @param sources The sources.
185      * @return Comma separated list of sources.
186      */
sourcesToString(Collection<Integer> sources)187     private String sourcesToString(Collection<Integer> sources) {
188         synchronized (mPauseRequestsLock) {
189             return sources.stream()
190                     .map(source -> sourceToString(source))
191                     .collect(Collectors.joining(", "));
192         }
193     }
194 }
195