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