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