1024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger/* 2024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger * Copyright (C) 2017 The Android Open Source Project 3024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger * 4024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger * Licensed under the Apache License, Version 2.0 (the "License"); 5024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger * you may not use this file except in compliance with the License. 6024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger * You may obtain a copy of the License at 7024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger * 8024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger * http://www.apache.org/licenses/LICENSE-2.0 9024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger * 10024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger * Unless required by applicable law or agreed to in writing, software 11024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger * distributed under the License is distributed on an "AS IS" BASIS, 12024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger * See the License for the specific language governing permissions and 14024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger * limitations under the License 15024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger */ 16024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger 17024aaf23881c142ba92194a001ac038253ae708eBrad Ebingerpackage android.telephony.ims.feature; 18024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger 191639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebingerimport android.annotation.IntDef; 20e0a7345c6a353411c73f7f45875478684472a91cBrad Ebingerimport android.content.Context; 21e0a7345c6a353411c73f7f45875478684472a91cBrad Ebingerimport android.content.Intent; 221639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebingerimport android.os.RemoteException; 23e0a7345c6a353411c73f7f45875478684472a91cBrad Ebingerimport android.telephony.SubscriptionManager; 241639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebingerimport android.util.Log; 251639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger 261639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebingerimport com.android.ims.internal.IImsFeatureStatusCallback; 271639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger 281639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebingerimport java.lang.annotation.Retention; 291639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebingerimport java.lang.annotation.RetentionPolicy; 301639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebingerimport java.util.ArrayList; 3113a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebingerimport java.util.Collections; 3213a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebingerimport java.util.Iterator; 331639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebingerimport java.util.List; 3413a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebingerimport java.util.Set; 3513a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebingerimport java.util.WeakHashMap; 361639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger 37024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger/** 38024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger * Base class for all IMS features that are supported by the framework. 39024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger * @hide 40024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger */ 411639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebingerpublic abstract class ImsFeature { 421639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger 431639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger private static final String LOG_TAG = "ImsFeature"; 44024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger 45e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger /** 46e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger * Action to broadcast when ImsService is up. 47e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger * Internal use only. 48e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger * Only defined here separately compatibility purposes with the old ImsService. 49e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger * @hide 50e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger */ 51e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger public static final String ACTION_IMS_SERVICE_UP = 52e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger "com.android.ims.IMS_SERVICE_UP"; 53e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger 54e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger /** 55e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger * Action to broadcast when ImsService is down. 56e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger * Internal use only. 57e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger * Only defined here separately for compatibility purposes with the old ImsService. 58e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger * @hide 59e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger */ 60e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger public static final String ACTION_IMS_SERVICE_DOWN = 61e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger "com.android.ims.IMS_SERVICE_DOWN"; 62e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger 63e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger /** 64e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger * Part of the ACTION_IMS_SERVICE_UP or _DOWN intents. 65e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger * A long value; the phone ID corresponding to the IMS service coming up or down. 66e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger * Only defined here separately for compatibility purposes with the old ImsService. 67e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger * @hide 68e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger */ 69e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger public static final String EXTRA_PHONE_ID = "android:phone_id"; 70e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger 71024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger // Invalid feature value 72024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger public static final int INVALID = -1; 731639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger // ImsFeatures that are defined in the Manifests. Ensure that these values match the previously 741639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger // defined values in ImsServiceClass for compatibility purposes. 75024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger public static final int EMERGENCY_MMTEL = 0; 76024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger public static final int MMTEL = 1; 77024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger public static final int RCS = 2; 78024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger // Total number of features defined 79024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger public static final int MAX = 3; 801639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger 811639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger // Integer values defining the state of the ImsFeature at any time. 821639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger @IntDef(flag = true, 831639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger value = { 841639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger STATE_NOT_AVAILABLE, 851639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger STATE_INITIALIZING, 861639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger STATE_READY, 871639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger }) 881639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger @Retention(RetentionPolicy.SOURCE) 891639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger public @interface ImsState {} 901639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger public static final int STATE_NOT_AVAILABLE = 0; 911639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger public static final int STATE_INITIALIZING = 1; 921639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger public static final int STATE_READY = 2; 931639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger 941639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger private List<INotifyFeatureRemoved> mRemovedListeners = new ArrayList<>(); 9513a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger private final Set<IImsFeatureStatusCallback> mStatusCallbacks = Collections.newSetFromMap( 9613a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger new WeakHashMap<IImsFeatureStatusCallback, Boolean>()); 971639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger private @ImsState int mState = STATE_NOT_AVAILABLE; 98e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; 99e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger private Context mContext; 1001639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger 1011639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger public interface INotifyFeatureRemoved { 1021639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger void onFeatureRemoved(int slotId); 1031639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger } 1041639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger 105e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger public void setContext(Context context) { 106e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger mContext = context; 107e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger } 108e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger 109e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger public void setSlotId(int slotId) { 110e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger mSlotId = slotId; 111e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger } 112e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger 1131639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger public void addFeatureRemovedListener(INotifyFeatureRemoved listener) { 1141639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger synchronized (mRemovedListeners) { 1151639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger mRemovedListeners.add(listener); 1161639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger } 1171639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger } 1181639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger 1191639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger public void removeFeatureRemovedListener(INotifyFeatureRemoved listener) { 1201639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger synchronized (mRemovedListeners) { 1211639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger mRemovedListeners.remove(listener); 1221639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger } 1231639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger } 1241639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger 1251639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger // Not final for testing. 1261639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger public void notifyFeatureRemoved(int slotId) { 1271639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger synchronized (mRemovedListeners) { 1281639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger mRemovedListeners.forEach(l -> l.onFeatureRemoved(slotId)); 1291639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger onFeatureRemoved(); 1301639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger } 1311639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger } 1321639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger 1331639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger public int getFeatureState() { 1341639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger return mState; 1351639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger } 1361639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger 1371639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger protected final void setFeatureState(@ImsState int state) { 1381639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger if (mState != state) { 1391639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger mState = state; 1401639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger notifyFeatureState(state); 1411639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger } 1421639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger } 1431639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger 14413a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger public void addImsFeatureStatusCallback(IImsFeatureStatusCallback c) { 14513a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger if (c == null) { 14613a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger return; 14713a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger } 14813a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger try { 14913a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger // If we have just connected, send queued status. 15013a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger c.notifyImsFeatureStatus(mState); 15113a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger // Add the callback if the callback completes successfully without a RemoteException. 15213a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger synchronized (mStatusCallbacks) { 15313a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger mStatusCallbacks.add(c); 15413a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger } 15513a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger } catch (RemoteException e) { 15613a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage()); 15713a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger } 15813a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger } 15913a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger 16013a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger public void removeImsFeatureStatusCallback(IImsFeatureStatusCallback c) { 16113a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger if (c == null) { 16213a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger return; 16313a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger } 16413a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger synchronized (mStatusCallbacks) { 16513a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger mStatusCallbacks.remove(c); 16613a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger } 1671639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger } 1681639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger 1691639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger /** 1701639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger * Internal method called by ImsFeature when setFeatureState has changed. 1711639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger * @param state 1721639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger */ 1731639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger private void notifyFeatureState(@ImsState int state) { 17413a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger synchronized (mStatusCallbacks) { 17513a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger for (Iterator<IImsFeatureStatusCallback> iter = mStatusCallbacks.iterator(); 17613a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger iter.hasNext(); ) { 17713a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger IImsFeatureStatusCallback callback = iter.next(); 17813a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger try { 17913a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger Log.i(LOG_TAG, "notifying ImsFeatureState=" + state); 18013a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger callback.notifyImsFeatureStatus(state); 18113a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger } catch (RemoteException e) { 18213a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger // remove if the callback is no longer alive. 18313a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger iter.remove(); 18413a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage()); 18513a64f078ad4a034b4f851391ed759c8f3ae6a16Brad Ebinger } 1861639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger } 1871639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger } 188e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger sendImsServiceIntent(state); 189e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger } 190e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger 191e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger /** 192e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger * Provide backwards compatibility using deprecated service UP/DOWN intents. 193e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger */ 194e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger private void sendImsServiceIntent(@ImsState int state) { 195e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger if(mContext == null || mSlotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { 196e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger return; 197e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger } 198e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger Intent intent; 199e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger switch (state) { 200e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger case ImsFeature.STATE_NOT_AVAILABLE: 201e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger case ImsFeature.STATE_INITIALIZING: 202e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger intent = new Intent(ACTION_IMS_SERVICE_DOWN); 203e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger break; 204e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger case ImsFeature.STATE_READY: 205e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger intent = new Intent(ACTION_IMS_SERVICE_UP); 206e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger break; 207e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger default: 208e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger intent = new Intent(ACTION_IMS_SERVICE_DOWN); 209e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger } 210e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger intent.putExtra(EXTRA_PHONE_ID, mSlotId); 211e0a7345c6a353411c73f7f45875478684472a91cBrad Ebinger mContext.sendBroadcast(intent); 2121639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger } 2131639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger 2141639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger /** 2151639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger * Called when the feature is being removed and must be cleaned up. 2161639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger */ 2171639c21be6e7cd7699db4080fcf2ccc5cb2006e6Brad Ebinger public abstract void onFeatureRemoved(); 218024aaf23881c142ba92194a001ac038253ae708eBrad Ebinger} 219