Home | History | Annotate | Download | only in internal
      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      */
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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