1 /* 2 * Copyright (C) 2017 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.telephony.ims.feature; 18 19 import android.annotation.IntDef; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.os.RemoteException; 23 import android.telephony.SubscriptionManager; 24 import android.util.Log; 25 26 import com.android.ims.internal.IImsFeatureStatusCallback; 27 28 import java.lang.annotation.Retention; 29 import java.lang.annotation.RetentionPolicy; 30 import java.util.ArrayList; 31 import java.util.Collections; 32 import java.util.Iterator; 33 import java.util.List; 34 import java.util.Set; 35 import java.util.WeakHashMap; 36 37 /** 38 * Base class for all IMS features that are supported by the framework. 39 * @hide 40 */ 41 public abstract class ImsFeature { 42 43 private static final String LOG_TAG = "ImsFeature"; 44 45 /** 46 * Action to broadcast when ImsService is up. 47 * Internal use only. 48 * Only defined here separately compatibility purposes with the old ImsService. 49 * @hide 50 */ 51 public static final String ACTION_IMS_SERVICE_UP = 52 "com.android.ims.IMS_SERVICE_UP"; 53 54 /** 55 * Action to broadcast when ImsService is down. 56 * Internal use only. 57 * Only defined here separately for compatibility purposes with the old ImsService. 58 * @hide 59 */ 60 public static final String ACTION_IMS_SERVICE_DOWN = 61 "com.android.ims.IMS_SERVICE_DOWN"; 62 63 /** 64 * Part of the ACTION_IMS_SERVICE_UP or _DOWN intents. 65 * A long value; the phone ID corresponding to the IMS service coming up or down. 66 * Only defined here separately for compatibility purposes with the old ImsService. 67 * @hide 68 */ 69 public static final String EXTRA_PHONE_ID = "android:phone_id"; 70 71 // Invalid feature value 72 public static final int INVALID = -1; 73 // ImsFeatures that are defined in the Manifests. Ensure that these values match the previously 74 // defined values in ImsServiceClass for compatibility purposes. 75 public static final int EMERGENCY_MMTEL = 0; 76 public static final int MMTEL = 1; 77 public static final int RCS = 2; 78 // Total number of features defined 79 public static final int MAX = 3; 80 81 // Integer values defining the state of the ImsFeature at any time. 82 @IntDef(flag = true, 83 value = { 84 STATE_NOT_AVAILABLE, 85 STATE_INITIALIZING, 86 STATE_READY, 87 }) 88 @Retention(RetentionPolicy.SOURCE) 89 public @interface ImsState {} 90 public static final int STATE_NOT_AVAILABLE = 0; 91 public static final int STATE_INITIALIZING = 1; 92 public static final int STATE_READY = 2; 93 94 private List<INotifyFeatureRemoved> mRemovedListeners = new ArrayList<>(); 95 private final Set<IImsFeatureStatusCallback> mStatusCallbacks = Collections.newSetFromMap( 96 new WeakHashMap<IImsFeatureStatusCallback, Boolean>()); 97 private @ImsState int mState = STATE_NOT_AVAILABLE; 98 private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; 99 private Context mContext; 100 101 public interface INotifyFeatureRemoved { onFeatureRemoved(int slotId)102 void onFeatureRemoved(int slotId); 103 } 104 setContext(Context context)105 public void setContext(Context context) { 106 mContext = context; 107 } 108 setSlotId(int slotId)109 public void setSlotId(int slotId) { 110 mSlotId = slotId; 111 } 112 addFeatureRemovedListener(INotifyFeatureRemoved listener)113 public void addFeatureRemovedListener(INotifyFeatureRemoved listener) { 114 synchronized (mRemovedListeners) { 115 mRemovedListeners.add(listener); 116 } 117 } 118 removeFeatureRemovedListener(INotifyFeatureRemoved listener)119 public void removeFeatureRemovedListener(INotifyFeatureRemoved listener) { 120 synchronized (mRemovedListeners) { 121 mRemovedListeners.remove(listener); 122 } 123 } 124 125 // Not final for testing. notifyFeatureRemoved(int slotId)126 public void notifyFeatureRemoved(int slotId) { 127 synchronized (mRemovedListeners) { 128 mRemovedListeners.forEach(l -> l.onFeatureRemoved(slotId)); 129 onFeatureRemoved(); 130 } 131 } 132 getFeatureState()133 public int getFeatureState() { 134 return mState; 135 } 136 setFeatureState(@msState int state)137 protected final void setFeatureState(@ImsState int state) { 138 if (mState != state) { 139 mState = state; 140 notifyFeatureState(state); 141 } 142 } 143 addImsFeatureStatusCallback(IImsFeatureStatusCallback c)144 public void addImsFeatureStatusCallback(IImsFeatureStatusCallback c) { 145 if (c == null) { 146 return; 147 } 148 try { 149 // If we have just connected, send queued status. 150 c.notifyImsFeatureStatus(mState); 151 // Add the callback if the callback completes successfully without a RemoteException. 152 synchronized (mStatusCallbacks) { 153 mStatusCallbacks.add(c); 154 } 155 } catch (RemoteException e) { 156 Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage()); 157 } 158 } 159 removeImsFeatureStatusCallback(IImsFeatureStatusCallback c)160 public void removeImsFeatureStatusCallback(IImsFeatureStatusCallback c) { 161 if (c == null) { 162 return; 163 } 164 synchronized (mStatusCallbacks) { 165 mStatusCallbacks.remove(c); 166 } 167 } 168 169 /** 170 * Internal method called by ImsFeature when setFeatureState has changed. 171 * @param state 172 */ notifyFeatureState(@msState int state)173 private void notifyFeatureState(@ImsState int state) { 174 synchronized (mStatusCallbacks) { 175 for (Iterator<IImsFeatureStatusCallback> iter = mStatusCallbacks.iterator(); 176 iter.hasNext(); ) { 177 IImsFeatureStatusCallback callback = iter.next(); 178 try { 179 Log.i(LOG_TAG, "notifying ImsFeatureState=" + state); 180 callback.notifyImsFeatureStatus(state); 181 } catch (RemoteException e) { 182 // remove if the callback is no longer alive. 183 iter.remove(); 184 Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage()); 185 } 186 } 187 } 188 sendImsServiceIntent(state); 189 } 190 191 /** 192 * Provide backwards compatibility using deprecated service UP/DOWN intents. 193 */ sendImsServiceIntent(@msState int state)194 private void sendImsServiceIntent(@ImsState int state) { 195 if(mContext == null || mSlotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { 196 return; 197 } 198 Intent intent; 199 switch (state) { 200 case ImsFeature.STATE_NOT_AVAILABLE: 201 case ImsFeature.STATE_INITIALIZING: 202 intent = new Intent(ACTION_IMS_SERVICE_DOWN); 203 break; 204 case ImsFeature.STATE_READY: 205 intent = new Intent(ACTION_IMS_SERVICE_UP); 206 break; 207 default: 208 intent = new Intent(ACTION_IMS_SERVICE_DOWN); 209 } 210 intent.putExtra(EXTRA_PHONE_ID, mSlotId); 211 mContext.sendBroadcast(intent); 212 } 213 214 /** 215 * Called when the feature is being removed and must be cleaned up. 216 */ onFeatureRemoved()217 public abstract void onFeatureRemoved(); 218 } 219