/* * Copyright (c) 2015, Motorola Mobility LLC * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - Neither the name of Motorola Mobility nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MOTOROLA MOBILITY LLC BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ package com.android.service.ims; import java.util.List; import android.content.Intent; import android.content.IntentFilter; import android.os.IBinder; import android.os.RemoteException; import android.content.Context; import android.app.Service; import android.os.ServiceManager; import android.os.Handler; import android.database.ContentObserver; import android.content.BroadcastReceiver; import android.provider.Settings; import android.net.ConnectivityManager; import com.android.ims.ImsManager; import com.android.ims.ImsConnectionStateListener; import com.android.ims.ImsException; import android.telephony.SubscriptionManager; import android.telephony.ims.ImsReasonInfo; import com.android.ims.RcsManager.ResultCode; import com.android.ims.internal.IRcsService; import com.android.ims.IRcsPresenceListener; import com.android.ims.internal.IRcsPresence; import com.android.ims.internal.Logger; import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.TelephonyIntents; import com.android.service.ims.presence.PresencePublication; import com.android.service.ims.presence.PresenceSubscriber; public class RcsService extends Service{ /** * The logger */ private Logger logger = Logger.getLogger(this.getClass().getName()); private RcsStackAdaptor mRcsStackAdaptor = null; private PresencePublication mPublication = null; private PresenceSubscriber mSubscriber = null; private BroadcastReceiver mReceiver = null; @Override public void onCreate() { super.onCreate(); logger.debug("RcsService onCreate"); mRcsStackAdaptor = RcsStackAdaptor.getInstance(this); mPublication = new PresencePublication(mRcsStackAdaptor, this); mRcsStackAdaptor.getListener().setPresencePublication(mPublication); mSubscriber = new PresenceSubscriber(mRcsStackAdaptor, this); mRcsStackAdaptor.getListener().setPresenceSubscriber(mSubscriber); mPublication.setSubscriber(mSubscriber); ConnectivityManager cm = ConnectivityManager.from(this); if (cm != null) { boolean enabled = Settings.Global.getInt(getContentResolver(), Settings.Global.MOBILE_DATA, 1) == 1; logger.debug("Mobile data enabled status: " + (enabled ? "ON" : "OFF")); onMobileDataEnabled(enabled); } // TODO: support MSIM ServiceManager.addService("rcs", mBinder); mObserver = new MobileDataContentObserver(); getContentResolver().registerContentObserver( Settings.Global.getUriFor(Settings.Global.MOBILE_DATA), false, mObserver); mSiminfoSettingObserver = new SimInfoContentObserver(); getContentResolver().registerContentObserver( SubscriptionManager.CONTENT_URI, false, mSiminfoSettingObserver); mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { logger.print("onReceive intent=" + intent); if(ImsManager.ACTION_IMS_SERVICE_UP.equalsIgnoreCase( intent.getAction())){ handleImsServiceUp(); } else if(ImsManager.ACTION_IMS_SERVICE_DOWN.equalsIgnoreCase( intent.getAction())){ handleImsServiceDown(); } else if(TelephonyIntents.ACTION_SIM_STATE_CHANGED.equalsIgnoreCase( intent.getAction())) { String stateExtra = intent.getStringExtra( IccCardConstants.INTENT_KEY_ICC_STATE); handleSimStateChanged(stateExtra); } } }; IntentFilter statusFilter = new IntentFilter(); statusFilter.addAction(ImsManager.ACTION_IMS_SERVICE_UP); statusFilter.addAction(ImsManager.ACTION_IMS_SERVICE_DOWN); statusFilter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); registerReceiver(mReceiver, statusFilter); } public void handleImsServiceUp() { if(mPublication != null) { mPublication.handleImsServiceUp(); } registerImsConnectionStateListener(); } public void handleImsServiceDown() { if(mPublication != null) { mPublication.handleImsServiceDown(); } } public void handleSimStateChanged(String state) { if(IccCardConstants.INTENT_VALUE_ICC_LOADED.equalsIgnoreCase(state)) { // ImsManager depends on a loaded SIM to get the default Voice Registration. registerImsConnectionStateListener(); } } @Override public int onStartCommand(Intent intent, int flags, int startId) { logger.debug("RcsService onStartCommand"); return super.onStartCommand(intent, flags, startId); } /** * Cleans up when the service is destroyed */ @Override public void onDestroy() { getContentResolver().unregisterContentObserver(mObserver); getContentResolver().unregisterContentObserver(mSiminfoSettingObserver); if (mReceiver != null) { unregisterReceiver(mReceiver); mReceiver = null; } mRcsStackAdaptor.finish(); mPublication.finish(); mPublication = null; mSubscriber = null; logger.debug("RcsService onDestroy"); super.onDestroy(); } public PresencePublication getPublication() { return mPublication; } public PresenceSubscriber getPresenceSubscriber(){ return mSubscriber; } IRcsPresence.Stub mIRcsPresenceImpl = new IRcsPresence.Stub(){ /** * Asyncrhonously request the latest capability for a given contact list. * The result will be saved to DB directly if the contactNumber can be found in DB. * And then send intent com.android.ims.presence.CAPABILITY_STATE_CHANGED to notify it. * @param contactsNumber the contact list which will request capability. * Currently only support phone number. * @param listener the listener to get the response. * @return the resultCode which is defined by ResultCode. * @note framework uses only. * @hide */ public int requestCapability(List contactsNumber, IRcsPresenceListener listener){ logger.debug("calling requestCapability"); if(mSubscriber == null){ logger.debug("requestCapability, mPresenceSubscriber == null"); return ResultCode.ERROR_SERVICE_NOT_AVAILABLE; } return mSubscriber.requestCapability(contactsNumber, listener); } /** * Asyncrhonously request the latest presence for a given contact. * The result will be saved to DB directly if it can be found in DB. And then send intent * com.android.ims.presence.AVAILABILITY_STATE_CHANGED to notify it. * @param contactNumber the contact which will request available. * Currently only support phone number. * @param listener the listener to get the response. * @return the resultCode which is defined by ResultCode. * @note framework uses only. * @hide */ public int requestAvailability(String contactNumber, IRcsPresenceListener listener){ if(mSubscriber == null){ logger.error("requestAvailability, mPresenceSubscriber is null"); return ResultCode.ERROR_SERVICE_NOT_AVAILABLE; } // check availability cache (in RAM). return mSubscriber.requestAvailability(contactNumber, listener, false); } /** * Same as requestAvailability. but requestAvailability will consider throttle to avoid too * fast call. Which means it will not send the request to network in next 60s for the same * request. * The error code SUBSCRIBE_TOO_FREQUENTLY will be returned under the case. * But for this funcation it will always send the request to network. * * @see IRcsPresenceListener * @see RcsManager.ResultCode * @see ResultCode.SUBSCRIBE_TOO_FREQUENTLY */ public int requestAvailabilityNoThrottle(String contactNumber, IRcsPresenceListener listener) { if(mSubscriber == null){ logger.error("requestAvailabilityNoThrottle, mPresenceSubscriber is null"); return ResultCode.ERROR_SERVICE_NOT_AVAILABLE; } // check availability cache (in RAM). return mSubscriber.requestAvailability(contactNumber, listener, true); } public int getPublishState() throws RemoteException { return mPublication.getPublishState(); } }; @Override public IBinder onBind(Intent arg0) { return mBinder; } /** * Receives notifications when Mobile data is enabled or disabled. */ private class MobileDataContentObserver extends ContentObserver { public MobileDataContentObserver() { super(new Handler()); } @Override public void onChange(final boolean selfChange) { boolean enabled = Settings.Global.getInt(getContentResolver(), Settings.Global.MOBILE_DATA, 1) == 1; logger.debug("Mobile data enabled status: " + (enabled ? "ON" : "OFF")); onMobileDataEnabled(enabled); } } /** Observer to get notified when Mobile data enabled status changes */ private MobileDataContentObserver mObserver; private void onMobileDataEnabled(final boolean enabled) { logger.debug("Enter onMobileDataEnabled: " + enabled); Thread thread = new Thread(new Runnable() { @Override public void run() { try{ if(mPublication != null){ mPublication.onMobileDataChanged(enabled); return; } }catch(Exception e){ logger.error("Exception onMobileDataEnabled:", e); } } }, "onMobileDataEnabled thread"); thread.start(); } private SimInfoContentObserver mSiminfoSettingObserver; /** * Receives notifications when the TelephonyProvider is changed. */ private class SimInfoContentObserver extends ContentObserver { public SimInfoContentObserver() { super(new Handler()); } @Override public void onChange(final boolean selfChange) { ImsManager imsManager = ImsManager.getInstance(RcsService.this, SubscriptionManager.getDefaultVoicePhoneId()); if (imsManager == null) { return; } boolean enabled = imsManager.isVtEnabledByUser(); logger.debug("vt enabled status: " + (enabled ? "ON" : "OFF")); onVtEnabled(enabled); } } private void onVtEnabled(boolean enabled) { if(mPublication != null){ mPublication.onVtEnabled(enabled); } } private final IRcsService.Stub mBinder = new IRcsService.Stub() { /** * return true if the rcs service is ready for use. */ public boolean isRcsServiceAvailable(){ logger.debug("calling isRcsServiceAvailable"); if(mRcsStackAdaptor == null){ return false; } return mRcsStackAdaptor.isImsEnableState(); } /** * Gets the presence interface. * * @see IRcsPresence */ public IRcsPresence getRcsPresenceInterface(){ return mIRcsPresenceImpl; } }; void registerImsConnectionStateListener() { try { ImsManager imsManager = ImsManager.getInstance(this, SubscriptionManager.getDefaultVoicePhoneId()); if (imsManager != null) { imsManager.addRegistrationListener(mImsConnectionStateListener); } } catch (ImsException e) { logger.error("addRegistrationListener exception=", e); } } private ImsConnectionStateListener mImsConnectionStateListener = new ImsConnectionStateListener() { @Override public void onImsConnected(int imsRadioTech) { logger.debug("onImsConnected imsRadioTech=" + imsRadioTech); if(mRcsStackAdaptor != null) { mRcsStackAdaptor.checkSubService(); } if(mPublication != null) { mPublication.onImsConnected(); } } @Override public void onImsDisconnected(ImsReasonInfo imsReasonInfo) { logger.debug("onImsDisconnected"); if(mPublication != null) { mPublication.onImsDisconnected(); } } @Override public void onFeatureCapabilityChanged(final int serviceClass, final int[] enabledFeatures, final int[] disabledFeatures) { logger.debug("onFeatureCapabilityChanged"); if(mPublication != null) { mPublication.onFeatureCapabilityChanged(serviceClass, enabledFeatures, disabledFeatures); } } }; }