Home | History | Annotate | Download | only in telecom
      1 /*
      2  * Copyright (C) 2013 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 android.telecom;
     18 
     19 import android.annotation.SystemApi;
     20 import android.util.ArrayMap;
     21 
     22 import java.util.Collections;
     23 import java.util.List;
     24 import java.util.Map;
     25 import java.util.Objects;
     26 import java.util.concurrent.CopyOnWriteArrayList;
     27 
     28 /**
     29  * A unified virtual device providing a means of voice (and other) communication on a device.
     30  *
     31  * {@hide}
     32  */
     33 @SystemApi
     34 public final class Phone {
     35 
     36     public abstract static class Listener {
     37         /**
     38          * Called when the audio state changes.
     39          *
     40          * @param phone The {@code Phone} calling this method.
     41          * @param audioState The new {@link AudioState}.
     42          */
     43         public void onAudioStateChanged(Phone phone, AudioState audioState) { }
     44 
     45         /**
     46          * Called to bring the in-call screen to the foreground. The in-call experience should
     47          * respond immediately by coming to the foreground to inform the user of the state of
     48          * ongoing {@code Call}s.
     49          *
     50          * @param phone The {@code Phone} calling this method.
     51          * @param showDialpad If true, put up the dialpad when the screen is shown.
     52          */
     53         public void onBringToForeground(Phone phone, boolean showDialpad) { }
     54 
     55         /**
     56          * Called when a {@code Call} has been added to this in-call session. The in-call user
     57          * experience should add necessary state listeners to the specified {@code Call} and
     58          * immediately start to show the user information about the existence
     59          * and nature of this {@code Call}. Subsequent invocations of {@link #getCalls()} will
     60          * include this {@code Call}.
     61          *
     62          * @param phone The {@code Phone} calling this method.
     63          * @param call A newly added {@code Call}.
     64          */
     65         public void onCallAdded(Phone phone, Call call) { }
     66 
     67         /**
     68          * Called when a {@code Call} has been removed from this in-call session. The in-call user
     69          * experience should remove any state listeners from the specified {@code Call} and
     70          * immediately stop displaying any information about this {@code Call}.
     71          * Subsequent invocations of {@link #getCalls()} will no longer include this {@code Call}.
     72          *
     73          * @param phone The {@code Phone} calling this method.
     74          * @param call A newly removed {@code Call}.
     75          */
     76         public void onCallRemoved(Phone phone, Call call) { }
     77     }
     78 
     79     // A Map allows us to track each Call by its Telecom-specified call ID
     80     private final Map<String, Call> mCallByTelecomCallId = new ArrayMap<>();
     81 
     82     // A List allows us to keep the Calls in a stable iteration order so that casually developed
     83     // user interface components do not incur any spurious jank
     84     private final List<Call> mCalls = new CopyOnWriteArrayList<>();
     85 
     86     // An unmodifiable view of the above List can be safely shared with subclass implementations
     87     private final List<Call> mUnmodifiableCalls = Collections.unmodifiableList(mCalls);
     88 
     89     private final InCallAdapter mInCallAdapter;
     90 
     91     private AudioState mAudioState;
     92 
     93     private final List<Listener> mListeners = new CopyOnWriteArrayList<>();
     94 
     95     /** {@hide} */
     96     Phone(InCallAdapter adapter) {
     97         mInCallAdapter = adapter;
     98     }
     99 
    100     /** {@hide} */
    101     final void internalAddCall(ParcelableCall parcelableCall) {
    102         Call call = new Call(this, parcelableCall.getId(), mInCallAdapter);
    103         mCallByTelecomCallId.put(parcelableCall.getId(), call);
    104         mCalls.add(call);
    105         checkCallTree(parcelableCall);
    106         call.internalUpdate(parcelableCall, mCallByTelecomCallId);
    107         fireCallAdded(call);
    108      }
    109 
    110     /** {@hide} */
    111     final void internalRemoveCall(Call call) {
    112         mCallByTelecomCallId.remove(call.internalGetCallId());
    113         mCalls.remove(call);
    114         fireCallRemoved(call);
    115     }
    116 
    117     /** {@hide} */
    118     final void internalUpdateCall(ParcelableCall parcelableCall) {
    119          Call call = mCallByTelecomCallId.get(parcelableCall.getId());
    120          if (call != null) {
    121              checkCallTree(parcelableCall);
    122              call.internalUpdate(parcelableCall, mCallByTelecomCallId);
    123          }
    124      }
    125 
    126     /** {@hide} */
    127     final void internalSetPostDialWait(String telecomId, String remaining) {
    128         Call call = mCallByTelecomCallId.get(telecomId);
    129         if (call != null) {
    130             call.internalSetPostDialWait(remaining);
    131         }
    132     }
    133 
    134     /** {@hide} */
    135     final void internalAudioStateChanged(AudioState audioState) {
    136         if (!Objects.equals(mAudioState, audioState)) {
    137             mAudioState = audioState;
    138             fireAudioStateChanged(audioState);
    139         }
    140     }
    141 
    142     /** {@hide} */
    143     final Call internalGetCallByTelecomId(String telecomId) {
    144         return mCallByTelecomCallId.get(telecomId);
    145     }
    146 
    147     /** {@hide} */
    148     final void internalBringToForeground(boolean showDialpad) {
    149         fireBringToForeground(showDialpad);
    150     }
    151 
    152     /**
    153      * Called to destroy the phone and cleanup any lingering calls.
    154      * @hide
    155      */
    156     final void destroy() {
    157         for (Call call : mCalls) {
    158             if (call.getState() != Call.STATE_DISCONNECTED) {
    159                 call.internalSetDisconnected();
    160             }
    161         }
    162     }
    163 
    164     /**
    165      * Adds a listener to this {@code Phone}.
    166      *
    167      * @param listener A {@code Listener} object.
    168      */
    169     public final void addListener(Listener listener) {
    170         mListeners.add(listener);
    171     }
    172 
    173     /**
    174      * Removes a listener from this {@code Phone}.
    175      *
    176      * @param listener A {@code Listener} object.
    177      */
    178     public final void removeListener(Listener listener) {
    179         if (listener != null) {
    180             mListeners.remove(listener);
    181         }
    182     }
    183 
    184     /**
    185      * Obtains the current list of {@code Call}s to be displayed by this in-call experience.
    186      *
    187      * @return A list of the relevant {@code Call}s.
    188      */
    189     public final List<Call> getCalls() {
    190         return mUnmodifiableCalls;
    191     }
    192 
    193     /**
    194      * Sets the microphone mute state. When this request is honored, there will be change to
    195      * the {@link #getAudioState()}.
    196      *
    197      * @param state {@code true} if the microphone should be muted; {@code false} otherwise.
    198      */
    199     public final void setMuted(boolean state) {
    200         mInCallAdapter.mute(state);
    201     }
    202 
    203     /**
    204      * Sets the audio route (speaker, bluetooth, etc...).  When this request is honored, there will
    205      * be change to the {@link #getAudioState()}.
    206      *
    207      * @param route The audio route to use.
    208      */
    209     public final void setAudioRoute(int route) {
    210         mInCallAdapter.setAudioRoute(route);
    211     }
    212 
    213     /**
    214      * Turns the proximity sensor on. When this request is made, the proximity sensor will
    215      * become active, and the touch screen and display will be turned off when the user's face
    216      * is detected to be in close proximity to the screen. This operation is a no-op on devices
    217      * that do not have a proximity sensor.
    218      */
    219     public final void setProximitySensorOn() {
    220         mInCallAdapter.turnProximitySensorOn();
    221     }
    222 
    223     /**
    224      * Turns the proximity sensor off. When this request is made, the proximity sensor will
    225      * become inactive, and no longer affect the touch screen and display. This operation is a
    226      * no-op on devices that do not have a proximity sensor.
    227      *
    228      * @param screenOnImmediately If true, the screen will be turned on immediately if it was
    229      * previously off. Otherwise, the screen will only be turned on after the proximity sensor
    230      * is no longer triggered.
    231      */
    232     public final void setProximitySensorOff(boolean screenOnImmediately) {
    233         mInCallAdapter.turnProximitySensorOff(screenOnImmediately);
    234     }
    235 
    236     /**
    237      * Obtains the current phone call audio state of the {@code Phone}.
    238      *
    239      * @return An object encapsulating the audio state.
    240      */
    241     public final AudioState getAudioState() {
    242         return mAudioState;
    243     }
    244 
    245     private void fireCallAdded(Call call) {
    246         for (Listener listener : mListeners) {
    247             listener.onCallAdded(this, call);
    248         }
    249     }
    250 
    251     private void fireCallRemoved(Call call) {
    252         for (Listener listener : mListeners) {
    253             listener.onCallRemoved(this, call);
    254         }
    255     }
    256 
    257     private void fireAudioStateChanged(AudioState audioState) {
    258         for (Listener listener : mListeners) {
    259             listener.onAudioStateChanged(this, audioState);
    260         }
    261     }
    262 
    263     private void fireBringToForeground(boolean showDialpad) {
    264         for (Listener listener : mListeners) {
    265             listener.onBringToForeground(this, showDialpad);
    266         }
    267     }
    268 
    269     private void checkCallTree(ParcelableCall parcelableCall) {
    270         if (parcelableCall.getParentCallId() != null &&
    271                 !mCallByTelecomCallId.containsKey(parcelableCall.getParentCallId())) {
    272             Log.wtf(this, "ParcelableCall %s has nonexistent parent %s",
    273                     parcelableCall.getId(), parcelableCall.getParentCallId());
    274         }
    275         if (parcelableCall.getChildCallIds() != null) {
    276             for (int i = 0; i < parcelableCall.getChildCallIds().size(); i++) {
    277                 if (!mCallByTelecomCallId.containsKey(parcelableCall.getChildCallIds().get(i))) {
    278                     Log.wtf(this, "ParcelableCall %s has nonexistent child %s",
    279                             parcelableCall.getId(), parcelableCall.getChildCallIds().get(i));
    280                 }
    281             }
    282         }
    283     }
    284 }
    285