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