diff options
author | Pavel Zhamaitsiak <pavelz@google.com> | 2016-05-02 13:52:24 -0700 |
---|---|---|
committer | Pavel Zhamaitsiak <pavelz@google.com> | 2016-05-02 13:52:24 -0700 |
commit | ac76c57e7a49d4f1431c751db3c810467a60ca48 (patch) | |
tree | 8a20ee971de05913567cde774b74e0021d12f9d1 /rcs | |
parent | cec5ed8b43d8fa5d93faa2ba954bb4992d954e94 (diff) | |
download | ims-ac76c57e7a49d4f1431c751db3c810467a60ca48.tar.gz |
Move rcs from frameworks/opt/net/ims to external/ims
Bug: 27811089
Change-Id: I4ac4948096373f1dfe119d21c7d0870ccdb061db
Diffstat (limited to 'rcs')
67 files changed, 15343 insertions, 0 deletions
diff --git a/rcs/Android.mk b/rcs/Android.mk new file mode 100644 index 0000000..7f1f7eb --- /dev/null +++ b/rcs/Android.mk @@ -0,0 +1,17 @@ +# Copyright 2013 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/rcs/presencepolling/Android.mk b/rcs/presencepolling/Android.mk new file mode 100644 index 0000000..f1f1471 --- /dev/null +++ b/rcs/presencepolling/Android.mk @@ -0,0 +1,44 @@ + # 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. + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_SRC_FILES += $(call all-java-files-under, ../../provider/java) + +LOCAL_JAVA_LIBRARIES := com.android.ims.rcsmanager \ + ims-common \ + telephony-common + +LOCAL_REQUIRED_MODULES := com.android.ims.rcsmanager + +LOCAL_PACKAGE_NAME := PresencePolling +LOCAL_CERTIFICATE := platform +#LOCAL_MODULE_TAGS := optional + +include $(BUILD_PACKAGE) + diff --git a/rcs/presencepolling/AndroidManifest.xml b/rcs/presencepolling/AndroidManifest.xml new file mode 100644 index 0000000..12db21a --- /dev/null +++ b/rcs/presencepolling/AndroidManifest.xml @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<!-- + * 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + package="com.android.service.ims.presence" + android:sharedUserId="android.uid.phone" + coreApp="true"> + + <uses-sdk android:minSdkVersion="19"/> + + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> + <uses-permission android:name="android.permission.BROADCAST_STICKY"/> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/> + <uses-permission android:name="android.permission.READ_CONTACTS"/> + <uses-permission android:name="android.permission.WRITE_CONTACTS"/> + <uses-permission android:name="android.permission.READ_PHONE_STATE"/> + <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> + <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> + <uses-permission android:name="android.permission.GET_ACCOUNTS" /> + <uses-permission android:name="com.android.rcs.eab.permission.READ_WRITE_EAB"/> + <uses-permission android:name="android.permission.READ_PROFILE"/> + <uses-permission android:name="com.android.ims.rcs.permission.STATUS_CHANGED"/> + <uses-permission android:name="com.android.ims.permission.PRESENCE_ACCESS"/> + + <application + android:label="@string/app_label" + android:singleUser="true" + android:process="com.android.ims.rcsservice"> + + <uses-library android:name="com.android.ims.rcsmanager" + android:required="true"/> + + <service + android:name=".PollingService" + android:excludeFromRecents="true" + android:singleUser="true" + android:permission="com.android.ims.permission.PRESENCE_ACCESS"> + </service> + + <receiver android:name=".DeviceBoot" androidprv:systemUserOnly="true"> + <intent-filter android:priority="103"> + <action android:name="com.android.ims.ACTION_PUBLISH_STATUS_CHANGED"/> + <action android:name="android.intent.action.BOOT_COMPLETED"/> + </intent-filter> + </receiver> + + <receiver android:name=".DeviceShutdown" androidprv:systemUserOnly="true"> + <intent-filter> + <action android:name="android.intent.action.ACTION_SHUTDOWN"/> + </intent-filter> + </receiver> + + <receiver android:name=".AlarmBroadcastReceiver" + androidprv:systemUserOnly="true" + android:permission="com.android.ims.permission.PRESENCE_ACCESS"> + <intent-filter> + <action android:name="com.android.service.ims.presence.periodical_capability_discovery"/> + <action android:name="com.android.service.ims.presence.capability_polling_retry"/> + <action android:name="android.provider.rcs.eab.EAB_NEW_CONTACT_INSERTED" /> + </intent-filter> + </receiver> + + <receiver android:name=".PresenceBroadcastReceiver" + androidprv:systemUserOnly="true" + android:permission="com.android.ims.permission.PRESENCE_ACCESS"> + <intent-filter> + <action android:name="com.android.ims.ACTION_PUBLISH_STATUS_CHANGED"/> + </intent-filter> + </receiver> + + <service android:name=".PersistService" + android:exported="false" + android:permission="com.android.ims.permission.PRESENCE_ACCESS"> + <intent-filter> + <action android:name="com.android.ims.ACTION_PRESENCE_CHANGED"/> + </intent-filter> + </service> + + <service + android:name="com.android.service.ims.presence.EABService" + android:enabled="true"> + </service> + + <provider + android:name=".EABProvider" + android:permission="com.android.rcs.eab.permission.READ_WRITE_EAB" + android:exported="true" + android:enabled="true" + android:authorities="com.android.rcs.eab" /> + </application> +</manifest> diff --git a/rcs/presencepolling/res/values/config.xml b/rcs/presencepolling/res/values/config.xml new file mode 100644 index 0000000..5d083a7 --- /dev/null +++ b/rcs/presencepolling/res/values/config.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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. +--> + +<resources> + <!-- Capability cache expiration in x seconds, default is 90 days. --> + <integer name="capability_cache_expiration">7776000</integer> + + <!-- Periodical capability poll in x seconds, default is 7 days. --> + <integer name="capability_poll_interval">604800</integer> + +</resources> diff --git a/rcs/presencepolling/res/values/strings.xml b/rcs/presencepolling/res/values/strings.xml new file mode 100644 index 0000000..053aca0 --- /dev/null +++ b/rcs/presencepolling/res/values/strings.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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. +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> +<string name="app_label">Presence</string> +<string name="ims_presence_permission">Presence</string> +<string name="ims_ims_permission_desc">Allow the application to poll presence information.</string> + +<string translatable="false" name="video_calling_contact_group">Video calling</string> +<string translatable="false" name="account_pretty_name">Video calling</string> + +</resources> + diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/AccountUtil.java b/rcs/presencepolling/src/com/android/service/ims/presence/AccountUtil.java new file mode 100644 index 0000000..0139f86 --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/AccountUtil.java @@ -0,0 +1,105 @@ +/* + * 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.presence; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.content.ContentResolver; +import android.content.Context; +import android.os.Bundle; +import android.provider.ContactsContract; +import com.android.ims.internal.Logger; + +public class AccountUtil { + private static Logger logger = Logger.getLogger("AccountUtil"); + + public static final String ACCOUNT_SETTING = "accountSetting"; + public static final String VTCALL_SETTING = "vtcall_enable"; // "vtCallSetting"; + public static final String PRESENCE_SETTING = "presence_enable";// "presenceSetting"; + public static final String ACCOUNT_TYPE = "com.android.rcs.eab.account"; + public static final String AUTHTOKEN_TYPE = "com.android.rcs.eab.account"; + public static final String ACCOUNT_NAME = " "; + + public static Account addRcsAccount(Context context) { + AccountManager mAccountManager = null; + mAccountManager = AccountManager.get(context); + Account[] vCallingAccounts = mAccountManager + .getAccountsByType(ACCOUNT_TYPE); + + if (vCallingAccounts.length == 0) { + logger.debug("Creating Video Calling Account"); + + Bundle bundle = new Bundle(); + bundle.putString("pretty_name", + context.getString(R.string.account_pretty_name)); + + Account vCallingAccount = new Account(ACCOUNT_NAME, ACCOUNT_TYPE); + if (!mAccountManager.addAccountExplicitly(vCallingAccount, null, + null)) { + logger.error("addRcsAccount() failed!"); + return null; + } + + ContentResolver.setIsSyncable(vCallingAccount, + ContactsContract.AUTHORITY, 0); + ContentResolver.setSyncAutomatically(vCallingAccount, + ContactsContract.AUTHORITY, false); + + logger.debug("Video Calling account created succesfuly"); + return vCallingAccount; + } else { + Account vCallingAccount = new Account(ACCOUNT_NAME, ACCOUNT_TYPE); + ContentResolver.setIsSyncable(vCallingAccount, + ContactsContract.AUTHORITY, 0); + + ContentResolver.setSyncAutomatically(vCallingAccount, + ContactsContract.AUTHORITY, false); + logger.debug("Video Calling Account already exists"); + return vCallingAccount; + } + } + + public static boolean removeRcsAccount(Context context) { + logger.debug("removeRcsAccount()"); + AccountManager mAccountManager = null; + boolean result = false; + mAccountManager = AccountManager.get(context); + Account[] vCallingAccounts = mAccountManager + .getAccountsByType(ACCOUNT_TYPE); + + if (vCallingAccounts.length == 0) { + logger.debug("Video Calling Account is not present. Do nothing."); + } else { + Account vCallingAccount = new Account(ACCOUNT_NAME, ACCOUNT_TYPE); + mAccountManager.removeAccount(vCallingAccount, null, null); + logger.debug("Video Calling Account removed."); + } + return result; + } +} diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/AlarmBroadcastReceiver.java b/rcs/presencepolling/src/com/android/service/ims/presence/AlarmBroadcastReceiver.java new file mode 100644 index 0000000..1e5c0ef --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/AlarmBroadcastReceiver.java @@ -0,0 +1,78 @@ +/* + * 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.presence; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.android.ims.internal.Logger; +import com.android.service.ims.presence.Contacts; + +public class AlarmBroadcastReceiver extends BroadcastReceiver { + private Logger logger = Logger.getLogger(this.getClass().getName()); + + private static final String ACTION_PERIODICAL_DISCOVERY_ALARM = + CapabilityPolling.ACTION_PERIODICAL_DISCOVERY_ALARM; + private static final String ACTION_POLLING_RETRY_ALARM = + PollingTask.ACTION_POLLING_RETRY_ALARM; + private static final String ACTION_EAB_NEW_CONTACT_INSERTED = + Contacts.ACTION_NEW_CONTACT_INSERTED; + + @Override + public void onReceive(Context context, Intent intent) { + logger.info("onReceive(), intent: " + intent + + ", context: " + context); + + String action = intent.getAction(); + if (ACTION_PERIODICAL_DISCOVERY_ALARM.equals(action)) { + CapabilityPolling capabilityPolling = CapabilityPolling.getInstance(null); + if (capabilityPolling != null) { + int pollingType = intent.getIntExtra("pollingType", + CapabilityPolling.ACTION_POLLING_NORMAL); + capabilityPolling.enqueueDiscovery(pollingType); + } + } else if (ACTION_POLLING_RETRY_ALARM.equals(action)) { + PollingsQueue queue = PollingsQueue.getInstance(null); + if (queue != null) { + long id = intent.getLongExtra("pollingTaskId", -1); + queue.retry(id); + } + } else if (ACTION_EAB_NEW_CONTACT_INSERTED.equals(action)) { + CapabilityPolling capabilityPolling = CapabilityPolling.getInstance(null); + if (capabilityPolling != null) { + String number = intent.getStringExtra(Contacts.NEW_PHONE_NUMBER); + capabilityPolling.enqueueNewContact(number); + } + } else { + logger.debug("No interest in this intent: " + action); + } + } +}; + diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/CapabilityPolling.java b/rcs/presencepolling/src/com/android/service/ims/presence/CapabilityPolling.java new file mode 100644 index 0000000..5d3e12f --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/CapabilityPolling.java @@ -0,0 +1,845 @@ +/* + * 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.presence; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.Cursor; +import android.net.Uri; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.os.Process; +import android.os.SystemClock; +import android.telephony.PhoneNumberUtils; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.text.format.Time; +import android.text.TextUtils; +import android.content.ComponentName; + +import com.android.ims.ImsConfig; +import com.android.ims.ImsException; +import com.android.ims.ImsManager; +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.telephony.TelephonyIntents; + +import com.android.ims.RcsException; +import com.android.ims.RcsManager; +import com.android.ims.RcsManager.ResultCode; +import com.android.ims.RcsPresence; +import com.android.ims.RcsPresence.PublishState; +import com.android.ims.RcsPresenceInfo; +import com.android.ims.internal.ContactNumberUtils; +import com.android.ims.internal.Logger; + +import java.util.ArrayList; +import java.util.List; + +public class CapabilityPolling { + private Logger logger = Logger.getLogger(this.getClass().getName()); + private final Context mContext; + + public static final String ACTION_PERIODICAL_DISCOVERY_ALARM = + "com.android.service.ims.presence.periodical_capability_discovery"; + private PendingIntent mDiscoveryAlarmIntent = null; + + public static final int ACTION_POLLING_NORMAL = 0; + public static final int ACTION_POLLING_NEW_CONTACTS = 1; + + private long mCapabilityPollInterval = 604800000L; + private long mMinCapabilityPollInterval = 60480000L; + private long mCapabilityCacheExpiration = 7776000000L; + private long mNextPollingTimeStamp = 0L; + private final Object mScheduleSyncObj = new Object(); + + private boolean mInitialized = false; + private AlarmManager mAlarmManager = null; + private EABContactManager mEABContactManager = null; + private boolean mStackAvailable = false; + private int mPublished = -1; + private int mProvisioned = -1; + + private HandlerThread mDiscoveryThread; + private Handler mDiscoveryHandler; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + logger.info("onReceive(), intent: " + intent + + ", context: " + context); + + String action = intent.getAction(); + if (RcsManager.ACTION_RCS_SERVICE_AVAILABLE.equals(action)) { + enqueueServiceStatusChanged(true); + } else if (RcsManager.ACTION_RCS_SERVICE_UNAVAILABLE.equals(action)) { + enqueueServiceStatusChanged(false); + } else if (RcsManager.ACTION_RCS_SERVICE_DIED.equals(action)) { + logger.warn("No handler for this intent: " + action); + } else if (RcsPresence.ACTION_PUBLISH_STATE_CHANGED.equals(action)) { + int state = intent.getIntExtra( + RcsPresence.EXTRA_PUBLISH_STATE, + RcsPresence.PublishState.PUBLISH_STATE_NOT_PUBLISHED); + enqueuePublishStateChanged(state); + } else if (ImsConfig.ACTION_IMS_CONFIG_CHANGED.equals(action)) { + int item = intent.getIntExtra(ImsConfig.EXTRA_CHANGED_ITEM, -1); + if ((ImsConfig.ConfigConstants.CAPABILITIES_POLL_INTERVAL == item) || + (ImsConfig.ConfigConstants.CAPABILITIES_CACHE_EXPIRATION == item)) { + enqueueSettingsChanged(); + } + } else if(TelephonyIntents.ACTION_SIM_STATE_CHANGED.equalsIgnoreCase(action)) { + String stateExtra = intent.getStringExtra( + IccCardConstants.INTENT_KEY_ICC_STATE); + logger.print("SIM_STATE_CHANGED: " + stateExtra); + if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equalsIgnoreCase(stateExtra)) { + enqueueSimLoaded(); + } + } else { + logger.debug("No interest in this intent: " + action); + } + } + }; + + private static CapabilityPolling sInstance = null; + public static synchronized CapabilityPolling getInstance(Context context) { + if ((sInstance == null) && (context != null)) { + sInstance = new CapabilityPolling(context); + } + + return sInstance; + } + + private CapabilityPolling(Context context) { + mContext = context; + + ContactNumberUtils.getDefault().setContext(mContext); + PresencePreferences.getInstance().setContext(mContext); + + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + mEABContactManager = new EABContactManager(mContext.getContentResolver(), + mContext.getPackageName()); + + setRcsTestMode(PresencePreferences.getInstance().getRcsTestMode()); + logger.debug("CapabilityPolling is created."); + } + + public void setRcsTestMode(boolean test) { + logger.setRcsTestMode(test); + + PresencePreferences pref = PresencePreferences.getInstance(); + if (pref != null) { + if (pref.getRcsTestMode() != test) { + pref.setRcsTestMode(test); + } + } + } + + private void initialise() { + if (mInitialized) { + return; + } + + PresenceSetting.init(mContext); + long capabilityPollInterval = PresenceSetting.getCapabilityPollInterval(); + logger.print("getCapabilityPollInterval: " + capabilityPollInterval); + if (capabilityPollInterval == -1) { + capabilityPollInterval = mContext.getResources().getInteger( + R.integer.capability_poll_interval); + } + if (capabilityPollInterval < 10) { + capabilityPollInterval = 10; + } + + long capabilityCacheExpiration = PresenceSetting.getCapabilityCacheExpiration(); + logger.print("getCapabilityCacheExpiration: " + capabilityCacheExpiration); + if (capabilityCacheExpiration == -1) { + capabilityCacheExpiration = mContext.getResources().getInteger( + R.integer.capability_cache_expiration); + } + if (capabilityCacheExpiration < 10) { + capabilityCacheExpiration = 10; + } + + int publishTimer = PresenceSetting.getPublishTimer(); + logger.print("getPublishTimer: " + publishTimer); + int publishTimerExtended = PresenceSetting.getPublishTimerExtended(); + logger.print("getPublishTimerExtended: " + publishTimerExtended); + int maxEntriesInRequest = PresenceSetting.getMaxNumberOfEntriesInRequestContainedList(); + logger.print("getMaxNumberOfEntriesInRequestContainedList: " + maxEntriesInRequest); + if ((capabilityPollInterval <= 30 * 60) || // default: 7 days + (capabilityCacheExpiration <= 60 * 60) || // default: 90 days + (maxEntriesInRequest <= 20) || // default: 100 + (publishTimer <= 10 * 60) || // default: 20 minutes + (publishTimerExtended <= 20 * 60)) { // default: 1 day + setRcsTestMode(true); + } + + if (capabilityCacheExpiration < capabilityPollInterval) { + capabilityPollInterval = capabilityCacheExpiration; + } + + mCapabilityPollInterval = capabilityPollInterval * 1000; + mMinCapabilityPollInterval = mCapabilityPollInterval / 10; + logger.info("mCapabilityPollInterval: " + mCapabilityPollInterval + + ", mMinCapabilityPollInterval: " + mMinCapabilityPollInterval); + + mCapabilityCacheExpiration = capabilityCacheExpiration * 1000; + logger.info("mCapabilityCacheExpiration: " + mCapabilityCacheExpiration); + + mInitialized = true; + } + + public void start() { + mDiscoveryThread = new HandlerThread("Presence-DiscoveryThread"); + mDiscoveryThread.start(); + mDiscoveryHandler = new Handler(mDiscoveryThread.getLooper(), mDiscoveryCallback); + + registerForBroadcasts(); + + if (isPollingReady()) { + schedulePolling(5 * 1000, ACTION_POLLING_NORMAL); + } + } + + public void stop() { + cancelDiscoveryAlarm(); + clearPollingTasks(); + mContext.unregisterReceiver(mReceiver); + mDiscoveryThread.quit(); + } + + private void registerForBroadcasts() { + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(RcsManager.ACTION_RCS_SERVICE_AVAILABLE); + intentFilter.addAction(RcsManager.ACTION_RCS_SERVICE_UNAVAILABLE); + intentFilter.addAction(RcsManager.ACTION_RCS_SERVICE_DIED); + intentFilter.addAction(RcsPresence.ACTION_PUBLISH_STATE_CHANGED); + intentFilter.addAction(ImsConfig.ACTION_IMS_CONFIG_CHANGED); + intentFilter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + mContext.registerReceiver(mReceiver, intentFilter); + } + + private boolean isPollingReady() { + if (mPublished == -1) { + RcsManager rcsManager = RcsManager.getInstance(mContext, 0); + if (rcsManager != null) { + mStackAvailable = rcsManager.isRcsServiceAvailable(); + logger.print("isPollingReady, mStackAvailable: " + mStackAvailable); + try { + RcsPresence rcsPresence = rcsManager.getRcsPresenceInterface(); + if (rcsPresence != null) { + int state = rcsPresence.getPublishState(); + mPublished = (PublishState.PUBLISH_STATE_200_OK == state) ? 1 : 0; + logger.print("isPollingReady, mPublished: " + mPublished); + } + } catch (RcsException ex) { + logger.warn("RcsPresence.getPublishState failed, exception: " + ex); + mPublished = -1; + } + } + } + + if (mProvisioned == -1) { + ImsManager imsManager = ImsManager.getInstance(mContext, + SubscriptionManager.getDefaultVoicePhoneId()); + if (imsManager != null) { + try { + ImsConfig imsConfig = imsManager.getConfigInterface(); + if (imsConfig != null) { + mProvisioned = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.EAB_SETTING_ENABLED); + logger.print("isPollingReady, mProvisioned: " + mProvisioned); + } + } catch (ImsException ex) { + logger.warn("ImsConfig.getEabProvisioned failed, exception: " + ex); + mProvisioned = -1; + } + } + } + + return mStackAvailable && (mPublished == 1) && (mProvisioned == 1); + } + + private void serviceStatusChanged(boolean enabled) { + logger.print("Enter serviceStatusChanged: " + enabled); + mStackAvailable = enabled; + + if (isPollingReady()) { + schedulePolling(0, ACTION_POLLING_NORMAL); + } else { + cancelDiscoveryAlarm(); + clearPollingTasks(); + } + } + + private void publishStateChanged(int state) { + logger.print("Enter publishStateChanged: " + state); + mPublished = (PublishState.PUBLISH_STATE_200_OK == state) ? 1 : 0; + if (mPublished == 1) { + PresencePreferences pref = PresencePreferences.getInstance(); + if (pref != null) { + String mdn_old = pref.getLine1Number(); + String subscriberId_old = pref.getSubscriberId(); + if (TextUtils.isEmpty(mdn_old) && TextUtils.isEmpty(subscriberId_old)) { + String mdn = getLine1Number(); + pref.setLine1Number(mdn); + String subscriberId = getSubscriberId(); + pref.setSubscriberId(subscriberId); + } + } + } + + if (isPollingReady()) { + schedulePolling(0, ACTION_POLLING_NORMAL); + } else { + cancelDiscoveryAlarm(); + clearPollingTasks(); + } + } + + private void provisionStateChanged() { + ImsManager imsManager = ImsManager.getInstance(mContext, + SubscriptionManager.getDefaultVoicePhoneId()); + if (imsManager == null) { + return; + } + + try { + ImsConfig imsConfig = imsManager.getConfigInterface(); + if (imsConfig == null) { + return; + } + boolean volteProvision = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.VLT_SETTING_ENABLED) + == ImsConfig.FeatureValueConstants.ON; + boolean vtProvision = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.LVC_SETTING_ENABLED) + == ImsConfig.FeatureValueConstants.ON; + boolean eabProvision = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.EAB_SETTING_ENABLED) + == ImsConfig.FeatureValueConstants.ON; + logger.print("Provision state changed, VolteProvision: " + + volteProvision + ", vtProvision: " + vtProvision + + ", eabProvision: " + eabProvision); + + if ((mProvisioned == 1) && !eabProvision) { + logger.print("EAB Provision is disabled, clear all capabilities!"); + if (mEABContactManager != null) { + mEABContactManager.updateAllCapabilityToUnknown(); + } + } + mProvisioned = eabProvision ? 1 : 0; + } catch (ImsException ex) { + logger.warn("ImsConfig.getEabProvisioned failed, exception: " + ex); + mProvisioned = -1; + return; + } + + if (isPollingReady()) { + schedulePolling(0, ACTION_POLLING_NORMAL); + } else { + cancelDiscoveryAlarm(); + clearPollingTasks(); + } + } + + private void settingsChanged() { + logger.print("Enter settingsChanged."); + cancelDiscoveryAlarm(); + clearPollingTasks(); + mInitialized = false; + + if (isPollingReady()) { + schedulePolling(0, ACTION_POLLING_NORMAL); + } + } + + private void newContactAdded(String number) { + logger.print("Enter newContactAdded: " + number); + if (TextUtils.isEmpty(number)) { + return; + } + + EABContactManager.Request request = new EABContactManager.Request(number) + .setLastUpdatedTimeStamp(0); + int result = mEABContactManager.update(request); + if (result <= 0) { + return; + } + + if (isPollingReady()) { + schedulePolling(5 * 1000, ACTION_POLLING_NEW_CONTACTS); + } + } + + private void verifyPollingResult(int counts) { + if (isPollingReady()) { + PresencePreferences pref = PresencePreferences.getInstance(); + if ((pref != null) && pref.getRcsTestMode()) { + counts = 1; + } + long lm = (long)(1 << (counts - 1)); + schedulePolling(30 * 1000 * lm, ACTION_POLLING_NORMAL); + } + } + + public synchronized void schedulePolling(long msec, int type) { + logger.print("schedulePolling msec=" + msec + " type=" + type); + if (!isPollingReady()) { + logger.debug("Cancel the polling since the network is not ready"); + return; + } + + if (type == ACTION_POLLING_NEW_CONTACTS) { + cancelDiscoveryAlarm(); + } + + if (mNextPollingTimeStamp != 0L) { + long scheduled = mNextPollingTimeStamp - System.currentTimeMillis(); + if ((scheduled > 0) && (scheduled < msec)) { + logger.print("There has been a discovery scheduled at " + + getTimeString(mNextPollingTimeStamp)); + return; + } + } + + long nextTime = System.currentTimeMillis() + msec; + logger.print("A new discovery needs to be started in " + + (msec / 1000) + " seconds at " + + getTimeString(nextTime)); + + cancelDiscoveryAlarm(); + + if (msec <= 0) { + enqueueDiscovery(type); + return; + } + + Intent intent = new Intent(ACTION_PERIODICAL_DISCOVERY_ALARM); + intent.setClass(mContext, AlarmBroadcastReceiver.class); + intent.putExtra("pollingType", type); + + mDiscoveryAlarmIntent = PendingIntent.getBroadcast(mContext, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + msec, mDiscoveryAlarmIntent); + + mNextPollingTimeStamp = nextTime; + } + + private synchronized void doCapabilityDiscovery(int type) { + logger.print("doCapabilityDiscovery type=" + type); + if (!isPollingReady()) { + logger.debug("doCapabilityDiscovery isPollingReady=false"); + return; + } + + long delay = 0; + List<Contacts.Item> list = null; + + initialise(); + + mNextPollingTimeStamp = 0L; + Cursor cursor = null; + EABContactManager.Query baseQuery = new EABContactManager.Query() + .orderBy(EABContactManager.COLUMN_LAST_UPDATED_TIMESTAMP, + EABContactManager.Query.ORDER_ASCENDING); + try { + logger.debug("doCapabilityDiscovery.query:\n" + baseQuery); + cursor = mEABContactManager.query(baseQuery); + if (cursor == null) { + logger.print("Cursor is null, there is no database found."); + return; + } + int count = cursor.getCount(); + if (count == 0) { + logger.print("Cursor.getCount() is 0, there is no items found in db."); + return; + } + + list = new ArrayList<Contacts.Item>(); + list.clear(); + + long current = System.currentTimeMillis(); + for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + long id = cursor.getLong(cursor.getColumnIndex(Contacts.Impl._ID)); + long last = cursor.getLong(cursor.getColumnIndex( + Contacts.Impl.CONTACT_LAST_UPDATED_TIMESTAMP)); + + Contacts.Item item = new Contacts.Item(id); + item.setLastUpdateTime(last); + item.setNumber(cursor.getString( + cursor.getColumnIndex(Contacts.Impl.CONTACT_NUMBER))); + item.setName(cursor.getString( + cursor.getColumnIndex(Contacts.Impl.CONTACT_NAME))); + item.setVolteTimestamp(cursor.getLong( + cursor.getColumnIndex(Contacts.Impl.VOLTE_CALL_CAPABILITY_TIMESTAMP))); + item.setVideoTimestamp(cursor.getLong( + cursor.getColumnIndex(Contacts.Impl.VIDEO_CALL_CAPABILITY_TIMESTAMP))); + + if ((current - last < 0) || + (current - last >= mCapabilityPollInterval - mMinCapabilityPollInterval)) { + logger.print("This item will be updated:\n" + + item); + if (item.isValid()) { + list.add(item); + } + } else { + logger.print("The first item which will be updated next time:\n" + item); + delay = randomCapabilityPollInterval() - (current - last); + if (delay > 0) { + break; + } + } + } + } catch (Exception ex) { + logger.warn("Exception in doCapabilityDiscovery: " + ex); + if (delay <= 0) { + delay = 5 * 60 * 1000; + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + + if (delay <= 0) { + delay = randomCapabilityPollInterval(); + } + logger.print("Polling delay: " + delay); + schedulePolling(delay, ACTION_POLLING_NORMAL); + + updateObsoleteItems(); + + if ((list != null) && (list.size() > 0)) { + PollingsQueue queue = PollingsQueue.getInstance(mContext); + if (queue != null) { + queue.setCapabilityPolling(this); + queue.add(type, list); + } + } + } + + private long randomCapabilityPollInterval() { + double random = Math.random() * 0.2 + 0.9; + logger.print("The random for this time polling is: " + random); + random = random * mCapabilityPollInterval; + return (long)random; + } + + private void updateObsoleteItems() { + long current = System.currentTimeMillis(); + long last = current - mCapabilityCacheExpiration; + long last3year = current - 3 * 365 * 24 * 3600000L; + StringBuilder sb = new StringBuilder(); + sb.append("(("); + sb.append(Contacts.Impl.VOLTE_CALL_CAPABILITY_TIMESTAMP + "<='" + last + "'"); + sb.append(" OR "); + sb.append(Contacts.Impl.VIDEO_CALL_CAPABILITY_TIMESTAMP + "<='" + last + "'"); + sb.append(") AND "); + sb.append(Contacts.Impl.CONTACT_LAST_UPDATED_TIMESTAMP + ">='" + last3year + "'"); + sb.append(")"); + EABContactManager.Query baseQuery = new EABContactManager.Query() + .setFilterByTime(sb.toString()) + .orderBy(EABContactManager.COLUMN_ID, + EABContactManager.Query.ORDER_ASCENDING); + + Cursor cursor = null; + List<Contacts.Item> list = null; + try { + logger.debug("updateObsoleteItems.query:\n" + baseQuery); + cursor = mEABContactManager.query(baseQuery); + if (cursor == null) { + logger.print("Cursor is null, there is no database found."); + return; + } + int count = cursor.getCount(); + if (count == 0) { + logger.print("Cursor.getCount() is 0, there is no obsolete items found."); + return; + } + + list = new ArrayList<Contacts.Item>(); + list.clear(); + + for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + long id = cursor.getLong(cursor.getColumnIndex(Contacts.Impl._ID)); + + Contacts.Item item = new Contacts.Item(id); + item.setLastUpdateTime(cursor.getLong( + cursor.getColumnIndex(Contacts.Impl.CONTACT_LAST_UPDATED_TIMESTAMP))); + item.setNumber(cursor.getString( + cursor.getColumnIndex(Contacts.Impl.CONTACT_NUMBER))); + item.setName(cursor.getString( + cursor.getColumnIndex(Contacts.Impl.CONTACT_NAME))); + item.setVolteTimestamp(cursor.getLong( + cursor.getColumnIndex(Contacts.Impl.VOLTE_CALL_CAPABILITY_TIMESTAMP))); + item.setVideoTimestamp(cursor.getLong( + cursor.getColumnIndex(Contacts.Impl.VIDEO_CALL_CAPABILITY_TIMESTAMP))); + logger.print("updateObsoleteItems, the obsolete item:\n" + item); + + if ((item.lastUpdateTime() > 0) && item.isValid()) { + list.add(item); + } + } + } catch (Exception ex) { + logger.warn("Exception in updateObsoleteItems: " + ex); + } finally { + if (cursor != null) { + cursor.close(); + } + } + + if ((list == null) || (list.size() <= 0)) { + return; + } + + for (Contacts.Item item : list) { + EABContactManager.Request request = new EABContactManager.Request(item.id()); + if (item.volteTimestamp() <= last) { + request.setVolteCallCapability(false); + request.setVolteCallCapabilityTimeStamp(current); + } + if (item.videoTimestamp() <= last) { + request.setVideoCallCapability(false); + request.setVideoCallCapabilityTimeStamp(current); + } + int result = mEABContactManager.update(request); + if (result <= 0) { + logger.print("Failed to update this request: " + request); + } + } + } + + private void cancelDiscoveryAlarm() { + if (mDiscoveryAlarmIntent != null) { + mAlarmManager.cancel(mDiscoveryAlarmIntent); + mDiscoveryAlarmIntent = null; + mNextPollingTimeStamp = 0L; + } + } + + private void clearPollingTasks() { + PollingsQueue queue = PollingsQueue.getInstance(null); + if (queue != null) { + queue.clear(); + } + } + + private String getTimeString(long time) { + if (time <= 0) { + time = System.currentTimeMillis(); + } + + Time tobj = new Time(); + tobj.set(time); + return String.format("%s.%s", tobj.format("%m-%d %H:%M:%S"), time % 1000); + } + + private static final int MSG_CHECK_DISCOVERY = 1; + private static final int MSG_NEW_CONTACT_ADDED = 2; + private static final int MSG_SERVICE_STATUS_CHANGED = 3; + private static final int MSG_SETTINGS_CHANGED = 4; + private static final int MSG_PUBLISH_STATE_CHANGED = 5; + private static final int MSG_SIM_LOADED = 6; + private static final int MSG_PROVISION_STATE_CHANGED = 7; + private static final int MSG_VERIFY_POLLING_RESULT = 8; + + public void enqueueDiscovery(int type) { + mDiscoveryHandler.removeMessages(MSG_CHECK_DISCOVERY); + mDiscoveryHandler.obtainMessage(MSG_CHECK_DISCOVERY, type, -1).sendToTarget(); + } + + public void enqueueNewContact(String number) { + mDiscoveryHandler.obtainMessage(MSG_NEW_CONTACT_ADDED, number).sendToTarget(); + } + + private void enqueueServiceStatusChanged(boolean enabled) { + mDiscoveryHandler.removeMessages(MSG_SERVICE_STATUS_CHANGED); + mDiscoveryHandler.obtainMessage(MSG_SERVICE_STATUS_CHANGED, + enabled ? 1 : 0, -1).sendToTarget(); + } + + private void enqueuePublishStateChanged(int state) { + mDiscoveryHandler.removeMessages(MSG_PUBLISH_STATE_CHANGED); + mDiscoveryHandler.obtainMessage(MSG_PUBLISH_STATE_CHANGED, + state, -1).sendToTarget(); + } + + public void enqueueSettingsChanged() { + mDiscoveryHandler.removeMessages(MSG_SETTINGS_CHANGED); + mDiscoveryHandler.obtainMessage(MSG_SETTINGS_CHANGED).sendToTarget(); + } + + private void enqueueSimLoaded() { + mDiscoveryHandler.removeMessages(MSG_SIM_LOADED); + mDiscoveryHandler.obtainMessage(MSG_SIM_LOADED).sendToTarget(); + } + + private void enqueueProvisionStateChanged() { + mDiscoveryHandler.removeMessages(MSG_PROVISION_STATE_CHANGED); + mDiscoveryHandler.obtainMessage(MSG_PROVISION_STATE_CHANGED).sendToTarget(); + } + + public void enqueueVerifyPollingResult(int counts) { + mDiscoveryHandler.removeMessages(MSG_VERIFY_POLLING_RESULT); + mDiscoveryHandler.obtainMessage(MSG_VERIFY_POLLING_RESULT, counts, -1).sendToTarget(); + } + + private Handler.Callback mDiscoveryCallback = new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + + if (msg.what == MSG_CHECK_DISCOVERY) { + doCapabilityDiscovery(msg.arg1); + } else if (msg.what == MSG_NEW_CONTACT_ADDED) { + newContactAdded((String)msg.obj); + } else if (msg.what == MSG_SERVICE_STATUS_CHANGED) { + serviceStatusChanged(msg.arg1 == 1); + } else if (msg.what == MSG_PUBLISH_STATE_CHANGED) { + publishStateChanged(msg.arg1); + } else if (msg.what == MSG_SETTINGS_CHANGED) { + settingsChanged(); + } else if(msg.what == MSG_SIM_LOADED) { + onSimLoaded(); + } else if(msg.what == MSG_PROVISION_STATE_CHANGED) { + provisionStateChanged(); + } else if(msg.what == MSG_VERIFY_POLLING_RESULT) { + verifyPollingResult(msg.arg1); + } else { + } + + return true; + } + }; + + private void onSimLoaded() { + PresencePreferences pref = PresencePreferences.getInstance(); + if (pref == null) { + return; + } + + String mdn_old = pref.getLine1Number(); + if (TextUtils.isEmpty(mdn_old)) { + return; + } + String subscriberId_old = pref.getSubscriberId(); + if (TextUtils.isEmpty(subscriberId_old)) { + return; + } + + String mdn = getLine1Number(); + String subscriberId = getSubscriberId(); + if (TextUtils.isEmpty(mdn) && TextUtils.isEmpty(subscriberId)) { + return; + } + + boolean mdnMatched = false; + if (TextUtils.isEmpty(mdn) || PhoneNumberUtils.compare(mdn_old, mdn)) { + mdnMatched = true; + } + boolean subscriberIdMatched = false; + if (TextUtils.isEmpty(subscriberId) || subscriberId.equals(subscriberId_old)) { + subscriberIdMatched = true; + } + if (mdnMatched && subscriberIdMatched) { + return; + } + + logger.print("Remove presence cache for Sim card changed!"); + pref.setLine1Number(""); + pref.setSubscriberId(""); + + if (mEABContactManager != null) { + mEABContactManager.updateAllCapabilityToUnknown(); + } + } + + private static final int DEFAULT_SUBSCRIPTION = 1; + private String getLine1Number() { + if (mContext == null) { + return null; + } + + TelephonyManager telephony = (TelephonyManager) + mContext.getSystemService(Context.TELEPHONY_SERVICE); + if (telephony == null) { + return null; + } + + String mdn = null; + if (TelephonyManager.getDefault().isMultiSimEnabled()) { + int subId = SubscriptionManager.getDefaultDataSubscriptionId(); + if (!SubscriptionManager.isValidSubscriptionId(subId)) { + subId = DEFAULT_SUBSCRIPTION; + } + mdn = telephony.getLine1Number(subId); + } else { + mdn = telephony.getLine1Number(); + } + + if ((mdn == null) || (mdn.length() == 0) || mdn.startsWith("00000")) { + return null; + } + + logger.print("getLine1Number: " + mdn); + return mdn; + } + + private String getSubscriberId() { + if (mContext == null) { + return null; + } + + TelephonyManager telephony = (TelephonyManager) + mContext.getSystemService(Context.TELEPHONY_SERVICE); + if (telephony == null) { + return null; + } + + String subscriberId = null; + if (TelephonyManager.getDefault().isMultiSimEnabled()) { + int subId = SubscriptionManager.getDefaultDataSubscriptionId(); + if (!SubscriptionManager.isValidSubscriptionId(subId)) { + subId = DEFAULT_SUBSCRIPTION; + } + subscriberId = telephony.getSubscriberId(subId); + } else { + subscriberId = telephony.getSubscriberId(); + } + + logger.print("getSubscriberId: " + subscriberId); + return subscriberId; + } +} diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/ContactDbUtil.java b/rcs/presencepolling/src/com/android/service/ims/presence/ContactDbUtil.java new file mode 100644 index 0000000..e4f4a21 --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/ContactDbUtil.java @@ -0,0 +1,220 @@ +/* + * 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.presence; + +import android.accounts.Account; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.ContactsContract.Groups; + +import android.provider.ContactsContract; +import android.provider.ContactsContract.CommonDataKinds.Phone; + +import com.android.ims.internal.EABContract; +import com.android.ims.internal.Logger; + +public class ContactDbUtil { + private static Logger logger = Logger.getLogger("ContactDbUtil"); + + public static void addVideoCallingContactGroup(Context context, Account vCallingAccount) { + logger.info("addVideoCallingContactGroup"); + ContentResolver contentResolver = context.getContentResolver(); + if (vCallingAccount == null) { + logger.error("vCallingAccount == null"); + return; + } + long videoCallingGroupId = 0; + final Cursor cursor = contentResolver.query(Groups.CONTENT_URI, + new String[] { Groups._ID }, + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=? AND " + + Groups.TITLE + "=?", + new String[] { AccountUtil.ACCOUNT_NAME, AccountUtil.ACCOUNT_TYPE, + context.getString(R.string.video_calling_contact_group) }, null); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + videoCallingGroupId = cursor.getLong(0); + } + } finally { + cursor.close(); + } + } + if (videoCallingGroupId == 0) { + logger.debug("addVideoCallingContactGroup, creating group"); + + // Video Calling group doesn't exist yet, so create it + final ContentValues contentValues = new ContentValues(); + contentValues.put(Groups.ACCOUNT_NAME, AccountUtil.ACCOUNT_NAME); + contentValues.put(Groups.ACCOUNT_TYPE, AccountUtil.ACCOUNT_TYPE); + contentValues.put(Groups.TITLE, context.getString(R.string.video_calling_contact_group)); + contentValues.put(Groups.GROUP_IS_READ_ONLY, 1); + contentValues.put(Groups.GROUP_VISIBLE, 1); + contentValues.put(Groups.SYSTEM_ID, "com.android.vt.eab"); + + final Uri newGroupUri = contentResolver.insert(Groups.CONTENT_URI, contentValues); + if (null != newGroupUri) { + videoCallingGroupId = ContentUris.parseId(newGroupUri); + } else { + logger.error("newGroupUri is null."); + } + } else { + logger.debug("addVideoCallingContactGroup, Video Calling Group, already exists!!!"); + + } + + logger.debug("videoCallingGroupId: " + videoCallingGroupId); + SharedPrefUtil.setVideoCallingGroupId(context, videoCallingGroupId); + } + + public static void removeVideoCallingContactGroup(Context context) { + logger.debug("removeVideoCallingContactGroup"); + long storedVideoCallingGroupId = SharedPrefUtil.getVideoCallingGroupId(context); + long videoCallingGroupId = 0; + ContentResolver contentResolver = context.getContentResolver(); + final Cursor cursor = contentResolver.query(Groups.CONTENT_URI, + new String[] { Groups._ID }, + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=? AND " + + Groups.TITLE + "=?", + new String[] { AccountUtil.ACCOUNT_NAME, AccountUtil.ACCOUNT_TYPE, + context.getString(R.string.video_calling_contact_group) }, null); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + videoCallingGroupId = cursor.getLong(0); + } + } finally { + cursor.close(); + } + } + logger.debug("videoCallingGroupId : " + videoCallingGroupId); + logger.debug("storedVideoCallingGroupId : " + storedVideoCallingGroupId); + if (videoCallingGroupId == 0) { + logger.debug("videoCallingGroupId : " + videoCallingGroupId + + " not present. Do nothing. "); + } else { + if (storedVideoCallingGroupId == videoCallingGroupId) { + String deleteWhereClause = Groups._ID + "='" + videoCallingGroupId + "'"; + contentResolver.delete(Groups.CONTENT_URI, deleteWhereClause, null); + logger.debug("Removing Video Calling Group."); + } + SharedPrefUtil.setVideoCallingGroupId(context, 0); + } + logger.debug("videoCallingGroupId: " + videoCallingGroupId); + } + + public static int resetVtCapability(ContentResolver resolver) { + if(resolver == null) { + logger.error("resetVtCapability, resolver = null"); + return 0; + } + + String selection = ContactsContract.Data.MIMETYPE + " = '" + Phone.CONTENT_ITEM_TYPE + "'"; + + ContentValues values = new ContentValues(); + values.put(ContactsContract.Data.CARRIER_PRESENCE, 0); // reset all. + int count = resolver.update(ContactsContract.Data.CONTENT_URI, values, selection, null); + logger.debug("resetVtCapability count=" + count); + return count; + } + + public static int updateVtCapability(ContentResolver resolver, String number, boolean enable) { + String[] projection = new String[] {EABContract.EABColumns.DATA_ID}; + + int updatedCount = 0; + Cursor cursor = null; + try { + cursor = resolver.query(Contacts.Impl.CONTENT_URI, + projection, "PHONE_NUMBERS_EQUAL(contact_number, ?, 0)", + new String[] {number}, null); + if(null != cursor) { + int count = cursor.getCount(); + logger.debug("updateVtCapability to Contact DB, count=" + count); + if(count <= 0) { + logger.error("updateVtCapability, no number to be updated"); + cursor.close(); + cursor = null; + return updatedCount; + } + + while(cursor.moveToNext()) { + long dataId = cursor.getLong(cursor.getColumnIndex( + EABContract.EABColumns.DATA_ID)); + // update one by one to avoid loosing matching error. + updatedCount += updateVtCapability(resolver, dataId, enable); + } + } + } catch (Exception e) { + logger.error("updateVtCapability exception", e); + } finally { + if(cursor != null) { + cursor.close(); + } + } + + logger.debug("updateVtCapability updatedCount=" + updatedCount); + return updatedCount; + } + + public static int updateVtCapability(ContentResolver resolver, long dataId, boolean enable) { + if(resolver == null) { + logger.error("resetVtCapability, resolver = null"); + return 0; + } + + String selection = ContactsContract.Data.MIMETYPE + " = '" + Phone.CONTENT_ITEM_TYPE + + "' and " + ContactsContract.Data._ID + "= '" + dataId + "'"; + + int oldValue = 0; + final Cursor cursor = resolver.query(ContactsContract.Data.CONTENT_URI, + new String[] { ContactsContract.Data._ID, ContactsContract.Data.CARRIER_PRESENCE }, + selection, null, null); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + oldValue = cursor.getInt(1); + } + } finally { + cursor.close(); + } + } + + + ContentValues values = new ContentValues(); + values.put(ContactsContract.Data.CARRIER_PRESENCE, + enable?(oldValue | ContactsContract.Data.CARRIER_PRESENCE_VT_CAPABLE): + (oldValue & ~ContactsContract.Data.CARRIER_PRESENCE_VT_CAPABLE)); + int count = resolver.update(ContactsContract.Data.CONTENT_URI, values, selection, null); + logger.debug("resetVtCapability count=" + count); + return count; + } +} diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/Contacts.java b/rcs/presencepolling/src/com/android/service/ims/presence/Contacts.java new file mode 100644 index 0000000..cbad25a --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/Contacts.java @@ -0,0 +1,297 @@ +/* + * 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.presence; + +import android.net.Uri; +import android.os.SystemClock; +import android.provider.BaseColumns; +import android.text.format.Time; +import com.android.ims.internal.EABContract; + +import com.android.ims.internal.ContactNumberUtils; + +public final class Contacts { + private Contacts() {} + + /** + * Intent that a new contact is inserted in EAB Provider. + * This intent will have a extra parameter with key NEW_PHONE_NUMBER. + */ + public static final String ACTION_NEW_CONTACT_INSERTED = + "android.provider.rcs.eab.EAB_NEW_CONTACT_INSERTED"; + + /** + * Key to bundle the new phone number inserted in EAB Provider. + */ + public static final String NEW_PHONE_NUMBER = "newPhoneNumber"; + + + public static final String AUTHORITY = EABContract.AUTHORITY; + + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); + + public static final class Impl implements BaseColumns { + private Impl() {} + + public static final String TABLE_NAME = + EABContract.EABColumns.TABLE_NAME; + + /** + * CONTENT_URI + * <P> + * "content://com.android.vt.eab/EABPresence" + * </P> + */ + public static final Uri CONTENT_URI = + Uri.withAppendedPath(Contacts.CONTENT_URI, TABLE_NAME); + + /** + * Key defining the contact number. + * <P> + * Type: TEXT + * </P> + */ + public static final String CONTACT_NUMBER = + EABContract.EABColumns.CONTACT_NUMBER; + + /** + * Key defining the contact name. + * <P> + * Type: TEXT + * </P> + */ + public static final String CONTACT_NAME = + EABContract.EABColumns.CONTACT_NAME; + + /** + * <p> + * Type: INT + * </p> + */ + public static final String VOLTE_STATUS = + EABContract.EABColumns.VOLTE_STATUS; + + /** + * Key defining the last updated timestamp. + * <P> + * Type: LONG + * </P> + */ + public static final String CONTACT_LAST_UPDATED_TIMESTAMP = + EABContract.EABColumns.CONTACT_LAST_UPDATED_TIMESTAMP; + + /** + * Key defining the VoLTE call service contact address. + * <P> + * Type: TEXT + * </P> + */ + public static final String VOLTE_CALL_SERVICE_CONTACT_ADDRESS = + EABContract.EABColumns.VOLTE_CALL_SERVICE_CONTACT_ADDRESS; + + /** + * Key defining the VoLTE call capability. + * <P> + * Type: TEXT + * </P> + */ + public static final String VOLTE_CALL_CAPABILITY = + EABContract.EABColumns.VOLTE_CALL_CAPABILITY; + + /** + * Key defining the VoLTE call capability timestamp. + * <P> + * Type: LONG + * </P> + */ + public static final String VOLTE_CALL_CAPABILITY_TIMESTAMP = + EABContract.EABColumns.VOLTE_CALL_CAPABILITY_TIMESTAMP; + + /** + * Key defining the VoLTE call availability. + * <P> + * Type: TEXT + * </P> + */ + public static final String VOLTE_CALL_AVAILABILITY = + EABContract.EABColumns.VOLTE_CALL_AVAILABILITY; + + /** + * Key defining the VoLTE call availability timestamp. + * <P> + * Type: LONG + * </P> + */ + public static final String VOLTE_CALL_AVAILABILITY_TIMESTAMP = + EABContract.EABColumns.VOLTE_CALL_AVAILABILITY_TIMESTAMP; + + /** + * Key defining the Video call service contact address. + * <P> + * Type: TEXT + * </P> + */ + public static final String VIDEO_CALL_SERVICE_CONTACT_ADDRESS = + EABContract.EABColumns.VIDEO_CALL_SERVICE_CONTACT_ADDRESS; + + /** + * Key defining the Video call capability. + * <P> + * Type: TEXT + * </P> + */ + public static final String VIDEO_CALL_CAPABILITY = + EABContract.EABColumns.VIDEO_CALL_CAPABILITY; + + /** + * Key defining the Video call capability timestamp. + * <P> + * Type: LONG + * </P> + */ + public static final String VIDEO_CALL_CAPABILITY_TIMESTAMP = + EABContract.EABColumns.VIDEO_CALL_CAPABILITY_TIMESTAMP; + + /** + * Key defining the Video call availability. + * <P> + * Type: TEXT + * </P> + */ + public static final String VIDEO_CALL_AVAILABILITY = + EABContract.EABColumns.VIDEO_CALL_AVAILABILITY; + + /** + * Key defining the Video call availability timestamp. + * <P> + * Type: LONG + * </P> + */ + public static final String VIDEO_CALL_AVAILABILITY_TIMESTAMP = + EABContract.EABColumns.VIDEO_CALL_AVAILABILITY_TIMESTAMP; + } + + public static class Item { + public Item(long id) { + mId = id; + } + + public long id() { + return mId; + } + + public String number() { + return mNumber; + } + + public void setNumber(String number) { + mNumber = ContactNumberUtils.getDefault().format(number); + } + + public boolean isValid() { + int res = ContactNumberUtils.getDefault().validate(mNumber); + return (res == ContactNumberUtils.NUMBER_VALID); + } + + public String name() { + return mName; + } + + public void setName(String name) { + mName = name; + } + + public long lastUpdateTime() { + return mLastUpdateTime; + } + + public void setLastUpdateTime(long time) { + mLastUpdateTime = time; + } + + public long volteTimestamp() { + return mVolteTimeStamp; + } + + public void setVolteTimestamp(long time) { + mVolteTimeStamp = time; + } + + public long videoTimestamp() { + return mVideoTimeStamp; + } + + public void setVideoTimestamp(long time) { + mVideoTimeStamp = time; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Contacts.Item)) return false; + + Contacts.Item that = (Contacts.Item) o; + return this.number().equalsIgnoreCase(that.number()); + } + + @Override + public String toString() { + return new StringBuilder(256) + .append("Contacts.Item { ") + .append("\nId: " + mId) + .append("\nNumber: " + mNumber) + .append("\nLast update time: " + mLastUpdateTime + "(" + + getTimeString(mLastUpdateTime) + ")") + .append("\nVolte capability timestamp: " + mVolteTimeStamp + "(" + + getTimeString(mVolteTimeStamp) + ")") + .append("\nVideo capability timestamp: " + mVideoTimeStamp + "(" + + getTimeString(mVideoTimeStamp) + ")") + .append("\nisValid: " + isValid()) + .append(" }") + .toString(); + } + + private String getTimeString(long time) { + if (time <= 0) { + time = System.currentTimeMillis(); + } + + Time tobj = new Time(); + tobj.set(time); + return String.format("%s.%s", tobj.format("%m-%d %H:%M:%S"), time % 1000); + } + + private long mId; + private String mNumber; + private String mName; + private long mLastUpdateTime; + private long mVolteTimeStamp; + private long mVideoTimeStamp; + } +} diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/DatabaseContentProvider.java b/rcs/presencepolling/src/com/android/service/ims/presence/DatabaseContentProvider.java new file mode 100644 index 0000000..0ae9bc9 --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/DatabaseContentProvider.java @@ -0,0 +1,434 @@ +/* + * 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.presence; + +import java.io.File; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteFullException; +import android.database.sqlite.SQLiteOpenHelper; +import android.net.Uri; + +import com.android.ims.internal.Logger; + +public abstract class DatabaseContentProvider extends ContentProvider { + static private Logger logger = Logger.getLogger("DatabaseContentProvider"); + + //Constants + public static final String ACTION_DEVICE_STORAGE_FULL = "com.android.vmm.DEVICE_STORAGE_FULL"; + + //Fields + protected SQLiteOpenHelper mDbHelper; + /*package*/final int mDbVersion; + private final String mDbName; + + /** + * Initializes the DatabaseContentProvider + * @param dbName the filename of the database + * @param dbVersion the current version of the database schema + * @param contentUri The base Uri of the syncable content in this provider + */ + public DatabaseContentProvider(String dbName, int dbVersion) { + super(); + mDbName = dbName; + mDbVersion = dbVersion; + } + + /** + * bootstrapDatabase() allows the implementer to set up their database + * after it is opened for the first time. this is a perfect place + * to create tables and triggers :) + * @param db + */ + protected void bootstrapDatabase(SQLiteDatabase db) { + } + + /** + * updgradeDatabase() allows the user to do whatever they like + * when the database is upgraded between versions. + * @param db - the SQLiteDatabase that will be upgraded + * @param oldVersion - the old version number as an int + * @param newVersion - the new version number as an int + * @return + */ + protected abstract boolean upgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion); + + /** + * downgradeDatabase() allows the user to do whatever they like when the + * database is downgraded between versions. + * + * @param db - the SQLiteDatabase that will be downgraded + * @param oldVersion - the old version number as an int + * @param newVersion - the new version number as an int + * @return + */ + protected abstract boolean downgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion); + + /** + * Safely wraps an ALTER TABLE table ADD COLUMN columnName columnType + * If columnType == null then it's set to INTEGER DEFAULT 0 + * @param db - db to alter + * @param table - table to alter + * @param columnDef + * @return + */ + protected static boolean addColumn(SQLiteDatabase db, String table, String columnName, + String columnType) { + StringBuilder sb = new StringBuilder(); + sb.append("ALTER TABLE ").append(table).append(" ADD COLUMN ").append(columnName).append( + ' ').append(columnType == null ? "INTEGER DEFAULT 0" : columnType).append(';'); + try { + db.execSQL(sb.toString()); + } catch (SQLiteException e) { + logger.debug("Alter table failed : "+ e.getMessage()); + return false; + } + return true; + } + + /** + * onDatabaseOpened() allows the user to do whatever they might + * need to do whenever the database is opened + * @param db - SQLiteDatabase that was just opened + */ + protected void onDatabaseOpened(SQLiteDatabase db) { + } + + private class DatabaseHelper extends SQLiteOpenHelper { + private File mDatabaseFile = null; + + DatabaseHelper(Context context, String name) { + // Note: context and name may be null for temp providers + super(context, name, null, mDbVersion); + mDatabaseFile = context.getDatabasePath(name); + } + + @Override + public void onCreate(SQLiteDatabase db) { + bootstrapDatabase(db); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + upgradeDatabase(db, oldVersion, newVersion); + } + + @Override + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + logger.debug("Enter: onDowngrade() - oldVersion = " + oldVersion + " newVersion = " + + newVersion); + downgradeDatabase(db, oldVersion, newVersion); + } + + @Override + public void onOpen(SQLiteDatabase db) { + onDatabaseOpened(db); + } + + @Override + public synchronized SQLiteDatabase getWritableDatabase() { + try { + return super.getWritableDatabase(); + } catch (InvalidDBException e) { + logger.error("getWritableDatabase - caught InvalidDBException "); + } + + // try to delete the database file + if (null != mDatabaseFile) { + logger.error("deleting mDatabaseFile."); + mDatabaseFile.delete(); + } + + // Return a freshly created database. + return super.getWritableDatabase(); + } + + @Override + public synchronized SQLiteDatabase getReadableDatabase() { + try { + return super.getReadableDatabase(); + } catch (InvalidDBException e) { + logger.error("getReadableDatabase - caught InvalidDBException "); + } + + // try to delete the database file + if (null != mDatabaseFile) { + logger.error("deleting mDatabaseFile."); + mDatabaseFile.delete(); + } + + // Return a freshly created database. + return super.getReadableDatabase(); + } + } + + /** + * deleteInternal allows getContentResolver().delete() to occur atomically + * via transactions and notify the uri automatically upon completion (provided + * rows were deleted) - otherwise, it functions exactly as getContentResolver.delete() + * would on a regular ContentProvider + * @param uri - uri to delete from + * @param selection - selection used for the uri + * @param selectionArgs - selection args replacing ?'s in the selection + * @return returns the number of rows deleted + */ + protected abstract int deleteInternal(final SQLiteDatabase db, Uri uri, String selection, + String[] selectionArgs); + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + int result = 0; + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + if (isClosed(db)) { + return result; + } + try { + //acquire reference to prevent from garbage collection + db.acquireReference(); + //beginTransaction can throw a runtime exception + //so it needs to be moved into the try + db.beginTransaction(); + result = deleteInternal(db, uri, selection, selectionArgs); + db.setTransactionSuccessful(); + } catch (SQLiteFullException fullEx) { + logger.error("" + fullEx); + sendStorageFullIntent(getContext()); + } catch (Exception e) { + logger.error("" + e); + } finally { + try { + db.endTransaction(); + } catch (SQLiteFullException fullEx) { + logger.error("" + fullEx); + sendStorageFullIntent(getContext()); + } catch (Exception e) { + logger.error("" + e); + } + //release reference + db.releaseReference(); + } + // don't check return value because it may be 0 if all rows deleted + getContext().getContentResolver().notifyChange(uri, null); + return result; + } + + /** + * insertInternal allows getContentResolver().insert() to occur atomically + * via transactions and notify the uri automatically upon completion (provided + * rows were added to the db) - otherwise, it functions exactly as getContentResolver().insert() + * would on a regular ContentProvider + * @param uri - uri on which to insert + * @param values - values to insert + * @return returns the uri of the row added + */ + protected abstract Uri insertInternal(final SQLiteDatabase db, Uri uri, ContentValues values); + + @Override + public Uri insert(Uri uri, ContentValues values) { + Uri result = null; + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + if (isClosed(db)) { + return result; + } + try { + db.acquireReference(); + //beginTransaction can throw a runtime exception + //so it needs to be moved into the try + db.beginTransaction(); + result = insertInternal(db, uri, values); + db.setTransactionSuccessful(); + } catch (SQLiteFullException fullEx) { + logger.warn("" + fullEx); + sendStorageFullIntent(getContext()); + } catch (Exception e) { + logger.warn("" + e); + } finally { + try { + db.endTransaction(); + } catch (SQLiteFullException fullEx) { + logger.warn("" + fullEx); + sendStorageFullIntent(getContext()); + } catch (Exception e) { + logger.warn("" + e); + } + db.releaseReference(); + } + if (result != null) { + getContext().getContentResolver().notifyChange(uri, null); + } + return result; + } + + @Override + public boolean onCreate() { + mDbHelper = new DatabaseHelper(getContext(), mDbName); + return onCreateInternal(); + } + + /** + * Called by onCreate. Should be overridden by any subclasses + * to handle the onCreate lifecycle event. + * + * @return + */ + protected boolean onCreateInternal() { + return true; + } + + /** + * queryInternal allows getContentResolver().query() to occur + * @param uri + * @param projection + * @param selection + * @param selectionArgs + * @param sortOrder + * @return Cursor holding the contents of the requested query + */ + protected abstract Cursor queryInternal(final SQLiteDatabase db, Uri uri, String[] projection, + String selection, String[] selectionArgs, String sortOrder); + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + SQLiteDatabase db = mDbHelper.getReadableDatabase(); + if (isClosed(db)) { + return null; + } + + try { + db.acquireReference(); + return queryInternal(db, uri, projection, selection, selectionArgs, sortOrder); + } finally { + db.releaseReference(); + } + } + + protected abstract int updateInternal(final SQLiteDatabase db, Uri uri, ContentValues values, + String selection, String[] selectionArgs); + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + int result = 0; + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + if (isClosed(db)) { + return result; + } + try { + db.acquireReference(); + //beginTransaction can throw a runtime exception + //so it needs to be moved into the try + db.beginTransaction(); + result = updateInternal(db, uri, values, selection, selectionArgs); + db.setTransactionSuccessful(); + } catch (SQLiteFullException fullEx) { + logger.error("" + fullEx); + sendStorageFullIntent(getContext()); + } catch (Exception e) { + logger.error("" + e); + } finally { + try { + db.endTransaction(); + } catch (SQLiteFullException fullEx) { + logger.error("" + fullEx); + sendStorageFullIntent(getContext()); + } catch (Exception e) { + logger.error("" + e); + } + db.releaseReference(); + } + if (result > 0) { + getContext().getContentResolver().notifyChange(uri, null); + } + return result; + } + + @Override + public int bulkInsert(Uri uri, ContentValues[] values) { + int added = 0; + if (values != null) { + int numRows = values.length; + SQLiteDatabase db = mDbHelper.getWritableDatabase(); + if (isClosed(db)) { + return added; + } + try { + db.acquireReference(); + //beginTransaction can throw a runtime exception + //so it needs to be moved into the try + db.beginTransaction(); + + for (int i = 0; i < numRows; i++) { + if (insertInternal(db, uri, values[i]) != null) { + added++; + } + } + db.setTransactionSuccessful(); + if (added > 0) { + getContext().getContentResolver().notifyChange(uri, null); + } + } catch (SQLiteFullException fullEx) { + logger.error("" + fullEx); + sendStorageFullIntent(getContext()); + } catch (Exception e) { + logger.error("" + e); + } finally { + try { + db.endTransaction(); + } catch (SQLiteFullException fullEx) { + logger.error("" + fullEx); + sendStorageFullIntent(getContext()); + } catch (Exception e) { + logger.error("" + e); + } + db.releaseReference(); + } + } + return added; + } + + private void sendStorageFullIntent(Context context) { + Intent fullStorageIntent = new Intent(ACTION_DEVICE_STORAGE_FULL); + context.sendBroadcast(fullStorageIntent); + } + + private boolean isClosed(SQLiteDatabase db) { + if (db == null || !db.isOpen()) { + logger.warn("Null DB returned from DBHelper for a writable/readable database."); + return true; + } + return false; + } + +} diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/DeviceBoot.java b/rcs/presencepolling/src/com/android/service/ims/presence/DeviceBoot.java new file mode 100644 index 0000000..096d342 --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/DeviceBoot.java @@ -0,0 +1,81 @@ +/* + * 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.presence; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.SystemProperties; + +import com.android.ims.RcsPresence; +import com.android.ims.RcsPresence.PublishState; +import com.android.ims.internal.Logger; + +/** + * Device boot event receiver: automatically starts the RCS service + */ +public class DeviceBoot extends BroadcastReceiver { + /** + * The logger + */ + private Logger logger = Logger.getLogger("PresencePolling", + this.getClass().getName()); + + private static boolean sServiceStarted = false; + private static boolean sEabServiceStarted = false; + + @Override + public void onReceive(Context context, Intent intent) { + logger.debug("onReceive() in PresencePolling, intent: " + + intent + ", context: " + context); + + String action = intent.getAction(); + if (RcsPresence.ACTION_PUBLISH_STATE_CHANGED.equalsIgnoreCase(action)) { + int state = intent.getIntExtra( + RcsPresence.EXTRA_PUBLISH_STATE, + RcsPresence.PublishState.PUBLISH_STATE_NOT_PUBLISHED); + logger.debug("Publish state: " + state); + } + + String rcsSupported = SystemProperties.get("persist.rcs.supported"); + logger.info("persist.rcs.supported: " + rcsSupported); + if (! "1".equals(rcsSupported)) { + return; + } + + if (! sServiceStarted) { + sServiceStarted = LauncherUtils.launchPollingService(context); + } + + if(!sEabServiceStarted) { + sEabServiceStarted = LauncherUtils.launchEabService(context); + } + } +} + diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/DeviceShutdown.java b/rcs/presencepolling/src/com/android/service/ims/presence/DeviceShutdown.java new file mode 100644 index 0000000..193fd09 --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/DeviceShutdown.java @@ -0,0 +1,54 @@ +/* + * 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.presence; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.android.ims.internal.Logger; + +/** + * Device shutdown event receiver: automatically stops the RCS service + */ +public class DeviceShutdown extends BroadcastReceiver { + /** + * The logger + */ + private Logger logger = Logger.getLogger(this.getClass().getName()); + + @Override + public void onReceive(Context context, Intent intent) { + logger.debug("onReceive() in PresencePolling, intent: " + + intent + ", context: " + context); + + LauncherUtils.stopPollingService(context); + } +} + diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/EABContactManager.java b/rcs/presencepolling/src/com/android/service/ims/presence/EABContactManager.java new file mode 100644 index 0000000..cefe50b --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/EABContactManager.java @@ -0,0 +1,932 @@ +/* + * 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.presence; + +import android.content.Context; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.CursorWrapper; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.net.Uri; +import android.text.format.Time; +import android.text.TextUtils; + +import com.android.ims.RcsPresenceInfo; +import com.android.ims.internal.Logger; + +import java.util.ArrayList; +import java.util.List; + +public class EABContactManager { + private Logger logger = Logger.getLogger(this.getClass().getName()); + + /** + * An identifier for a particular EAB contact number, unique across the system. + * Clients use this ID to make subsequent calls related to the contact. + */ + public final static String COLUMN_ID = Contacts.Impl._ID; + + /** + * Timestamp when the presence was last updated, in {@link System#currentTimeMillis + * System.currentTimeMillis()} (wall clock time in UTC). + */ + public final static String COLUMN_LAST_UPDATED_TIMESTAMP = + Contacts.Impl.CONTACT_LAST_UPDATED_TIMESTAMP; + + /** + * columns to request from EABProvider. + * @hide + */ + public static final String[] CONTACT_COLUMNS = new String[] { + Contacts.Impl._ID, + Contacts.Impl.CONTACT_NUMBER, + Contacts.Impl.CONTACT_NAME, + Contacts.Impl.CONTACT_LAST_UPDATED_TIMESTAMP, + Contacts.Impl.VOLTE_CALL_SERVICE_CONTACT_ADDRESS, + Contacts.Impl.VOLTE_CALL_CAPABILITY, + Contacts.Impl.VOLTE_CALL_CAPABILITY_TIMESTAMP, + Contacts.Impl.VOLTE_CALL_AVAILABILITY, + Contacts.Impl.VOLTE_CALL_AVAILABILITY_TIMESTAMP, + Contacts.Impl.VIDEO_CALL_SERVICE_CONTACT_ADDRESS, + Contacts.Impl.VIDEO_CALL_CAPABILITY, + Contacts.Impl.VIDEO_CALL_CAPABILITY_TIMESTAMP, + Contacts.Impl.VIDEO_CALL_AVAILABILITY, + Contacts.Impl.VIDEO_CALL_AVAILABILITY_TIMESTAMP, + Contacts.Impl.VOLTE_STATUS + }; + + /** + * This class contains all the information necessary to request a new contact. + */ + public static class Request { + private long mId = -1; + private String mContactNumber = null; + private String mContactName = null; + + private int mVolteCallCapability = -1; + private long mVolteCallCapabilityTimeStamp = -1; + private int mVolteCallAvailability = -1; + private long mVolteCallAvailabilityTimeStamp = -1; + private String mVolteCallServiceContactAddress = null; + + private int mVideoCallCapability = -1; + private long mVideoCallCapabilityTimeStamp = -1; + private int mVideoCallAvailability = -1; + private long mVideoCallAvailabilityTimeStamp = -1; + private String mVideoCallServiceContactAddress = null; + + private long mContactLastUpdatedTimeStamp = -1; + private int mFieldUpdatedFlags = 0; + + private static int sVolteCallCapabilityFlag = 0x0001; + private static int sVolteCallCapabilityTimeStampFlag = 0x0002; + private static int sVolteCallAvailabilityFlag = 0x0004; + private static int sVolteCallAvailabilityTimeStampFlag = 0x0008; + private static int sVolteCallServiceContactAddressFlag = 0x0010; + private static int sVideoCallCapabilityFlag = 0x0020; + private static int sVideoCallCapabilityTimeStampFlag = 0x0040; + private static int sVideoCallAvailabilityFlag = 0x0080; + private static int sVideoCallAvailabilityTimeStampFlag = 0x0100; + private static int sVideoCallServiceContactAddressFlag = 0x0200; + private static int sContactLastUpdatedTimeStampFlag = 0x0400; + + /** + * @param id the contact id. + */ + public Request(long id) { + if (id < 0) { + throw new IllegalArgumentException( + "Can't update EAB presence item with id: " + id); + } + + mId = id; + } + + public Request(String number) { + if (TextUtils.isEmpty(number)) { + throw new IllegalArgumentException( + "Can't update EAB presence item with number: " + number); + } + + mContactNumber = number; + } + + public long getContactId() { + return mId; + } + + public String getContactNumber() { + return mContactNumber; + } + + /** + * Set Volte call service contact address. + * @param address contact from NOTIFY + * @return this object + */ + public Request setVolteCallServiceContactAddress(String address) { + mVolteCallServiceContactAddress = address; + mFieldUpdatedFlags |= sVolteCallServiceContactAddressFlag; + return this; + } + + /** + * Set Volte call capability. + * @param b wheter volte call is supported or not + * @return this object + */ + public Request setVolteCallCapability(boolean b) { + mVolteCallCapability = b ? 1 : 0; + mFieldUpdatedFlags |= sVolteCallCapabilityFlag; + return this; + } + + public Request setVolteCallCapability(int val) { + mVolteCallCapability = val; + mFieldUpdatedFlags |= sVolteCallCapabilityFlag; + return this; + } + + /** + * Set Volte call availability. + * @param b wheter volte call is available or not + * @return this object + */ + public Request setVolteCallAvailability(boolean b) { + mVolteCallAvailability = b ? 1 : 0; + mFieldUpdatedFlags |= sVolteCallAvailabilityFlag; + return this; + } + + public Request setVolteCallAvailability(int val) { + mVolteCallAvailability = val; + mFieldUpdatedFlags |= sVolteCallAvailabilityFlag; + return this; + } + + /** + * Set Video call service contact address. + * @param address contact from NOTIFY. + * @return this object + */ + public Request setVideoCallServiceContactAddress(String address) { + mVideoCallServiceContactAddress = address; + mFieldUpdatedFlags |= sVideoCallServiceContactAddressFlag; + return this; + } + + /** + * Set Video call capability. + * @param b wheter volte call is supported or not + * @return this object + */ + public Request setVideoCallCapability(boolean b) { + mVideoCallCapability = b ? 1 : 0; + mFieldUpdatedFlags |= sVideoCallCapabilityFlag; + return this; + } + + public Request setVideoCallCapability(int val) { + mVideoCallCapability = val; + mFieldUpdatedFlags |= sVideoCallCapabilityFlag; + return this; + } + + /** + * Set Video call availability. + * @param b wheter volte call is available or not + * @return this object + */ + public Request setVideoCallAvailability(boolean b) { + mVideoCallAvailability = b ? 1 : 0; + mFieldUpdatedFlags |= sVideoCallAvailabilityFlag; + return this; + } + + public Request setVideoCallAvailability(int val) { + mVideoCallAvailability = val; + mFieldUpdatedFlags |= sVideoCallAvailabilityFlag; + return this; + } + + /** + * Set the update timestamp. + * @param long timestamp the last update timestamp + * @return this object + */ + public Request setLastUpdatedTimeStamp(long timestamp) { + mContactLastUpdatedTimeStamp = timestamp; + mFieldUpdatedFlags |= sContactLastUpdatedTimeStampFlag; + return this; + } + + public Request setVolteCallCapabilityTimeStamp(long timestamp) { + mVolteCallCapabilityTimeStamp = timestamp; + mFieldUpdatedFlags |= sVolteCallCapabilityTimeStampFlag; + return this; + } + + public Request setVolteCallAvailabilityTimeStamp(long timestamp) { + mVolteCallAvailabilityTimeStamp = timestamp; + mFieldUpdatedFlags |= sVolteCallAvailabilityTimeStampFlag; + return this; + } + + public Request setVideoCallCapabilityTimeStamp(long timestamp) { + mVideoCallCapabilityTimeStamp = timestamp; + mFieldUpdatedFlags |= sVideoCallCapabilityTimeStampFlag; + return this; + } + + public Request setVideoCallAvailabilityTimeStamp(long timestamp) { + mVideoCallAvailabilityTimeStamp = timestamp; + mFieldUpdatedFlags |= sVideoCallAvailabilityTimeStampFlag; + return this; + } + + public Request reset() { + mVolteCallCapability = -1; + mVolteCallCapabilityTimeStamp = -1; + mVolteCallAvailability = -1; + mVolteCallAvailabilityTimeStamp = -1; + mVolteCallServiceContactAddress = null; + + mVideoCallCapability = -1; + mVideoCallCapabilityTimeStamp = -1; + mVideoCallAvailability = -1; + mVideoCallAvailabilityTimeStamp = -1; + mVideoCallServiceContactAddress = null; + + mContactLastUpdatedTimeStamp = -1; + mFieldUpdatedFlags = 0; + return this; + } + + /** + * @return ContentValues to be passed to EABProvider.update() + */ + ContentValues toContentValues() { + ContentValues values = new ContentValues(); + + if ((mFieldUpdatedFlags & sVolteCallCapabilityFlag) > 0) { + values.put(Contacts.Impl.VOLTE_CALL_CAPABILITY, + mVolteCallCapability); + } + if ((mFieldUpdatedFlags & sVolteCallCapabilityTimeStampFlag) > 0) { + values.put(Contacts.Impl.VOLTE_CALL_CAPABILITY_TIMESTAMP, + mVolteCallCapabilityTimeStamp); + } + if ((mFieldUpdatedFlags & sVolteCallAvailabilityFlag) > 0) { + values.put(Contacts.Impl.VOLTE_CALL_AVAILABILITY, + mVolteCallAvailability); + } + if ((mFieldUpdatedFlags & sVolteCallAvailabilityTimeStampFlag) > 0) { + values.put(Contacts.Impl.VOLTE_CALL_AVAILABILITY_TIMESTAMP, + mVolteCallAvailabilityTimeStamp); + } + if ((mFieldUpdatedFlags & sVolteCallServiceContactAddressFlag) > 0) { + values.put(Contacts.Impl.VOLTE_CALL_SERVICE_CONTACT_ADDRESS, + mVolteCallServiceContactAddress); + } + + if ((mFieldUpdatedFlags & sVideoCallCapabilityFlag) > 0) { + values.put(Contacts.Impl.VIDEO_CALL_CAPABILITY, + mVideoCallCapability); + } + if ((mFieldUpdatedFlags & sVideoCallCapabilityTimeStampFlag) > 0) { + values.put(Contacts.Impl.VIDEO_CALL_CAPABILITY_TIMESTAMP, + mVideoCallCapabilityTimeStamp); + } + if ((mFieldUpdatedFlags & sVideoCallAvailabilityFlag) > 0) { + values.put(Contacts.Impl.VIDEO_CALL_AVAILABILITY, + mVideoCallAvailability); + } + if ((mFieldUpdatedFlags & sVideoCallAvailabilityTimeStampFlag) > 0) { + values.put(Contacts.Impl.VIDEO_CALL_AVAILABILITY_TIMESTAMP, + mVideoCallAvailabilityTimeStamp); + } + if ((mFieldUpdatedFlags & sVideoCallServiceContactAddressFlag) > 0) { + values.put(Contacts.Impl.VIDEO_CALL_SERVICE_CONTACT_ADDRESS, + mVideoCallServiceContactAddress); + } + + if ((mFieldUpdatedFlags & sContactLastUpdatedTimeStampFlag) > 0 ) { + values.put(Contacts.Impl.CONTACT_LAST_UPDATED_TIMESTAMP, + mContactLastUpdatedTimeStamp); + } + + return values; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(512); + sb.append("EABContactManager.Request { "); + if (mId != -1) { + sb.append("\nId: " + mId); + } + if (!TextUtils.isEmpty(mContactNumber)) { + sb.append("\nContact Number: " + mContactNumber); + } + if (!TextUtils.isEmpty(mContactName)) { + sb.append("\nContact Name: " + mContactName); + } + + if ((mFieldUpdatedFlags & sVolteCallCapabilityFlag) > 0) { + sb.append("\nVolte call capability: " + mVolteCallCapability); + } + if ((mFieldUpdatedFlags & sVolteCallCapabilityTimeStampFlag) > 0) { + sb.append("\nVolte call capability timestamp: " + mVolteCallCapabilityTimeStamp + + "(" + getTimeString(mVolteCallCapabilityTimeStamp) + ")"); + } + if ((mFieldUpdatedFlags & sVolteCallAvailabilityFlag) > 0) { + sb.append("\nVolte call availability: " + mVolteCallAvailability); + } + if ((mFieldUpdatedFlags & sVolteCallAvailabilityTimeStampFlag) > 0) { + sb.append("\nVolte call availablity timestamp: " + mVolteCallAvailabilityTimeStamp + + "(" + getTimeString(mVolteCallAvailabilityTimeStamp) + ")"); + } + if ((mFieldUpdatedFlags & sVolteCallServiceContactAddressFlag) > 0) { + sb.append("\nVolte Call Service address: " + mVolteCallServiceContactAddress); + } + + if ((mFieldUpdatedFlags & sVideoCallCapabilityFlag) > 0) { + sb.append("\nVideo call capability: " + mVideoCallCapability); + } + if ((mFieldUpdatedFlags & sVideoCallCapabilityTimeStampFlag) > 0) { + sb.append("\nVideo call capability timestamp: " + mVideoCallCapabilityTimeStamp + + "(" + getTimeString(mVideoCallCapabilityTimeStamp) + ")"); + } + if ((mFieldUpdatedFlags & sVideoCallAvailabilityFlag) > 0) { + sb.append("\nVideo call availability: " + mVideoCallAvailability); + } + if ((mFieldUpdatedFlags & sVideoCallAvailabilityTimeStampFlag) > 0) { + sb.append("\nVideo call availablity timestamp: " + mVideoCallAvailabilityTimeStamp + + "(" + getTimeString(mVideoCallAvailabilityTimeStamp) + ")"); + } + if ((mFieldUpdatedFlags & sVideoCallServiceContactAddressFlag) > 0) { + sb.append("\nVideo Call Service address: " + mVideoCallServiceContactAddress); + } + + if ((mFieldUpdatedFlags & sContactLastUpdatedTimeStampFlag) > 0 ) { + sb.append("\nContact last update time: " + mContactLastUpdatedTimeStamp + + "(" + getTimeString(mContactLastUpdatedTimeStamp) + ")"); + } + + sb.append(" }"); + return sb.toString(); + } + } + + /** + * This class may be used to filter EABProvider queries. + */ + public static class Query { + /** + * Constant for use with {@link #orderBy} + * @hide + */ + public static final int ORDER_ASCENDING = 1; + + /** + * Constant for use with {@link #orderBy} + * @hide + */ + public static final int ORDER_DESCENDING = 2; + + private long[] mIds = null; + private String mContactNumber = null; + private List<String> mTimeFilters = null; + private String mOrderByColumn = COLUMN_LAST_UPDATED_TIMESTAMP; + private int mOrderDirection = ORDER_ASCENDING; + + /** + * Include only the contacts with the given IDs. + * @return this object + */ + public Query setFilterById(long... ids) { + mIds = ids; + return this; + } + + /** + * Include only the contacts with the given number. + * @return this object + */ + public Query setFilterByNumber(String number) { + mContactNumber = number; + return this; + } + + /** + * Include the contacts that meet the specified time condition. + * @return this object + */ + public Query setFilterByTime(String selection) { + if (mTimeFilters == null) { + mTimeFilters = new ArrayList<String>(); + } + + mTimeFilters.add(selection); + return this; + } + + /** + * Include only the contacts that has not been updated before the last time. + * @return this object + */ + public Query setFilterByTime(String column, long last) { + if (mTimeFilters == null) { + mTimeFilters = new ArrayList<String>(); + } + + mTimeFilters.add(column + "<='" + last + "'"); + return this; + } + + /** + * Include only the contacts that has not been updated after the eariest time. + * @return this object + */ + public Query setFilterByEarliestTime(String column, long earliest) { + if (mTimeFilters == null) { + mTimeFilters = new ArrayList<String>(); + } + + mTimeFilters.add(column + ">='" + earliest + "'"); + return this; + } + + /** + * Change the sort order of the returned Cursor. + * + * @param column one of the COLUMN_* constants; currently, only + * {@link #COLUMN_LAST_MODIFIED_TIMESTAMP} and {@link #COLUMN_TOTAL_SIZE_BYTES} are + * supported. + * @param direction either {@link #ORDER_ASCENDING} or {@link #ORDER_DESCENDING} + * @return this object + * @hide + */ + public Query orderBy(String column, int direction) { + if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) { + throw new IllegalArgumentException("Invalid direction: " + direction); + } + + if (column.equals(COLUMN_ID)) { + mOrderByColumn = Contacts.Impl._ID; + } else if (column.equals(COLUMN_LAST_UPDATED_TIMESTAMP)) { + mOrderByColumn = Contacts.Impl.CONTACT_LAST_UPDATED_TIMESTAMP; + } else { + throw new IllegalArgumentException("Cannot order by " + column); + } + mOrderDirection = direction; + return this; + } + + /** + * Run this query using the given ContentResolver. + * @param projection the projection to pass to ContentResolver.query() + * @return the Cursor returned by ContentResolver.query() + */ + Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri) { + Uri uri = baseUri; + List<String> selectionParts = new ArrayList<String>(); + String[] selectionArgs = null; + + if (mIds != null) { + selectionParts.add(getWhereClauseForIds(mIds)); + selectionArgs = getWhereArgsForIds(mIds); + } + + if (!TextUtils.isEmpty(mContactNumber)) { + String number = mContactNumber; + if (number.startsWith("tel:")) { + number = number.substring(4); + } + String escapedPhoneNumber = DatabaseUtils.sqlEscapeString(number); + String cselection = "(" + Contacts.Impl.CONTACT_NUMBER + "=" + escapedPhoneNumber; + cselection += " OR PHONE_NUMBERS_EQUAL(" + Contacts.Impl.CONTACT_NUMBER + ", "; + cselection += escapedPhoneNumber + ", 0))"; + + selectionParts.add(cselection); + } + + if (mTimeFilters != null) { + String cselection = joinStrings(" OR ", mTimeFilters); + int size = mTimeFilters.size(); + if (size > 1) { + cselection = "(" + cselection + ")"; + } + selectionParts.add(cselection); + } + + String selection = joinStrings(" AND ", selectionParts); + String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC"); + String orderBy = mOrderByColumn + " " + orderDirection; + + return resolver.query(uri, projection, selection, selectionArgs, orderBy); + } + + private String joinStrings(String joiner, Iterable<String> parts) { + StringBuilder builder = new StringBuilder(); + boolean first = true; + for (String part : parts) { + if (!first) { + builder.append(joiner); + } + builder.append(part); + first = false; + } + return builder.toString(); + } + + @Override + public String toString() { + List<String> selectionParts = new ArrayList<String>(); + String[] selectionArgs = null; + + if (mIds != null) { + selectionParts.add(getWhereClauseForIds(mIds)); + selectionArgs = getWhereArgsForIds(mIds); + } + + if (!TextUtils.isEmpty(mContactNumber)) { + String number = mContactNumber; + if (number.startsWith("tel:")) { + number = number.substring(4); + } + String escapedPhoneNumber = DatabaseUtils.sqlEscapeString(number); + String cselection = "(" + Contacts.Impl.CONTACT_NUMBER + "=" + escapedPhoneNumber; + cselection += " OR PHONE_NUMBERS_EQUAL(" + Contacts.Impl.CONTACT_NUMBER + ", "; + cselection += escapedPhoneNumber + ", 0))"; + + selectionParts.add(cselection); + } + + if (mTimeFilters != null) { + String cselection = joinStrings(" OR ", mTimeFilters); + int size = mTimeFilters.size(); + if (size > 1) { + cselection = "(" + cselection + ")"; + } + selectionParts.add(cselection); + } + + String selection = joinStrings(" AND ", selectionParts); + String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC"); + String orderBy = mOrderByColumn + " " + orderDirection; + + StringBuilder sb = new StringBuilder(512); + sb.append("EABContactManager.Query { "); + sb.append("\nSelection: " + selection); + sb.append("\nSelectionArgs: " + selectionArgs); + sb.append("\nOrderBy: " + orderBy); + sb.append(" }"); + return sb.toString(); + } + } + + private ContentResolver mResolver; + private String mPackageName; + private Uri mBaseUri = Contacts.Impl.CONTENT_URI; + + /** + * @hide + */ + public EABContactManager(ContentResolver resolver, String packageName) { + mResolver = resolver; + mPackageName = packageName; + } + + /** + * Query the presence manager about contacts that have been requested. + * @param query parameters specifying filters for this query + * @return a Cursor over the result set of contacts, with columns consisting of all the + * COLUMN_* constants. + */ + public Cursor query(Query query) { + Cursor underlyingCursor = query.runQuery(mResolver, CONTACT_COLUMNS, mBaseUri); + if (underlyingCursor == null) { + return null; + } + + return new CursorTranslator(underlyingCursor, mBaseUri); + } + + /** + * Update a contact presence status. + * + * @param request the parameters specifying this contact presence + * @return an ID for the contact, unique across the system. This ID is used to make future + * calls related to this contact. + */ + public int update(Request request) { + if (request == null) { + return 0; + } + + long id = request.getContactId(); + String number = request.getContactNumber(); + if ((id == -1) && TextUtils.isEmpty(number)) { + throw new IllegalArgumentException("invalid request for contact update."); + } + + ContentValues values = request.toContentValues(); + if (id != -1) { + logger.debug("Update contact " + id + " with request: " + values); + return mResolver.update(ContentUris.withAppendedId(mBaseUri, id), values, + null, null); + } else { + Query query = new Query().setFilterByNumber(number) + .orderBy(COLUMN_ID, Query.ORDER_ASCENDING); + long[] ids = null; + Cursor cursor = null; + try { + cursor = query(query); + if (cursor == null) { + return 0; + } + int count = cursor.getCount(); + if (count == 0) { + return 0; + } + + ids = new long[count]; + int idx = 0; + for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + id = cursor.getLong(cursor.getColumnIndex(Contacts.Impl._ID)); + ids[idx++] = id; + if (idx >= count) { + break; + } + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + + if ((ids == null) || (ids.length == 0)) { + return 0; + } + + if (ids.length == 1) { + logger.debug("Update contact " + ids[0] + " with request: " + values); + return mResolver.update(ContentUris.withAppendedId(mBaseUri, ids[0]), values, + null, null); + } + + logger.debug("Update contact " + number + " with request: " + values); + return mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), + getWhereArgsForIds(ids)); + } + } + + /** + * Get the EABProvider URI for the contact with the given ID. + * + * @hide + */ + public Uri getContactUri(long id) { + return ContentUris.withAppendedId(mBaseUri, id); + } + + /** + * Get a parameterized SQL WHERE clause to select a bunch of IDs. + */ + static String getWhereClauseForIds(long[] ids) { + StringBuilder whereClause = new StringBuilder(); + whereClause.append("("); + for (int i = 0; i < ids.length; i++) { + if (i > 0) { + whereClause.append("OR "); + } + whereClause.append(COLUMN_ID); + whereClause.append(" = ? "); + } + whereClause.append(")"); + return whereClause.toString(); + } + + /** + * Get the selection args for a clause returned by {@link #getWhereClauseForIds(long[])}. + */ + static String[] getWhereArgsForIds(long[] ids) { + String[] whereArgs = new String[ids.length]; + for (int i = 0; i < ids.length; i++) { + whereArgs[i] = Long.toString(ids[i]); + } + return whereArgs; + } + + static String getTimeString(long time) { + if (time <= 0) { + time = System.currentTimeMillis(); + } + + Time tobj = new Time(); + tobj.set(time); + return String.format("%s.%s", tobj.format("%m-%d %H:%M:%S"), time % 1000); + } + + /** + * This class wraps a cursor returned by EABProvider -- the "underlying cursor" -- and + * presents a different set of columns, those defined in the COLUMN_* constants. + * Some columns correspond directly to underlying values while others are computed from + * underlying data. + */ + private static class CursorTranslator extends CursorWrapper { + private Uri mBaseUri; + + public CursorTranslator(Cursor cursor, Uri baseUri) { + super(cursor); + mBaseUri = baseUri; + } + + @Override + public int getInt(int columnIndex) { + return (int) getLong(columnIndex); + } + + @Override + public long getLong(int columnIndex) { + return super.getLong(columnIndex); + } + + @Override + public String getString(int columnIndex) { + return super.getString(columnIndex); + } + } + + public void updateAllCapabilityToUnknown() { + if (mResolver == null) { + logger.error("updateAllCapabilityToUnknown, mResolver=null"); + return; + } + + ContentValues values = new ContentValues(); + values.put(Contacts.Impl.CONTACT_LAST_UPDATED_TIMESTAMP, (String)null); + values.put(Contacts.Impl.VOLTE_CALL_SERVICE_CONTACT_ADDRESS, (String)null); + values.put(Contacts.Impl.VOLTE_CALL_CAPABILITY, (String)null); + values.put(Contacts.Impl.VOLTE_CALL_CAPABILITY_TIMESTAMP, (String)null); + values.put(Contacts.Impl.VOLTE_CALL_AVAILABILITY, (String)null); + values.put(Contacts.Impl.VOLTE_CALL_AVAILABILITY_TIMESTAMP, (String)null); + + values.put(Contacts.Impl.VIDEO_CALL_SERVICE_CONTACT_ADDRESS, (String)null); + values.put(Contacts.Impl.VIDEO_CALL_CAPABILITY, (String)null); + values.put(Contacts.Impl.VIDEO_CALL_CAPABILITY_TIMESTAMP, (String)null); + values.put(Contacts.Impl.VIDEO_CALL_AVAILABILITY, (String)null); + values.put(Contacts.Impl.VIDEO_CALL_AVAILABILITY_TIMESTAMP, (String)null); + + try { + int count = ContactDbUtil.resetVtCapability(mResolver); + logger.print("update Contact DB: updateAllCapabilityToUnknown count=" + count); + + count = mResolver.update(Contacts.Impl.CONTENT_URI, + values, null, null); + logger.print("update EAB DB: updateAllCapabilityToUnknown count=" + count); + } catch (Exception ex) { + logger.error("updateAllCapabilityToUnknown exception: " + ex); + } + } + + public void updateAllVtCapabilityToUnknown() { + if (mResolver == null) { + logger.error("updateAllVtCapabilityToUnknown mResolver=null"); + return; + } + + ContentValues values = new ContentValues(); + values.put(Contacts.Impl.CONTACT_LAST_UPDATED_TIMESTAMP, (String)null); + values.put(Contacts.Impl.VIDEO_CALL_SERVICE_CONTACT_ADDRESS, (String)null); + values.put(Contacts.Impl.VIDEO_CALL_CAPABILITY, (String)null); + values.put(Contacts.Impl.VIDEO_CALL_CAPABILITY_TIMESTAMP, (String)null); + values.put(Contacts.Impl.VIDEO_CALL_AVAILABILITY, (String)null); + values.put(Contacts.Impl.VIDEO_CALL_AVAILABILITY_TIMESTAMP, (String)null); + + try { + int count = ContactDbUtil.resetVtCapability(mResolver); + logger.print("update Contact DB: updateAllVtCapabilityToUnknown count=" + count); + + count = mResolver.update(Contacts.Impl.CONTENT_URI, + values, null, null); + logger.print("update EAB DB: updateAllVtCapabilityToUnknown count=" + count); + } catch (Exception ex) { + logger.error("updateAllVtCapabilityToUnknown exception: " + ex); + } + } + + // if updateLastTimestamp is true, the rcsPresenceInfo is from network. + // if the updateLastTimestamp is false, It is used to update the availabilty to unknown only. + // And the availability will be updated only when it has expired, so we don't update the + // timestamp to make sure the availablity still in expired status and will be subscribed from + // network afterwards. + public int update(RcsPresenceInfo rcsPresenceInfo, boolean updateLastTimestamp) { + if (rcsPresenceInfo == null) { + return 0; + } + + String number = rcsPresenceInfo.getContactNumber(); + if (TextUtils.isEmpty(number)) { + logger.error("Failed to update for the contact number is empty."); + return 0; + } + + ContentValues values = new ContentValues(); + + int volteStatus = rcsPresenceInfo.getVolteStatus(); + if(volteStatus != RcsPresenceInfo.VolteStatus.VOLTE_UNKNOWN) { + values.put(Contacts.Impl.VOLTE_STATUS, volteStatus); + } + + if(updateLastTimestamp){ + values.put(Contacts.Impl.CONTACT_LAST_UPDATED_TIMESTAMP, + (long)System.currentTimeMillis()); + } + + int lteCallCapability = rcsPresenceInfo.getServiceState( + RcsPresenceInfo.ServiceType.VOLTE_CALL); + long lteCallTimestamp = rcsPresenceInfo.getTimeStamp( + RcsPresenceInfo.ServiceType.VOLTE_CALL); + values.put(Contacts.Impl.VOLTE_CALL_AVAILABILITY, lteCallCapability); + values.put(Contacts.Impl.VOLTE_CALL_AVAILABILITY_TIMESTAMP, (long)lteCallTimestamp); + if(rcsPresenceInfo.getServiceState(RcsPresenceInfo.ServiceType.VOLTE_CALL) + != RcsPresenceInfo.ServiceState.UNKNOWN){ + String lteCallContactAddress = + rcsPresenceInfo.getServiceContact(RcsPresenceInfo.ServiceType.VOLTE_CALL); + if (!TextUtils.isEmpty(lteCallContactAddress)) { + values.put(Contacts.Impl.VOLTE_CALL_SERVICE_CONTACT_ADDRESS, lteCallContactAddress); + } + + values.put(Contacts.Impl.VOLTE_CALL_CAPABILITY, lteCallCapability); + values.put(Contacts.Impl.VOLTE_CALL_CAPABILITY_TIMESTAMP, lteCallTimestamp); + } + + int videoCallCapability = rcsPresenceInfo.getServiceState( + RcsPresenceInfo.ServiceType.VT_CALL); + long videoCallTimestamp = rcsPresenceInfo.getTimeStamp( + RcsPresenceInfo.ServiceType.VT_CALL); + values.put(Contacts.Impl.VIDEO_CALL_AVAILABILITY, videoCallCapability); + values.put(Contacts.Impl.VIDEO_CALL_AVAILABILITY_TIMESTAMP, (long)videoCallTimestamp); + if(rcsPresenceInfo.getServiceState(RcsPresenceInfo.ServiceType.VT_CALL) + != RcsPresenceInfo.ServiceState.UNKNOWN){ + String videoCallContactAddress = + rcsPresenceInfo.getServiceContact(RcsPresenceInfo.ServiceType.VT_CALL); + if (!TextUtils.isEmpty(videoCallContactAddress)) { + values.put(Contacts.Impl.VIDEO_CALL_SERVICE_CONTACT_ADDRESS, + videoCallContactAddress); + } + + values.put(Contacts.Impl.VIDEO_CALL_CAPABILITY, videoCallCapability); + values.put(Contacts.Impl.VIDEO_CALL_CAPABILITY_TIMESTAMP, videoCallTimestamp); + } + + int count = 0; + try{ + count = ContactDbUtil.updateVtCapability(mResolver, number, + (videoCallCapability == RcsPresenceInfo.ServiceState.ONLINE)); + logger.print("update rcsPresenceInfo to Contact DB, count=" + count); + + count = mResolver.update(Contacts.Impl.CONTENT_URI, values, + "PHONE_NUMBERS_EQUAL(contact_number, ?, 0)", new String[] {number}); + logger.print("update rcsPresenceInfo to EAB: update count=" + count + + " rcsPresenceInfo=" + rcsPresenceInfo); + }catch(Exception e){ + logger.error("updateCapability exception", e); + } + + return count; + } +} + diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/EABDbUtil.java b/rcs/presencepolling/src/com/android/service/ims/presence/EABDbUtil.java new file mode 100644 index 0000000..8e851dc --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/EABDbUtil.java @@ -0,0 +1,428 @@ +/* + * 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.presence; + +import static com.android.service.ims.presence.AccountUtil.ACCOUNT_TYPE; + +import java.util.ArrayList; +import java.util.List; + +import android.content.ContentProviderOperation; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.OperationApplicationException; +import android.database.Cursor; +import android.database.sqlite.SQLiteException; +import android.os.RemoteException; +import android.provider.ContactsContract; +import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.Data; +import android.provider.ContactsContract.CommonDataKinds.Phone; + +import com.android.ims.internal.ContactNumberUtils; +import com.android.ims.internal.EABContract; +import com.android.ims.internal.Logger; + +public class EABDbUtil { + static private Logger logger = Logger.getLogger("EABDbUtil"); + + public static boolean validateAndSyncFromContactsDb(Context context) { + logger.debug("Enter validateAndSyncFromContactsDb"); + boolean response = true; + // Get the last stored contact changed timestamp and sync only delta contacts. + long contactLastChange = SharedPrefUtil.getLastContactChangedTimestamp(context, 0); + logger.debug("contact last updated time before init :" + contactLastChange); + ContentResolver contentResolver = context.getContentResolver(); + String[] projection = new String[] { ContactsContract.Contacts._ID, + ContactsContract.Contacts.HAS_PHONE_NUMBER, + ContactsContract.Contacts.DISPLAY_NAME, + ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP }; + String selection = ContactsContract.Contacts.HAS_PHONE_NUMBER + "> '0' AND " + + ContactsContract.Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + + " >'" + contactLastChange + "'"; + String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " asc"; + Cursor cursor = contentResolver.query(Contacts.CONTENT_URI, projection, selection, + null, sortOrder); + ArrayList<PresenceContact> allEligibleContacts = new ArrayList<PresenceContact>(); + + logger.debug("cursor count : " + cursor.getCount()); + if (cursor.moveToFirst()) { + do { + String id = cursor.getString(cursor.getColumnIndex(Contacts._ID)); + Long time = cursor.getLong(cursor.getColumnIndex( + Contacts.CONTACT_LAST_UPDATED_TIMESTAMP)); + // Update the latest contact last modified timestamp. + if (contactLastChange < time) { + contactLastChange = time; + } + String[] commonDataKindsProjection = new String[] { + ContactsContract.CommonDataKinds.Phone.NUMBER, + ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, + ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID, + ContactsContract.CommonDataKinds.Phone.CONTACT_ID }; + Cursor pCur = contentResolver.query( + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + commonDataKindsProjection, + ContactsContract.CommonDataKinds.Phone.CONTACT_ID + + " = ?", new String[] { id }, null); + ArrayList<String> phoneNumList = new ArrayList<String>(); + + if (pCur.moveToFirst()) { + do { + String contactNumber = pCur.getString(pCur.getColumnIndex( + ContactsContract.CommonDataKinds.Phone.NUMBER)); + //contactNumber = filterEligibleContact(context, pContactNumber); + if (validateEligibleContact(context, contactNumber)) { + String contactName = pCur.getString(pCur.getColumnIndex( + ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); + String rawContactId = pCur.getString(pCur.getColumnIndex( + ContactsContract.CommonDataKinds.Phone.RAW_CONTACT_ID)); + String contactId = pCur.getString(pCur.getColumnIndex( + ContactsContract.CommonDataKinds.Phone.CONTACT_ID)); + // TODO: HACK - To be resolved as part of EAB Provider rework. + if (phoneNumList.contains(contactNumber)) continue; + phoneNumList.add(contactNumber); + + String dataId = getDataId(contentResolver,rawContactId, contactNumber); + if (null != dataId) { + allEligibleContacts.add(new PresenceContact(contactName, + contactNumber, rawContactId, contactId, dataId)); + logger.debug("Eligible List Name: " + contactName + + " Number:" + contactNumber + + " RawContactID: " + rawContactId + + " contactId: " + contactId + " Data.ID : " + dataId); + } else { + logger.debug("dataId is null. Don't add contact to " + + "allEligibleContacts."); + } + } + } while (pCur.moveToNext()); + } + pCur.close(); + } while (cursor.moveToNext()); + } + if (null != cursor) { + cursor.close(); + } + if (allEligibleContacts.size() > 0) { + logger.debug("Adding : " + allEligibleContacts.size() + + " new contact numbers to EAB db."); + addContactsToEabDb(context, allEligibleContacts); + logger.debug("contact last updated time after init :" + contactLastChange); + SharedPrefUtil.saveLastContactChangedTimestamp(context, contactLastChange); + SharedPrefUtil.saveLastContactDeletedTimestamp(context, contactLastChange); + } + logger.debug("Exit validateAndSyncFromContactsDb contact numbers synced : " + + allEligibleContacts.size()); + return response; + } + + private static String getDataId(ContentResolver contentResolver, + String rawContactId, String pContactNumber) { + String dataId = null; + String where = Data.RAW_CONTACT_ID + " = '" + rawContactId + "' AND " + + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + + " AND " + Data.DATA1 + "='" + pContactNumber + "'"; + Cursor cur = null; + try { + cur = contentResolver.query(Data.CONTENT_URI, + new String[] { Data._ID }, where, null, null); + if (cur.moveToFirst()) { + dataId = cur.getString(cur.getColumnIndex(Data._ID)); + } + } catch (SQLiteException e) { + logger.debug("SQLiteException while querying for dataId : " + e.toString()); + } catch (Exception e) { + logger.debug("Exception while querying for dataId : " + e); + } finally { + if (null != cur) { + cur.close(); + } + } + return dataId; + } + + public static void addContactsToEabDb(Context context, + ArrayList<PresenceContact> contactList) { + ArrayList<ContentProviderOperation> operation = new ArrayList<ContentProviderOperation>(); + + logger.debug("Adding Contacts to EAB DB"); + // To avoid the following exception - Too many content provider operations + // between yield points. The maximum number of operations per yield point is + // 500 for exceuteDB() + int yieldPoint = 300; + for (int j = 0; j < contactList.size(); j++) { + addContactToEabDb(context, operation, contactList.get(j).getDisplayName(), + contactList.get(j).getPhoneNumber(), contactList.get(j).getRawContactId(), + contactList.get(j).getContactId(), contactList.get(j).getDataId()); + if (yieldPoint == j) { + exceuteDB(context, operation); + operation = null; + operation = new ArrayList<ContentProviderOperation>(); + yieldPoint += 300; + } + } + exceuteDB(context, operation); + } + + private static void addContactToEabDb( + Context context, ArrayList<ContentProviderOperation> ops, String displayName, + String phoneNumber, String rawContactId, String contactId, + String dataId) { + ops.add(ContentProviderOperation + .newInsert(EABContract.EABColumns.CONTENT_URI) + .withValue(EABContract.EABColumns.CONTACT_NAME, displayName) + .withValue(EABContract.EABColumns.CONTACT_NUMBER, phoneNumber) + .withValue(EABContract.EABColumns.ACCOUNT_TYPE, ACCOUNT_TYPE) + .withValue(EABContract.EABColumns.RAW_CONTACT_ID, rawContactId) + .withValue(EABContract.EABColumns.CONTACT_ID, contactId) + .withValue(EABContract.EABColumns.DATA_ID, dataId).build()); + } + + public static void deleteContactsFromEabDb(Context context, + ArrayList<PresenceContact> contactList) { + ArrayList<ContentProviderOperation> operation = new ArrayList<ContentProviderOperation>(); + + logger.debug("Deleting Contacts from EAB DB"); + String[] contactIdList = new String[contactList.size()]; + // To avoid the following exception - Too many content provider operations + // between yield points. The maximum number of operations per yield point is + // 500 for exceuteDB() + int yieldPoint = 300; + for (int j = 0; j < contactList.size(); j++) { + contactIdList[j] = contactList.get(j).getContactId().toString(); + deleteContactFromEabDb(context, operation, contactIdList[j]); + if (yieldPoint == j) { + exceuteDB(context, operation); + operation = null; + operation = new ArrayList<ContentProviderOperation>(); + yieldPoint += 300; + } + } + exceuteDB(context, operation); + } + + private static void deleteContactFromEabDb(Context context, + ArrayList<ContentProviderOperation> ops, String contactId) { + // Add operation only if there is an entry in EABProvider table. + String[] eabProjection = new String[] { + EABContract.EABColumns.CONTACT_NUMBER, + EABContract.EABColumns.CONTACT_ID }; + String eabWhereClause = null; + if (ContactsContract.Profile.MIN_ID == Long.valueOf(contactId)) { + eabWhereClause = EABContract.EABColumns.CONTACT_ID + " >='" + contactId + "'"; + } else { + eabWhereClause = EABContract.EABColumns.CONTACT_ID + " ='" + contactId + "'"; + } + Cursor eabDeleteCursor = context.getContentResolver().query( + EABContract.EABColumns.CONTENT_URI, eabProjection, + eabWhereClause, null, null); + if (null != eabDeleteCursor) { + int count = eabDeleteCursor.getCount(); + logger.debug("cursor count : " + count); + if (count > 0) { + eabDeleteCursor.moveToNext(); + long eabDeleteContactId = eabDeleteCursor. + getLong(eabDeleteCursor.getColumnIndex(EABContract.EABColumns.CONTACT_ID)); + logger.debug("eabDeleteContactId : " + eabDeleteContactId); + if (ContactsContract.Profile.MIN_ID == Long.valueOf(contactId)) { + if (ContactsContract.isProfileId(eabDeleteContactId)) { + logger.debug("Deleting Profile contact."); + ops.add(ContentProviderOperation + .newDelete(EABContract.EABColumns.CONTENT_URI) + .withSelection(EABContract.EABColumns.CONTACT_ID + " >= ?", + new String[] { contactId }).build()); + } else { + logger.debug("Not a Profile contact. Do nothing."); + } + } else { + ops.add(ContentProviderOperation + .newDelete(EABContract.EABColumns.CONTENT_URI) + .withSelection(EABContract.EABColumns.CONTACT_ID + " = ?", + new String[] { contactId }).build()); + } + } + eabDeleteCursor.close(); + } + + } + + public static void deleteNumbersFromEabDb(Context context, + ArrayList<PresenceContact> contactList) { + ArrayList<ContentProviderOperation> operation = new ArrayList<ContentProviderOperation>(); + + logger.debug("Deleting Number from EAB DB"); + String[] rawContactIdList = new String [contactList.size()]; + String[] DataIdList = new String [contactList.size()]; + // To avoid the following exception - Too many content provider operations + // between yield points. The maximum number of operations per yield point is + // 500 for exceuteDB() + int yieldPoint = 300; + for (int j = 0; j < contactList.size(); j++) { + rawContactIdList[j] = contactList.get(j).getRawContactId(); + DataIdList[j] = contactList.get(j).getDataId(); + deleteNumberFromEabDb(context, operation, rawContactIdList[j], DataIdList[j]); + if (yieldPoint == j) { + exceuteDB(context, operation); + operation = null; + operation = new ArrayList<ContentProviderOperation>(); + yieldPoint += 300; + } + } + exceuteDB(context, operation); + } + + private static void deleteNumberFromEabDb(Context context, + ArrayList<ContentProviderOperation> ops, String rawContactId, String dataId) { + // Add operation only if there is an entry in EABProvider table. + String[] eabProjection = new String[] { + EABContract.EABColumns.CONTACT_NUMBER }; + String eabWhereClause = EABContract.EABColumns.RAW_CONTACT_ID + " ='" + rawContactId + + "' AND " + EABContract.EABColumns.DATA_ID + " ='" + dataId + "'"; + Cursor eabDeleteCursor = context.getContentResolver().query( + EABContract.EABColumns.CONTENT_URI, eabProjection, + eabWhereClause, null, null); + if (null != eabDeleteCursor) { + int count = eabDeleteCursor.getCount(); + logger.debug("Delete number cursor count : " + count); + if (count > 0) { + ops.add(ContentProviderOperation.newDelete(EABContract.EABColumns.CONTENT_URI) + .withSelection(EABContract.EABColumns.RAW_CONTACT_ID + " = ? AND " + + EABContract.EABColumns.DATA_ID + " = ?", + new String[] { rawContactId, dataId }).build()); + } + eabDeleteCursor.close(); + } + } + + public static void updateNamesInEabDb(Context context, + ArrayList<PresenceContact> contactList) { + ArrayList<ContentProviderOperation> operation = new ArrayList<ContentProviderOperation>(); + + logger.debug("Update name in EAB DB"); + String[] phoneNameList = new String[contactList.size()]; + String[] phoneNumberList = new String[contactList.size()]; + String[] rawContactIdList = new String [contactList.size()]; + String[] dataIdList = new String [contactList.size()]; + // To avoid the following exception - Too many content provider operations + // between yield points. The maximum number of operations per yield point is + // 500 for exceuteDB() + int yieldPoint = 300; + for (int j = 0; j < contactList.size(); j++) { + phoneNameList[j] = contactList.get(j).getDisplayName(); + phoneNumberList[j] = contactList.get(j).getPhoneNumber(); + rawContactIdList[j] = contactList.get(j).getRawContactId(); + dataIdList[j] = contactList.get(j).getDataId(); + updateNameInEabDb(context, operation, phoneNameList[j], + phoneNumberList[j], rawContactIdList[j], dataIdList[j]); + if (yieldPoint == j) { + exceuteDB(context, operation); + operation = null; + operation = new ArrayList<ContentProviderOperation>(); + yieldPoint += 300; + } + } + exceuteDB(context, operation); + } + + private static void updateNameInEabDb(Context context, + ArrayList<ContentProviderOperation> ops, String phoneName, + String phoneNumber, String rawContactId, String dataId) { + ContentValues values = new ContentValues(); + values.put(EABContract.EABColumns.CONTACT_NAME, phoneName); + + String where = EABContract.EABColumns.CONTACT_NUMBER + " = ? AND " + + EABContract.EABColumns.RAW_CONTACT_ID + " = ? AND " + + EABContract.EABColumns.DATA_ID + " = ?"; + ops.add(ContentProviderOperation + .newUpdate(EABContract.EABColumns.CONTENT_URI) + .withValues(values) + .withSelection(where, new String[] { phoneNumber, rawContactId, dataId }).build()); + } + + private static void exceuteDB(Context context, ArrayList<ContentProviderOperation> ops) { + if (ops.size() == 0) { + logger.debug("exceuteDB return as operation size is 0."); + return; + } + try { + context.getContentResolver().applyBatch(EABContract.AUTHORITY, ops); + } catch (RemoteException e) { + e.printStackTrace(); + } catch (OperationApplicationException e) { + e.printStackTrace(); + } + ops.clear(); + logger.debug("exceuteDB return with successful operation."); + return; + } + + public static boolean validateEligibleContact(Context context, String mdn) { + boolean number = false; + if (null == mdn) { + logger.debug("validateEligibleContact - mdn is null."); + return number; + } + List<String> mdbList = new ArrayList<String>(); + mdbList.add(mdn); + ContactNumberUtils mNumberUtils = ContactNumberUtils.getDefault(); + mNumberUtils.setContext(context); + int numberType = mNumberUtils.validate(mdbList); + logger.debug("ContactNumberUtils.validate response : " + numberType); + if ( ContactNumberUtils.NUMBER_VALID == numberType) { + number = true; + } + logger.debug("Exiting validateEligibleContact with value : " + number); + return number; + } + + public static String filterEligibleContact(Context context, String mdn) { + String number = null; + if (null == mdn) { + logger.debug("filterEligibleContact - mdn is null."); + return number; + } + logger.debug("Before filterEligibleContact validation : " + mdn); + List<String> mdbList = new ArrayList<String>(); + mdbList.add(mdn); + ContactNumberUtils mNumberUtils = ContactNumberUtils.getDefault(); + mNumberUtils.setContext(context); + int numberType = mNumberUtils.validate(mdbList); + logger.debug("ContactNumberUtils.validate response : " + numberType); + if ( ContactNumberUtils.NUMBER_VALID == numberType) { + String[] mdnFormatted = mNumberUtils.format(mdbList); + if (mdnFormatted.length > 0 ){ + number = mdnFormatted[0]; + } + } + logger.debug("After filterEligibleContact validation / formatting : " + number); + return number; + } +} diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/EABProvider.java b/rcs/presencepolling/src/com/android/service/ims/presence/EABProvider.java new file mode 100644 index 0000000..651b1cd --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/EABProvider.java @@ -0,0 +1,453 @@ +/* + * 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.presence; + +import java.io.FileNotFoundException; + +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Intent; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.SQLException; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.provider.BaseColumns; +import android.provider.ContactsContract.Contacts; +import android.content.ComponentName; + +import com.android.ims.RcsPresenceInfo; +import com.android.ims.internal.EABContract; +import com.android.ims.internal.Logger; + +/** + * @author Vishal Patil (A22809) + */ + +public class EABProvider extends DatabaseContentProvider{ + private Logger logger = Logger.getLogger(this.getClass().getName()); + + private static final String EAB_DB_NAME = "rcseab.db"; + + private static final int EAB_DB_VERSION = 2; + + private static final int EAB_TABLE = 1; + + private static final int EAB_TABLE_ID = 2; + + private static final int EAB_GROUPITEMS_TABLE = 3; + + private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH) { + { + addURI(EABContract.AUTHORITY, EABContract.EABColumns.TABLE_NAME, EAB_TABLE); + addURI(EABContract.AUTHORITY, EABContract.EABColumns.TABLE_NAME + "/#", EAB_TABLE_ID); + addURI(EABContract.AUTHORITY, EABContract.EABColumns.GROUPITEMS_NAME, + EAB_GROUPITEMS_TABLE); + } + }; + + /* Statement to create VMM Album table. */ + private static final String EAB_CREATE_STATEMENT = "create table if not exists " + + EABContract.EABColumns.TABLE_NAME + + "(" + + EABContract.EABColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + EABContract.EABColumns.CONTACT_NAME + " TEXT, " + + EABContract.EABColumns.CONTACT_NUMBER + " TEXT, " + + EABContract.EABColumns.RAW_CONTACT_ID + " TEXT, " + + EABContract.EABColumns.CONTACT_ID + " TEXT, " + + EABContract.EABColumns.DATA_ID + " TEXT, " + + EABContract.EABColumns.ACCOUNT_TYPE + " TEXT, " + + EABContract.EABColumns.VOLTE_CALL_SERVICE_CONTACT_ADDRESS + " TEXT, " + + EABContract.EABColumns.VOLTE_CALL_CAPABILITY + " INTEGER, " + + EABContract.EABColumns.VOLTE_CALL_CAPABILITY_TIMESTAMP + " LONG, " + + EABContract.EABColumns.VOLTE_CALL_AVAILABILITY + " INTEGER, " + + EABContract.EABColumns.VOLTE_CALL_AVAILABILITY_TIMESTAMP + " LONG, " + + EABContract.EABColumns.VIDEO_CALL_SERVICE_CONTACT_ADDRESS + " TEXT, " + + EABContract.EABColumns.VIDEO_CALL_CAPABILITY + " INTEGER, " + + EABContract.EABColumns.VIDEO_CALL_CAPABILITY_TIMESTAMP + " LONG, " + + EABContract.EABColumns.VIDEO_CALL_AVAILABILITY + " INTEGER, " + + EABContract.EABColumns.VIDEO_CALL_AVAILABILITY_TIMESTAMP + " LONG, " + + EABContract.EABColumns.CONTACT_LAST_UPDATED_TIMESTAMP + " LONG " + + ");"; + + private static final String EAB_DROP_STATEMENT = "drop table if exists " + + EABContract.EABColumns.TABLE_NAME + ";"; + + public EABProvider() { + super(EAB_DB_NAME, EAB_DB_VERSION); + } + + @Override + public void bootstrapDatabase(SQLiteDatabase db) { + logger.info("Enter: bootstrapDatabase() Creating new EAB database"); + upgradeDatabase(db, 0, EAB_DB_VERSION); + logger.info("Exit: bootstrapDatabase()"); + } + + /* + * In upgradeDatabase, + * oldVersion - the current version of Provider on the phone. + * newVersion - the version of Provider where the phone will be upgraded to. + */ + @Override + public boolean upgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion) { + logger.debug("Enter: upgradeDatabase() - oldVersion = " + oldVersion + + " newVersion = " + newVersion); + + if (oldVersion == newVersion) { + logger.debug("upgradeDatabase oldVersion == newVersion, No Upgrade Required"); + return true; + } + + try { + if (oldVersion == 0) { + db.execSQL(EAB_CREATE_STATEMENT); + + oldVersion++; + logger.debug("upgradeDatabase : DB has been upgraded to " + oldVersion); + } + if (oldVersion == 1) { + addColumn(db, EABContract.EABColumns.TABLE_NAME, + EABContract.EABColumns.VOLTE_STATUS, "INTEGER NOT NULL DEFAULT -1"); + + oldVersion++; + logger.debug("upgradeDatabase : DB has been upgraded to " + oldVersion); + } + } catch (SQLException exception) { + logger.error("Exception during upgradeDatabase. " + exception.getMessage()); + // DB file had problem + throw new InvalidDBException(); + } + + // add further upgrade code above this + if (oldVersion == newVersion) { + logger.debug("DB upgrade complete : to " + newVersion); + } + + logger.debug("Exit: upgradeDatabase()"); + return true; + } + + @Override + protected boolean downgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion) { + // throwing the custom created Exception to catch it in + // getWritableDatabase or getReadableDatabase + throw new InvalidDBException(); + } + + @Override + protected int deleteInternal(SQLiteDatabase db, Uri uri, String selection, + String[] selectionArgs) { + logger.info("Enter: deleteInternal()"); + final int match = URI_MATCHER.match(uri); + String table = null; + switch (match) { + case EAB_TABLE_ID: + if (selection == null) { + selection = "_id=?"; + selectionArgs = new String[] {uri.getPathSegments().get(1)}; + } + case EAB_TABLE: + table = EABContract.EABColumns.TABLE_NAME; + break; + default: + logger.info("No match for " + uri); + logger.info("Exit: deleteInternal()"); + return 0; + } + logger.debug("Deleting from the table" + table + " selection= " + selection); + printDeletingValues(uri, selection, selectionArgs); + logger.info("Exit: deleteInternal()"); + return db.delete(table, selection, selectionArgs); + } + + @Override + protected Uri insertInternal(SQLiteDatabase db, Uri uri, ContentValues values) { + logger.info("Enter: insertInternal()"); + final int match = URI_MATCHER.match(uri); + String table = null; + String nullColumnHack = null; + switch (match) { + case EAB_TABLE: + table = EABContract.EABColumns.TABLE_NAME; + break; + default: + logger.warn("No match for " + uri); + logger.info("Exit: insertInternal() with null"); + return null; + } + values = verifyIfMdnExists(values); + // Do the insert. + logger.debug("Inserting to the table" + table + " values=" + values.toString()); + + final long id = db.insert(table, nullColumnHack, values); + if (id > 0) { + String contactNumber = values.getAsString(EABContract.EABColumns.CONTACT_NUMBER); + sendInsertBroadcast(contactNumber); + logger.info("Exit: insertInternal()"); + return ContentUris.withAppendedId(uri, id); + } else { + logger.info("Exit: insertInternal() with null"); + return null; + } + } + + @Override + protected Cursor queryInternal(SQLiteDatabase db, Uri uri, String[] projection, + String selection, String[] selectionArgs, String sortOrder) { + logger.info("Enter: queryInternal()"); + final int match = URI_MATCHER.match(uri); + + switch (match) { + case EAB_TABLE_ID: + long id = ContentUris.parseId(uri); + logger.debug("queryInternal id=" + id); + String idSelection = BaseColumns._ID + "=" + id; + if(null != selection) { + selection = "((" + idSelection + ") AND (" + selection + "))"; + } else { + selection = idSelection; + } + break; + case EAB_GROUPITEMS_TABLE: + SQLiteQueryBuilder sqb = new SQLiteQueryBuilder(); + sqb.setTables(EABContract.EABColumns.TABLE_NAME); + String rawquery = "select DISTINCT " + EABContract.EABColumns.CONTACT_ID + + " from " + EABContract.EABColumns.TABLE_NAME + + " where " + EABContract.EABColumns.VOLTE_CALL_CAPABILITY + " >'" + + RcsPresenceInfo.ServiceState.OFFLINE+"' AND " + + EABContract.EABColumns.VIDEO_CALL_CAPABILITY + " >'" + + RcsPresenceInfo.ServiceState.OFFLINE + "'"; + StringBuffer sb = new StringBuffer(); + Cursor cursor = db.rawQuery(rawquery, null); + if (cursor != null && cursor.moveToFirst()) { + do { + if (sb.length() != 0) sb.append(","); + String contactId = cursor.getString(cursor + .getColumnIndex(EABContract.EABColumns.CONTACT_ID)); + sb.append(contactId); + } while (cursor.moveToNext()); + } + if (cursor != null) cursor.close(); + String contactSel = Contacts._ID + " IN ( " + sb.toString() + ")"; + return getContext().getContentResolver().query(Contacts.CONTENT_URI, projection, + contactSel, null, sortOrder); + } + + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + + String groupBy = uri.getQueryParameter("groupby"); + String having = null; + + switch (match) { + case EAB_TABLE: + case EAB_TABLE_ID: + qb.setTables(EABContract.EABColumns.TABLE_NAME); + break; + default: + logger.warn("No match for " + uri); + logger.info("Exit: queryInternal()"); + return null; + } + logger.info("Exit: queryInternal()"); + return qb.query(db, projection, selection, selectionArgs, groupBy, having, sortOrder); + } + + @Override + protected int updateInternal(SQLiteDatabase db, Uri uri, ContentValues values, + String selection, String[] selectionArgs) { + logger.info("Enter: updateInternal()"); + int result = 0; + final int match = URI_MATCHER.match(uri); + + switch (match) { + case EAB_TABLE_ID: + long id = ContentUris.parseId(uri); + logger.debug("updateInternal id=" + id); + String idSelection = BaseColumns._ID + "=" + id; + if(null != selection){ + selection = "((" + idSelection + ") AND (" + selection + "))"; + } else { + selection = idSelection; + } + break; + } + + String table = null; + switch (match) { + case EAB_TABLE: + case EAB_TABLE_ID: + table = EABContract.EABColumns.TABLE_NAME; + break; + default: + logger.warn("No match for " + uri); + break; + } + + if (table != null && values != null) { + logger.debug("Updating the table " + table + " values= " + values.toString()); + result = db.update(table, values, selection, selectionArgs); + } + logger.info("Exit: updateInternal()"); + return result; + } + + @Override + public String getType(Uri uri) { + logger.info("Enter: getType()"); + final int match = URI_MATCHER.match(uri); + switch (match) { + case EAB_TABLE: + return EABContract.EABColumns.CONTENT_TYPE; + case EAB_TABLE_ID: + return EABContract.EABColumns.CONTENT_ITEM_TYPE; + default: + throw (new IllegalArgumentException("EABProvider URI: " + uri)); + } + } + + @Override + public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { + return null; + } + + private void sendInsertBroadcast(String contactNumber) { + Intent intent = new Intent(com.android.service.ims.presence.Contacts + .ACTION_NEW_CONTACT_INSERTED); + ComponentName component = new ComponentName("com.android.service.ims.presence", + "com.android.service.ims.presence.AlarmBroadcastReceiver"); + intent.setComponent(component); + + intent.putExtra(com.android.service.ims.presence.Contacts.NEW_PHONE_NUMBER, contactNumber); + getContext().sendBroadcast(intent); + } + + private ContentValues verifyIfMdnExists(ContentValues cvalues) { + String phoneNumber = null; + if (cvalues.containsKey(EABContract.EABColumns.CONTACT_NUMBER)) { + phoneNumber = cvalues.getAsString(EABContract.EABColumns.CONTACT_NUMBER); + } else { + return cvalues; + } + if (null == phoneNumber) { + return cvalues; + } + String[] projection = new String[] { + EABContract.EABColumns.VOLTE_CALL_SERVICE_CONTACT_ADDRESS, + EABContract.EABColumns.VOLTE_CALL_CAPABILITY, + EABContract.EABColumns.VOLTE_CALL_CAPABILITY_TIMESTAMP, + EABContract.EABColumns.VOLTE_CALL_AVAILABILITY, + EABContract.EABColumns.VOLTE_CALL_AVAILABILITY_TIMESTAMP, + EABContract.EABColumns.VIDEO_CALL_SERVICE_CONTACT_ADDRESS, + EABContract.EABColumns.VIDEO_CALL_CAPABILITY, + EABContract.EABColumns.VIDEO_CALL_CAPABILITY_TIMESTAMP, + EABContract.EABColumns.VIDEO_CALL_AVAILABILITY, + EABContract.EABColumns.VIDEO_CALL_AVAILABILITY_TIMESTAMP}; + String whereClause = "PHONE_NUMBERS_EQUAL(" + + EABContract.EABColumns.CONTACT_NUMBER + ", ?, 0)"; + String[] selectionArgs = new String[] { phoneNumber }; + Cursor cursor = getContext().getContentResolver().query(EABContract.EABColumns.CONTENT_URI, + projection, whereClause, selectionArgs, null); + if ((null != cursor) && (cursor.getCount() > 0)) { + logger.debug("Number : " + phoneNumber + + " is already stored in EAB DB - cursor count is " + cursor.getCount()); + logger.error("Inserting another copy of MDN to EAB DB."); + // Update data only from first cursor element. + cursor.moveToNext(); + cvalues.put(EABContract.EABColumns.VOLTE_CALL_SERVICE_CONTACT_ADDRESS, + cursor.getString(cursor. + getColumnIndex(EABContract.EABColumns.VOLTE_CALL_SERVICE_CONTACT_ADDRESS))); + cvalues.put(EABContract.EABColumns.VOLTE_CALL_CAPABILITY, cursor.getString(cursor + .getColumnIndex(EABContract.EABColumns.VOLTE_CALL_CAPABILITY))); + cvalues.put(EABContract.EABColumns.VOLTE_CALL_CAPABILITY_TIMESTAMP, + cursor.getLong(cursor + .getColumnIndex(EABContract.EABColumns.VOLTE_CALL_CAPABILITY_TIMESTAMP))); + cvalues.put(EABContract.EABColumns.VOLTE_CALL_AVAILABILITY, cursor.getString(cursor + .getColumnIndex(EABContract.EABColumns.VOLTE_CALL_AVAILABILITY))); + cvalues.put(EABContract.EABColumns.VOLTE_CALL_AVAILABILITY_TIMESTAMP, + cursor.getLong(cursor + .getColumnIndex(EABContract.EABColumns.VOLTE_CALL_AVAILABILITY_TIMESTAMP))); + cvalues.put(EABContract.EABColumns.VIDEO_CALL_SERVICE_CONTACT_ADDRESS, + cursor.getString(cursor + .getColumnIndex(EABContract.EABColumns.VIDEO_CALL_SERVICE_CONTACT_ADDRESS))); + cvalues.put(EABContract.EABColumns.VIDEO_CALL_CAPABILITY, cursor.getString(cursor + .getColumnIndex(EABContract.EABColumns.VIDEO_CALL_CAPABILITY))); + cvalues.put(EABContract.EABColumns.VIDEO_CALL_CAPABILITY_TIMESTAMP, + cursor.getLong(cursor + .getColumnIndex(EABContract.EABColumns.VIDEO_CALL_CAPABILITY_TIMESTAMP))); + cvalues.put(EABContract.EABColumns.VIDEO_CALL_AVAILABILITY, cursor.getString(cursor + .getColumnIndex(EABContract.EABColumns.VIDEO_CALL_AVAILABILITY))); + cvalues.put(EABContract.EABColumns.VIDEO_CALL_AVAILABILITY_TIMESTAMP, + cursor.getLong(cursor + .getColumnIndex(EABContract.EABColumns.VIDEO_CALL_AVAILABILITY_TIMESTAMP))); + cvalues.put(EABContract.EABColumns.CONTACT_LAST_UPDATED_TIMESTAMP, 0); + } + if (null != cursor) { + cursor.close(); + } + return cvalues; + } + + private void printDeletingValues(Uri uri, String selection, String[] selectionArgs) { + String[] projection = new String[] { + EABContract.EABColumns.CONTACT_NUMBER, + EABContract.EABColumns.CONTACT_NAME, + EABContract.EABColumns.RAW_CONTACT_ID, + EABContract.EABColumns.CONTACT_ID, + EABContract.EABColumns.DATA_ID}; + Cursor cursor = getContext().getContentResolver().query(EABContract.EABColumns.CONTENT_URI, + projection, selection, selectionArgs, null); + if ((null != cursor) && (cursor.getCount() > 0)) { + logger.debug("Before deleting the cursor count is " + cursor.getCount()); + // Update data only from first cursor element. + while (cursor.moveToNext()) { + long dataId = cursor.getLong(cursor.getColumnIndex( + EABContract.EABColumns.DATA_ID)); + long contactId = cursor.getLong(cursor.getColumnIndex( + EABContract.EABColumns.CONTACT_ID)); + long rawContactId = cursor.getLong(cursor.getColumnIndex( + EABContract.EABColumns.RAW_CONTACT_ID)); + String phoneNumber = cursor.getString(cursor.getColumnIndex( + EABContract.EABColumns.CONTACT_NUMBER)); + String displayName = cursor.getString(cursor.getColumnIndex( + EABContract.EABColumns.CONTACT_NAME)); + logger.debug("Deleting : dataId : " + dataId + " contactId :" + contactId + + " rawContactId :" + rawContactId + " phoneNumber :" + phoneNumber + + " displayName :" + displayName); + } + } else { + logger.error("cursor is null!"); + } + if (null != cursor) { + cursor.close(); + } + } +} diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/EABService.java b/rcs/presencepolling/src/com/android/service/ims/presence/EABService.java new file mode 100644 index 0000000..e79293c --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/EABService.java @@ -0,0 +1,1142 @@ +/* + * 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.presence; + +import java.util.ArrayList; +import java.util.List; +import java.util.Calendar; + +import android.accounts.Account; +import android.app.Service; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ComponentName; +import android.content.ServiceConnection; +import android.database.ContentObserver; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.SystemProperties; +import android.provider.ContactsContract; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.telephony.PhoneNumberUtils; +import android.text.TextUtils; + +import android.telephony.SubscriptionManager; +import com.android.ims.internal.EABContract; + +import com.android.ims.ImsConfig; +import com.android.ims.ImsManager; +import com.android.ims.ImsException; + +import com.android.ims.RcsManager; +import com.android.ims.RcsManager.ResultCode; +import com.android.ims.RcsPresence; +import com.android.ims.RcsException; +import com.android.ims.RcsPresenceInfo; +import com.android.ims.IRcsPresenceListener; +import com.android.ims.internal.Logger; + +public class EABService extends Service { + + private Logger logger = Logger.getLogger(this.getClass().getName()); + + private Context mContext; + private Looper mServiceLooper = null; + private ServiceHandler mServiceHandler = null; + // Used to avoid any content observer processing during EABService + // initialisation as anyways it will check for Contact db changes as part of boot-up. + private boolean isEABServiceInitializing = true; + + private static final int BOOT_COMPLETED = 0; + private static final int CONTACT_TABLE_MODIFIED = 1; + private static final int CONTACT_PROFILE_TABLE_MODIFIED = 2; + private static final int EAB_RESET_CONTENT_OBSERVERS = 3; + + private static final int SYNC_COMPLETE_DELAY_TIMER = 3 * 1000; // 3 seconds. + private static final String VOLTE_NUMBER = "volte_number"; + private static final String VOLTE_NUMBER_STATE = "state"; + private static final String ACTION_EAB_RESET_CONTENT_OBSERVERS = + "com.android.rcs.eab.ACTION_EAB_RESET_CONTENT_OBSERVERS"; + + // Framework interface files. + private RcsManager mRcsManager = null; + private RcsPresence mRcsPresence = null; + + public EABService() { + super(); + logger.info("EAB Service constructed"); + } + + @Override + public IBinder onBind(Intent arg0) { + return null; + } + + /** + * When "clear data" is done for contact storage in system settings, EAB + * Provider must me cleared and PREF_KEY_CHANGE and PREF_KEY_DELETE keys + * should not cleared. + */ + private BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + logger.debug("onReceive intent: " + action); + + if(ContactsContract.Intents.CONTACTS_DATABASE_CREATED.equals(action)) { + logger.debug("Contacts database created."); + // Delete all entries from EAB Provider as it has to be re-synced with Contact db. + mContext.getContentResolver().delete( + EABContract.EABColumns.CONTENT_URI, null, null); + // Initialise EABProvider. + logger.debug("Resetting timestamp values in shared pref."); + SharedPrefUtil.resetEABSharedPref(mContext); + // init the EAB db after re-setting. + ensureInitDone(); + } else if(Intent.ACTION_TIME_CHANGED.equals(action) || + Intent.ACTION_TIMEZONE_CHANGED.equals(action)) { + Calendar cal = Calendar.getInstance(); + long currentTimestamp = cal.getTimeInMillis(); + long lastChangedTimestamp = SharedPrefUtil.getLastContactChangedTimestamp( + mContext, currentTimestamp); + logger.debug("lastChangedTimestamp=" + lastChangedTimestamp + + " currentTimestamp=" + currentTimestamp); + // Changed time backwards. + if(lastChangedTimestamp > currentTimestamp) { + logger.debug("Resetting timestamp values in shared pref."); + SharedPrefUtil.resetEABSharedPref(mContext); + CapabilityPolling capabilityPolling = CapabilityPolling.getInstance(null); + if (capabilityPolling != null) { + capabilityPolling.enqueueDiscovery(CapabilityPolling.ACTION_POLLING_NORMAL); + } + } + } + } + }; + + private ContentObserver mContactChangedListener = null; + private class ContactChangedListener extends ContentObserver { + public ContactChangedListener() { + super(null); + } + + @Override + public boolean deliverSelfNotifications() { + return false; + } + + @Override + public void onChange(boolean selfChange) { + logger.debug("onChange for ContactChangedListener"); + sendDelayedContactChangeMsg(); + } + } + + private ContentObserver mContactProfileListener = null; + private class ContactProfileListener extends ContentObserver { + public ContactProfileListener() { + super(null); + } + + @Override + public boolean deliverSelfNotifications() { + return false; + } + + @Override + public void onChange(boolean selfChange) { + logger.debug("onChange for ContactProfileListener"); + sendDelayedContactProfileMsg(); + } + } + + @Override + public void onCreate() { + logger.debug("Enter : onCreate"); + mContext = getApplicationContext(); + HandlerThread thread = new HandlerThread("EABServiceHandler"); + thread.start(); + + mServiceLooper = thread.getLooper(); + if (mServiceLooper != null) { + mServiceHandler = new ServiceHandler(mServiceLooper); + } else { + logger.debug("mServiceHandler could not be initialized since looper is null"); + } + + IntentFilter filter = new IntentFilter(); + filter.addAction(ContactsContract.Intents.CONTACTS_DATABASE_CREATED); + filter.addAction(Intent.ACTION_TIME_CHANGED); + filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); + registerReceiver(mReceiver, filter); + + initializeRcsInterfacer(); + + initResetContentObserverAlarm(); + super.onCreate(); + } + + @Override + public void onDestroy() { + cancelResetContentObserverAlarm(); + unregisterContentObservers(); + if (null != mReceiver) { + unregisterReceiver(mReceiver); + } + if (null != mServiceHandler) { + mServiceHandler = null; + } + } + + private void initializeRcsInterfacer() { + // Get instance of mRcsManagr. + if (null == mRcsManager) { + mRcsManager = RcsManager.getInstance(this, 0); + } + try{ + if (null == mRcsPresence) { + mRcsPresence = mRcsManager.getRcsPresenceInterface(); + logger.debug("mRcsManager : " + mRcsManager + " mRcsPresence : " + mRcsPresence); + } + }catch (RcsException e){ + logger.error("getRcsPresenceInterface() exception : ", e); + mRcsPresence = null; + } catch (Exception e) { + logger.error("getRcsPresenceInterface() exception : ", e); + mRcsPresence = null; + mRcsManager = null; + } + } + + private void registerContentObservers() { + logger.debug("Registering for Contact and Profile Change Listener."); + mContactChangedListener = new ContactChangedListener(); + getContentResolver().registerContentObserver( + ContactsContract.Contacts.CONTENT_URI, true, + mContactChangedListener); + + mContactProfileListener = new ContactProfileListener(); + getContentResolver().registerContentObserver( + ContactsContract.Profile.CONTENT_URI, true, + mContactProfileListener); + } + + private void unregisterContentObservers() { + logger.debug("Un-registering for Contact and Profile Change Listener."); + if (null != mContactChangedListener) { + getContentResolver().unregisterContentObserver( + mContactChangedListener); + mContactChangedListener = null; + } + if (null != mContactProfileListener) { + getContentResolver().unregisterContentObserver( + mContactProfileListener); + mContactProfileListener = null; + } + } + + private void resetContentObservers() { + unregisterContentObservers(); + registerContentObservers(); + } + + private void initResetContentObserverAlarm() { + logger.debug("initResetAlarm, content Observers rest every 12 hours"); + long startInterval = System.currentTimeMillis() + AlarmManager.INTERVAL_HALF_DAY; + + Intent intent = new Intent(ACTION_EAB_RESET_CONTENT_OBSERVERS); + ((AlarmManager) getSystemService(Context.ALARM_SERVICE)).setInexactRepeating( + AlarmManager.RTC_WAKEUP, + startInterval, + AlarmManager.INTERVAL_HALF_DAY, + PendingIntent.getBroadcast(mContext, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT)); + + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_EAB_RESET_CONTENT_OBSERVERS); + registerReceiver(mResetContentObserverReceiver, filter); + } + + private BroadcastReceiver mResetContentObserverReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + logger.debug("Received, ACTION_EAB_RESET_CONTENT_OBSERVERS"); + mServiceHandler.sendMessage(mServiceHandler.obtainMessage( +EAB_RESET_CONTENT_OBSERVERS)); + } + }; + + private void cancelResetContentObserverAlarm() { + Intent intent = new Intent(ACTION_EAB_RESET_CONTENT_OBSERVERS); + PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + ((AlarmManager) getSystemService(Context.ALARM_SERVICE)).cancel(pi); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + // As the return type is START_STICKY, check for null intent is not + // needed as if this service's process is killed while it is started, + // system will try to re-create the service with a null intent object if + // there are not any pending start commands + if (intent != null) { + logger.debug("Enter : onStartCommand for intent : " + intent.getAction()); + } + registerContentObservers(); + Message msg = mServiceHandler.obtainMessage(BOOT_COMPLETED); + mServiceHandler.sendMessage(msg); + // This service should be a always-on service. + return START_STICKY; + } + + private final class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + logger.debug("Enter: handleMessage"); + ArrayList<RcsPresenceInfo> dataInfoList = null; + + switch (msg.what) { + case BOOT_COMPLETED: + logger.debug("case BOOT_COMPLETED"); + ensureInitDone(); + isEABServiceInitializing = false; + break; + case EAB_RESET_CONTENT_OBSERVERS: + logger.debug("case EAB_RESET_CONTENT_OBSERVERS"); + resetContentObservers(); + break; + case CONTACT_TABLE_MODIFIED: + logger.debug("case CONTACT_TABLE_MODIFIED"); + ensureVideoCallingGroupCreation(); + validateAndSyncFromContactsDb(); + break; + case CONTACT_PROFILE_TABLE_MODIFIED: + logger.debug("case CONTACT_PROFILE_TABLE_MODIFIED"); + validateAndSyncFromProfileDb(); + break; + default: + logger.debug("default usecase hit! Do nothing"); + break; + } + logger.debug("Exit: handleMessage"); + } + } + + // synchronized is used to prevent sync happening in parallel due to + // multiple content change notifys from contacts observer. + private synchronized void validateAndSyncFromContactsDb() { + logger.debug("Enter : validateAndSyncFromContactsDb()"); + checkForContactNumberChanges(); + checkForDeletedContact(); + logger.debug("Exit : validateAndSyncFromContactsDb()"); + } + + // synchronized is used to prevent sync happening in parallel due to + // multiple content change notify from contacts observer. + private synchronized void validateAndSyncFromProfileDb() { + logger.debug("Enter : validateAndSyncFromProfileDb()"); + checkForProfileNumberChanges(); + checkForDeletedProfileContacts(); + logger.debug("Exit : validateAndSyncFromProfileDb()"); + } + + private void ensureInitDone() { + logger.debug("Enter : ensureInitDone()"); + ensureVideoCallingGroupCreation(); + if(SharedPrefUtil.isInitDone(mContext)) { + logger.debug("EAB initialized already!!! Just Sync with Contacts db."); + validateAndSyncFromContactsDb(); + validateAndSyncFromProfileDb(); + return; + } else { + logger.debug("Initializing EAB Provider."); + // This API will sync the numbers from Contacts db to EAB db based on + // contact last updated timestamp. + EABDbUtil.validateAndSyncFromContactsDb(mContext); + // This API's will sync the profile numbers from Contacts db to EAB db based on + // contact last updated timestamp. + validateAndSyncFromProfileDb(); + SharedPrefUtil.setInitDone(mContext, true); + } + } + + private void ensureVideoCallingGroupCreation() { + logger.debug("TODO: ensureVideoCallingGroupCreation"); + /* + if (!isRcsProvisioned()) { + logger.debug("Device is not provisioned. Remove Video calling group and account."); + ContactDbUtil.removeVideoCallingContactGroup(mContext); + AccountUtil.removeRcsAccount(mContext); + } else { + logger.debug("Device is provisioned. Create Video calling group and account."); + Account vCallingAccount = AccountUtil.addRcsAccount(mContext); + ContactDbUtil.addVideoCallingContactGroup(mContext, vCallingAccount); + }*/ + } + + private void sendDelayedContactChangeMsg() { + logger.debug("Enter: sendDelayedContactChangeMsg()"); + if (null != mServiceHandler && !isEABServiceInitializing) { + // Remove any previous message for CONTACT_TABLE_MODIFIED. + if (mServiceHandler.hasMessages(CONTACT_TABLE_MODIFIED)) { + mServiceHandler.removeMessages(CONTACT_TABLE_MODIFIED); + logger.debug("Removed previous CONTACT_TABLE_MODIFIED msg."); + } + + logger.debug("Sending new CONTACT_TABLE_MODIFIED msg."); + // Send a new delayed message for CONTACT_TABLE_MODIFIED. + Message msg = mServiceHandler.obtainMessage(CONTACT_TABLE_MODIFIED); + mServiceHandler.sendMessageDelayed(msg, SYNC_COMPLETE_DELAY_TIMER); + } + } + + private void sendDelayedContactProfileMsg() { + logger.debug("Enter: sendDelayedContactProfileMsg()"); + if (null != mServiceHandler && !isEABServiceInitializing) { + // Remove any previous message for CONTACT_PROFILE_TABLE_MODIFIED. + if (mServiceHandler.hasMessages(CONTACT_PROFILE_TABLE_MODIFIED)) { + mServiceHandler.removeMessages(CONTACT_PROFILE_TABLE_MODIFIED); + logger.debug("Removed previous CONTACT_PROFILE_TABLE_MODIFIED msg."); + } + + logger.debug("Sending new CONTACT_PROFILE_TABLE_MODIFIED msg."); + // Send a new delayed message for CONTACT_PROFILE_TABLE_MODIFIED. + Message msg = mServiceHandler.obtainMessage(CONTACT_PROFILE_TABLE_MODIFIED); + mServiceHandler.sendMessageDelayed(msg, SYNC_COMPLETE_DELAY_TIMER); + } + } + + private void checkForContactNumberChanges() { + logger.debug("Enter: checkForContactNumberChanges()"); + String[] projection = new String[] { + ContactsContract.Data._ID, + ContactsContract.Data.CONTACT_ID, + ContactsContract.Data.RAW_CONTACT_ID, + ContactsContract.Data.MIMETYPE, + ContactsContract.Data.DATA1, + ContactsContract.Data.DISPLAY_NAME, + ContactsContract.Data.CONTACT_LAST_UPDATED_TIMESTAMP }; + + long contactLastChange = SharedPrefUtil.getLastContactChangedTimestamp(mContext, 0); + logger.debug("contactLastChange : " + contactLastChange); + + String selection = ContactsContract.Data.MIMETYPE + " = '" + Phone.CONTENT_ITEM_TYPE + + "' AND " + ContactsContract.Data.CONTACT_LAST_UPDATED_TIMESTAMP + " > '" + + contactLastChange + "'"; + String sortOrder = ContactsContract.Data.CONTACT_LAST_UPDATED_TIMESTAMP + " desc"; + + Cursor cursor = getContentResolver().query(ContactsContract.Data.CONTENT_URI, + projection, selection, null, sortOrder); + + if (null != cursor) { + int count = cursor.getCount(); + logger.debug("cursor count : " + count); + if (count > 0) { + ArrayList<Long> uniqueRawContactIds = new ArrayList<Long>(); + while (cursor.moveToNext()) { + Long dataId = Long.valueOf(cursor.getLong(cursor.getColumnIndex( + ContactsContract.Data._ID))); + Long contactId = Long.valueOf(cursor.getLong(cursor.getColumnIndex( + ContactsContract.Data.CONTACT_ID))); + Long rawContactId = Long.valueOf(cursor.getLong(cursor.getColumnIndex( + ContactsContract.Data.RAW_CONTACT_ID))); + String phoneNumber = cursor.getString(cursor.getColumnIndex( + ContactsContract.Data.DATA1)); + String displayName = cursor.getString(cursor.getColumnIndex( + ContactsContract.Data.DISPLAY_NAME)); + logger.debug("dataId : " + dataId + " rawContactId :" + rawContactId + + " contactId : " + contactId + + " phoneNumber :" + phoneNumber + " displayName :" + displayName); + verifyInsertOrUpdateAction(dataId, contactId, rawContactId, phoneNumber, + displayName); + if (uniqueRawContactIds.isEmpty()) { + uniqueRawContactIds.add(rawContactId); + } else if (!uniqueRawContactIds.contains(rawContactId)) { + uniqueRawContactIds.add(rawContactId); + } else { + // Do nothing. + logger.debug("uniqueRawContactIds already contains rawContactId : " + + rawContactId); + } + } + checkForPhoneNumberDelete(uniqueRawContactIds); + // Save the largest timestamp returned. Only need the first one due to + // the sort order. + cursor.moveToFirst(); + long timestamp = cursor.getLong(cursor + .getColumnIndex(ContactsContract.Data.CONTACT_LAST_UPDATED_TIMESTAMP)); + if (timestamp > 0) { + SharedPrefUtil.saveLastContactChangedTimestamp(mContext, timestamp); + } + } + } else { + logger.error("cursor is null!"); + } + if (null != cursor) { + cursor.close(); + } + logger.debug("Exit: checkForContactNumberChanges()"); + } + + private void verifyInsertOrUpdateAction(Long dataId, Long contactId, + Long rawContactId, String phoneNumber, String displayName) { + logger.debug("Enter: verifyInsertOrUpdateAction() phoneNumber : " + phoneNumber); + if (null == phoneNumber){ + logger.error("Error: return as phoneNumber is null"); + return; + } + // Check if the contact is already available in EAB Provider. + String[] eabProjection = new String[] { + EABContract.EABColumns.CONTACT_NUMBER, + EABContract.EABColumns.CONTACT_NAME }; + String eabWhereClause = EABContract.EABColumns.DATA_ID + " ='" + dataId.toString() + + "' AND " + EABContract.EABColumns.RAW_CONTACT_ID + " ='" + + rawContactId.toString() + "'"; + logger.debug("eabWhereClause : " + eabWhereClause); + + Cursor eabCursor = getContentResolver().query(EABContract.EABColumns.CONTENT_URI, + eabProjection, eabWhereClause, null, null); + if (null != eabCursor) { + int eabCursorCount = eabCursor.getCount(); + logger.debug("EAB cursor count : " + eabCursorCount); + if (eabCursorCount > 0) { + while (eabCursor.moveToNext()) { + // EABProvider has entry for dataId & rawContactId. Try to + // match the contact number. + String eabPhoneNumber = eabCursor.getString(eabCursor + .getColumnIndex(EABContract.EABColumns.CONTACT_NUMBER)); + String eabDisplayName = eabCursor.getString(eabCursor + .getColumnIndex(EABContract.EABColumns.CONTACT_NAME)); + logger.debug("phoneNumber : " + phoneNumber + + " eabPhoneNumber :" + eabPhoneNumber); + // Contact names should match and both numbers should not be + // null & should not match. + if ((null != eabPhoneNumber) + && !PhoneNumberUtils.compare(mContext, phoneNumber, eabPhoneNumber)) { + // Update use-case. + handlePhoneNumberChanged(dataId, contactId, rawContactId, + eabPhoneNumber, phoneNumber, displayName); + } else { + if ((null != eabPhoneNumber) + && PhoneNumberUtils.compare(mContext, phoneNumber, eabPhoneNumber) + && !TextUtils.equals(displayName, eabDisplayName)) { + handlePhoneNameUpdate(dataId, contactId, rawContactId, + phoneNumber, displayName); + } else { + // Do nothing. + logger.debug("The contact name and number is already available " + + "in EAB Provider."); + } + } + } + } else { + // insert use-case. + handlePhoneNumberInsertion(dataId, contactId, rawContactId, phoneNumber, + displayName); + } + } + if (null != eabCursor) { + eabCursor.close(); + } + logger.debug("Exit: verifyInsertOrUpdateAction()"); + } + + private void checkForPhoneNumberDelete(ArrayList<Long> uniqueRawContactIds) { + logger.debug("Enter: checkForPhoneNumberDelete() "); + if (null != uniqueRawContactIds && uniqueRawContactIds.size() > 0) { + for (int i = 0; i < uniqueRawContactIds.size(); i++) { + Long rawContactId = uniqueRawContactIds.get(i); + int contactsDbCount = 0; + int eabDbCursorCount = 0; + + // Find the total number of dataIds under the rawContactId in + // Contacts Provider DB. + String[] projection = new String[] { ContactsContract.Data._ID, + ContactsContract.Data.CONTACT_ID, + ContactsContract.Data.RAW_CONTACT_ID, + ContactsContract.Data.MIMETYPE, + ContactsContract.Data.DATA1, + ContactsContract.Data.DISPLAY_NAME }; + + // Get LastContactChangedTimestamp for knowing which contact + // number deleted from the contact id. + long contactLastChange = SharedPrefUtil.getLastContactChangedTimestamp( + mContext, 0); + + String selection = ContactsContract.Data.MIMETYPE + " = '" + + Phone.CONTENT_ITEM_TYPE + "' AND " + + ContactsContract.Data.CONTACT_LAST_UPDATED_TIMESTAMP + + " > '" + contactLastChange + "' AND " + + ContactsContract.Data.RAW_CONTACT_ID + " = '" + + rawContactId + "'"; + + String sortOrder = ContactsContract.Data.RAW_CONTACT_ID + " desc"; + + Cursor contactDbCursor = getContentResolver().query( + ContactsContract.Data.CONTENT_URI, projection, + selection, null, sortOrder); + + if (null != contactDbCursor) { + contactsDbCount = contactDbCursor.getCount(); + logger.debug("contactDbCursor count : " + contactsDbCount); + } + + // Find the total number of dataIds under the rawContactId in + // EAB Provider DB. + String[] eabProjection = new String[] { + EABContract.EABColumns.CONTACT_ID, + EABContract.EABColumns.RAW_CONTACT_ID, + EABContract.EABColumns.DATA_ID, + EABContract.EABColumns.CONTACT_NUMBER, + EABContract.EABColumns.CONTACT_NAME }; + + String eabWhereClause = EABContract.EABColumns.RAW_CONTACT_ID + + " ='" + rawContactId.toString() + "'"; + + Cursor eabDbCursor = getContentResolver().query( + EABContract.EABColumns.CONTENT_URI, eabProjection, + eabWhereClause, null, null); + if (null != eabDbCursor) { + eabDbCursorCount = eabDbCursor.getCount(); + logger.debug("eabDbCursor count : " + eabDbCursorCount); + } + if (0 == contactsDbCount && 0 == eabDbCursorCount) { + // Error scenario. Continue for checking the next rawContactId. + logger.error("Both cursor counts are 0. move to next rawContactId"); + } else { + if (contactsDbCount == eabDbCursorCount) { + // Do nothing as both DB have the same number of contacts. + logger.debug("Both the databases have the same number of contacts." + + " Do nothing."); + } else if (contactsDbCount > eabDbCursorCount) { + logger.error("EAB DB has less contacts then Contacts DB. Do nothing!"); + } else if (contactsDbCount < eabDbCursorCount) { + // find and number and delete it from EAB Provider. + logger.debug("Delete usecase hit. Find and delete contact from EAB DB."); + ArrayList <Long> eabDataIdList = new ArrayList <Long>(); + while (eabDbCursor.moveToNext()) { + String eabPhoneNumber = eabDbCursor.getString(eabDbCursor + .getColumnIndex(EABContract.EABColumns.CONTACT_NUMBER)); + logger.debug("eabPhoneNumber :" + eabPhoneNumber); + Long eabDataId = Long.valueOf(eabDbCursor.getLong(eabDbCursor + .getColumnIndex(EABContract.EABColumns.DATA_ID))); + logger.debug("eabDataId :" + eabDataId); + if (eabDataIdList.isEmpty()) { + eabDataIdList.add(eabDataId); + } else if (!eabDataIdList.contains(eabDataId) ) { + eabDataIdList.add(eabDataId); + } else { + // Something is wrong. There can not be duplicate numbers. + logger.error("Duplicate entry for PhoneNumber :" + eabPhoneNumber + + " with DataId : " + eabDataId + " found in EABProvider."); + } + } + logger.debug("Before computation eabDataIdList size :" + + eabDataIdList.size()); + while (contactDbCursor.moveToNext()) { + String contactPhoneNumber = contactDbCursor.getString(contactDbCursor + .getColumnIndex(ContactsContract.Data.DATA1)); + Long contactDataId = Long.valueOf(contactDbCursor.getLong( + contactDbCursor + .getColumnIndex(ContactsContract.Data._ID))); + logger.debug("contactPhoneNumber : " + contactPhoneNumber + + " dataId : " + contactDataId); + if (eabDataIdList.contains(contactDataId) ) { + eabDataIdList.remove(contactDataId); + logger.debug("Number removed from eabDataIdList"); + } else { + // Something is wrong. There can not be new number in Contacts DB. + logger.error("Number :" + contactPhoneNumber + + " with DataId : " + contactDataId + + " not found in EABProvider."); + } + } + logger.debug("After computation eabPhoneNumberList size :" + + eabDataIdList.size()); + if (eabDataIdList.size() > 0) { + handlePhoneNumbersDeleted(rawContactId, eabDataIdList); + } + } + } + if (null != contactDbCursor) { + contactDbCursor.close(); + } + if (null != eabDbCursor) { + eabDbCursor.close(); + } + } + } else { + // Do nothing. + logger.debug("uniqueRawContactIds is null or empty. Do nothing. "); + } + logger.debug("Exit: checkForPhoneNumberDelete() "); + } + + private void checkForDeletedContact() { + logger.debug("Enter: checkForDeletedContact()"); + String[] projection = new String[] { + ContactsContract.DeletedContacts.CONTACT_ID, + ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP }; + + long contactLastDeleted = SharedPrefUtil.getLastContactDeletedTimestamp(mContext, 0); + logger.debug("contactLastDeleted : " + contactLastDeleted); + + String selection = ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP + + " > '" + contactLastDeleted + "'"; + + String sortOrder = ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP + " desc"; + + Cursor cursor = getContentResolver().query( + ContactsContract.DeletedContacts.CONTENT_URI, projection, + selection, null, sortOrder); + if (null != cursor) { + int count = cursor.getCount(); + logger.debug("cursor count : " + count); + if (count > 0) { + while (cursor.moveToNext()) { + Long contactId = Long.valueOf(cursor.getLong(cursor + .getColumnIndex(ContactsContract.DeletedContacts.CONTACT_ID))); + logger.debug("contactId : " + contactId); + handleContactDeleted(contactId); + } + // Save the largest returned timestamp. Only need the first + // cursor element due to the sort order. + cursor.moveToFirst(); + long timestamp = cursor.getLong(cursor + .getColumnIndex( + ContactsContract.DeletedContacts.CONTACT_DELETED_TIMESTAMP)); + if (timestamp > 0) { + SharedPrefUtil.saveLastContactDeletedTimestamp(mContext, timestamp); + } + } + } + if (null != cursor) { + cursor.close(); + } + logger.debug("Exit: checkForDeletedContact()"); + } + + private void checkForProfileNumberChanges() { + logger.debug("Enter: checkForProfileNumberChanges()"); + String[] projection = new String[] { + ContactsContract.Contacts.Entity.CONTACT_ID, + ContactsContract.Contacts.Entity.RAW_CONTACT_ID, + ContactsContract.Contacts.Entity.DATA_ID, + ContactsContract.Contacts.Entity.MIMETYPE, + ContactsContract.Contacts.Entity.DATA1, + ContactsContract.Contacts.Entity.DISPLAY_NAME, + ContactsContract.Contacts.Entity.CONTACT_LAST_UPDATED_TIMESTAMP}; + + long profileContactLastChange = SharedPrefUtil.getLastProfileContactChangedTimestamp( + mContext, 0); + logger.debug("profileContactLastChange : " + profileContactLastChange); + + String selection = ContactsContract.Contacts.Entity.MIMETYPE + " ='" + + Phone.CONTENT_ITEM_TYPE + "' AND " + + ContactsContract.Contacts.Entity.CONTACT_LAST_UPDATED_TIMESTAMP + " > '" + + profileContactLastChange + "'"; + String sortOrder = ContactsContract.Contacts.Entity.CONTACT_LAST_UPDATED_TIMESTAMP + + " desc"; + // Construct the Uri to access Profile's Entity view. + Uri profileUri = ContactsContract.Profile.CONTENT_URI; + Uri entiryUri = Uri.withAppendedPath(profileUri, + ContactsContract.Contacts.Entity.CONTENT_DIRECTORY); + + Cursor cursor = getContentResolver().query(entiryUri, projection, selection, null, + sortOrder); + if (null != cursor) { + int count = cursor.getCount(); + logger.debug("cursor count : " + count); + if (count > 0) { + ArrayList <String> profileNumberList = new ArrayList <String>(); + ArrayList <Long> profileDataIdList = new ArrayList <Long>(); + Long contactId = null; + Long rawContactId = null; + while (cursor.moveToNext()) { + contactId = Long.valueOf(cursor.getLong(cursor.getColumnIndex( + ContactsContract.Contacts.Entity.CONTACT_ID))); + rawContactId = Long.valueOf(cursor.getLong(cursor.getColumnIndex( + ContactsContract.Contacts.Entity.RAW_CONTACT_ID))); + Long dataId = Long.valueOf(cursor.getLong(cursor.getColumnIndex( + ContactsContract.Contacts.Entity.DATA_ID))); + String contactNumber = cursor.getString(cursor.getColumnIndex( + ContactsContract.Contacts.Entity.DATA1)); + String profileName = cursor.getString(cursor.getColumnIndex( + ContactsContract.Contacts.Entity.DISPLAY_NAME)); + logger.debug("Profile Name : " + profileName + + " Profile Number : " + contactNumber + + " profile dataId : " + dataId + + " profile rawContactId : " + rawContactId + + " profile contactId : " + contactId); + if (profileDataIdList.isEmpty()) { + profileDataIdList.add(dataId); + profileNumberList.clear(); + profileNumberList.add(contactNumber); + } else if (!profileDataIdList.contains(dataId)) { + profileDataIdList.add(dataId); + profileNumberList.add(contactNumber); + } else { + // There are duplicate entries in Profile's Table + logger.error("Duplicate entry in Profile's Table for contact :" + + contactNumber + " dataId : " + dataId); + } + verifyInsertOrUpdateAction(dataId, contactId, rawContactId, contactNumber, + profileName); + } + checkForProfilePhoneNumberDelete(contactId, rawContactId, profileDataIdList); + // Save the largest timestamp returned. Only need the first cursor element + // due to sort order. + cursor.moveToFirst(); + long timestamp = cursor.getLong(cursor.getColumnIndex( + ContactsContract.Contacts.Entity.CONTACT_LAST_UPDATED_TIMESTAMP)); + if (timestamp > 0) { + SharedPrefUtil.saveLastProfileContactChangedTimestamp(mContext, timestamp); + } + } else { + logger.error("cursor is zero. Do nothing."); + } + } else { + logger.error("ContactsContract.Profile.CONTENT_URI cursor is null!"); + } + if (null != cursor) { + cursor.close(); + } + logger.debug("Exit: checkForProfileNumberChanges()"); + } + + private void checkForProfilePhoneNumberDelete(Long profileContactId, + Long profileRawContactId, ArrayList<Long> profileDataIdList) { + logger.debug("Enter: checkForProfilePhoneNumberDelete()"); + if (!ContactsContract.isProfileId(profileContactId)) { + logger.error("Not a Profile Contact Id : " + profileContactId); + return; + } + int eabDbCursorCount = 0; + int profileContactsDbCount = profileDataIdList.size(); + logger.error("profileContactsDbCount size : " + profileContactsDbCount); + // Find the total number of dataIds under the rawContactId in EAB Provider DB. + String[] eabProjection = new String[] { + EABContract.EABColumns.CONTACT_ID, + EABContract.EABColumns.DATA_ID, + EABContract.EABColumns.CONTACT_NUMBER}; + String eabWhereClause = EABContract.EABColumns.CONTACT_ID + " ='" + + profileContactId.toString() + "'"; + + Cursor eabDbCursor = getContentResolver().query( EABContract.EABColumns.CONTENT_URI, + eabProjection, + eabWhereClause, null, null); + if (null != eabDbCursor) { + eabDbCursorCount = eabDbCursor.getCount(); + logger.debug("eabDbCursor count : " + eabDbCursorCount); + } + if (0 == profileContactsDbCount && 0 == eabDbCursorCount) { + // Error scenario. Continue for checking the next rawContactId. + logger.error("Both cursor counts are 0. Do nothing"); + } else { + if (profileContactsDbCount == eabDbCursorCount) { + // Do nothing as both DB have the same number of contacts. + logger.debug("Both the databases have the same number of contacts. Do nothing."); + } else if (profileContactsDbCount > eabDbCursorCount) { + logger.error("EAB DB has less contacts then Contacts DB. Do nothing!"); + } else if (profileContactsDbCount < eabDbCursorCount) { + // find and number and delete it from EAB Provider. + logger.debug("Delete usecase hit. Find and delete contact from EAB DB."); + ArrayList <Long> eabDataIdList = new ArrayList <Long>(); + while (eabDbCursor.moveToNext()) { + Long eabDataId = Long.valueOf(eabDbCursor.getLong(eabDbCursor + .getColumnIndex(EABContract.EABColumns.DATA_ID))); + logger.debug("eabDataId : " + eabDataId); + if (eabDataIdList.isEmpty()) { + eabDataIdList.add(eabDataId); + } else if (!eabDataIdList.contains(eabDataId) ) { + eabDataIdList.add(eabDataId); + } else { + // Something is wrong. There can not be duplicate numbers. + logger.error("Duplicate entry for eabDataId in EABProvider : " + + eabDataId); + } + } + logger.debug("Before computation eabDataIdList size : " + eabDataIdList.size()); + for (int i = 0; i < profileDataIdList.size(); i++) { + Long contactDataId = profileDataIdList.get(i); + logger.debug("Profile contactDataId : " + contactDataId); + if (eabDataIdList.contains(contactDataId) ) { + eabDataIdList.remove(contactDataId); + logger.debug("Number removed from eabDataIdList"); + } else { + // Something is wrong. There can not be new number in Contacts DB. + logger.error("DataId : " + contactDataId + + " not found in EAB Provider DB."); + } + } + logger.debug("After computation eabDataIdList size : " + eabDataIdList.size()); + if (eabDataIdList.size() > 0) { + handlePhoneNumbersDeleted(profileRawContactId, eabDataIdList); + } + } + } + if (null != eabDbCursor) { + eabDbCursor.close(); + } + logger.debug("Exit: checkForProfilePhoneNumberDelete() "); + } + + private void checkForDeletedProfileContacts() { + logger.debug("Enter: checkForDeletedProfileContacts()"); + String[] projection = new String[] { + ContactsContract.Contacts.Entity.DATA1, + ContactsContract.Contacts.Entity.DISPLAY_NAME, + ContactsContract.Contacts.Entity.CONTACT_LAST_UPDATED_TIMESTAMP}; + + String selection = ContactsContract.Contacts.Entity.MIMETYPE + " ='" + + Phone.CONTENT_ITEM_TYPE + "'"; + // Construct the Uri to access Profile's Entity view. + Uri profileUri = ContactsContract.Profile.CONTENT_URI; + Uri entiryUri = Uri.withAppendedPath(profileUri, + ContactsContract.Contacts.Entity.CONTENT_DIRECTORY); + + // Due to issue in AOSP contact profile db, table + // ContactsContract.Profile.CONTENT_URI can not be checked for + // selection = ContactsContract.Profile.HAS_PHONE_NUMBER + " = '" + 1 + "'". + // This is resulting in 0 cursor count even when there are valid + // contact numbers under contacts profile db. + Cursor cursor = getContentResolver().query(entiryUri, projection, selection, null, null); + if (null != cursor) { + int count = cursor.getCount(); + logger.debug("Profile contact cursor count : " + count); + if (count == 0) { + logger.debug("cursor count is Zero. There are no contacts in Contact Profile db."); + handleContactProfileDeleted(); + } else { + logger.debug("Profile is available. Do nothing"); + } + cursor.close(); + } + logger.debug("Exit: checkForDeletedProfileContacts()"); + } + + private void handlePhoneNumberInsertion(Long dataId, Long contactId, + Long rawContactId, String phoneNumber, String contactName) { + + logger.debug("handlePhoneNumberInsertion() rawContactId : " + + rawContactId + " dataId :" + dataId + " contactId :" + + contactId + " phoneNumber :" + phoneNumber + " contactName :" + + contactName); + if (!EABDbUtil.validateEligibleContact(mContext, phoneNumber)) { + logger.debug("Return as number is not elegible for VT."); + return; + } + String sRawContactId = null; + String sDataId = null; + String sContactId = null; + if (null != rawContactId) { + sRawContactId = rawContactId.toString(); + } + if (null != dataId) { + sDataId = dataId.toString(); + } + if (null != contactId) { + sContactId = contactId.toString(); + } + ArrayList<PresenceContact> contactListToInsert = new ArrayList<PresenceContact>(); + contactListToInsert.add(new PresenceContact(contactName, phoneNumber, + sRawContactId, sContactId, sDataId)); + + EABDbUtil.addContactsToEabDb(getApplicationContext(), + contactListToInsert); + } + + private void handlePhoneNumberChanged(Long dataId, Long contactId, + Long rawContactId, String oldPhoneNumber, String newPhoneNumber, + String contactName) { + + logger.debug("handlePhoneNumberChanged() rawContactId : " + rawContactId + + " dataId :" + dataId + " oldPhoneNumber :" + oldPhoneNumber + + " newPhoneNumber :" + newPhoneNumber + " contactName :" + + contactName); + + if (null == oldPhoneNumber && null == newPhoneNumber) { + logger.debug("Both old and new numbers are null."); + return; + } + + ArrayList<PresenceContact> contactListToInsert = new ArrayList<PresenceContact>(); + ArrayList<PresenceContact> contactListToDelete = new ArrayList<PresenceContact>(); + String sRawContactId = null; + String sDataId = null; + String sContactId = null; + if (null != rawContactId) { + sRawContactId = rawContactId.toString(); + } + if (null != dataId) { + sDataId = dataId.toString(); + } + if (null != contactId) { + sContactId = contactId.toString(); + } + logger.debug("Removing old number and inserting new number in EABProvider."); + if (null != oldPhoneNumber) { + contactListToDelete.add(new PresenceContact(contactName, + oldPhoneNumber, sRawContactId, sContactId, sDataId)); + // Delete old number from EAB Presence Table + EABDbUtil.deleteNumbersFromEabDb(getApplicationContext(), contactListToDelete); + } + if (null != newPhoneNumber) { + if (EABDbUtil.validateEligibleContact(mContext, newPhoneNumber)) { + contactListToInsert.add(new PresenceContact(contactName, + newPhoneNumber, sRawContactId, sContactId, sDataId)); + // Insert new number from EAB Presence Table + EABDbUtil.addContactsToEabDb(getApplicationContext(), contactListToInsert); + } else { + logger.debug("Return as number is not elegible for VT."); + } + } + } + + private void handlePhoneNumbersDeleted(Long rawContactId, ArrayList <Long> contactDataIdList) { + ArrayList<PresenceContact> phoneNumberToDeleteList = new ArrayList<PresenceContact>(); + for (int v = 0; v < contactDataIdList.size(); v++) { + Long staleDataId = contactDataIdList.get(v); + if (null != staleDataId) { + logger.debug("calling delete for staleNumber :" + staleDataId); + String sRawContactId = null; + if (null != rawContactId) { + sRawContactId = rawContactId.toString(); + } + phoneNumberToDeleteList.add(new PresenceContact(null, null, + sRawContactId, null, staleDataId.toString())); + } + } + // Delete number from EAB Provider table. + EABDbUtil.deleteNumbersFromEabDb(getApplicationContext(), phoneNumberToDeleteList); + } + + private void handlePhoneNameUpdate(Long dataId, Long contactId, + Long rawContactId, String phoneNumber, String newDisplayName) { + logger.debug("handlePhoneNameUpdate() rawContactId : " + rawContactId + + " dataId :" + dataId + " newDisplayName :" + newDisplayName); + String sRawContactId = null; + String sDataId = null; + String sContactId = null; + if (null != rawContactId) { + sRawContactId = rawContactId.toString(); + } + if (null != dataId) { + sDataId = dataId.toString(); + } + if (null != contactId) { + sContactId = contactId.toString(); + } + ArrayList<PresenceContact> contactNameToUpdate = new ArrayList<PresenceContact>(); + contactNameToUpdate.add(new PresenceContact(newDisplayName, + phoneNumber, sRawContactId, sContactId, sDataId)); + + EABDbUtil.updateNamesInEabDb(getApplicationContext(), contactNameToUpdate); + } + + private void handleContactDeleted(Long contactId) { + + if (null == contactId) { + logger.debug("handleContactDeleted : contactId is null"); + } + ArrayList<PresenceContact> contactListToDelete = new ArrayList<PresenceContact>(); + contactListToDelete.add(new PresenceContact(null, null, null, contactId.toString(), null)); + + //ContactDbUtil.deleteRawContact(getApplicationContext(), contactListToDelete); + EABDbUtil.deleteContactsFromEabDb(mContext, contactListToDelete); + } + + private void handleContactProfileDeleted() { + Long contactProfileMinId = Long.valueOf(ContactsContract.Profile.MIN_ID); + logger.debug("contactProfileMinId : " + contactProfileMinId); + + ArrayList<PresenceContact> contactListToDelete = new ArrayList<PresenceContact>(); + contactListToDelete.add(new PresenceContact(null, null, null, + contactProfileMinId.toString(), null)); + + EABDbUtil.deleteContactsFromEabDb(mContext, contactListToDelete); + } + + private boolean isRcsProvisioned(){ + boolean isVoLTEProvisioned = false; + boolean isLvcProvisioned = false; + boolean isEabProvisioned = false; + ImsManager imsManager = null; + ImsConfig imsConfig = null; + + // Get instance of imsManagr. + imsManager = ImsManager.getInstance(mContext, + SubscriptionManager.getDefaultVoiceSubscriptionId()); + try { + imsConfig = imsManager.getConfigInterface(); + logger.debug("imsConfig initialized."); + } catch (Exception e) { + logger.error("getConfigInterface() exception:", e); + imsConfig = null; + } + + if (null != imsConfig) { + try { + isVoLTEProvisioned = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.VLT_SETTING_ENABLED) + == ImsConfig.FeatureValueConstants.ON; + isLvcProvisioned = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.LVC_SETTING_ENABLED) + == ImsConfig.FeatureValueConstants.ON; + isEabProvisioned = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.EAB_SETTING_ENABLED) + == ImsConfig.FeatureValueConstants.ON; + } catch (ImsException e) { + logger.error("ImsException in isRcsProvisioned() : ", e); + } + } else { + logger.debug("isRcsProvisioned - imsConfig is null"); + } + logger.debug("isVoLTEProvisioned : " + isVoLTEProvisioned + " isLvcProvisioned : " + + isLvcProvisioned + + " isEabProvisioned : " + isEabProvisioned); + return (isVoLTEProvisioned && isLvcProvisioned && isEabProvisioned); + } +} diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/InvalidDBException.java b/rcs/presencepolling/src/com/android/service/ims/presence/InvalidDBException.java new file mode 100644 index 0000000..1e7fdeb --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/InvalidDBException.java @@ -0,0 +1,41 @@ +/* + * 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.presence; + +import android.database.sqlite.SQLiteException; + +//A custom exception for onDowngrade() +public class InvalidDBException extends SQLiteException { + public InvalidDBException() { + } + + public InvalidDBException(String error) { + super(error); + } +} diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/LauncherUtils.java b/rcs/presencepolling/src/com/android/service/ims/presence/LauncherUtils.java new file mode 100644 index 0000000..8362142 --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/LauncherUtils.java @@ -0,0 +1,82 @@ +/* + * 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.presence; + +import android.content.Context; +import android.content.Intent; + +import com.android.ims.internal.Logger; + +/** + * Launcher utility functions + * + */ +public class LauncherUtils { + private static Logger logger = Logger.getLogger(LauncherUtils.class.getName()); + + /** + * Launch the Presence service. + * + * @param context application context + * @param boot Boot flag + */ + public static boolean launchPollingService(Context context) { + logger.debug("Launch polling service"); + + Intent intent = new Intent(context, PollingService.class); + return (context.startService(intent) != null); + } + + /** + * Stop the Presence service. + * + * @param context application context + */ + public static boolean stopPollingService(Context context) { + logger.debug("Stop polling service"); + + Intent intent = new Intent(context, PollingService.class); + return context.stopService(intent); + } + + public static boolean launchEabService(Context context) { + logger.debug("Launch Eab Service"); + + Intent intent = new Intent(context, EABService.class); + return (context.startService(intent) != null); + } + + public static boolean stopEabService(Context context) { + logger.debug("Stop EAB service"); + + Intent intent = new Intent(context, EABService.class); + return context.stopService(intent); + } +} + diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/PersistService.java b/rcs/presencepolling/src/com/android/service/ims/presence/PersistService.java new file mode 100644 index 0000000..bbd9cee --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/PersistService.java @@ -0,0 +1,215 @@ +/* + * 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.presence; + +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; +import android.app.Service; +import android.os.SystemProperties; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.HandlerThread; +import android.os.Process; +import java.util.ArrayList; +import android.content.ContentValues; +import android.text.TextUtils; + +import com.android.ims.internal.Logger; +import com.android.ims.RcsManager.ResultCode; +import com.android.ims.RcsPresence; +import com.android.ims.RcsPresenceInfo; + +/** + * This service essentially plays the role of a "worker thread", allowing us to store + * presence information. + */ +public class PersistService extends Service { + private Logger logger = Logger.getLogger(this.getClass().getName()); + + private static final int MESSAGE_PRESENCE_CHANGED = 1; + private static final int MESSAGE_PUBLISH_STATE_CHANGED = 2; + + private int mVltProvisionErrorCount = 0; + private Looper mServiceLooper = null; + private ServiceHandler mServiceHandler = null; + private EABContactManager mEABContactManager = null; + + @Override + public void onCreate() { + mEABContactManager = new EABContactManager(getContentResolver(), getPackageName()); + + // separate thread because the service normally runs in the process's + // main thread, which we don't want to block. + HandlerThread thread = new HandlerThread("PersistServiceThread", + Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + + mServiceLooper = thread.getLooper(); + mServiceHandler = new ServiceHandler(mServiceLooper); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if(intent == null) { + return Service.START_NOT_STICKY; + } + + if(RcsPresence.ACTION_PRESENCE_CHANGED.equals(intent.getAction())) { + Message msg = mServiceHandler.obtainMessage(MESSAGE_PRESENCE_CHANGED); + msg.arg1 = startId; + msg.obj = intent; + mServiceHandler.sendMessage(msg); + return Service.START_NOT_STICKY; + } else if(RcsPresence.ACTION_PUBLISH_STATE_CHANGED.equals(intent.getAction())) { + Message msg = mServiceHandler.obtainMessage(MESSAGE_PUBLISH_STATE_CHANGED); + msg.arg1 = startId; + msg.obj = intent; + mServiceHandler.sendMessage(msg); + return Service.START_NOT_STICKY; + } else { + logger.debug("unknown intent=" + intent); + } + + return Service.START_NOT_STICKY; + } + + @Override + public void onDestroy() { + mServiceLooper.quit(); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + private final class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { + super(looper); + } + + /** + * Handler to save the presence information. + */ + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + logger.print( "Thread=" + Thread.currentThread().getName() + " received " + + msg); + if(msg == null){ + logger.error("msg=null"); + return; + } + + int serviceId = msg.arg1; + Intent intent = (Intent)msg.obj; + logger.print("handleMessage serviceId: " + serviceId + " intent: " + intent); + switch (msg.what) { + case MESSAGE_PRESENCE_CHANGED: + if (intent != null) { + if (RcsPresence.ACTION_PRESENCE_CHANGED.equals(intent.getAction())) { + handlePresence(intent); + } else { + logger.debug("I don't care about intent: " + intent); + } + intent.setComponent(null); + sendBroadcast(intent); + } + break; + + case MESSAGE_PUBLISH_STATE_CHANGED: + if (intent != null) { + if (RcsPresence.ACTION_PUBLISH_STATE_CHANGED.equals(intent.getAction())) { + int publishState = intent.getIntExtra(RcsPresence.EXTRA_PUBLISH_STATE, + RcsPresence.PublishState.PUBLISH_STATE_200_OK); + handlePublishState(publishState); + } else { + logger.debug("I don't care about intent: " + intent); + } + } + break; + + default: + logger.debug("unknown message:" + msg); + } + } + } + + private void handlePublishState(int publishState) { + if(publishState == RcsPresence.PublishState.PUBLISH_STATE_VOLTE_PROVISION_ERROR) { + mVltProvisionErrorCount += 1; + if(mVltProvisionErrorCount >= 3){ + if(mEABContactManager != null) { + mEABContactManager.updateAllCapabilityToUnknown(); + logger.print("updateAllCapabilityToUnknown for publish 403 error"); + Intent intent = new Intent(RcsPresence.ACTION_PRESENCE_CHANGED); + sendBroadcast(intent); + } else { + logger.error("mEABContactManager is null"); + } + } + } else if(publishState == RcsPresence.PublishState.PUBLISH_STATE_RCS_PROVISION_ERROR){ + mVltProvisionErrorCount = 0; + if(mEABContactManager != null) { + mEABContactManager.updateAllVtCapabilityToUnknown(); + logger.print("updateAllVtCapabilityToUnknown for publish 404 error"); + Intent intent = new Intent(RcsPresence.ACTION_PRESENCE_CHANGED); + sendBroadcast(intent); + } else { + logger.error("mEABContactManager is null"); + } + } else { + mVltProvisionErrorCount = 0; + } + } + + private void handlePresence(Intent intent) { + if(intent == null) { + return; + } + + // save the presence information. + ArrayList<RcsPresenceInfo> rcsPresenceInfoList = intent.getParcelableArrayListExtra( + RcsPresence.EXTRA_PRESENCE_INFO_LIST); + boolean updateLastTimestamp = intent.getBooleanExtra("updateLastTimestamp", true); + logger.print("updateLastTimestamp=" + updateLastTimestamp + + " RcsPresenceInfoList=" + rcsPresenceInfoList); + for(int i=0; i< rcsPresenceInfoList.size(); i++){ + RcsPresenceInfo rcsPresenceInfoTmp = rcsPresenceInfoList.get(i); + if((rcsPresenceInfoTmp != null) && !TextUtils.isEmpty( + rcsPresenceInfoTmp.getContactNumber())){ + mEABContactManager.update(rcsPresenceInfoTmp, updateLastTimestamp); + } + } + } +} + + diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/PollingAction.java b/rcs/presencepolling/src/com/android/service/ims/presence/PollingAction.java new file mode 100644 index 0000000..fc297ae --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/PollingAction.java @@ -0,0 +1,209 @@ +/* + * 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.presence; + +import android.content.Context; +import android.os.AsyncTask; +import android.os.Looper; + +import com.android.ims.IRcsPresenceListener; +import com.android.ims.RcsManager; +import com.android.ims.RcsPresence; +import com.android.ims.RcsException; +import com.android.ims.RcsManager.ResultCode; +import com.android.ims.internal.Logger; + +import java.util.ArrayList; +import java.util.List; + +public class PollingAction extends AsyncTask<Void, Integer, Integer> { + private Logger logger = Logger.getLogger(this.getClass().getName()); + + private Context mContext; + private PollingTask mPollingTask; + private int mResult; + private int mRequestId = -1; + + private final Object mPollingSyncObj = new Object(); + private boolean mIsPolling = false; + private boolean mFullUpdated = false; + + private IRcsPresenceListener mClientListener = new IRcsPresenceListener.Stub() { + public void onSuccess(int reqId) { + logger.print("onSuccess() is called. reqId=" + reqId); + } + + public void onError(int reqId, int code) { + logger.print("onError() is called, reqId=" + reqId + " error code: " + code); + synchronized(mPollingSyncObj) { + mResult = code; + mIsPolling = false; + mPollingSyncObj.notifyAll(); + } + } + + public void onFinish(int reqId) { + logger.print("onFinish() is called, reqId=" + reqId); + if (reqId == mRequestId) { + synchronized(mPollingSyncObj) { + mFullUpdated = true; + mIsPolling = false; + mPollingSyncObj.notifyAll(); + } + } + } + + public void onTimeout(int reqId) { + logger.print("onTimeout() is called, reqId=" + reqId); + if (reqId == mRequestId) { + synchronized(mPollingSyncObj) { + mIsPolling = false; + mPollingSyncObj.notifyAll(); + } + } + } + }; + + public PollingAction(Context context, PollingTask task) { + mContext = context; + mPollingTask = task; + logger.info("PollingAction(), task=" + mPollingTask); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + mPollingTask.onPreExecute(); + } + + @Override + protected Integer doInBackground(Void... params) { + logger.debug("doInBackground(), Thread = " + Thread.currentThread().getName()); + + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + int requestExpiration = PresenceSetting.getCapabilityPollListSubscriptionExpiration(); + logger.print("getCapabilityPollListSubscriptionExpiration: " + requestExpiration); + if (requestExpiration == -1) { + requestExpiration = 30; + } + requestExpiration += 30; + + mResult = ResultCode.SUBSCRIBE_NOT_FOUND; + ArrayList<String> uriList = new ArrayList<String>(); + uriList.clear(); + + List<Contacts.Item> contacts = mPollingTask.mContacts; + for (int i = 0; i < contacts.size(); i++) { + Contacts.Item item = contacts.get(i); + uriList.add(getCompleteUri(item.number())); + } + int size = uriList.size(); + if (size <= 0) { + logger.debug("No contacts in polling task, no action."); + } else { + mResult = ResultCode.ERROR_SERVICE_NOT_AVAILABLE; + synchronized(mPollingSyncObj) { + RcsManager rcsManager = RcsManager.getInstance(mContext, 0); + if (rcsManager == null) { + logger.debug("rcsManager == null"); + } else { + try { + RcsPresence rcsPresence = rcsManager.getRcsPresenceInterface(); + if (rcsPresence == null) { + logger.debug("rcsPresence == null"); + } else { + logger.print("call requestCapability: " + size); + // If ret > 0 then it is the request Id, or it is result code. + int ret = rcsPresence.requestCapability(uriList, mClientListener); + if (ret > 0) { + mRequestId = ret; + mResult = ResultCode.SUCCESS; + } else { + mRequestId = -1; + mResult = ret; + } + } + } catch (RcsException ex) { + logger.print("RcsException", ex); + } + } + + if (mResult == ResultCode.SUCCESS) { + logger.print("Capability discovery success, RequestId = " + mRequestId); + mIsPolling = true; + } else { + logger.info("Capability discovery failure result = " + mResult); + mIsPolling = false; + } + + final long endTime = System.currentTimeMillis() + requestExpiration * 1000; + while(true) { + if (!mIsPolling) { + break; + } + + long delay = endTime - System.currentTimeMillis(); + if (delay <= 0) { + break; + } + + try { + mPollingSyncObj.wait(delay); + } catch (InterruptedException ex) { + } + } + } + } + + logger.print("The action final result = " + mResult); + mPollingTask.onPostExecute(mResult); + if (ResultCode.SUBSCRIBE_TEMPORARY_ERROR == mResult) { + mPollingTask.retry(); + } else { + mPollingTask.finish(mFullUpdated); + } + + return mResult; + } + + private String getCompleteUri(String phone) { + phone = "tel:" + phone; + return phone; + } + + @Override + protected void onPostExecute(Integer result) { + super.onPostExecute(result); + logger.print("onPostExecute(), result = " + result); + } +} + diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/PollingService.java b/rcs/presencepolling/src/com/android/service/ims/presence/PollingService.java new file mode 100644 index 0000000..d6e4410 --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/PollingService.java @@ -0,0 +1,118 @@ +/* + * 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.presence; + +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; + +import com.android.ims.internal.Logger; + +public class PollingService extends Service { + /** + * The logger + */ + private Logger logger = Logger.getLogger(this.getClass().getName()); + + private CapabilityPolling mCapabilityPolling = null; + + /** + * Constructor + */ + public PollingService() { + logger.debug("PollingService()"); + } + + @Override + public void onCreate() { + logger.debug("onCreate()"); + + if (isEabSupported()) { + mCapabilityPolling = CapabilityPolling.getInstance(this); + mCapabilityPolling.start(); + } + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + logger.debug("onStartCommand(), intent: " + intent + + ", flags: " + flags + ", startId: " + startId); + + if (!isRcsSupported()) { + stopSelf(); + return START_NOT_STICKY; + } + + return super.onStartCommand(intent, flags, startId); + } + + /** + * Cleans up when the service is destroyed + */ + @Override + public void onDestroy() { + logger.debug("onDestroy()"); + + if (mCapabilityPolling != null) { + mCapabilityPolling.stop(); + mCapabilityPolling = null; + } + + super.onDestroy(); + } + + @Override + public IBinder onBind(Intent intent) { + logger.debug("onBind(), intent: " + intent); + + if (!isRcsSupported()) { + return null; + } + + logger.debug("onBind add services here"); + return null; + } + + private boolean isRcsSupported() { + String rcsSupported = SystemProperties.get("persist.rcs.supported"); + logger.info("persist.rcs.supported: " + rcsSupported); + return "1".equals(rcsSupported); + } + + private boolean isEabSupported() { + String eabSupported = SystemProperties.get("persist.eab.supported"); + logger.info("persist.eab.supported: " + eabSupported); + return ("0".equals(eabSupported)) ? false : true; + } +} + diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/PollingTask.java b/rcs/presencepolling/src/com/android/service/ims/presence/PollingTask.java new file mode 100644 index 0000000..69c4018 --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/PollingTask.java @@ -0,0 +1,256 @@ +/* + * 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.presence; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.SystemClock; +import android.text.format.Time; + +import com.android.ims.internal.Logger; + +import java.util.ArrayList; +import java.util.List; + +public class PollingTask { + private Logger logger = Logger.getLogger(this.getClass().getName()); + private Context mContext = null; + + private static long sMaxId = 0; + public static final String ACTION_POLLING_RETRY_ALARM = + "com.android.service.ims.presence.capability_polling_retry"; + private PendingIntent mRetryAlarmIntent = null; + + public long mId; + public int mType; + public List<Contacts.Item> mContacts = new ArrayList<Contacts.Item>(); + + private long mTimeUnit; + private int mTotalRetry; + private int mCurrentRetry; + private long mLastUpdateTime; + + private boolean mCancelled = false; + private boolean mCompleted = false; + + public PollingTask(int type, List<Contacts.Item> list) { + mId = sMaxId++; + mType = type; + + mContacts.clear(); + for(int i = 0; i < list.size(); i++) { + Contacts.Item item = list.get(i); + mContacts.add(item); + } + + mCurrentRetry = 0; + mTotalRetry = 5; + mTimeUnit = 1800; // 1800s = 30 minutes + if (CapabilityPolling.ACTION_POLLING_NEW_CONTACTS == mType) { + mTotalRetry = 4; + mTimeUnit = 60; // 60s = 1 minute + } + mLastUpdateTime = 0; + } + + public void execute() { + PollingsQueue queue = PollingsQueue.getInstance(null); + if (queue == null) { + return; + } + + PollingAction action = new PollingAction(queue.getContext(), this); + action.execute(); + } + + public void finish(boolean fullUpdated) { + cancelRetryAlarm(); + + PollingsQueue queue = PollingsQueue.getInstance(null); + if (queue == null) { + return; + } + + mCompleted = fullUpdated ? true : false; + queue.remove(this); + } + + public void retry() { + mCurrentRetry += 1; + logger.print("retry mCurrentRetry=" + mCurrentRetry); + if (mCurrentRetry > mTotalRetry) { + logger.print("Retry finished for task: " + this); + updateTimeStampToCurrent(); + finish(false); + return; + } + + if (mCancelled) { + logger.print("Task is cancelled: " + this); + finish(false); + return; + } + + long delay = mTimeUnit; + for (int i = 0; i < mCurrentRetry - 1; i++) { + delay *= 2; + } + + logger.print("retry delay=" + delay); + + scheduleRetry(delay * 1000); + } + + public void cancel() { + mCancelled = true; + logger.print("Cancel this task: " + this); + + if (mRetryAlarmIntent != null) { + cancelRetryAlarm(); + finish(false); + } + } + + public void onPreExecute() { + logger.print("onPreExecute(), id = " + mId); + cancelRetryAlarm(); + } + + public void onPostExecute(int result) { + logger.print("onPostExecute(), result = " + result); + mLastUpdateTime = System.currentTimeMillis(); + } + + private void updateTimeStampToCurrent() { + if (mContext == null) { + return; + } + + EABContactManager contactManager = new EABContactManager( + mContext.getContentResolver(), mContext.getPackageName()); + for (int i = 0; i < mContacts.size(); i++) { + Contacts.Item item = mContacts.get(i); + + String number = item.number(); + long current = System.currentTimeMillis(); + EABContactManager.Request request = new EABContactManager.Request(number) + .setLastUpdatedTimeStamp(current); + int result = contactManager.update(request); + if (result <= 0) { + logger.debug("failed to update timestamp for contact: " + number); + } + } + } + + private void cancelRetryAlarm() { + if (mRetryAlarmIntent != null) { + if (mContext != null) { + AlarmManager alarmManager = (AlarmManager) + mContext.getSystemService(Context.ALARM_SERVICE); + alarmManager.cancel(mRetryAlarmIntent); + } + mRetryAlarmIntent = null; + } + } + + private void scheduleRetry(long msec) { + logger.print("scheduleRetry msec=" + msec); + + cancelRetryAlarm(); + + if (mContext == null) { + PollingsQueue queue = PollingsQueue.getInstance(null); + if (queue != null) { + mContext = queue.getContext(); + } + } + + if (mContext != null) { + Intent intent = new Intent(ACTION_POLLING_RETRY_ALARM); + intent.setClass(mContext, AlarmBroadcastReceiver.class); + intent.putExtra("pollingTaskId", mId); + + mRetryAlarmIntent = PendingIntent.getBroadcast(mContext, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + + AlarmManager alarmManager = (AlarmManager) + mContext.getSystemService(Context.ALARM_SERVICE); + alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + msec, mRetryAlarmIntent); + } + } + + private String getTimeString(long time) { + if (time <= 0) { + time = System.currentTimeMillis(); + } + + Time tobj = new Time(); + tobj.set(time); + return String.format("%s.%s", tobj.format("%m-%d %H:%M:%S"), time % 1000); + } + + public boolean isCompleted() { + return mCompleted; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PollingTask)) return false; + + PollingTask that = (PollingTask)o; + return (this.mId == that.mId) && (this.mType == that.mType); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("PollingTask { "); + sb.append("\nId: " + mId); + sb.append("\nType: " + mType); + sb.append("\nCurrentRetry: " + mCurrentRetry); + sb.append("\nTotalRetry: " + mTotalRetry); + sb.append("\nTimeUnit: " + mTimeUnit); + sb.append("\nLast update time: " + mLastUpdateTime + "(" + + getTimeString(mLastUpdateTime) + ")"); + sb.append("\nContacts: " + mContacts.size()); + for (int i = 0; i < mContacts.size(); i++) { + sb.append("\n"); + Contacts.Item item = mContacts.get(i); + sb.append("Number " + i + ": " + item.number()); + } + sb.append("\nCompleted: " + mCompleted); + sb.append(" }"); + return sb.toString(); + } +} + diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/PollingsQueue.java b/rcs/presencepolling/src/com/android/service/ims/presence/PollingsQueue.java new file mode 100644 index 0000000..b833de7 --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/PollingsQueue.java @@ -0,0 +1,246 @@ +/* + * 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.presence; + +import android.content.Context; + +import com.android.ims.internal.Logger; + +import java.util.ArrayList; +import java.util.List; + +public class PollingsQueue { + private Logger logger = Logger.getLogger(this.getClass().getName()); + + private Context mContext; + private CapabilityPolling mCapabilityPolling; + private List<PollingTask> mPollingTasks = new ArrayList<PollingTask>(); + private boolean mAskVerifyResult = false; + private int mVerifyCounts = 0; + + private static PollingsQueue sInstance = null; + public static synchronized PollingsQueue getInstance(Context context) { + if ((sInstance == null) && (context != null)) { + sInstance = new PollingsQueue(context); + } + + return sInstance; + } + + private PollingsQueue(Context context) { + mContext = context; + mPollingTasks.clear(); + } + + public synchronized void setCapabilityPolling(CapabilityPolling cp) { + mCapabilityPolling = cp; + } + + public CapabilityPolling getCapabilityPolling() { + return mCapabilityPolling; + } + + public Context getContext() { + return mContext; + } + + public synchronized void clear() { + mPollingTasks.clear(); + } + + public synchronized void add(int type, List<Contacts.Item> list) { + if (list.size() <= 0) { + return; + } + + List<Contacts.Item> contacts = new ArrayList<Contacts.Item>(); + contacts.clear(); + for(int i = 0; i < list.size(); i++) { + Contacts.Item item = list.get(i); + + boolean bExist = false; + for(int j = 0; j < contacts.size(); j++) { + Contacts.Item item0 = contacts.get(j); + if (item.equals(item0)) { + bExist = true; + break; + } + } + + for (int j = 0; j < mPollingTasks.size(); j++) { + if (bExist) { + break; + } + PollingTask task = mPollingTasks.get(j); + for(int k = 0; k < task.mContacts.size(); k++) { + Contacts.Item item0 = task.mContacts.get(k); + if (item.equals(item0)) { + bExist = true; + break; + } + } + } + if (bExist) { + continue; + } + + contacts.add(item); + if (type == CapabilityPolling.ACTION_POLLING_NORMAL) { + if (item.lastUpdateTime() == 0) { + type = CapabilityPolling.ACTION_POLLING_NEW_CONTACTS; + } + } + } + + PollingTask taskCancelled = null; + int nTasks = mPollingTasks.size(); + logger.print("Before add(), the existing tasks number: " + nTasks); + + if (contacts.size() <= 0) { + logger.debug("Empty/duplicated list, no request added."); + return; + } + + int maxEntriesInRequest = PresenceSetting.getMaxNumberOfEntriesInRequestContainedList(); + logger.print("getMaxNumberOfEntriesInRequestContainedList: " + maxEntriesInRequest); + if (maxEntriesInRequest == -1) { + maxEntriesInRequest = 100; + } + + int noOfIterations = contacts.size() / maxEntriesInRequest; + for (int loop = 0; loop <= noOfIterations; loop++) { + int entriesInRequest = maxEntriesInRequest; + if (loop == noOfIterations) { + entriesInRequest = contacts.size() % maxEntriesInRequest; + } + + List<Contacts.Item> cl = new ArrayList<Contacts.Item>(); + cl.clear(); + int pos = loop * maxEntriesInRequest; + for (int i = 0; i < entriesInRequest; i++) { + Contacts.Item item = contacts.get(pos); + cl.add(item); + pos++; + } + + if (cl.size() > 0) { + PollingTask task = new PollingTask(type, cl); + logger.debug("One new polling task added: " + task); + + boolean bInserted = false; + for (int i = 0; i < mPollingTasks.size(); i++) { + PollingTask task0 = mPollingTasks.get(i); + if (task.mType > task0.mType) { + bInserted = true; + mPollingTasks.add(i, task); + if ((i == 0) && (taskCancelled == null)) { + taskCancelled = task0; + } + break; + } + } + if (!bInserted) { + mPollingTasks.add(task); + } + } + } + + logger.print("After add(), the total tasks number: " + mPollingTasks.size()); + if (nTasks <= 0) { + if (mPollingTasks.size() > 0) { + PollingTask task = mPollingTasks.get(0); + task.execute(); + } + } else { + if (taskCancelled != null) { + taskCancelled.cancel(); + } + } + } + + public synchronized void remove(PollingTask task) { + int nTasks = mPollingTasks.size(); + if (nTasks <= 0) { + return; + } + + logger.print("Remove polling task: " + task); + if (task != null) { + for (int i = 0; i < nTasks; i++) { + PollingTask task0 = mPollingTasks.get(i); + if (task0.equals(task)) { + if (task.isCompleted()) { + mVerifyCounts = 0; + } else { + mAskVerifyResult = true; + } + mPollingTasks.remove(i); + break; + } + } + } + + if (mPollingTasks.size() > 0) { + PollingTask task0 = mPollingTasks.get(0); + task0.execute(); + } else { + if (mAskVerifyResult) { + mAskVerifyResult = false; + mVerifyCounts++; + if (mCapabilityPolling != null) { + mCapabilityPolling.enqueueVerifyPollingResult(mVerifyCounts); + } + } + } + } + + public synchronized void retry(long id) { + int nTasks = mPollingTasks.size(); + if (nTasks <= 0) { + return; + } + + PollingTask task0 = null; + for (int i = 0; i < nTasks; i++) { + PollingTask task = mPollingTasks.get(i); + if (task.mId == id) { + task0 = task; + break; + } + } + + if (task0 == null) { + logger.debug("Trigger wrong retry: " + id); + task0 = mPollingTasks.get(0); + } + + task0.execute(); + } +} + diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/PresenceBroadcastReceiver.java b/rcs/presencepolling/src/com/android/service/ims/presence/PresenceBroadcastReceiver.java new file mode 100644 index 0000000..6d716bd --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/PresenceBroadcastReceiver.java @@ -0,0 +1,56 @@ +/* + * 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.presence; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.android.ims.RcsPresence; + +import com.android.ims.internal.Logger; + +public class PresenceBroadcastReceiver extends BroadcastReceiver { + private Logger logger = Logger.getLogger(this.getClass().getName()); + + @Override + public void onReceive(Context context, Intent intent) { + logger.print("onReceive(), intent: " + intent + + ", context: " + context); + + String action = intent.getAction(); + if (RcsPresence.ACTION_PUBLISH_STATE_CHANGED.equals(action)) { + intent.setClass(context, PersistService.class); + context.startService(intent); + } else { + logger.debug("No interest in this intent: " + action); + } + } +}; + diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/PresenceContact.java b/rcs/presencepolling/src/com/android/service/ims/presence/PresenceContact.java new file mode 100644 index 0000000..a9bd8a5 --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/PresenceContact.java @@ -0,0 +1,106 @@ +/* + * 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.presence; + +public class PresenceContact { + public static final int VIDEO_CALLING_NOT_AVAILABLE = 0; + public static final int VIDEO_CALLING_AVAILABLE = 1; + + String mDisplayName = null; + String mPhoneNumber = null; + String mRawContactId = null; + String mContactId = null; + String mDataId = null; + boolean mIsVolteCapable = false; + boolean mIsVtCapable = false; + int mVtStatus = VIDEO_CALLING_NOT_AVAILABLE; + + String mVtUri = null; + + public PresenceContact(String name, String number, String rawContactId, + String contactId, String dataId) { + mDisplayName = name; + mPhoneNumber = number; + mRawContactId = rawContactId; + mContactId = contactId; + mDataId = dataId; + } + + public String getDisplayName() { + return mDisplayName; + } + + public String getPhoneNumber() { + return mPhoneNumber; + } + + public String getRawContactId() { + return mRawContactId; + } + + public String getContactId() { + return mContactId; + } + + public String getDataId() { + return mDataId; + } + + public boolean isVolteCapable() { + return mIsVolteCapable; + } + + public void setIsVolteCapable(boolean isVolteCapable) { + mIsVolteCapable = isVolteCapable; + } + + public boolean isVtCapable() { + return mIsVtCapable; + } + + public void setIsVtCapable(boolean isVtCapable) { + mIsVtCapable = isVtCapable; + } + + public int getVtStatus() { + return mVtStatus; + } + + public void setVtStatus(int vtAvailable) { + mVtStatus = vtAvailable; + } + + public String getVtUri() { + return mVtUri; + } + + public void setVtUri(String vtUri) { + mVtUri = vtUri; + } +} diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/PresencePreferences.java b/rcs/presencepolling/src/com/android/service/ims/presence/PresencePreferences.java new file mode 100644 index 0000000..b777c5c --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/PresencePreferences.java @@ -0,0 +1,136 @@ +/* + * 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.presence; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; + +public class PresencePreferences { + private static final String PREFERENCES = "PresencePolling"; + private static PresencePreferences sInstance; + + private Context mContext = null; + private SharedPreferences mCommonPref = null; + + private PresencePreferences() { + } + + public void setContext(Context context) { + if (mContext == null) { + mContext = context; + mCommonPref = mContext.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE); + } + } + + public static PresencePreferences getInstance(){ + if (null == sInstance) { + sInstance = new PresencePreferences(); + } + return sInstance; + } + + private static final String CAPABILITY_DISCOVERY_TIME = "CapabilityDiscoveryTime"; + + public long lastCapabilityDiscoveryTime() { + if (mCommonPref == null) { + return 0; + } + + return mCommonPref.getLong(CAPABILITY_DISCOVERY_TIME, 0); + } + + public void updateCapabilityDiscoveryTime() { + if (mCommonPref == null) { + return; + } + + Editor editor = mCommonPref.edit(); + long time = System.currentTimeMillis(); + editor.putLong(CAPABILITY_DISCOVERY_TIME, time); + editor.commit(); + } + + private static final String PHONE_SUBSCRIBER_ID = "PhoneSubscriberId"; + public String getSubscriberId() { + if (mCommonPref == null) { + return null; + } + + return mCommonPref.getString(PHONE_SUBSCRIBER_ID, null); + } + + public void setSubscriberId(String id) { + if (mCommonPref == null) { + return; + } + + Editor editor = mCommonPref.edit(); + editor.putString(PHONE_SUBSCRIBER_ID, id); + editor.commit(); + } + + private static final String PHONE_LINE1_NUMBER = "PhoneLine1Number"; + public String getLine1Number() { + if (mCommonPref == null) { + return null; + } + + return mCommonPref.getString(PHONE_LINE1_NUMBER, null); + } + + public void setLine1Number(String number) { + if (mCommonPref == null) { + return; + } + + Editor editor = mCommonPref.edit(); + editor.putString(PHONE_LINE1_NUMBER, number); + editor.commit(); + } + + private static final String RCS_TEST_MODE = "RcsTestMode"; + public boolean getRcsTestMode() { + if (mCommonPref == null) { + return false; + } + + return mCommonPref.getInt(RCS_TEST_MODE, 0) == 1; + } + + public void setRcsTestMode(boolean test) { + if (mCommonPref == null) { + return; + } + + Editor editor = mCommonPref.edit(); + editor.putInt(RCS_TEST_MODE, test ? 1 : 0); + editor.commit(); + } +} diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/PresenceSetting.java b/rcs/presencepolling/src/com/android/service/ims/presence/PresenceSetting.java new file mode 100644 index 0000000..9452237 --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/PresenceSetting.java @@ -0,0 +1,185 @@ +/* + * 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.presence; + +import android.content.Context; + +import com.android.ims.ImsConfig; +import com.android.ims.ImsException; +import com.android.ims.ImsManager; + +import com.android.ims.internal.Logger; + +public class PresenceSetting { + private static Logger logger = Logger.getLogger("PresenceSetting"); + private static Context sContext = null; + + public static void init(Context context) { + sContext = context; + } + + public static long getCapabilityPollInterval() { + long value = -1; + if (sContext != null) { + ImsManager imsManager = ImsManager.getInstance(sContext, 0); + if (imsManager != null) { + try { + ImsConfig imsConfig = imsManager.getConfigInterface(); + if (imsConfig != null) { + value = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.CAPABILITIES_POLL_INTERVAL); + logger.debug("Read ImsConfig_CapabilityPollInterval: " + value); + } + } catch (ImsException ex) { + } + } + } + if (value <= 0) { + value = 604800L; + logger.error("Failed to get CapabilityPollInterval, the default: " + value); + } + return value; + } + + public static long getCapabilityCacheExpiration() { + long value = -1; + if (sContext != null) { + ImsManager imsManager = ImsManager.getInstance(sContext, 0); + if (imsManager != null) { + try { + ImsConfig imsConfig = imsManager.getConfigInterface(); + if (imsConfig != null) { + value = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.CAPABILITIES_CACHE_EXPIRATION); + logger.debug("Read ImsConfig_CapabilityCacheExpiration: " + value); + } + } catch (ImsException ex) { + } + } + } + if (value <= 0) { + value = 7776000L; + logger.error("Failed to get CapabilityCacheExpiration, the default: " + value); + } + return value; + } + + public static int getPublishTimer() { + int value = -1; + if (sContext != null) { + ImsManager imsManager = ImsManager.getInstance(sContext, 0); + if (imsManager != null) { + try { + ImsConfig imsConfig = imsManager.getConfigInterface(); + if (imsConfig != null) { + value = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.PUBLISH_TIMER); + logger.debug("Read ImsConfig_PublishTimer: " + value); + } + } catch (ImsException ex) { + } + } + } + if (value <= 0) { + value = (int)1200; + logger.error("Failed to get PublishTimer, the default: " + value); + } + return value; + } + + public static int getPublishTimerExtended() { + int value = -1; + if (sContext != null) { + ImsManager imsManager = ImsManager.getInstance(sContext, 0); + if (imsManager != null) { + try { + ImsConfig imsConfig = imsManager.getConfigInterface(); + if (imsConfig != null) { + value = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.PUBLISH_TIMER_EXTENDED); + logger.debug("Read ImsConfig_PublishTimerExtended: " + value); + } + } catch (ImsException ex) { + } + } + } + if (value <= 0) { + value = (int)86400; + logger.error("Failed to get PublishTimerExtended, the default: " + value); + } + return value; + } + + public static int getMaxNumberOfEntriesInRequestContainedList() { + int value = -1; + if (sContext != null) { + ImsManager imsManager = ImsManager.getInstance(sContext, 0); + if (imsManager != null) { + try { + ImsConfig imsConfig = imsManager.getConfigInterface(); + if (imsConfig != null) { + value = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.MAX_NUMENTRIES_IN_RCL); + logger.debug("Read ImsConfig_MaxNumEntriesInRCL: " + value); + } + } catch (ImsException ex) { + } + } + } + if (value <= 0) { + value = (int)100; + logger.error("Failed to get MaxNumEntriesInRCL, the default: " + value); + } + return value; + } + + public static int getCapabilityPollListSubscriptionExpiration() { + int value = -1; + if (sContext != null) { + ImsManager imsManager = ImsManager.getInstance(sContext, 0); + if (imsManager != null) { + try { + ImsConfig imsConfig = imsManager.getConfigInterface(); + if (imsConfig != null) { + value = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.CAPAB_POLL_LIST_SUB_EXP); + logger.debug("Read ImsConfig_CapabPollListSubExp: " + value); + } + } catch (ImsException ex) { + } + } + } + if (value <= 0) { + value = (int)30; + logger.error("Failed to get CapabPollListSubExp, the default: " + value); + } + return value; + } +} + diff --git a/rcs/presencepolling/src/com/android/service/ims/presence/SharedPrefUtil.java b/rcs/presencepolling/src/com/android/service/ims/presence/SharedPrefUtil.java new file mode 100644 index 0000000..9e9a31e --- /dev/null +++ b/rcs/presencepolling/src/com/android/service/ims/presence/SharedPrefUtil.java @@ -0,0 +1,197 @@ +/* + * 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.presence; + +import android.content.Context; +import android.content.SharedPreferences; + +public class SharedPrefUtil { + public static final String EAB_SHARED_PREF = "com.android.vt.eab"; + public static final String INIT_DONE = "init_done"; + private static final String CONTACT_CHANGED_PREF_KEY = "timestamp_change"; + private static final String CONTACT_DELETE_PREF_KEY = "timestamp_delete"; + private static final String CONTACT_PROFILE_CHANGED_PREF_KEY = "profile_timestamp_change"; + public static final String VIDEO_CALLING_GROUP_ID = "video_calling_group_id"; + public static final String VIDEO_CALLING_WARN_SHOW = "video_calling_warning_alert_show"; + public static final String VIDEO_CALL_OFF_WARN_SHOW = "video_call_off_alert_show"; + public static final String MOBILE_DATA_ON_WARN_SHOW = "mobile_data_on_alert_show"; + public static final String VOLTE_ON_WARN_SHOW = "volte_on_alert_show"; + public static final String TTY_OFF_WARN_SHOW = "tty_off_alert_show"; + public static final String VT_ON_WARN_SHOW = "vt_on_alert_show"; + + public static boolean isInitDone(Context context) { + SharedPreferences eabPref = context.getSharedPreferences( + EAB_SHARED_PREF, Context.MODE_PRIVATE); + return eabPref.getBoolean(INIT_DONE, false); + } + + public static void setInitDone(Context context, boolean initDone){ + SharedPreferences.Editor eabPref = context.getSharedPreferences( + EAB_SHARED_PREF, Context.MODE_PRIVATE).edit(); + eabPref.putBoolean(INIT_DONE, initDone).commit(); + } + + public static long getLastContactChangedTimestamp(Context context, long time) { + SharedPreferences pref = context.getSharedPreferences(EAB_SHARED_PREF, + Context.MODE_PRIVATE); + return pref.getLong(CONTACT_CHANGED_PREF_KEY, time); + } + + public static void saveLastContactChangedTimestamp(Context context, long time) { + SharedPreferences.Editor eabPref = context.getSharedPreferences( + EAB_SHARED_PREF, Context.MODE_PRIVATE).edit(); + eabPref.putLong(CONTACT_CHANGED_PREF_KEY, time).commit(); + } + + public static long getLastProfileContactChangedTimestamp(Context context, long time) { + SharedPreferences pref = context.getSharedPreferences(EAB_SHARED_PREF, + Context.MODE_PRIVATE); + return pref.getLong(CONTACT_PROFILE_CHANGED_PREF_KEY, time); + } + + public static void saveLastProfileContactChangedTimestamp(Context context, long time) { + SharedPreferences.Editor eabPref = context.getSharedPreferences( + EAB_SHARED_PREF, Context.MODE_PRIVATE).edit(); + eabPref.putLong(CONTACT_PROFILE_CHANGED_PREF_KEY, time).commit(); + } + + public static long getLastContactDeletedTimestamp(Context context, long time) { + SharedPreferences pref = context.getSharedPreferences(EAB_SHARED_PREF, + Context.MODE_PRIVATE); + return pref.getLong(CONTACT_DELETE_PREF_KEY, time); + } + + public static void saveLastContactDeletedTimestamp(Context context, long time) { + SharedPreferences.Editor eabPref = context.getSharedPreferences( + EAB_SHARED_PREF, Context.MODE_PRIVATE).edit(); + eabPref.putLong(CONTACT_DELETE_PREF_KEY, time).commit(); + } + + public static void setVideoCallingGroupId(Context context, + long videoCallingGroupId) { + SharedPreferences.Editor eabPref = context.getSharedPreferences( + EAB_SHARED_PREF, Context.MODE_PRIVATE).edit(); + eabPref.putLong(VIDEO_CALLING_GROUP_ID, videoCallingGroupId).commit(); + } + + public static long getVideoCallingGroupId(Context context){ + SharedPreferences eabPref = context.getSharedPreferences( + EAB_SHARED_PREF, Context.MODE_PRIVATE); + return eabPref.getLong(VIDEO_CALLING_GROUP_ID, 0L); + } + + public static void setVideoCallingWarnDisable(Context context, + boolean enable) { + SharedPreferences.Editor eabPref = context.getSharedPreferences( + EAB_SHARED_PREF, Context.MODE_PRIVATE).edit(); + eabPref.putBoolean(VIDEO_CALLING_WARN_SHOW, enable).apply(); + } + + public static boolean isVideoCallingWarnDisabled(Context context){ + SharedPreferences eabPref = context.getSharedPreferences( + EAB_SHARED_PREF, Context.MODE_PRIVATE); + return eabPref.getBoolean(VIDEO_CALLING_WARN_SHOW, false); + } + + public static void setVideoCallOffWarnDisable(Context context, boolean enable){ + SharedPreferences.Editor eabPref = context.getSharedPreferences(EAB_SHARED_PREF, + Context.MODE_PRIVATE).edit(); + eabPref.putBoolean(VIDEO_CALL_OFF_WARN_SHOW, enable).apply(); + } + + public static boolean isVideoCallOffWarnDisabled(Context context){ + SharedPreferences eabPref = context.getSharedPreferences(EAB_SHARED_PREF, + Context.MODE_PRIVATE); + return eabPref.getBoolean(VIDEO_CALL_OFF_WARN_SHOW, false); + } + + public static void setMobileDataOnWarnDisable(Context context, boolean enable){ + SharedPreferences.Editor eabPref = context.getSharedPreferences(EAB_SHARED_PREF, + Context.MODE_PRIVATE).edit(); + eabPref.putBoolean(MOBILE_DATA_ON_WARN_SHOW, enable).apply(); + } + + public static boolean isMobileDataOnWarnDisabled(Context context){ + SharedPreferences eabPref = context.getSharedPreferences(EAB_SHARED_PREF, + Context.MODE_PRIVATE); + return eabPref.getBoolean(MOBILE_DATA_ON_WARN_SHOW, false); + } + + public static void setVolteOnWarnDisable(Context context, boolean enable){ + SharedPreferences.Editor eabPref = context.getSharedPreferences(EAB_SHARED_PREF, + Context.MODE_PRIVATE).edit(); + eabPref.putBoolean(VOLTE_ON_WARN_SHOW, enable).apply(); + } + + public static boolean isVolteOnWarnDisabled(Context context){ + SharedPreferences eabPref = context.getSharedPreferences(EAB_SHARED_PREF, + Context.MODE_PRIVATE); + return eabPref.getBoolean(VOLTE_ON_WARN_SHOW, false); + } + + public static void setTtyOffWarnDisable(Context context, boolean enable){ + SharedPreferences.Editor eabPref = context.getSharedPreferences(EAB_SHARED_PREF, + Context.MODE_PRIVATE).edit(); + eabPref.putBoolean(TTY_OFF_WARN_SHOW, enable).apply(); + } + + public static boolean isTtyOffWarnDisabled(Context context){ + SharedPreferences eabPref = context.getSharedPreferences(EAB_SHARED_PREF, + Context.MODE_PRIVATE); + return eabPref.getBoolean(TTY_OFF_WARN_SHOW, false); + } + + public static void setVTOnWarnDisable(Context context, boolean enable){ + SharedPreferences.Editor eabPref = context.getSharedPreferences(EAB_SHARED_PREF, + Context.MODE_PRIVATE).edit(); + eabPref.putBoolean(VT_ON_WARN_SHOW, enable).apply(); + } + + public static boolean isVTOnWarnDisabled(Context context){ + SharedPreferences eabPref = context.getSharedPreferences(EAB_SHARED_PREF, + Context.MODE_PRIVATE); + return eabPref.getBoolean(VT_ON_WARN_SHOW, false); + } + + public static void resetEABSharedPref(Context context) { + SharedPreferences.Editor eabPref = context.getSharedPreferences( + EAB_SHARED_PREF, Context.MODE_PRIVATE).edit(); + eabPref.putBoolean(INIT_DONE, false); + eabPref.putLong(CONTACT_CHANGED_PREF_KEY, 0); + eabPref.putLong(CONTACT_DELETE_PREF_KEY, 0); + eabPref.putLong(CONTACT_PROFILE_CHANGED_PREF_KEY, 0); + eabPref.putBoolean(VIDEO_CALLING_WARN_SHOW, false); + eabPref.putBoolean(VIDEO_CALL_OFF_WARN_SHOW, false); + eabPref.putBoolean(MOBILE_DATA_ON_WARN_SHOW, false); + eabPref.putBoolean(VOLTE_ON_WARN_SHOW, false); + eabPref.putBoolean(TTY_OFF_WARN_SHOW, false); + eabPref.putBoolean(VT_ON_WARN_SHOW, false); + eabPref.commit(); + } +} diff --git a/rcs/rcsmanager/Android.mk b/rcs/rcsmanager/Android.mk new file mode 100644 index 0000000..93452c5 --- /dev/null +++ b/rcs/rcsmanager/Android.mk @@ -0,0 +1,54 @@ + # 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. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/src/java +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src/java) +LOCAL_SRC_FILES += src/java/com/android/ims/internal/IRcsService.aidl \ + src/java/com/android/ims/internal/IRcsPresence.aidl \ + src/java/com/android/ims/IRcsPresenceListener.aidl + +LOCAL_JAVA_LIBRARIES += ims-common + +#LOCAL_MODULE_TAGS := optional +LOCAL_MODULE := com.android.ims.rcsmanager +LOCAL_REQUIRED_MODULES := com.android.ims.rcsmanager.xml +include $(BUILD_JAVA_LIBRARY) + +# We need to put the permissions XML file into /system/etc/permissions/ so the +# JAR can be dynamically loaded. +include $(CLEAR_VARS) +LOCAL_MODULE := com.android.ims.rcsmanager.xml +#LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions +LOCAL_SRC_FILES := $(LOCAL_MODULE) +include $(BUILD_PREBUILT) + + diff --git a/rcs/rcsmanager/AndroidManifest.xml b/rcs/rcsmanager/AndroidManifest.xml new file mode 100644 index 0000000..630c170 --- /dev/null +++ b/rcs/rcsmanager/AndroidManifest.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.ims.rcsmanager" + android:versionCode="1" + android:versionName="1.0" > + + <uses-sdk android:minSdkVersion="18" /> + +</manifest> diff --git a/rcs/rcsmanager/CleanSpec.mk b/rcs/rcsmanager/CleanSpec.mk new file mode 100644 index 0000000..c104bc4 --- /dev/null +++ b/rcs/rcsmanager/CleanSpec.mk @@ -0,0 +1,60 @@ +<!-- + * 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. +--> + +# If you don't need to do a full clean build but would like to touch +# a file or delete some intermediate files, add a clean step to the end +# of the list. These steps will only be run once, if they haven't been +# run before. +# +# E.g.: +# $(call add-clean-step, touch -c external/sqlite/sqlite3.h) +# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates) +# +# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with +# files that are missing or have been moved. +# +# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory. +# Use $(OUT_DIR) to refer to the "out" directory. +# +# If you need to re-do something that's already mentioned, just copy +# the command and add it to the bottom of the list. E.g., if a change +# that you made last week required touching a file and a change you +# made today requires touching the same file, just copy the old +# touch step and add it to the end of the list. +# +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ + +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/com.android.ims.rcsmanager_intermediates) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/product/*/obj/JAVA_LIBRARIES/com.android.ims.rcsmanager_intermediates) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/product/*/obj/ETC/com.android.ims.rcsmanager.xml_intermediates) + +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ diff --git a/rcs/rcsmanager/com.android.ims.rcsmanager.xml b/rcs/rcsmanager/com.android.ims.rcsmanager.xml new file mode 100644 index 0000000..0737f5d --- /dev/null +++ b/rcs/rcsmanager/com.android.ims.rcsmanager.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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. +--> + +<permissions> + <library name="com.android.ims.rcsmanager" + file="/system/framework/com.android.ims.rcsmanager.jar"/> +</permissions> diff --git a/rcs/rcsmanager/src/java/com/android/ims/IRcsPresenceListener.aidl b/rcs/rcsmanager/src/java/com/android/ims/IRcsPresenceListener.aidl new file mode 100644 index 0000000..fbffe73 --- /dev/null +++ b/rcs/rcsmanager/src/java/com/android/ims/IRcsPresenceListener.aidl @@ -0,0 +1,97 @@ +/* + * 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.ims; + +/** + * RcsPresenceListener is used by requestCapability and requestAvailability to return the + * status. The reqId which returned by requestCapability and requestAvailability can be + * used to match the result with request when share the implementation of IRcsPresenceListener. + * + * There are following normal cases: + * 1> The presence service got terminated notify after submitted the SUBSCRIBE request. + * onSuccess() ---> onFinish() + * 2> The presence service submitted the request to network and got "200 OK" for SIP response. + * but it didn't get "terminated notify" in expries + T1 timer. + * onSuccess() ---> onTimeout() + * 3> The presence service got error from SIP response for SUBSCRIBE request. + * onError() + * + * @hide + */ +interface IRcsPresenceListener { + /** + * It is called when it gets "200 OK" from network for SIP request or + * it gets "404 xxxx" for SIP request of single contact number (means not capable). + * + * @param reqId the request ID which returned by requestCapability or + * requestAvailability + * + * @see RcsPresence#requestCapability + * @see RcsPresence#requestAvailability + */ + void onSuccess(in int reqId); + + /** + * It is called when it gets local error or gets SIP error from network. + * + * @param reqId the request ID which returned by requestCapability or + * requestAvailability + * @param resultCode the result code which defined in RcsManager.ResultCode. + * + * @see RcsPresence#requestCapability + * @see RcsPresence#requestAvailability + * @see RcsManager.ResultCode + */ + void onError(in int reqId, in int resultCode); + + /** + * It is called when it gets the "terminated notify" from network. + * The presence service will not receive notify for the request any more after it got + * "terminated notify" from network. So onFinish means the requestCapability or + * requestAvailability had been finished completely. + * + * @param reqId the request ID which returned by requestCapability or + * requestAvailability + * + * @see RcsPresence#requestCapability + * @see RcsPresence#requestAvailability + */ + void onFinish(in int reqId); + + /** + * It is called when it is timeout to wait for the "terminated notify" from network. + * + * @param reqId the request ID which returned by requestCapability or + * requestAvailability + * + * @see RcsPresence#requestCapability + * @see RcsPresence#requestAvailability + */ + void onTimeout(in int reqId); +} diff --git a/rcs/rcsmanager/src/java/com/android/ims/RcsException.java b/rcs/rcsmanager/src/java/com/android/ims/RcsException.java new file mode 100644 index 0000000..0d5f36e --- /dev/null +++ b/rcs/rcsmanager/src/java/com/android/ims/RcsException.java @@ -0,0 +1,64 @@ +/* + * 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.ims; + +/** + * This class defines a general RCS-related exception. + * + * @hide + */ +public class RcsException extends Exception { + + /** + * Refer to {@link RcsManager.ResultCode} + */ + private int mCode; + + public RcsException() { + } + + public RcsException(String message, int code) { + super(message); + mCode = code; + } + + public RcsException(String message, Throwable cause, int code) { + super(message, cause); + mCode = code; + } + + /** + * Gets the detailed exception code when RcsException is throwed + * + * @return the exception code in {@link RcsManager.ResultCode} + */ + public int getCode() { + return mCode; + } +} diff --git a/rcs/rcsmanager/src/java/com/android/ims/RcsManager.java b/rcs/rcsmanager/src/java/com/android/ims/RcsManager.java new file mode 100644 index 0000000..644d567 --- /dev/null +++ b/rcs/rcsmanager/src/java/com/android/ims/RcsManager.java @@ -0,0 +1,384 @@ +/* + * 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.ims; + +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.telephony.Rlog; + +import com.android.ims.internal.IRcsService; +import com.android.ims.internal.IRcsPresence; + +import java.util.HashMap; + +/** + * Provides APIs for Rcs services, currently it supports presence only. + * This class is the starting point for any RCS actions. + * You can acquire an instance of it with {@link #getInstance getInstance()}. + * + * @hide + */ +public class RcsManager { + /** + * For accessing the RCS related service. + * Internal use only. + * + * @hide + */ + private static final String RCS_SERVICE = "rcs"; + + /** + * Part of the ACTION_RCS_SERVICE_AVAILABLE and ACTION_RCS_SERVICE_UNAVAILABLE intents. + * A long value; the subId corresponding to the RCS service. For MSIM implementation. + * + * @see #ACTION_RCS_SERVICE_AVAILABLE + * @see #ACTION_RCS_SERVICE_UNAVAILABLE + */ + public static final String EXTRA_SUBID = "android:subid"; + + /** + * Action to broadcast when RcsService is available. + * + * @see #EXTRA_SUBID + */ + public static final String ACTION_RCS_SERVICE_AVAILABLE = + "com.android.ims.ACTION_RCS_SERVICE_AVAILABLE"; + + /** + * Action to broadcast when RcsService is unavailable (such as ims is not registered). + * + * @see #EXTRA_SUBID + */ + public static final String ACTION_RCS_SERVICE_UNAVAILABLE = + "com.android.ims.ACTION_RCS_SERVICE_UNAVAILABLE"; + + /** + * Action to broadcast when RcsService is died. + * The caller can listen to the intent to clean the pending request. + * + * It takes the extra parameter subid as well since it depends on OEM implementation for + * RcsService. It will not send broadcast for ACTION_RCS_SERVICE_UNAVAILABLE under the case. + * + * @see #EXTRA_SUBID + */ + public static final String ACTION_RCS_SERVICE_DIED = + "com.android.ims.ACTION_RCS_SERVICE_DIED"; + + public static class ResultCode { + /** + * The code is used when the request is success. + */ + public static final int SUCCESS =0; + + /** + * Return this code if the service doesn't be enabled on the phone. + * As per the requirement the feature can be enabled/disabled by DM. + */ + public static final int ERROR_SERVICE_NOT_ENABLED = -1; + + /** + * Return this code if the service didn't publish yet. + */ + public static final int ERROR_SERVICE_NOT_PUBLISHED = -2; + + /** + * The service is not available, for example it is 1x only + */ + public static final int ERROR_SERVICE_NOT_AVAILABLE = -3; + + /** + * SUBSCRIBE Error base + */ + public static final int SUBSCRIBER_ERROR_CODE_START = ERROR_SERVICE_NOT_AVAILABLE; + + /** + * Temporary error and need retry later. + * such as: + * 503 Service Unavailable + * Device shall retry with exponential back-off + * + * 408 Request Timeout + * Device shall retry with exponential back-off + * + * 423 Interval Too Short. Requested expiry interval too short and server rejects it + * Device shall re-attempt subscription after changing the expiration interval in + * the Expires header field to be equal to or greater than the expiration interval + * within the Min-Expires header field of the 423 response + */ + public static final int SUBSCRIBE_TEMPORARY_ERROR = SUBSCRIBER_ERROR_CODE_START - 1; + + /** + * receives 403 (reason="User Not Registered"). + * Re-Register to IMS then retry the single resource subscription if capability polling. + * availability fetch: no retry. + */ + public static final int SUBSCRIBE_NOT_REGISTERED = SUBSCRIBER_ERROR_CODE_START - 2; + + /** + * Responding for 403 - not authorized (Requestor) + * No retry. + */ + public static final int SUBSCRIBE_NOT_AUTHORIZED_FOR_PRESENCE = + SUBSCRIBER_ERROR_CODE_START - 3; + + /** + * Responding for "403 Forbidden" or "403" + * Handle it as same as 404 Not found. + * No retry. + */ + public static final int SUBSCRIBE_FORBIDDEN = SUBSCRIBER_ERROR_CODE_START - 4; + + /** + * Responding for 404 (target number) + * No retry. + */ + public static final int SUBSCRIBE_NOT_FOUND = SUBSCRIBER_ERROR_CODE_START - 5; + + /** + * Responding for 413 - Too Large. Top app need shrink the size + * of request contact list and resend the request + */ + public static final int SUBSCRIBE_TOO_LARGE = SUBSCRIBER_ERROR_CODE_START - 6; + + /** + * All subscribe errors not covered by specific errors + * Other 4xx/5xx/6xx + * + * Device shall not retry + */ + public static final int SUBSCRIBE_GENIRIC_FAILURE = SUBSCRIBER_ERROR_CODE_START - 7; + + /** + * Invalid parameter - The caller should check the parameter. + */ + public static final int SUBSCRIBE_INVALID_PARAM = SUBSCRIBER_ERROR_CODE_START - 8; + + /** + * Fetch error - The RCS statck failed to fetch the presence information. + */ + public static final int SUBSCRIBE_FETCH_ERROR = SUBSCRIBER_ERROR_CODE_START - 9; + + /** + * Request timeout - The RCS statck returns timeout error. + */ + public static final int SUBSCRIBE_REQUEST_TIMEOUT = SUBSCRIBER_ERROR_CODE_START - 10; + + /** + * Insufficient memory - The RCS statck returns the insufficient memory error. + */ + public static final int SUBSCRIBE_INSUFFICIENT_MEMORY = SUBSCRIBER_ERROR_CODE_START - 11; + + /** + * Lost network error - The RCS statck returns the lost network error. + */ + public static final int SUBSCRIBE_LOST_NETWORK = SUBSCRIBER_ERROR_CODE_START - 12; + + /** + * Not supported error - The RCS statck returns the not supported error. + */ + public static final int SUBSCRIBE_NOT_SUPPORTED = SUBSCRIBER_ERROR_CODE_START - 13; + + /** + * Generic error - RCS Presence stack returns generic error + */ + public static final int SUBSCRIBE_GENERIC = SUBSCRIBER_ERROR_CODE_START - 14; + + /** + * There is a request for the same number in queue. + */ + public static final int SUBSCRIBE_ALREADY_IN_QUEUE = SUBSCRIBER_ERROR_CODE_START - 16; + + /** + * Request too frequently. + */ + public static final int SUBSCRIBE_TOO_FREQUENTLY = SUBSCRIBER_ERROR_CODE_START - 17; + + /** + * The last Subscriber error code + */ + public static final int SUBSCRIBER_ERROR_CODE_END = SUBSCRIBER_ERROR_CODE_START - 17; + }; + + private static final String TAG = "RcsManager"; + private static final boolean DBG = true; + + private static HashMap<Integer, RcsManager> sRcsManagerInstances = + new HashMap<Integer, RcsManager>(); + + private Context mContext; + private int mSubId; + private IRcsService mRcsService = null; + private RcsServiceDeathRecipient mDeathRecipient = new RcsServiceDeathRecipient(); + + // Interface for presence + // TODO: Could add other RCS service such RcsChat, RcsFt later. + private RcsPresence mRcsPresence = null; + + /** + * Gets a manager instance. + * + * @param context application context for creating the manager object + * @param subId the subscription ID for the RCS Service + * @return the manager instance corresponding to the subId + */ + public static RcsManager getInstance(Context context, int subId) { + synchronized (sRcsManagerInstances) { + if (sRcsManagerInstances.containsKey(subId)){ + return sRcsManagerInstances.get(subId); + } + + RcsManager mgr = new RcsManager(context, subId); + sRcsManagerInstances.put(subId, mgr); + + return mgr; + } + } + + private RcsManager(Context context, int subId) { + mContext = context; + mSubId = subId; + createRcsService(true); + } + + /** + * return true if the rcs service is ready for use. + */ + public boolean isRcsServiceAvailable() { + if (DBG) Rlog.d(TAG, "call isRcsServiceAvailable ..."); + + boolean ret = false; + + try { + checkAndThrowExceptionIfServiceUnavailable(); + + ret = mRcsService.isRcsServiceAvailable(); + } catch (RemoteException e) { + // return false under the case. + Rlog.e(TAG, "isRcsServiceAvailable RemoteException", e); + }catch (RcsException e){ + // return false under the case. + Rlog.e(TAG, "isRcsServiceAvailable RcsException", e); + } + + if (DBG) Rlog.d(TAG, "isRcsServiceAvailable ret =" + ret); + return ret; + } + + /** + * Gets the presence interface + * + * @return the RcsPresence instance. + * @throws if getting the RcsPresence interface results in an error. + */ + public RcsPresence getRcsPresenceInterface() throws RcsException { + + if (mRcsPresence == null) { + checkAndThrowExceptionIfServiceUnavailable(); + + try { + IRcsPresence rcsPresence = mRcsService.getRcsPresenceInterface(); + if (rcsPresence == null) { + throw new RcsException("getRcsPresenceInterface()", + ResultCode.ERROR_SERVICE_NOT_AVAILABLE); + } + mRcsPresence = new RcsPresence(rcsPresence); + } catch (RemoteException e) { + throw new RcsException("getRcsPresenceInterface()", e, + ResultCode.ERROR_SERVICE_NOT_AVAILABLE); + } + } + if (DBG) Rlog.d(TAG, "getRcsPresenceInterface(), mRcsPresence= " + mRcsPresence); + return mRcsPresence; + } + + /** + * Binds the RCS service only if the service is not created. + */ + private void checkAndThrowExceptionIfServiceUnavailable() + throws RcsException { + if (mRcsService == null) { + createRcsService(true); + + if (mRcsService == null) { + throw new RcsException("Service is unavailable", + ResultCode.ERROR_SERVICE_NOT_AVAILABLE); + } + } + } + + private static String getRcsServiceName(int subId) { + // use the same mechanism as IMS_SERVICE? + return RCS_SERVICE; + } + + /** + * Binds the RCS service. + */ + private void createRcsService(boolean checkService) { + if (checkService) { + IBinder binder = ServiceManager.checkService(getRcsServiceName(mSubId)); + + if (binder == null) { + return; + } + } + + IBinder b = ServiceManager.getService(getRcsServiceName(mSubId)); + + if (b != null) { + try { + b.linkToDeath(mDeathRecipient, 0); + } catch (RemoteException e) { + } + } + + mRcsService = IRcsService.Stub.asInterface(b); + } + + /** + * Death recipient class for monitoring RCS service. + */ + private class RcsServiceDeathRecipient implements IBinder.DeathRecipient { + @Override + public void binderDied() { + mRcsService = null; + mRcsPresence = null; + + if (mContext != null) { + Intent intent = new Intent(ACTION_RCS_SERVICE_DIED); + intent.putExtra(EXTRA_SUBID, mSubId); + mContext.sendBroadcast(new Intent(intent)); + } + } + } +} diff --git a/rcs/rcsmanager/src/java/com/android/ims/RcsPresence.java b/rcs/rcsmanager/src/java/com/android/ims/RcsPresence.java new file mode 100644 index 0000000..c52e7e9 --- /dev/null +++ b/rcs/rcsmanager/src/java/com/android/ims/RcsPresence.java @@ -0,0 +1,305 @@ +/* + * 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.ims; + +import java.util.List; +import java.util.ArrayList; +import android.content.Intent; +import android.os.RemoteException; +import android.util.Log; + +import com.android.ims.internal.IRcsPresence; +import com.android.ims.RcsManager.ResultCode; + +/** + * + * @hide + */ +public class RcsPresence { + static final String TAG = "RcsPresence"; + private boolean DBG = true; + private IRcsPresence mIRcsPresence = null; + + /** + * Key to retrieve the RcsPresenceInfo list from intent ACTION_PRESENCE_CHANGED + * The RcsPresenceInfo list can be got by the following function call: + * ArrayList<RcsPresenceInfo> rcsPresenceInfoList = intent.getParcelableArrayListExtra( + * RcsPresence.EXTRA_PRESENCE_INFO_LIST); + * + * @see RcsPresenceInfo + */ + public static final String EXTRA_PRESENCE_INFO_LIST = "presence_info_list"; + + /** + * Key to retrieve the subscription ID. This is for muliti-SIM implementation. + */ + public static final String EXTRA_SUBID = "android:subid"; + + /** + * The intent will be broadcasted when the presence changed. + * It happens on the following cases: + * 1. When the phone gets a NOTIFY from network. + * 2. When the phone gets some SUBSCRIBE error from network for some case (such as 404). + * It takes two extra parameters: + * 1. EXTRA_PRESENCE_INFO_LIST + * need to get it by the following statement: + * ArrayList<RcsPresenceInfo> rcsPresenceInfoList = intent.getParcelableArrayListExtra( + * RcsPresence.EXTRA_PRESENCE_INFO_LIST); + * 2. EXTRA_SUBID + * + * @see RcsPresenceInfO + * @see EXTRA_PRESENCE_INFO_LIST + * @see EXTRA_SUBID + */ + public static final String ACTION_PRESENCE_CHANGED = "com.android.ims.ACTION_PRESENCE_CHANGED"; + + /** + * Key to retrieve "publish_state" for + * intent ACTION_PUBLISH_STATE_CHANGED. + * + * @See PublishState. + * @see ACTION_PUBLISH_STATE_CHANGED + */ + public static final String EXTRA_PUBLISH_STATE = "publish_state"; + + /** + * The intent will be broadcasted when latest publish status changed + * It takes two extra parameters: + * 1. EXTRA_PUBLISH_STATE + * 2. EXTRA_SUBID + * + * @see #EXTRA_PUBLISH_STATE + * @see #EXTRA_SUBID + * @see PublishState + */ + public static final String ACTION_PUBLISH_STATE_CHANGED = + "com.android.ims.ACTION_PUBLISH_STATUS_CHANGED"; + + /** + * The last publish state + */ + public static class PublishState { + /** + * The phone is PUBLISH_STATE_200_OK when + * the response of the last publish is "200 OK" + */ + public static final int PUBLISH_STATE_200_OK = 0; + + /** + * The phone didn't publish after power on. + * the phone didn't get any publish response yet. + */ + public static final int PUBLISH_STATE_NOT_PUBLISHED = 1; + + /** + * The phone is PUBLISH_STATE_VOLTE_PROVISION_ERROR when the response is one of items + * in config_volte_provision_error_on_publish_response for PUBLISH or + * in config_volte_provision_error_on_subscribe_response for SUBSCRIBE. + */ + public static final int PUBLISH_STATE_VOLTE_PROVISION_ERROR = 2; + + /** + * The phone is PUBLISH_STATE_RCS_PROVISION_ERROR when the response is one of items + * in config_rcs_provision_error_on_publish_response for PUBLISH or + * in config_rcs_provision_error_on_subscribe_response for SUBSCRIBE. + */ + public static final int PUBLISH_STATE_RCS_PROVISION_ERROR = 3; + + /** + * The phone is PUBLISH_STATE_REQUEST_TIMEOUT when + * The response of the last publish is "408 Request Timeout". + */ + public static final int PUBLISH_STATE_REQUEST_TIMEOUT = 4; + + /** + * The phone is PUBLISH_STATE_OTHER_ERROR when + * the response of the last publish is other temp error. Such as + * 503 Service Unavailable + * Device shall retry with exponential back-off + * + * 423 Interval Too Short. Requested expiry interval too short and server rejects it + * Device shall re-attempt subscription after changing the expiration interval in + * the Expires header field to be equal to or greater than the expiration interval + * within the Min-Expires header field of the 423 response + * + * ... + */ + public static final int PUBLISH_STATE_OTHER_ERROR = 5; + }; + + /** + * Constructor. + * @param presenceService the IRcsPresence which get by RcsManager.getRcsPresenceInterface. + * + * @see RcsManager#getRcsPresenceInterface + */ + public RcsPresence(IRcsPresence presenceService) { + if (DBG) Log.d(TAG, "IRcsPresence creates"); + + mIRcsPresence = presenceService; + } + + /** + * Send the request to the server to get the capability. + * 1. If the presence service sent the request to network successfully + * then it will return the request ID (>0). It will not wait for the response from + * network. The response from network will be returned by callback onSuccess() or onError(). + * 2. If the presence service failed to send the request to network then it will return error + * code which is defined by RcsManager.ResultCode (<0). + * 3. If the network returns "200 OK" for a request then the listener.onSuccess() will be + * called by presence service. + * 4. If the network resturns "404" for a single target number then it means the target + * number is not VoLte capable, so the listener.onSuccess() will be called and intent + * ACTION_PRESENCE_CHANGED will be broadcasted by presence service. + * 5. If the network returns other error then the listener.onError() will be called by + * presence service. + * 6. If the network returns "200 OK" then we can expect the presence service receives notify + * from network. If the presence service receives notify then it will broadcast the + * intent ACTION_PRESENCE_CHANGED. If the notify state is "terminated" then the + * listener.onFinish() will be called by presence service as well. + * 7. If the presence service doesn't get response after "Subscribe Expiration + T1" then the + * listener.onTimeout() will be called by presence service. + * + * @param contactsNumber the contact number list which will be requested. + * @param listener the IRcsPresenceListener which will return the status and response. + * + * @return the request ID if it is >0. Or it is RcsManager.ResultCode for error. + * + * @see IRcsPresenceListener + * @see RcsManager.ResultCode + */ + public int requestCapability(List<String> contactsNumber, + IRcsPresenceListener listener) throws RcsException { + if (DBG) Log.d(TAG, "call requestCapability, contactsNumber=" + contactsNumber); + int ret = ResultCode.ERROR_SERVICE_NOT_AVAILABLE; + + try { + ret = mIRcsPresence.requestCapability(contactsNumber, listener); + } catch (RemoteException e) { + throw new RcsException("requestCapability", e, + ResultCode.ERROR_SERVICE_NOT_AVAILABLE); + } + + if (DBG) Log.d(TAG, "requestCapability ret =" + ret); + return ret; + } + + /** + * Send the request to the server to get the availability. + * 1. If the presence service sent the request to network successfully then it will return + * the request ID (>0). + * 2. If the presence serive failed to send the request to network then it will return error + * code which is defined by RcsManager.ResultCode (<0). + * 3. If the network returns "200 OK" for a request then the listener.onSuccess() will be + * called by presence service. + * 4. If the network resturns "404" then it means the target number is not VoLte capable, + * so the listener.onSuccess() will be called and intent ACTION_PRESENCE_CHANGED will be + * broadcasted by presence service. + * 5. If the network returns other error code then the listener.onError() will be called by + * presence service. + * 6. If the network returns "200 OK" then we can expect the presence service receives notify + * from network. If the presence service receives notify then it will broadcast the intent + * ACTION_PRESENCE_CHANGED. If the notify state is "terminated" then the listener.onFinish() + * will be called by presence service as well. + * 7. If the presence service doesn't get response after "Subscribe Expiration + T1" then it + * will call listener.onTimeout(). + * + * @param contactNumber the contact which will request the availability. + * Only support phone number at present. + * @param listener the IRcsPresenceListener to get the response. + * + * @return the request ID if it is >0. Or it is RcsManager.ResultCode for error. + * + * @see IRcsPresenceListener + * @see RcsManager.ResultCode + * @see RcsPresence.ACTION_PRESENCE_CHANGED + */ + public int requestAvailability(String contactNumber, IRcsPresenceListener listener) + throws RcsException { + if (DBG) Log.d(TAG, "call requestAvailability, contactNumber=" + contactNumber); + int ret = ResultCode.ERROR_SERVICE_NOT_AVAILABLE; + + try { + ret = mIRcsPresence.requestAvailability(contactNumber, listener); + } catch (RemoteException e) { + throw new RcsException("requestAvailability", e, + ResultCode.ERROR_SERVICE_NOT_AVAILABLE); + } + + if (DBG) Log.d(TAG, "requestAvailability ret =" + ret); + return ret; + } + + /** + * 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 RcsPresence.ACTION_PRESENCE_CHANGED + * @see ResultCode.SUBSCRIBE_TOO_FREQUENTLY + */ + public int requestAvailabilityNoThrottle(String contactNumber, IRcsPresenceListener listener) + throws RcsException { + if (DBG) Log.d(TAG, "call requestAvailabilityNoThrottle, contactNumber=" + contactNumber); + int ret = ResultCode.ERROR_SERVICE_NOT_AVAILABLE; + + try { + ret = mIRcsPresence.requestAvailabilityNoThrottle(contactNumber, listener); + } catch (RemoteException e) { + throw new RcsException("requestAvailabilityNoThrottle", e, + ResultCode.ERROR_SERVICE_NOT_AVAILABLE); + } + + if (DBG) Log.d(TAG, "requestAvailabilityNoThrottle ret =" + ret); + return ret; + } + + /** + * Get the latest publish state. + * + * @see PublishState + */ + public int getPublishState() throws RcsException { + int ret = PublishState.PUBLISH_STATE_NOT_PUBLISHED; + try { + ret = mIRcsPresence.getPublishState(); + } catch (RemoteException e) { + throw new RcsException("getPublishState", e, + ResultCode.ERROR_SERVICE_NOT_AVAILABLE); + } + + if (DBG) Log.d(TAG, "getPublishState ret =" + ret); + return ret; + } +} + diff --git a/rcs/rcsmanager/src/java/com/android/ims/RcsPresenceInfo.aidl b/rcs/rcsmanager/src/java/com/android/ims/RcsPresenceInfo.aidl new file mode 100644 index 0000000..58b6130 --- /dev/null +++ b/rcs/rcsmanager/src/java/com/android/ims/RcsPresenceInfo.aidl @@ -0,0 +1,34 @@ +/* + * 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.ims; + +/** + * @hide + */ +parcelable RcsPresenceInfo; diff --git a/rcs/rcsmanager/src/java/com/android/ims/RcsPresenceInfo.java b/rcs/rcsmanager/src/java/com/android/ims/RcsPresenceInfo.java new file mode 100644 index 0000000..8a5b913 --- /dev/null +++ b/rcs/rcsmanager/src/java/com/android/ims/RcsPresenceInfo.java @@ -0,0 +1,354 @@ +/* + * 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.ims; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.net.Uri; + +/** + * RcsPresenceInfo is the class for presence information. + * It is used to pass information to application for inent ACTION_PRESENCE_CHANGED + * need to get it by the following statement: + * ArrayList<RcsPresenceInfo> rcsPresenceInfoList = intent.getParcelableArrayListExtra( + * RcsPresence.EXTRA_PRESENCE_INFO_LIST); + * + * @see RcsPresence#ACTION_PRESENCE_CHANGED + * + * @hide + */ +public class RcsPresenceInfo implements Parcelable { + /** + * Key for save contact_number. + * It is passed by getCapabilityByContacts or getAvailability + * + * @see #getContactNumber + */ + private static final String CONTACT_NUMBER = "contact_number"; + + /** + * Key for the flag to indicate if the number is volte enabled. + * + * @see #getVolteStatus + */ + public static final String VOLTE_STATUS = "volte_status"; + + /** + * The Volte status: + * If the contact got the 404 for single contact fetch. + * or it got "rejected", "noresource" and "giveup", then it is + * VOLTE_DISABLED. Or it is VOLTE_ENBLED. + * If we didn't get a success polling yet then it is VOLTE_UNKNOWN. + */ + public static class VolteStatus{ + /** + * Didn't poll yet. + */ + public static final int VOLTE_UNKNOWN = -1; + + /** + * Volte disabled for 404 response for single contact fetch + * or get "rejected", "noresource" and "giveup" notification. + */ + public static final int VOLTE_DISABLED = 0; + + /** + * Volte enabled for get proper notification. + */ + public static final int VOLTE_ENABLED = 1; + } + + /** + * For extension consideration we deinfed the sercice type here. + * Currently we only support the VoLte call and VT call. + * + * The service type for RCS + */ + public static interface ServiceType { + /** + * For VoLte call. + */ + public static final int VOLTE_CALL = 1; + + /** + * For VT call. + */ + public static final int VT_CALL = 2; + } + + /** + * Service state + * + * @see #getServiceState + */ + public static class ServiceState { + /** + * ONLINE means the servie is available. + */ + public static final int ONLINE = 1; + + /** + * OFFLINE means the service is not available. + */ + public static final int OFFLINE = 0; + + /** + * UNKNOWN means the presence service information didn't be got yet. + */ + public static final int UNKNOWN = -1; + } + + /** + * The presence information is maintained by key and value pair. + * ServiceInfoKey defines the key of the current supported information. + */ + public static class ServiceInfoKey { + /** + * Service type. It is defined by ServiceType. + * + * @see ServiceType + */ + public static final String SERVICE_TYPE = "service_type"; // VOLTE_CALL,etc + + /** + * Service state. It is defined by ServiceState. + * + * @see ServiceState + * @see #getServiceState + */ + public static final String STATE = "state"; // ONLINE, etc. + + /** + * The service contact. For example, the phone requests presence information for number + * "12345678", the service responses the presence with "987654321" as the service number + * of video call. Then the phone should start the video call with "987654321". + * The "987654321" is the service number. + * + * @see #getServiceContact + */ + public static final String SERVICE_CONTACT = "service_contact"; + + /** + * The timestamp which got from network. + * + * @see #getTimeStamp + */ + public static final String TIMESTAMP = "timestamp"; + } + + /** + * Return the contact number. + * It is passed by getCapabilityByContacts or getAvailability + * + * @return the contact number which has been passed in. + * + * @see #CONTACT_NUMBER + */ + public String getContactNumber() { + return mServiceInfo.getString(CONTACT_NUMBER); + } + + /** + * @Return the VolteStatus. + */ + public int getVolteStatus(){ + return mServiceInfo.getInt(VOLTE_STATUS); + } + + /** + * Return the ServiceState of the specific serviceType. + * + * @param serviceType it is defined by ServiceType. + * + * @return the service presence state which has been described in ServiceInfoKey. + * + * @see ServiceType + * @see ServiceState + * @see ServiceInfoKey#STATE + */ + public int getServiceState(int serviceType) { + return getServiceInfo(serviceType, ServiceInfoKey.STATE, ServiceState.UNKNOWN); + } + + /** + * Return the service contact of the specific serviceType. + * + * @param serviceType It is defined by ServiceType. + * + * @return the service contact which is described in ServiceInfoKey. + * + * @see ServiceType + * @see ServiceInfoKey#SERVICE_CONTACT + */ + public String getServiceContact(int serviceType) { + return getServiceInfo(serviceType, ServiceInfoKey.SERVICE_CONTACT, ""); + } + + /** + * Return the timestamp. + * + * @param serviceType It is defined by ServiceType. + * + * @return the timestamp which has been got from server. + * + * @see ServiceType + * @see ServiceInfoKey#TIMESTAMP + */ + public long getTimeStamp(int serviceType) { + return getServiceInfo(serviceType, ServiceInfoKey.TIMESTAMP, 0L); + } + + /** + * @hide + */ + public RcsPresenceInfo() { + } + + /** + * @hide + */ + public RcsPresenceInfo(Parcel source) { + mServiceInfo.readFromParcel(source); + } + + /** + * @hide + */ + private Bundle getBundle() { + return mServiceInfo; + } + + /** + * @hide + */ + public RcsPresenceInfo(String contactNumber,int volteStatus, + int ipVoiceCallState, String ipVoiceCallServiceNumber, long ipVoiceCallTimestamp, + int ipVideoCallState, String ipVideoCallServiceNumber, long ipVideoCallTimestamp) { + mServiceInfo.putString(CONTACT_NUMBER, contactNumber); + mServiceInfo.putInt(VOLTE_STATUS, volteStatus); + + set(ServiceType.VOLTE_CALL, ipVoiceCallState, ipVoiceCallServiceNumber, + ipVoiceCallTimestamp); + + set(ServiceType.VT_CALL, ipVideoCallState, ipVideoCallServiceNumber, + ipVideoCallTimestamp); + } + + private void set(int serviceType, int state, String serviceNumber, long timestamp) { + Bundle capability = new Bundle(); + + capability.putInt(ServiceInfoKey.SERVICE_TYPE, serviceType); + capability.putInt(ServiceInfoKey.STATE, state); + capability.putString(ServiceInfoKey.SERVICE_CONTACT, serviceNumber); + capability.putLong(ServiceInfoKey.TIMESTAMP, timestamp); + + mServiceInfo.putBundle(String.valueOf(serviceType), capability); + } + + /** + * Overload + * @hide + */ + public static final Parcelable.Creator<RcsPresenceInfo> CREATOR = new + Parcelable.Creator<RcsPresenceInfo>() { + public RcsPresenceInfo createFromParcel(Parcel in) { + return new RcsPresenceInfo(in); + } + + public RcsPresenceInfo[] newArray(int size) { + return new RcsPresenceInfo[size]; + } + }; + + /** + * Overload + * @hide + */ + public void writeToParcel(Parcel dest, int flags) { + mServiceInfo.writeToParcel(dest, flags); + } + + /** + * Overload + * @hide + */ + public int describeContents() { + return 0; + } + + private Bundle mServiceInfo = new Bundle(); + + private String getServiceInfo(int serviceType, String infoKey, String defaultValue) { + Bundle serviceInfo = mServiceInfo.getBundle(String.valueOf(serviceType)); + + if (serviceInfo != null) { + return serviceInfo.getString(infoKey); + } + return defaultValue; + } + + private long getServiceInfo(int serviceType, String infoKey, long defaultValue) { + Bundle serviceInfo = mServiceInfo.getBundle(String.valueOf(serviceType)); + if (serviceInfo != null) { + return serviceInfo.getLong(infoKey); + } + + return defaultValue; + } + + private int getServiceInfo(int serviceType, String infoType, int defaultValue) { + Bundle serviceInfo = mServiceInfo.getBundle(String.valueOf(serviceType)); + if (serviceInfo != null) { + return serviceInfo.getInt(infoType); + } + return defaultValue; + } + + private Uri getServiceInfo(int serviceType, String infoKey, Uri defaultValue) { + Bundle serviceInfo = mServiceInfo.getBundle(String.valueOf(serviceType)); + if (serviceInfo != null) { + return (Uri)serviceInfo.getParcelable(infoKey); + } + + return defaultValue; + } + + public String toString() { + return" contactNumber=" + getContactNumber() + + " volteStatus=" + getVolteStatus() + + " ipVoiceCallSate=" + getServiceState(ServiceType.VOLTE_CALL) + + " ipVoiceCallServiceNumber=" + getServiceContact(ServiceType.VOLTE_CALL) + + " ipVoiceCallTimestamp=" + getTimeStamp(ServiceType.VOLTE_CALL) + + " ipVideoCallSate=" + getServiceState(ServiceType.VT_CALL) + + " ipVideoCallServiceNumber=" + getServiceContact(ServiceType.VT_CALL) + + " ipVideoCallTimestamp=" + getTimeStamp(ServiceType.VT_CALL); + } +} + diff --git a/rcs/rcsmanager/src/java/com/android/ims/internal/ContactNumberUtils.java b/rcs/rcsmanager/src/java/com/android/ims/internal/ContactNumberUtils.java new file mode 100644 index 0000000..b69b42c --- /dev/null +++ b/rcs/rcsmanager/src/java/com/android/ims/internal/ContactNumberUtils.java @@ -0,0 +1,343 @@ +/* + * 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.ims.internal; + +import android.content.Context; +import android.os.Build; +import android.telephony.PhoneNumberUtils; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Log; + +import java.util.List; +import java.util.ArrayList; + +/** + * @hide + */ +public class ContactNumberUtils { + /** + * Sample code: + * + * ContactNumberUtils mNumberUtils = ContactNumberUtils.getDefault(); + * mNumberUtils.setContext(this); + * + * number = mNumberUtils.format(number); + * int result = mNumberUtils.validate(number); + * if (ContactNumberUtils.NUMBER_VALID == result) { + * } + */ + public static ContactNumberUtils getDefault() { + if(sInstance == null) { + sInstance = new ContactNumberUtils(); + } + + return sInstance; + } + + public void setContext(Context context) { + mContext = context; + } + + /** + * Format contact number to the common format + * + * @param phoneNumber read from contact db. + * @return formatted contact number. + */ + public String format(final String phoneNumber) { + String number = phoneNumber; + if (TextUtils.isEmpty(number)) { + return null; + } + + number = PhoneNumberUtils.stripSeparators(number); + + int len = number.length(); + if (len == NUMBER_LENGTH_NO_AREA_CODE) { + number = addAreaCode(number); + } + + number = PhoneNumberUtils.normalizeNumber(number); + + len = number.length(); + if (len == NUMBER_LENGTH_NORMAL) { + if (!number.startsWith("+1")) { + number = "+1" + number; + } + } else if (len == NUMBER_LENGTH_NORMAL + 1) { + if (number.startsWith("1")) { + number = "+" + number; + } + } else if (len >= NUMBER_LENGTH_NORMAL + 4) { + if (number.startsWith("011")) { + number = "+" + number.substring(3); + } + } + + return number; + } + + /** + * Contact nubmer error code. + */ + public static int NUMBER_VALID = 0; + public static int NUMBER_EMERGENCY = 1; + public static int NUMBER_SHORT_CODE = 2; + public static int NUMBER_PRELOADED_ENTRY = 3; + public static int NUMBER_FREE_PHONE = 4; + public static int NUMBER_INVALID = 5; + + /** + * Check if it is a valid contact number for presence + * + * @param phoneNumber read from contact db. + * @return contact number error code. + */ + public int validate(final String phoneNumber) { + String number = phoneNumber; + if (TextUtils.isEmpty(number)) { + return NUMBER_INVALID; + } + + if(number.contains("*")) { + return NUMBER_PRELOADED_ENTRY; + } + + number = PhoneNumberUtils.stripSeparators(number); + if (!number.equals(PhoneNumberUtils.convertKeypadLettersToDigits(number))) { + return NUMBER_INVALID; + } + + if (PhoneNumberUtils.isEmergencyNumber(number)) { + return NUMBER_EMERGENCY; + // TODO: To handle short code + //} else if ((mContext != null) && PhoneNumberUtils.isN11Number(mContext, number)) { + // return NUMBER_SHORT_CODE; + } else if (number.startsWith("#")) { + return NUMBER_PRELOADED_ENTRY; + } else if (isInExcludedList(number)) { + return NUMBER_FREE_PHONE; + } + + int len = number.length(); + if (len > "+1-nnn-nnn-nnnn".length()) { + return NUMBER_INVALID; + } else if (len < NUMBER_LENGTH_NO_AREA_CODE) { + return NUMBER_INVALID; + } + + number = format(number); + if (number.startsWith("+1")) { + len = number.length(); + if (len == NUMBER_LENGTH_NORMAL + 2) { + return NUMBER_VALID; + } + } + + return NUMBER_INVALID; + } + + + /** + * Some utility functions for presence service only. + * @hide + */ + public String[] format(List<String> numbers) { + if ((numbers == null) || (numbers.size() == 0)) { + return null; + } + + int size = numbers.size(); + String[] outContactsArray = new String[size]; + for (int i = 0; i < size; i++) { + String number = numbers.get(i); + outContactsArray[i] = format(number); + if (DEBUG) { + Log.d(TAG, "outContactsArray[" + i + "] = " + outContactsArray[i]); + } + } + + return outContactsArray; + } + + public String[] format(String[] numbers) { + if ((numbers == null) || (numbers.length == 0)) { + return null; + } + + int length = numbers.length; + String[] outContactsArray = new String[length]; + for (int i = 0; i < length; i++) { + String number = numbers[i]; + outContactsArray[i] = format(number); + if (DEBUG) { + Log.d(TAG, "outContactsArray[" + i + "] = " + outContactsArray[i]); + } + } + + return outContactsArray; + } + public int validate(List<String> numbers) { + if ((numbers == null) || (numbers.size() == 0)) { + return NUMBER_INVALID; + } + + int size = numbers.size(); + for (int i = 0; i < size; i++) { + String number = numbers.get(i); + int result = validate(number); + if (result != NUMBER_VALID) { + return result; + } + } + + return NUMBER_VALID; + } + + public int validate(String[] numbers) { + if ((numbers == null) || (numbers.length == 0)) { + return NUMBER_INVALID; + } + + int length = numbers.length; + for (int i = 0; i < length; i++) { + String number = numbers[i]; + int result = validate(number); + if (result != NUMBER_VALID) { + return result; + } + } + + return NUMBER_VALID; + } + + /** + * The logger related. + */ + private static final boolean DEBUG = Build.IS_DEBUGGABLE; + private static final String TAG = "ContactNumberUtils"; + + /** + * Contact number length. + */ + private static final int NUMBER_LENGTH_NORMAL = 10; + private static final int NUMBER_LENGTH_NO_AREA_CODE = 7; + + /** + * Save the singleton instance. + */ + private static ContactNumberUtils sInstance = null; + private Context mContext = null; + + /** + * Constructor + */ + private ContactNumberUtils() { + if (DEBUG) { + Log.d(TAG, "ContactNumberUtils constructor"); + } + } + + /** + * Add device's own area code to the number which length is 7. + */ + private String addAreaCode(String number) { + if (mContext == null) { + if (DEBUG) { + Log.e(TAG, "mContext is null, please update context."); + } + return number; + } + + String mdn = null; + TelephonyManager tm = (TelephonyManager) + mContext.getSystemService(Context.TELEPHONY_SERVICE); + mdn = tm.getLine1Number(); + + if ((mdn == null) || (mdn.length() == 0) || mdn.startsWith("00000")) { + return number; + } + + mdn = PhoneNumberUtils.stripSeparators(mdn); + if (mdn.length() >= NUMBER_LENGTH_NORMAL) { + mdn = mdn.substring(mdn.length() - NUMBER_LENGTH_NORMAL); + } + mdn = mdn.substring(0, 3); + + number = mdn + number; + return number; + } + + /** + * The excluded number list. + */ + private static ArrayList<String> sExcludes = null; + + private boolean isInExcludedList(String number){ + if (sExcludes == null) { + sExcludes = new ArrayList<String>(); + sExcludes.add("800"); + sExcludes.add("822"); + sExcludes.add("833"); + sExcludes.add("844"); + sExcludes.add("855"); + sExcludes.add("866"); + sExcludes.add("877"); + sExcludes.add("880882"); + sExcludes.add("888"); + sExcludes.add("900"); + sExcludes.add("911"); + } + + String tempNumber = format(number); + if(TextUtils.isEmpty(tempNumber)) { + return true; //exclude empty/null string. + } + + if(tempNumber.startsWith("1")) { + tempNumber = tempNumber.substring(1); + } else if(tempNumber.startsWith("+1")) { + tempNumber = tempNumber.substring(2); + } + + if(TextUtils.isEmpty(tempNumber)) { + return true; //exclude empty/null string. + } + + for (String num : sExcludes) { + if(tempNumber.startsWith(num)) { + return true; + } + } + + return false; + } +} + diff --git a/rcs/rcsmanager/src/java/com/android/ims/internal/EABContract.java b/rcs/rcsmanager/src/java/com/android/ims/internal/EABContract.java new file mode 100644 index 0000000..7796071 --- /dev/null +++ b/rcs/rcsmanager/src/java/com/android/ims/internal/EABContract.java @@ -0,0 +1,254 @@ +/* + * 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.ims.internal; + +import android.net.Uri; +import android.provider.BaseColumns; + +/** + * <p> + * The contract between the EAB provider and applications. Contains + * definitions for the supported URIs and data columns. + * </p> + * <h3>Overview</h3> + * <p> + * EABContract defines the data model of EAB related information. + * This data is stored in a table EABPresence. + * </p> + * + * @hide + * + */ +public final class EABContract { + /** + * This authority is used for writing to or querying from the EAB provider. + */ + public static final String AUTHORITY = "com.android.rcs.eab"; + + /** + * The content:// style URL for the top-level EAB authority + */ + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); + + /** + * Class for EABProviderItem + */ + public static class EABColumns implements BaseColumns { + public static final String TABLE_NAME = "EABPresence"; + public static final String GROUPITEMS_NAME = "EABGroupDetails"; + + /** + * CONTENT_URI + * <P> + * "content://com.android.vt.eab/EABPresence" + * </P> + */ + public static final Uri CONTENT_URI = Uri.withAppendedPath(EABContract.CONTENT_URI, + TABLE_NAME); + + public static final String CONTENT_TYPE = + "vnd.android.cursor.dir/vnd.android.rcs.eab.provider.eabprovider"; + + public static final String CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/vnd.android.rcs.eab.provider.eabprovider"; + + /** + * Key defining the contact number. + * <P> + * Type: TEXT + * </P> + */ + public static final String CONTACT_NUMBER = "contact_number"; + + /** + * Key defining the contact name. + * <P> + * Type: TEXT + * </P> + */ + public static final String CONTACT_NAME = "contact_name"; + + /** + * Key defining the reference to ContactContract raw_contact_id of the number. + * <P> + * Type: TEXT + * </P> + */ + public static final String RAW_CONTACT_ID = "raw_contact_id"; + + /** + * Key defining the reference to ContactContract contact_id of the number. + * <P> + * Type: TEXT + * </P> + */ + public static final String CONTACT_ID = "contact_id"; + + /** + * Key defining the reference to ContactContract data_id of the number. + * <P> + * Type: TEXT + * </P> + */ + public static final String DATA_ID = "data_id"; + + /** + * Key defining the account type. + * <P> + * Type: TEXT + * </P> + */ + public static final String ACCOUNT_TYPE = "account_type"; + + /** + * Key defining the VoLTE call service contact address. + * <P> + * Type: TEXT + * </P> + */ + public static final String VOLTE_CALL_SERVICE_CONTACT_ADDRESS = "volte_contact_address"; + + /** + * Key defining the VoLTE call capability. + * <P> + * Type: TEXT + * </P> + */ + public static final String VOLTE_CALL_CAPABILITY = "volte_call_capability"; + + /** + * Key defining the VoLTE call capability timestamp. + * <P> + * Type: TEXT + * </P> + */ + public static final String VOLTE_CALL_CAPABILITY_TIMESTAMP = "volte_capability_timestamp"; + + /** + * Key defining the VoLTE call availability. + * <P> + * Type: LONG + * </P> + */ + public static final String VOLTE_CALL_AVAILABILITY = "volte_call_avalibility"; + + /** + * Key defining the VoLTE call availability timestamp. + * <P> + * Type: TEXT + * </P> + */ + public static final String VOLTE_CALL_AVAILABILITY_TIMESTAMP = + "volte_availability_timestamp"; + + /** + * Key defining the Video call service contact address. + * <P> + * Type: LONG + * </P> + */ + public static final String VIDEO_CALL_SERVICE_CONTACT_ADDRESS = "video_contact_address"; + + /** + * Key defining the Video call capability. + * <P> + * Type: TEXT + * </P> + */ + public static final String VIDEO_CALL_CAPABILITY = "video_call_capability"; + + /** + * Key defining the Video call capability timestamp. + * <P> + * Type: TEXT + * </P> + */ + public static final String VIDEO_CALL_CAPABILITY_TIMESTAMP = "video_capability_timestamp"; + + /** + * Key defining the Video call availability. + * <P> + * Type: LONG + * </P> + */ + public static final String VIDEO_CALL_AVAILABILITY = "video_call_availability"; + + /** + * Key defining the Video call availability timestamp. + * <P> + * Type: TEXT + * </P> + */ + public static final String VIDEO_CALL_AVAILABILITY_TIMESTAMP = + "video_availability_timestamp"; + + /** + * Key defining the Video call availability timestamp. + * <P> + * Type: LONG + * </P> + */ + public static final String CONTACT_LAST_UPDATED_TIMESTAMP = + "contact_last_updated_timestamp"; + + /** + * Key defining the volte status. + * <p> + * Type: INT + * </p> + */ + public static final String VOLTE_STATUS = "volte_status"; + + /** + * @hide + */ + public static final String[] PROJECTION = new String[] { + _ID, + CONTACT_NAME, + CONTACT_NUMBER, + RAW_CONTACT_ID, + CONTACT_ID, + DATA_ID, + ACCOUNT_TYPE, + VOLTE_CALL_SERVICE_CONTACT_ADDRESS, + VOLTE_CALL_CAPABILITY, + VOLTE_CALL_AVAILABILITY_TIMESTAMP, + VOLTE_CALL_AVAILABILITY, + VOLTE_CALL_CAPABILITY_TIMESTAMP, + VIDEO_CALL_SERVICE_CONTACT_ADDRESS, + VIDEO_CALL_CAPABILITY, + VIDEO_CALL_CAPABILITY_TIMESTAMP, + VIDEO_CALL_AVAILABILITY, + VIDEO_CALL_AVAILABILITY_TIMESTAMP, + CONTACT_LAST_UPDATED_TIMESTAMP, + VOLTE_STATUS + }; + } +} + diff --git a/rcs/rcsmanager/src/java/com/android/ims/internal/IRcsPresence.aidl b/rcs/rcsmanager/src/java/com/android/ims/internal/IRcsPresence.aidl new file mode 100644 index 0000000..c0c67c0 --- /dev/null +++ b/rcs/rcsmanager/src/java/com/android/ims/internal/IRcsPresence.aidl @@ -0,0 +1,123 @@ +/* + * 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.ims.internal; + +import com.android.ims.IRcsPresenceListener; + +import java.util.List; + +/** + * @hide + */ +interface IRcsPresence { + /** + * Send the request to the server to get the capability. + * 1. If the presence service sent the request to network successfully + * then it will return the request ID (>0). It will not wait for the response from + * network. The response from network will be returned by callback onSuccess() or onError(). + * 2. If the presence service failed to send the request to network then it will return error + * code which is defined by RcsManager.ResultCode (<0). + * 3. If the network returns "200 OK" for a request then the listener.onSuccess() will be + * called by presence service. + * 4. If the network resturns "404" for a single target number then it means the target + * number is not VoLte capable, so the listener.onSuccess() will be called and intent + * ACTION_PRESENCE_CHANGED will be broadcasted by presence service. + * 5. If the network returns other error then the listener.onError() will be called by + * presence service. + * 6. If the network returns "200 OK" then we can expect the presence service receives notify + * from network. If the presence service receives notify then it will broadcast the + * intent ACTION_PRESENCE_CHANGED. If the notify state is "terminated" then the + * listener.onFinish() will be called by presence service as well. + * 7. If the presence service doesn't get response after "Subscribe Expiration + T1" then the + * listener.onTimeout() will be called by presence service. + * + * @param contactsNumber the contact number list which will be requested. + * @param listener the IRcsPresenceListener which will return the status and response. + * + * @return the request ID if it is >0. Or it is RcsManager.ResultCode for error. + * + * @see IRcsPresenceListener + * @see RcsManager.ResultCode + */ + int requestCapability(in List<String> contactsNumber, + in IRcsPresenceListener listener); + + /** + * Send the request to the server to get the availability. + * 1. If the presence service sent the request to network successfully then it will return + * the request ID (>0). + * 2. If the presence serive failed to send the request to network then it will return error + * code which is defined by RcsManager.ResultCode (<0). + * 3. If the network returns "200 OK" for a request then the listener.onSuccess() will be + * called by presence service. + * 4. If the network resturns "404" then it means the target number is not VoLte capable, + * so the listener.onSuccess() will be called and intent ACTION_PRESENCE_CHANGED will be + * broadcasted by presence service. + * 5. If the network returns other error code then the listener.onError() will be called by + * presence service. + * 6. If the network returns "200 OK" then we can expect the presence service receives notify + * from network. If the presence service receives notify then it will broadcast the intent + * ACTION_PRESENCE_CHANGED. If the notify state is "terminated" then the listener.onFinish() + * will be called by presence service as well. + * 7. If the presence service doesn't get response after "Subscribe Expiration + T1" then it + * will call listener.onTimeout(). + * + * @param contactNumber the contact which will request the availability. + * Only support phone number at present. + * @param listener the IRcsPresenceListener to get the response. + * + * @return the request ID if it is >0. Or it is RcsManager.ResultCode for error. + * + * @see IRcsPresenceListener + * @see RcsManager.ResultCode + * @see RcsPresence.ACTION_PRESENCE_CHANGED + */ + int requestAvailability(in String contactNumber, in IRcsPresenceListener listener); + + /** + * 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 RcsPresence.ACTION_PRESENCE_CHANGED + * @see ResultCode.SUBSCRIBE_TOO_FREQUENTLY + */ + int requestAvailabilityNoThrottle(in String contactNumber, in IRcsPresenceListener listener); + + /** + * Get the latest publish state. + * + * @see RcsPresence.PublishState + */ + int getPublishState(); +} diff --git a/rcs/rcsmanager/src/java/com/android/ims/internal/IRcsService.aidl b/rcs/rcsmanager/src/java/com/android/ims/internal/IRcsService.aidl new file mode 100644 index 0000000..34ad1b3 --- /dev/null +++ b/rcs/rcsmanager/src/java/com/android/ims/internal/IRcsService.aidl @@ -0,0 +1,48 @@ +/* + * 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.ims.internal; + +import com.android.ims.internal.IRcsPresence; + +/** + * @hide + */ +interface IRcsService { + /** + * Detect if the rcs service is available. + */ + boolean isRcsServiceAvailable(); + + /** + * RcsPresence interface to get the presence information. + * + * @see IRcsPresence + */ + IRcsPresence getRcsPresenceInterface(); +} diff --git a/rcs/rcsmanager/src/java/com/android/ims/internal/Logger.java b/rcs/rcsmanager/src/java/com/android/ims/internal/Logger.java new file mode 100644 index 0000000..9e6c6a8 --- /dev/null +++ b/rcs/rcsmanager/src/java/com/android/ims/internal/Logger.java @@ -0,0 +1,239 @@ +/* + * 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.ims.internal; + +import java.lang.String; +import android.util.Log; + +import android.os.Build; +import android.text.TextUtils; + +/** + * Logger + * + * @hide + */ +public class Logger { + /** + * DEBUG level + */ + public static int DEBUG_LEVEL = 0; + + /** + * INFO level + */ + public static int INFO_LEVEL = 1; + + /** + * WARN level + */ + public static int WARN_LEVEL = 2; + + /** + * ERROR level + */ + public static int ERROR_LEVEL = 3; + + /** + * FATAL level + */ + public static int FATAL_LEVEL = 4; + + /** + * RCS test mode flag + */ + public static boolean rcsTestMode = false; + + /** + * Trace level + */ + public static int traceLevel = DEBUG_LEVEL; + + /** + * Log tag name + */ + private static String tagname = "rcs"; + + /** + * Classname + */ + private String classname; + + /** + * Constructor + * + * @param classname Classname + */ + private Logger(String tagName, String classname) { + if(!TextUtils.isEmpty(tagName)) { + this.tagname = tagName; + } + + int index = classname.lastIndexOf('.'); + if (index != -1) { + this.classname = classname.substring(index+1); + } else { + this.classname = classname; + } + + if(Build.IS_DEBUGGABLE || rcsTestMode){ + traceLevel = DEBUG_LEVEL; + }else{ + traceLevel = ERROR_LEVEL; + } + } + + public static void setRcsTestMode(boolean test) { + rcsTestMode = test; + + if (Build.IS_DEBUGGABLE || rcsTestMode) { + traceLevel = DEBUG_LEVEL; + } else { + traceLevel = ERROR_LEVEL; + } + } + + /** + * Is logger activated. Reserved for future debug tool to turn on/off the log only. + * + * @return boolean + */ + public boolean isActivated() { + return true; + } + + /** + * Debug trace + * + * @param trace Trace + */ + public void debug(String trace) { + if (isActivated() && (traceLevel <= DEBUG_LEVEL)) { + Log.d(tagname, "[" + classname +"] " + trace); + } + } + + /** + * Debug trace + * + * @param trace Trace + * @param e the exception which need to be printed. + */ + public void debug(String trace, Throwable e) { + if (isActivated() && (traceLevel <= DEBUG_LEVEL)) { + Log.d(tagname, "[" + classname +"] " + trace, e); + } + } + + /** + * Info trace + * + * @param trace Trace + */ + public void info(String trace) { + if (isActivated() && (traceLevel <= INFO_LEVEL)) { + Log.i(tagname, "[" + classname +"] " + trace); + } + } + + /** + * Warning trace + * + * @param trace Trace + */ + public void warn(String trace) { + if (isActivated() && (traceLevel <= WARN_LEVEL)) { + Log.w(tagname, "[" + classname +"] " + trace); + } + } + + /** + * Error trace + * + * @param trace Trace + */ + public void error(String trace) { + if (isActivated() && (traceLevel <= ERROR_LEVEL)) { + Log.e(tagname, "[" + classname +"] " + trace); + } + } + + /** + * Error trace + * + * @param trace Trace + * @param e Exception + */ + public void error(String trace, Throwable e) { + if (isActivated() && (traceLevel <= ERROR_LEVEL)) { + Log.e(tagname, "[" + classname +"] " + trace, e); + } + } + + /* + * Print the debug log and don't consider the traceLevel + * + * @param trace Trace + * @param e Exception + */ + public void print(String trace) { + Log.i(tagname, "[" + classname +"] " + trace); + } + + /** + * Print the debug log and don't consider the traceLevel + * + * @param trace Trace + * @param e Exception + */ + public void print(String trace, Throwable e) { + Log.i(tagname, "[" + classname +"] " + trace, e); + } + + /** + * Create a static instance + * + * @param classname Classname + * @return Instance + */ + public static synchronized Logger getLogger(String tagName, String classname) { + return new Logger(tagName, classname); + } + + /** + * Create a static instance + * + * @param classname Classname + * @return Instance + */ + public static synchronized Logger getLogger(String classname) { + return new Logger(tagname, classname); + } +} + diff --git a/rcs/rcsservice/Android.mk b/rcs/rcsservice/Android.mk new file mode 100644 index 0000000..281f178 --- /dev/null +++ b/rcs/rcsservice/Android.mk @@ -0,0 +1,47 @@ + # 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. + +LOCAL_PATH := $(call my-dir) + +########################################################################## +# Build the application : Presence.apk +########################################################################## + +include $(CLEAR_VARS) +# This is the target being built. (Name of APK) +LOCAL_PACKAGE_NAME := RcsService +# Only compile source java files in this apk. +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src) + +LOCAL_JAVA_LIBRARIES := telephony-common ims-common com.android.ims.rcsmanager + +#LOCAL_MODULE_TAGS := optional +LOCAL_CERTIFICATE := platform + +# Tell it to build an APK +include $(BUILD_PACKAGE) + diff --git a/rcs/rcsservice/AndroidManifest.xml b/rcs/rcsservice/AndroidManifest.xml new file mode 100644 index 0000000..2c44518 --- /dev/null +++ b/rcs/rcsservice/AndroidManifest.xml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<!-- + * 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:versionCode="1" + android:versionName="2.4.6" + coreApp="true" + android:sharedUserId="android.uid.phone" + package="com.android.service.ims"> + + <uses-sdk android:minSdkVersion="19"/> + + <protected-broadcast android:name="com.android.ims.ACTION_RCS_SERVICE_AVAILABLE" /> + <protected-broadcast android:name="com.android.ims.ACTION_RCS_SERVICE_UNAVAILABLE" /> + <protected-broadcast android:name="com.android.ims.ACTION_RCS_SERVICE_DIED" /> + <protected-broadcast android:name="com.android.ims.ACTION_PRESENCE_CHANGED" /> + <protected-broadcast android:name="com.android.ims.ACTION_PUBLISH_STATUS_CHANGED" /> + + <permission + android:name="com.android.ims.rcs.permission.STATUS_CHANGED" + android:protectionLevel="signatureOrSystem" /> + + <permission android:name="com.android.ims.permission.PRESENCE_ACCESS" + android:label="@string/ims_presence_permission" + android:description="@string/ims_ims_permission_desc" + android:protectionLevel="signatureOrSystem" /> + + <uses-permission android:name="android.permission.READ_PHONE_STATE"/> + <uses-permission android:name="android.permission.BROADCAST_STICKY"/> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> + <uses-permission android:name="com.android.ims.rcs.permission.STATUS_CHANGED"/> + <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> + <uses-permission android:name="android.permission.ACCESS_UCE_PRESENCE_SERVICE"/> + <uses-permission android:name="com.android.ims.permission.PRESENCE_ACCESS"/> + <uses-permission android:name="com.android.rcs.eab.permission.READ_WRITE_EAB"/> + + <application android:name="RcsServiceApp" android:persistent="true" + android:process="com.android.ims.rcsservice"> + <uses-library android:name="com.android.ims.rcsmanager" + android:required="true"/> + + <service android:name="com.android.service.ims.RcsService" + android:exported="true" + android:enabled="true" + android:permission="com.android.ims.permission.PRESENCE_ACCESS"> + </service> + + <receiver android:name="com.android.service.ims.DeviceShutdown" + androidprv:systemUserOnly="true"> + <intent-filter> + <action android:name="android.intent.action.ACTION_SHUTDOWN"/> + </intent-filter> + </receiver> + + <receiver android:name="com.android.service.ims.presence.AlarmBroadcastReceiver" + android:permission="com.android.ims.permission.PRESENCE_ACCESS" + androidprv:systemUserOnly="true"> + <intent-filter> + <action android:name="com.android.service.ims.presence.retry" /> + </intent-filter> + </receiver> + </application> +</manifest> diff --git a/rcs/rcsservice/res/values/config.xml b/rcs/rcsservice/res/values/config.xml new file mode 100644 index 0000000..c62c09e --- /dev/null +++ b/rcs/rcsservice/res/values/config.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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. +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string-array translatable="false" name="config_volte_provision_error_on_publish_response"> + <item>403 not authorized for presence</item> + </string-array> + + <string-array translatable="false" name="config_rcs_provision_error_on_publish_response"> + <item>404 not found</item> + </string-array> + + <string-array translatable="false" name="config_volte_provision_error_on_subscribe_response"> + <item>403 not authorized for presence</item> + </string-array> + + <string-array translatable="false" name="config_rcs_provision_error_on_subscribe_response"> + <item>403 forbidden</item> + </string-array> + +</resources> + diff --git a/rcs/rcsservice/res/values/strings.xml b/rcs/rcsservice/res/values/strings.xml new file mode 100644 index 0000000..cb4bbc3 --- /dev/null +++ b/rcs/rcsservice/res/values/strings.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * 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. +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> +<string name="ims_presence_permission">Presence</string> +<string name="ims_ims_permission_desc">Allow the application to publish and subscribe the presence information.</string> +</resources> + diff --git a/rcs/rcsservice/src/com/android/service/ims/DeviceShutdown.java b/rcs/rcsservice/src/com/android/service/ims/DeviceShutdown.java new file mode 100644 index 0000000..ffc10fc --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/DeviceShutdown.java @@ -0,0 +1,54 @@ +/* + * 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 android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.android.ims.internal.Logger; + +/** + * Device shutdown event receiver: automatically stops the RCS service + */ +public class DeviceShutdown extends BroadcastReceiver { + /** + * The logger + */ + private Logger logger = Logger.getLogger(this.getClass().getName()); + + @Override + public void onReceive(Context context, Intent intent) { + logger.debug("Device shutdown"); + + // Need wait RCS stack become unavailable before close RCS service. + RcsStackAdaptor.setInPowerDown(true); + } +} + diff --git a/rcs/rcsservice/src/com/android/service/ims/LauncherUtils.java b/rcs/rcsservice/src/com/android/service/ims/LauncherUtils.java new file mode 100644 index 0000000..407a5a7 --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/LauncherUtils.java @@ -0,0 +1,75 @@ +/* + * 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 android.content.Context; +import android.content.Intent; +import android.content.ComponentName; + +import com.android.ims.internal.Logger; +import com.android.service.ims.RcsService; + +/** + * Launcher utility functions + * + */ +public class LauncherUtils { + private static Logger logger = Logger.getLogger(LauncherUtils.class.getName()); + + /** + * Launch the Presence service. + * + * @param context application context + * @param boot Boot flag + */ + public static void launchRcsService(Context context) { + logger.debug("Launch RCS service"); + + ComponentName comp = new ComponentName(context.getPackageName(), + RcsService.class.getName()); + ComponentName service = context.startService(new Intent().setComponent(comp)); + if (service == null) { + logger.error("Could Not Start Service " + comp.toString()); + } else { + logger.debug("ImsService Auto Boot Started Successfully"); + } + } + + /** + * Stop the Presence service. + * + * @param context application context + */ + public static void stopRcsService(Context context) { + logger.debug("Stop RCS service"); + + context.stopService(new Intent(context, RcsService.class)); + } +} + diff --git a/rcs/rcsservice/src/com/android/service/ims/RcsService.java b/rcs/rcsservice/src/com/android/service/ims/RcsService.java new file mode 100644 index 0000000..d5ce570 --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/RcsService.java @@ -0,0 +1,403 @@ +/* + * 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 android.net.Uri; + +import java.util.List; + +import android.content.Intent; +import android.app.PendingIntent; +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.ImsConfig.FeatureValueConstants; +import com.android.ims.ImsManager; +import com.android.ims.ImsConfig; +import com.android.ims.ImsReasonInfo; +import com.android.ims.ImsConnectionStateListener; +import com.android.ims.ImsServiceClass; +import com.android.ims.ImsException; +import android.telephony.SubscriptionManager; + +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.RcsPresence.PublishState; + +import com.android.ims.internal.Logger; +import com.android.service.ims.RcsStackAdaptor; + +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); + + mVtSettingObserver = new VtSettingContentObserver(); + getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.VT_IMS_ENABLED), + false, mVtSettingObserver); + + registerImsConnectionStateListener(); + + 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(); + } + } + }; + + IntentFilter statusFilter = new IntentFilter(); + statusFilter.addAction(ImsManager.ACTION_IMS_SERVICE_UP); + statusFilter.addAction(ImsManager.ACTION_IMS_SERVICE_DOWN); + registerReceiver(mReceiver, statusFilter); + } + + public void handleImsServiceUp() { + if(mPublication != null) { + mPublication.handleImsServiceUp(); + } + + registerImsConnectionStateListener(); + } + + public void handleImsServiceDown() { + if(mPublication != null) { + mPublication.handleImsServiceDown(); + } + } + + + @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(mVtSettingObserver); + 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<String> 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){ + logger.debug("calling requestAvailability, contactNumber=" + contactNumber); + 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) { + logger.debug("calling requestAvailabilityNoThrottle, contactNumber=" + contactNumber); + 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 VtSettingContentObserver mVtSettingObserver; + + /** + * Receives notifications when Mobile data is enabled or disabled. + */ + private class VtSettingContentObserver extends ContentObserver { + public VtSettingContentObserver() { + super(new Handler()); + } + + @Override + public void onChange(final boolean selfChange) { + boolean enabled = Settings.Global.getInt(getContentResolver(), + Settings.Global.VT_IMS_ENABLED, 1) == 1; + 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(ImsServiceClass.MMTEL, + mImsConnectionStateListener); + } + } catch (ImsException e) { + logger.error("addRegistrationListener exception=", e); + } + } + + private ImsConnectionStateListener mImsConnectionStateListener = + new ImsConnectionStateListener() { + @Override + public void onImsConnected() { + logger.debug("onImsConnected"); + 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); + } + } + }; +} + diff --git a/rcs/rcsservice/src/com/android/service/ims/RcsServiceApp.java b/rcs/rcsservice/src/com/android/service/ims/RcsServiceApp.java new file mode 100644 index 0000000..c1f683d --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/RcsServiceApp.java @@ -0,0 +1,46 @@ +/* + * 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 android.app.Application; +import android.content.Context; +import com.android.ims.internal.Logger; +import android.os.SystemProperties; + +public class RcsServiceApp extends Application { + private Logger logger = Logger.getLogger(this.getClass().getName()); + + public void onCreate() { + super.onCreate(); + logger.debug("in onCreate() of RcsServiceApp"); + + LauncherUtils.launchRcsService(this); + } +} + diff --git a/rcs/rcsservice/src/com/android/service/ims/RcsSettingUtils.java b/rcs/rcsservice/src/com/android/service/ims/RcsSettingUtils.java new file mode 100644 index 0000000..eb415cf --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/RcsSettingUtils.java @@ -0,0 +1,255 @@ +/* + * 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.lang.String; +import android.telephony.TelephonyManager; +import android.content.Context; +import com.android.ims.ImsConfig; +import com.android.ims.ImsManager; +import com.android.ims.ImsException; +import android.os.SystemProperties; + +import com.android.ims.RcsManager.ResultCode; + +import com.android.ims.internal.Logger; + +public class RcsSettingUtils{ + /* + * The logger + */ + static private Logger logger = Logger.getLogger("RcsSettingUtils"); + + public RcsSettingUtils() { + } + + public static boolean isFeatureProvisioned(Context context, + int featureId, boolean defaultValue) { + // Don't need provision. + if (!context.getResources().getBoolean( + com.android.internal.R.bool.config_carrier_volte_provisioned)) { + return true; + } + + boolean provisioned = defaultValue; + ImsManager imsManager = ImsManager.getInstance(context, 0); + if (imsManager != null) { + try { + ImsConfig imsConfig = imsManager.getConfigInterface(); + if (imsConfig != null) { + provisioned = imsConfig.getProvisionedValue(featureId) + == ImsConfig.FeatureValueConstants.ON; + } + } catch (ImsException ex) { + } + } + + logger.debug("featureId=" + featureId + " provisioned=" + provisioned); + return provisioned; + } + + public static boolean isVowifiProvisioned(Context context) { + return isFeatureProvisioned(context, + ImsConfig.ConfigConstants.VOICE_OVER_WIFI_SETTING_ENABLED, false); + } + + public static boolean isLvcProvisioned(Context context) { + return isFeatureProvisioned(context, + ImsConfig.ConfigConstants.LVC_SETTING_ENABLED, false); + } + + public static boolean isEabProvisioned(Context context) { + return isFeatureProvisioned(context, + ImsConfig.ConfigConstants.EAB_SETTING_ENABLED, false); + } + + public static int getSIPT1Timer(Context context) { + int sipT1Timer = 0; + + ImsManager imsManager = ImsManager.getInstance(context, 0); + if (imsManager != null) { + try { + ImsConfig imsConfig = imsManager.getConfigInterface(); + if (imsConfig != null) { + sipT1Timer = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.SIP_T1_TIMER); + } + } catch (ImsException ex) { + } + } + + logger.debug("sipT1Timer=" + sipT1Timer); + return sipT1Timer; + } + + /** + * Capability discovery status of Enabled (1), or Disabled (0). + */ + public static boolean getCapabilityDiscoveryEnabled(Context context) { + boolean capabilityDiscoveryEnabled = false; + + ImsManager imsManager = ImsManager.getInstance(context, 0); + if (imsManager != null) { + try { + ImsConfig imsConfig = imsManager.getConfigInterface(); + if (imsConfig != null) { + capabilityDiscoveryEnabled = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.CAPABILITY_DISCOVERY_ENABLED) + == ImsConfig.FeatureValueConstants.ON; + } + } catch (ImsException ex) { + } + } + + logger.debug("capabilityDiscoveryEnabled=" + capabilityDiscoveryEnabled); + return capabilityDiscoveryEnabled; + } + + /** + * The Maximum number of MDNs contained in one Request Contained List. + */ + public static int getMaxNumbersInRCL(Context context) { + int maxNumbersInRCL = 100; + + ImsManager imsManager = ImsManager.getInstance(context, 0); + if (imsManager != null) { + try { + ImsConfig imsConfig = imsManager.getConfigInterface(); + if (imsConfig != null) { + maxNumbersInRCL = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.MAX_NUMENTRIES_IN_RCL); + } + } catch (ImsException ex) { + } + } + + logger.debug("maxNumbersInRCL=" + maxNumbersInRCL); + return maxNumbersInRCL; + } + + /** + * Expiration timer for subscription of a Request Contained List, used in capability polling. + */ + public static int getCapabPollListSubExp(Context context) { + int capabPollListSubExp = 30; + + ImsManager imsManager = ImsManager.getInstance(context, 0); + if (imsManager != null) { + try { + ImsConfig imsConfig = imsManager.getConfigInterface(); + if (imsConfig != null) { + capabPollListSubExp = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.CAPAB_POLL_LIST_SUB_EXP); + } + } catch (ImsException ex) { + } + } + + logger.debug("capabPollListSubExp=" + capabPollListSubExp); + return capabPollListSubExp; + } + + /** + * Peiod of time the availability information of a contact is cached on device. + */ + public static int getAvailabilityCacheExpiration(Context context) { + int availabilityCacheExpiration = 30; + + ImsManager imsManager = ImsManager.getInstance(context, 0); + if (imsManager != null) { + try { + ImsConfig imsConfig = imsManager.getConfigInterface(); + if (imsConfig != null) { + availabilityCacheExpiration = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.AVAILABILITY_CACHE_EXPIRATION); + } + } catch (ImsException ex) { + } + } + + logger.debug("availabilityCacheExpiration=" + availabilityCacheExpiration); + return availabilityCacheExpiration; + } + + public static boolean isMobileDataEnabled(Context context) { + boolean mobileDataEnabled = false; + ImsManager imsManager = ImsManager.getInstance(context, 0); + if (imsManager != null) { + try { + ImsConfig imsConfig = imsManager.getConfigInterface(); + if (imsConfig != null) { + mobileDataEnabled = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.MOBILE_DATA_ENABLED) + == ImsConfig.FeatureValueConstants.ON; + } + } catch (ImsException ex) { + } + } + + logger.debug("mobileDataEnabled=" + mobileDataEnabled); + return mobileDataEnabled; + } + + public static void setMobileDataEnabled(Context context, boolean mobileDataEnabled) { + logger.debug("mobileDataEnabled=" + mobileDataEnabled); + ImsManager imsManager = ImsManager.getInstance(context, 0); + if (imsManager != null) { + try { + ImsConfig imsConfig = imsManager.getConfigInterface(); + if (imsConfig != null) { + imsConfig.setProvisionedValue( + ImsConfig.ConfigConstants.MOBILE_DATA_ENABLED, mobileDataEnabled? + ImsConfig.FeatureValueConstants.ON:ImsConfig.FeatureValueConstants.OFF); + } + } catch (ImsException ex) { + logger.debug("ImsException", ex); + } + } + } + + public static int getPublishThrottle(Context context) { + int publishThrottle = 60000; + + ImsManager imsManager = ImsManager.getInstance(context, 0); + if (imsManager != null) { + try { + ImsConfig imsConfig = imsManager.getConfigInterface(); + if (imsConfig != null) { + publishThrottle = imsConfig.getProvisionedValue( + ImsConfig.ConfigConstants.SOURCE_THROTTLE_PUBLISH); + } + } catch (ImsException ex) { + } + } + + logger.debug("publishThrottle=" + publishThrottle); + return publishThrottle; + } +} + diff --git a/rcs/rcsservice/src/com/android/service/ims/RcsStackAdaptor.java b/rcs/rcsservice/src/com/android/service/ims/RcsStackAdaptor.java new file mode 100644 index 0000000..ebf9370 --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/RcsStackAdaptor.java @@ -0,0 +1,710 @@ +/* + * 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 java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.Context; +import android.content.Intent; +import android.app.PendingIntent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.telephony.TelephonyManager; +import android.app.AlarmManager; +import android.os.SystemClock; +import android.os.SystemProperties; +import com.android.ims.ImsConfig; +import com.android.ims.ImsManager; +import com.android.ims.ImsException; +import android.telephony.SubscriptionManager; + +import com.android.ims.IRcsPresenceListener; +import com.android.ims.RcsPresence; +import com.android.ims.RcsManager.ResultCode; +import com.android.ims.RcsPresence.PublishState; + +import com.android.ims.internal.Logger; +import com.android.ims.internal.ContactNumberUtils; +import com.android.service.ims.presence.PresencePublication; +import com.android.service.ims.R; + +import com.android.ims.internal.uce.presence.IPresenceService; +import com.android.ims.internal.uce.presence.PresCapInfo; +import com.android.ims.internal.uce.common.CapInfo; +import com.android.ims.internal.uce.uceservice.IUceService; +import com.android.ims.internal.uce.uceservice.ImsUceManager; +import com.android.ims.internal.uce.common.UceLong; +import com.android.ims.internal.uce.common.StatusCode; + +import com.android.ims.IRcsPresenceListener; +import com.android.ims.RcsPresenceInfo; + +import com.android.service.ims.presence.StackListener; +import com.android.service.ims.presence.PresenceInfoParser; +import com.android.service.ims.presence.AlarmBroadcastReceiver; + +public class RcsStackAdaptor{ + private static final boolean DEBUG = true; + + // The logger + private Logger logger = Logger.getLogger(this.getClass().getName()); + + private static final int PRESENCE_INIT_IMS_UCE_SERVICE = 1; + + private Context mContext = null; + + // true when the stack presence service got available. (Called IQPresListener_ServiceAvailable) + private volatile boolean mImsEnableState = false; + + // provision status can be set by both subscribe and pubilish + // for unprovisioned for 403 or 404 + private volatile int mPublishingState = PublishState.PUBLISH_STATE_NOT_PUBLISHED; + + // It is initializing the stack presence service. + private volatile boolean mIsIniting = false; + + // The time which the stack presence service got initialized. + private volatile long mLastInitSubService = -1; //last time which inited the sub service + + // Used for synchronizing + private final Object mSyncObj = new Object(); + + // We need wait RCS stack become unavailable before close RCS service. + static private boolean sInPowerDown = false; + + // This could happen when the stack first launch or modem panic. + private static final int PRESENCE_INIT_TYPE_RCS_SERVICE_AVAILABLE =1; + + // The initialization was triggered by retry. + private static final int PRESENCE_INIT_TYPE_RETRY = 2; + + // The initialization was triggered by retry. + private static final int PRESENCE_INIT_TYPE_IMS_REGISTERED = 3; + + // The maximum retry count for initializing the stack service. + private static final int MAX_RETRY_COUNT = 6;//Maximum time is 64s + + public boolean isImsEnableState() { + return mImsEnableState; + } + + synchronized public void setImsEnableState(boolean imsEnableState) { + logger.debug("imsEnableState=" + imsEnableState); + + mImsEnableState = imsEnableState; + } + + // The UCE manager for RCS stack. + private ImsUceManager mImsUceManager = null; + + // The stack RCS Service instance. + private IUceService mStackService = null; + + // The stack presence service + private IPresenceService mStackPresService = null; + + // The stack Presence Service handle. + private int mStackPresenceServiceHandle; + + // The listener which listen to the response for presence service. + private StackListener mListenerHandler = null; + + // The handler of the listener. + private UceLong mListenerHandle = new UceLong(); + + // The singleton. + private static RcsStackAdaptor sInstance = null; + + // Constructor + private RcsStackAdaptor(Context context) { + mContext = context; + + init(); + } + + public static synchronized RcsStackAdaptor getInstance(Context context) { + if ((sInstance == null) && (context != null)) { + sInstance = new RcsStackAdaptor(context); + } + + return sInstance; + } + + private Handler mMsgHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + + logger.debug( "Thread=" + Thread.currentThread().getName() + " received " + + msg); + if(msg == null){ + logger.error("msg=null"); + return; + } + + switch (msg.what) { + case PRESENCE_INIT_IMS_UCE_SERVICE: + logger.debug("handleMessage msg=PRESENCE_INIT_IMS_UCE_SERVICE" ); + doInitImsUceService(); + break; + + default: + logger.debug("handleMessage unknown msg=" + msg.what); + } + } + }; + + public StackListener getListener(){ + return mListenerHandler; + } + + public int checkStackAndPublish(){ + if(!RcsSettingUtils.getCapabilityDiscoveryEnabled(mContext)){ + logger.error("getCapabilityDiscoveryEnabled = false"); + return ResultCode.ERROR_SERVICE_NOT_ENABLED; + } + + int ret = checkStackStatus(); + if(ret !=ResultCode.SUCCESS){ + logger.error("checkStackAndPublish ret=" + ret); + return ret; + } + + if(!isPublished()){ + logger.error( + "checkStackAndPublish ERROR_SERVICE_NOT_PUBLISHED"); + return ResultCode.ERROR_SERVICE_NOT_PUBLISHED; + } + + return ResultCode.SUCCESS; + } + + private boolean isPublished(){ + if(getPublishState() != PublishState.PUBLISH_STATE_200_OK){ + logger.error("Didnt' publish properly"); + return false; + } + + return true; + } + + public void setPublishState(int publishState) { + logger.print("mPublishingState=" + mPublishingState + " publishState=" + publishState); + if(mPublishingState != publishState) { + // save it for recovery when PresenceService crash. + SystemProperties.set("rcs.publish.status", + String.valueOf(publishState)); + + // broadcast publish state change intent + Intent publishIntent = new Intent(RcsPresence.ACTION_PUBLISH_STATE_CHANGED); + publishIntent.putExtra(RcsPresence.EXTRA_PUBLISH_STATE, + publishState); + mContext.sendStickyBroadcast(publishIntent); + } + + mPublishingState = publishState; + } + + public int getPublishState(){ + return mPublishingState; + } + + public int checkStackStatus(){ + if(!RcsSettingUtils.isEabProvisioned(mContext)) { + logger.error("Didn't get EAB provisioned by DM"); + return ResultCode.ERROR_SERVICE_NOT_ENABLED; + } + + // Don't send request to RCS stack when it is under powering off. + // RCS stack is sending UNPUBLISH. It could get race PUBLISH trigger under the case. + if(sInPowerDown) { + logger.error("checkStackStatus: under powering off"); + return ResultCode.ERROR_SERVICE_NOT_AVAILABLE; + } + + if(mStackService == null){ + logger.error("checkStackStatus: mStackService == null"); + return ResultCode.ERROR_SERVICE_NOT_AVAILABLE; + } + + if(mStackPresService == null){ + logger.error("Didn't init sub rcs service."); + return ResultCode.ERROR_SERVICE_NOT_AVAILABLE; + } + + if(!mImsEnableState){ + logger.error("mImsEnableState = false"); + return ResultCode.ERROR_SERVICE_NOT_AVAILABLE; + } + + return ResultCode.SUCCESS; + } + + public int requestCapability(String[] formatedContacts, int taskId){ + logger.print("requestCapability formatedContacts=" + formatedContacts); + + int ret = ResultCode.SUCCESS; + try{ + StatusCode retCode; + if(formatedContacts.length == 1){ + retCode = mStackPresService.getContactCap( + mStackPresenceServiceHandle, formatedContacts[0], taskId); + }else{ + retCode = mStackPresService.getContactListCap( + mStackPresenceServiceHandle, formatedContacts, taskId); + } + logger.print("GetContactListCap retCode=" + retCode); + + ret = RcsUtils.statusCodeToResultCode(retCode.getStatusCode()); + logger.debug("requestCapability ret=" + ret); + }catch(Exception e){ + logger.error("requestCapability exception", e); + ret = ResultCode.ERROR_SERVICE_NOT_AVAILABLE; + } + + return ret; + } + + public int requestAvailability(String formatedContact, int taskId){ + logger.debug("requestAvailability ..."); + + int ret = ResultCode.SUCCESS; + try{ + StatusCode retCode = mStackPresService.getContactCap( + mStackPresenceServiceHandle, formatedContact, taskId); + logger.print("getContactCap retCode=" + retCode); + + ret = RcsUtils.statusCodeToResultCode(retCode.getStatusCode()); + + logger.debug("requestAvailability ret=" + ret); + }catch(Exception e){ + logger.error("requestAvailability exception", e); + ret = ResultCode.ERROR_SERVICE_NOT_AVAILABLE; + } + + return ret; + } + + public int requestPublication(RcsPresenceInfo presenceInfo, IRcsPresenceListener listener) { + logger.debug("requestPublication ..."); + + // Don't use checkStackAndPublish() + // since it will check publish status which in dead loop. + int ret = checkStackStatus(); + if(ret != ResultCode.SUCCESS){ + logger.error("requestPublication ret=" + ret); + return ret; + } + + TelephonyManager teleMgr = (TelephonyManager) mContext.getSystemService( + Context.TELEPHONY_SERVICE); + if(teleMgr == null){ + logger.error("teleMgr = null"); + return PresencePublication.PUBLISH_GENIRIC_FAILURE; + } + + String myNumUri = null; + String myDomain = teleMgr.getIsimDomain(); + logger.debug("myDomain=" + myDomain); + if(myDomain != null && myDomain.length() !=0){ + String[] impu = teleMgr.getIsimImpu(); + + if(impu !=null){ + for(int i=0; i<impu.length; i++){ + logger.debug("impu[" + i + "]=" + impu[i]); + if(impu[i] != null && impu[i].startsWith("sip:") && + impu[i].endsWith(myDomain)){ + myNumUri = impu[i]; + break; + } + } + } + } + + String myNumber = PresenceInfoParser.getPhoneFromUri(myNumUri); + + if(myNumber == null){ + myNumber = ContactNumberUtils.getDefault().format(teleMgr.getLine1Number()); + if(myDomain != null && myDomain.length() !=0){ + myNumUri = "sip:" + myNumber + "@" + myDomain; + }else{ + myNumUri = "tel:" + myNumber; + } + } + + logger.print("myNumUri=" + myNumUri + " myNumber=" + myNumber); + if(myNumUri == null || myNumber == null){ + logger.error("Didn't find number or impu."); + return PresencePublication.PUBLISH_GENIRIC_FAILURE; + } + + int taskId = TaskManager.getDefault().addPublishTask(myNumber, listener); + try{ + PresCapInfo pMyCapInfo = new PresCapInfo(); + // Fill cap info + pMyCapInfo.setContactUri(myNumUri); + + CapInfo capInfo = new CapInfo(); + capInfo.setIpVoiceSupported(presenceInfo.getServiceState( + RcsPresenceInfo.ServiceType.VOLTE_CALL) + == RcsPresenceInfo.ServiceState.ONLINE); + capInfo.setIpVideoSupported(presenceInfo.getServiceState( + RcsPresenceInfo.ServiceType.VT_CALL) + == RcsPresenceInfo.ServiceState.ONLINE); + capInfo.setCdViaPresenceSupported(true); + + capInfo.setFtSupported(false); // TODO: support FT + capInfo.setImSupported(false);//TODO: support chat + capInfo.setFullSnFGroupChatSupported(false); //TODO: support chat + + pMyCapInfo.setCapInfo(capInfo); + + logger.print( "myNumUri = " + myNumUri + + " audioSupported = " + capInfo.isIpVoiceSupported() + + " videoSupported= " + capInfo.isIpVideoSupported() + ); + + + StatusCode status = mStackPresService.publishMyCap( + mStackPresenceServiceHandle, pMyCapInfo, taskId); + logger.print("PublishMyCap status=" + status.getStatusCode()); + + ret = RcsUtils.statusCodeToResultCode(status.getStatusCode()); + + logger.debug("requestPublication ret=" + ret); + if(ret != ResultCode.SUCCESS){ + logger.error("requestPublication remove taskId=" + taskId); + TaskManager.getDefault().removeTask(taskId); + return ret; + } + }catch(RemoteException e){ + e.printStackTrace(); + logger.error("Exception when call mStackPresService.getContactCap"); + logger.error("requestPublication remove taskId=" + taskId); + TaskManager.getDefault().removeTask(taskId); + + return ResultCode.ERROR_SERVICE_NOT_AVAILABLE; + } + + return ResultCode.SUCCESS; + } + + private void createListeningThread() { + HandlerThread listenerThread = new HandlerThread("Listener", + android.os.Process.THREAD_PRIORITY_BACKGROUND); + + listenerThread.start(); + Looper listenerLooper = listenerThread.getLooper(); + mListenerHandler = new StackListener(mContext, listenerLooper); + } + + private void initImsUceService(){ + // Send message to avoid ANR + Message reinitMessage = mMsgHandler.obtainMessage( + PRESENCE_INIT_IMS_UCE_SERVICE, null); + mMsgHandler.sendMessage(reinitMessage); + } + + synchronized private void doInitImsUceService(){ + logger.debug("doInitImsUceService"); + + if(mStackService != null){ + logger.debug("doInitImsUceService mStackService != null"); + return; + } + + IntentFilter filter = new IntentFilter(); + filter.addAction(ImsUceManager.ACTION_UCE_SERVICE_UP); + filter.addAction(ImsUceManager.ACTION_UCE_SERVICE_DOWN); + + mRcsServiceReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // do something based on the intent's action + logger.print("onReceive intent " + intent); + String action = intent.getAction(); + if(ImsUceManager.ACTION_UCE_SERVICE_UP.equals(action)) { + synchronized(mSyncObj){ + startInitPresenceTimer(0, PRESENCE_INIT_TYPE_RCS_SERVICE_AVAILABLE); + } + } else if(ImsUceManager.ACTION_UCE_SERVICE_DOWN.equals(action)) { + synchronized(mSyncObj){ + clearImsUceService(); + } + } else { + logger.debug("unknown intent " + intent); + } + } + }; + + mContext.registerReceiver(mRcsServiceReceiver, filter); + + if(mImsUceManager == null) { + mImsUceManager = ImsUceManager.getInstance(mContext, + SubscriptionManager.from(mContext).getDefaultDataPhoneId()); + if(mImsUceManager == null) { + logger.error("Can't init mImsUceManager"); + return; + } + } + + mImsUceManager.createUceService(false); + mStackService = mImsUceManager.getUceServiceInstance(); + logger.debug("doInitImsUceService mStackService=" + mStackService); + + if(mStackService != null) { + startInitPresenceTimer(0, PRESENCE_INIT_TYPE_RCS_SERVICE_AVAILABLE); + } + } + + private PendingIntent mRetryAlarmIntent = null; + public static final String ACTION_RETRY_ALARM = "com.android.service.ims.presence.retry"; + private AlarmManager mAlarmManager = null; + private BroadcastReceiver mRcsServiceReceiver = null; + + /* + * Init All Sub service of RCS + */ + int initAllSubRcsServices(IUceService uceService, int currentRetry) { + int ret = -1; + synchronized (mSyncObj){ + // we could receive useless retry since we could fail to cancel the retry timer. + // We need to ignore such retry if the sub service had been initialized already. + if(mStackPresService != null && currentRetry>0){ + logger.debug("mStackPresService != null and currentRetry=" + currentRetry + + " ignore it"); + return 0; + } + + logger.debug("Init All Services Under RCS uceService=" + uceService); + if(uceService == null){ + logger.error("initAllServices : uceService is NULL "); + mIsIniting = false; + mLastInitSubService = -1; + return ret; + } + + try { + if(mStackPresService != null){ + logger.print("RemoveListener and QRCSDestroyPresService"); + mStackPresService.removeListener(mStackPresenceServiceHandle, + mListenerHandle); + uceService.destroyPresenceService(mStackPresenceServiceHandle); + + mStackPresService = null; + } + + boolean serviceStatus = false; + serviceStatus = uceService.getServiceStatus(); + if (true == serviceStatus && mStackPresService == null) {//init only once. + logger.print("initAllService : serviceStatus = true "); + logger.debug("Create PresService"); + mStackPresenceServiceHandle = mStackService.createPresenceService( + mListenerHandler.mPresenceListener, mListenerHandle); + mStackPresService = mStackService.getPresenceService(); + ret = 0; + } else { + logger.error("initAllService : serviceStatus = false "); + } + } catch (RemoteException e) { + logger.error("initAllServices : DeadObjectException dialog "); + e.printStackTrace(); + } + mIsIniting = false; + } + return ret; + } + + // Init sub service when IMS get registered. + public void checkSubService() { + logger.debug("checkSubService"); + if(mStackPresService == null) { + // Cancel the retry timer. + if(mIsIniting){ + if(mRetryAlarmIntent != null){ + mAlarmManager.cancel(mRetryAlarmIntent); + mRetryAlarmIntent = null; + } + mIsIniting = false; + } + + // force to init imediately. + startInitPresenceTimer(0, PRESENCE_INIT_TYPE_IMS_REGISTERED); + } + } + + public void startInitThread(int times){ + final int currentRetry = times; + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + if (currentRetry >=0 && currentRetry <= MAX_RETRY_COUNT) { + refreshUceService(); + + if (initAllSubRcsServices(mStackService, currentRetry) >=0){ + logger.debug("init sub rcs service successfully."); + mAlarmManager.cancel(mRetryAlarmIntent); + } else { + startInitPresenceTimer(currentRetry + 1, PRESENCE_INIT_TYPE_RETRY); + } + } else { + logger.debug("Retry times=" + currentRetry); + mAlarmManager.cancel(mRetryAlarmIntent); + } + } + }, "initAllSubRcsServices thread"); + + thread.start(); + } + + private void init() { + createListeningThread(); + logger.debug("after createListeningThread"); + + if(mAlarmManager == null){ + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + } + + initImsUceService(); + + setInPowerDown(false); + logger.debug("init finished"); + } + + private void startInitPresenceTimer(int times, int initType){ + synchronized (mSyncObj){ + logger.print("set the retry alarm, times=" + times + " initType=" + initType + + " mIsIniting=" + mIsIniting); + if(mIsIniting){ + //initing is on going in 5 seconds, discard this one. + if(mLastInitSubService != -1 && + System.currentTimeMillis() - mLastInitSubService < 5000){ + logger.print("already in initing. ignore it"); + return; + }//else suppose the init has problem. so continue + } + + mIsIniting = true; + + Intent intent = new Intent(ACTION_RETRY_ALARM); + intent.putExtra("times", times); + intent.setClass(mContext, AlarmBroadcastReceiver.class); + mRetryAlarmIntent = PendingIntent.getBroadcast(mContext, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + + // Wait for 1s to ignore duplicate init request as possible as we can. + long timeSkip = 1000; + if(times < 0 || times >= MAX_RETRY_COUNT){ + times = MAX_RETRY_COUNT; + } + + //Could failed to cancel a timer in 1s. So use exponential retry to make sure it + //will be stopped for non-VoLte SIM. + timeSkip = (timeSkip << times); + logger.debug("timeSkip = " + timeSkip); + + mLastInitSubService = System.currentTimeMillis(); + + //the timer intent could have a longer delay. call directly at first time + if(times == 0) { + startInitThread(0); + } else { + mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + timeSkip, mRetryAlarmIntent); + } + } + } + + private void refreshUceService() { + logger.debug("refreshUceService mImsUceManager=" + mImsUceManager + + " mStackService=" + mStackService); + + if(mImsUceManager == null) { + mImsUceManager = ImsUceManager.getInstance(mContext, + SubscriptionManager.from(mContext).getDefaultDataPhoneId()); + if(mImsUceManager == null) { + logger.error("Can't init mImsUceManager"); + return; + } + } + + if(mStackService == null) { + mImsUceManager.createUceService(false); + mStackService = mImsUceManager.getUceServiceInstance(); + } + + logger.debug("refreshUceService mStackService=" + mStackService); + } + + private void clearImsUceService() { + mImsUceManager = null; + mStackService = null; + mStackPresService = null; + } + + public void finish() { + if(mRetryAlarmIntent != null){ + mAlarmManager.cancel(mRetryAlarmIntent); + mRetryAlarmIntent = null; + } + + if (mRcsServiceReceiver != null) { + mContext.unregisterReceiver(mRcsServiceReceiver); + mRcsServiceReceiver = null; + } + + clearImsUceService(); + } + + protected void finalize() throws Throwable { + finish(); + } + + static public boolean isInPowerDown() { + return sInPowerDown; + } + + static void setInPowerDown(boolean inPowerDown) { + sInPowerDown = inPowerDown; + } +} + diff --git a/rcs/rcsservice/src/com/android/service/ims/RcsUtils.java b/rcs/rcsservice/src/com/android/service/ims/RcsUtils.java new file mode 100644 index 0000000..bbfacd2 --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/RcsUtils.java @@ -0,0 +1,109 @@ +/* + * 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.lang.String; +import android.telephony.TelephonyManager; +import android.content.Context; + +import com.android.ims.internal.uce.common.StatusCode; + +import com.android.ims.RcsManager.ResultCode; + +import com.android.ims.internal.Logger; + +public class RcsUtils{ + /* + * The logger + */ + static private Logger logger = Logger.getLogger("RcsUtils"); + + public RcsUtils() { + } + + static public int statusCodeToResultCode(int sipStatusCode){ + if(sipStatusCode == StatusCode.UCE_SUCCESS || + sipStatusCode == StatusCode.UCE_SUCCESS_ASYC_UPDATE){ + return ResultCode.SUCCESS; + } + + if(sipStatusCode == StatusCode.UCE_INVALID_PARAM) { + return ResultCode.SUBSCRIBE_INVALID_PARAM; + } + + if(sipStatusCode == StatusCode.UCE_FETCH_ERROR) { + return ResultCode.SUBSCRIBE_FETCH_ERROR; + } + + if(sipStatusCode == StatusCode.UCE_REQUEST_TIMEOUT) { + return ResultCode.SUBSCRIBE_REQUEST_TIMEOUT; + } + + if(sipStatusCode == StatusCode.UCE_INSUFFICIENT_MEMORY) { + return ResultCode.SUBSCRIBE_INSUFFICIENT_MEMORY; + } + + if(sipStatusCode == StatusCode.UCE_LOST_NET) { + return ResultCode.SUBSCRIBE_LOST_NETWORK; + } + + if(sipStatusCode == StatusCode.UCE_NOT_SUPPORTED){ + return ResultCode.SUBSCRIBE_NOT_SUPPORTED; + } + + if(sipStatusCode == StatusCode.UCE_NOT_FOUND){ + return ResultCode.SUBSCRIBE_NOT_FOUND; + } + + if(sipStatusCode == StatusCode.UCE_FAILURE || + sipStatusCode == StatusCode.UCE_INVALID_SERVICE_HANDLE || + sipStatusCode == StatusCode.UCE_INVALID_LISTENER_HANDLE){ + return ResultCode.SUBSCRIBE_GENERIC; + } + + return ResultCode.SUBSCRIBE_GENERIC; + } + + static public String toContactString(String[] contacts) { + if(contacts == null) { + return null; + } + + String result = ""; + for(int i=0; i<contacts.length; i++) { + result += contacts[i]; + if(i != contacts.length -1) { + result += ";"; + } + } + + return result; + } +} + diff --git a/rcs/rcsservice/src/com/android/service/ims/Task.java b/rcs/rcsservice/src/com/android/service/ims/Task.java new file mode 100644 index 0000000..a58aebf --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/Task.java @@ -0,0 +1,104 @@ +/* + * 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.Set; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; +import android.os.RemoteException; + +import com.android.ims.internal.Logger; +import com.android.ims.IRcsPresenceListener; + +/** + * Task + */ +public class Task{ + /* + * The logger + */ + private Logger logger = Logger.getLogger(this.getClass().getName()); + + // filled before send the reques + public int mTaskId; + + // filled before send the request + // map to QRCS_PRES_CMD_ID + // QRCS_PRES_CMD_PUBLISHMYCAP + // QRCS_PRES_CMD_GETCONTACTCAP, we use it for capability + // QRCS_PRES_CMD_GETCONTACTLISTCAP, we use it for availability + // QRCS_PRES_CMD_SETNEWFEATURETAG + public int mCmdId; + + // filled after IQPresListener_CMDStatus + // QRCS_STATUSCODE, possible values are: + // QRCS_SUCCESS + // QRCS_FAILURE + // QRCS_SUCCESS_ASYC_UPDATE + // QRCS_INVALID_SERVICE_HANDLE + // QRCS_INVALID_LISTENER_HANDLE + // QRCS_INVALID_PARAM + // QRCS_FETCH_ERROR + // QRCS_REQUEST_TIMEOUT + // QRCS_INSUFFICIENT_MEMORY + // QRCS_LOST_NET + // QRCS_NOT_SUPPORTED + // QRCS_NOT_FOUND + // Note: have converted it to ResultCode. + public int mCmdStatus; + + //filled after IQPresListener_CMDStatus + public int mSipRequestId; + + // filled after IQPresListener_SipResponseReceived + public int mSipResponseCode; + + // filled after IQPresListener_SipResponseReceived + public String mSipReasonPhrase; + + // filled before send the request + public IRcsPresenceListener mListener; + + public Task(int taskId, int cmdId, IRcsPresenceListener listener){ + mTaskId = taskId; + mCmdId = cmdId; + mListener = listener; + } + + public String toString(){ + return "Task: mTaskId=" + mTaskId + + " mCmdId=" + mCmdId + + " mCmdStatus=" + mCmdStatus + + " mSipRequestId=" + mSipRequestId + + " mSipResponseCode=" + mSipResponseCode + + " mSipReasonPhrase=" + mSipReasonPhrase; + } +}; + diff --git a/rcs/rcsservice/src/com/android/service/ims/TaskManager.java b/rcs/rcsservice/src/com/android/service/ims/TaskManager.java new file mode 100644 index 0000000..4e931ac --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/TaskManager.java @@ -0,0 +1,394 @@ +/* + * 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.Set; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.List; +import java.util.ArrayList; +import android.os.RemoteException; +import android.content.Context; +import android.os.Handler; +import android.os.Message; +import android.os.HandlerThread; +import android.os.Looper; +import android.telephony.PhoneNumberUtils; + +import com.android.ims.internal.uce.presence.PresCmdStatus; + +import com.android.ims.internal.Logger; +import com.android.ims.RcsManager.ResultCode; +import com.android.ims.RcsPresenceInfo; +import com.android.ims.IRcsPresenceListener; + +import com.android.service.ims.presence.PresenceTask; +import com.android.service.ims.presence.PresenceCapabilityTask; +import com.android.service.ims.presence.PresenceAvailabilityTask; + +/** + * TaskManager + */ +public class TaskManager{ + /* + * The logger + */ + private Logger logger = Logger.getLogger(this.getClass().getName()); + + private static TaskManager sTaskManager = null; + + private int mTaskId = 0; + + public final static int TASK_TYPE_GET_CAPABILITY = 1; + public final static int TASK_TYPE_GET_AVAILABILITY = 2; + public final static int TASK_TYPE_PUBLISH = 3; + + private Map<String, Task> mTaskMap; + + private final Object mSyncObj = new Object(); + + private static final int TASK_MANAGER_ON_TERMINATED = 1; + private static final int TASK_MANAGER_ON_TIMEOUT = 2; + + private static MessageHandler sMsgHandler; + + public TaskManager(){ + logger.debug("TaskManager created."); + mTaskMap = new HashMap<String, Task>(); + + HandlerThread messageHandlerThread = new HandlerThread("MessageHandler", + android.os.Process.THREAD_PRIORITY_BACKGROUND); + + messageHandlerThread.start(); + Looper messageHandlerLooper = messageHandlerThread.getLooper(); + sMsgHandler = new MessageHandler(messageHandlerLooper); + } + + public static synchronized TaskManager getDefault(){ + if(sTaskManager == null){ + sTaskManager = new TaskManager(); + } + + return sTaskManager; + } + + public synchronized int generateTaskId(){ + return mTaskId++; + } + + public void putTask(int taskId, Task task){ + synchronized (mSyncObj){ + putTaskInternal(taskId, task); + } + } + + private synchronized void putTaskInternal(int taskId, Task task){ + Task sameKeyTask = mTaskMap.put(String.valueOf(taskId), task); + + logger.debug("Added Task: " + task + "Original same key task:" + sameKeyTask); + } + + public int addCapabilityTask(Context context, String[] contacts, + IRcsPresenceListener listener, long timeout){ + int taskId = TaskManager.getDefault().generateTaskId(); + synchronized (mSyncObj){ + Task task = new PresenceCapabilityTask(context, taskId, TASK_TYPE_GET_CAPABILITY, + listener, contacts, timeout); + putTaskInternal(taskId, task); + } + + return taskId; + } + + public int addAvailabilityTask(String contact, IRcsPresenceListener listener){ + int taskId = TaskManager.getDefault().generateTaskId(); + synchronized (mSyncObj){ + String[] contacts = new String[1]; + contacts[0] = contact; + Task task = new PresenceAvailabilityTask(taskId, TASK_TYPE_GET_AVAILABILITY, + listener, contacts); + putTaskInternal(taskId, task); + } + + return taskId; + } + + public int addPublishTask(String contact, IRcsPresenceListener listener){ + int taskId = TaskManager.getDefault().generateTaskId(); + synchronized (mSyncObj){ + String[] contacts = new String[1]; + contacts[0] = contact; + Task task = new PresenceTask(taskId, TASK_TYPE_PUBLISH, listener, contacts); + putTaskInternal(taskId, task); + } + + return taskId; + } + + // If need to call getTask in this class please add another one getTaskInternal + public Task getTask(int taskId){ + synchronized (mSyncObj){ + return mTaskMap.get(String.valueOf(taskId)); + } + } + + public void removeTask(int taskId){ + synchronized (mSyncObj){ + Task task = mTaskMap.remove(String.valueOf(taskId)); + if(task instanceof PresenceCapabilityTask){ + ((PresenceCapabilityTask)task).cancelTimer(); + } + logger.debug("Removed Task: " + task); + } + } + + public Task getTaskByRequestId(int sipRequestId){ + synchronized (mSyncObj){ + Set<String> keys= mTaskMap.keySet(); + if(keys == null){ + logger.debug("getTaskByRequestId keys=null"); + return null; + } + + for(String key:keys){ + if(mTaskMap.get(key).mSipRequestId == sipRequestId){ + logger.debug("getTaskByRequestId, sipRequestId=" + sipRequestId + + " task=" + mTaskMap.get(key)); + return mTaskMap.get(key); + } + } + } + + logger.debug("getTaskByRequestId, sipRequestId=" + sipRequestId + " task=null"); + return null; + } + + public void onTerminated(String contact){ // for single number capability polling + logger.debug("onTerminated contact=" + contact); + if(contact == null){ + return; + } + + synchronized (mSyncObj){ + Set<String> keys= mTaskMap.keySet(); + if(keys == null){ + logger.debug("onTerminated keys is null"); + return; + } + + for(String key:keys){ + Task task = mTaskMap.get(key); + if(task == null){ + continue; + } + + if(task instanceof PresenceCapabilityTask){ + PresenceCapabilityTask capabilityTask = (PresenceCapabilityTask)task; + if(capabilityTask.mContacts != null && capabilityTask.mContacts[0] != null && + PhoneNumberUtils.compare(contact, capabilityTask.mContacts[0])){ + if(!capabilityTask.isWaitingForNotify()){ + logger.debug("onTerminated the tesk is not waiting for NOTIFY yet"); + continue; + } + + MessageData messageData = new MessageData(); + messageData.mTask = capabilityTask; + messageData.mReason = null; + + Message notifyMessage = sMsgHandler.obtainMessage( + TASK_MANAGER_ON_TERMINATED, + messageData); + sMsgHandler.sendMessage(notifyMessage); + } + } + } + } + } + + public void onTerminated(int requestId, String reason){ + logger.debug("onTerminated requestId=" + requestId + " reason=" + reason); + + Task task = getTaskByRequestId(requestId); + if(task == null){ + logger.debug("onTerminated Can't find request " + requestId); + return; + } + + synchronized (mSyncObj){ + if(task instanceof PresenceCapabilityTask){ + MessageData messageData = new MessageData(); + messageData.mTask = (PresenceCapabilityTask)task; + messageData.mReason = reason; + + Message notifyMessage = sMsgHandler.obtainMessage(TASK_MANAGER_ON_TERMINATED, + messageData); + sMsgHandler.sendMessage(notifyMessage); + } + } + } + + public void onTimeout(int taskId){ + logger.debug("onTimeout taskId=" + taskId); + + Task task = getTask(taskId); + if(task == null){ + logger.debug("onTimeout task = null"); + return; + } + synchronized (mSyncObj){ + if(task instanceof PresenceCapabilityTask){ + MessageData messageData = new MessageData(); + messageData.mTask = (PresenceCapabilityTask)task; + messageData.mReason = null; + + Message timeoutMessage = sMsgHandler.obtainMessage(TASK_MANAGER_ON_TIMEOUT, + messageData); + sMsgHandler.sendMessage(timeoutMessage); + }else{ + logger.debug("not PresenceCapabilityTask, taskId=" + taskId); + } + } + } + + public class MessageData{ + public PresenceCapabilityTask mTask; + public String mReason; + } + + public class MessageHandler extends Handler{ + MessageHandler(Looper looper){ + super(looper); + } + + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + + logger.debug( "Thread=" + Thread.currentThread().getName() + " received " + + msg); + + + + if(msg == null){ + logger.error("msg=null"); + return; + } + + switch (msg.what) { + case TASK_MANAGER_ON_TERMINATED: + { + MessageData messageData = (MessageData) msg.obj; + if(messageData != null && messageData.mTask != null){ + messageData.mTask.onTerminated(messageData.mReason); + } + break; + } + + case TASK_MANAGER_ON_TIMEOUT: + { + MessageData messageData = (MessageData) msg.obj; + if(messageData != null && messageData.mTask != null){ + messageData.mTask.onTimeout(); + } + break; + } + + default: + logger.debug("handleMessage unknown msg=" + msg.what); + } + } + } + + public void clearTimeoutAvailabilityTask(long availabilityExpire) { + logger.debug("clearTimeoutAvailabilityTask"); + + synchronized (mSyncObj) { + long currentTime = System.currentTimeMillis(); + + Iterator<Map.Entry<String, Task>> iterator = mTaskMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry<String, Task> entry = iterator.next(); + + Task task = (Task) entry.getValue(); + logger.debug("Currently existing Availability task, key: " + entry.getKey() + + ", Task: " + task); + + if ((task != null) && (task instanceof PresenceAvailabilityTask)) { + PresenceAvailabilityTask presenceTask = (PresenceAvailabilityTask)task; + + long notifyTimestamp = presenceTask.getNotifyTimestamp(); + long createTimestamp = presenceTask.getCreateTimestamp(); + logger.debug("createTimestamp=" + createTimestamp + " notifyTimestamp=" + + notifyTimestamp + " currentTime=" + currentTime); + + // remove it if it didn't get notify in 60s. + // or get notify for 60s + if(((notifyTimestamp != 0) && + (notifyTimestamp + availabilityExpire < currentTime)) || + (notifyTimestamp == 0) && + (createTimestamp + availabilityExpire < currentTime)) { + logger.debug("remove expired availability task:" + presenceTask); + iterator.remove(); + } + } + } + } + } + + public PresenceAvailabilityTask getAvailabilityTaskByContact(String contact){ + synchronized (mSyncObj){ + Set<String> keys= mTaskMap.keySet(); + if(keys == null){ + logger.debug("getTaskByContact keys=null"); + return null; + } + + for(String key:keys){ + Task task = mTaskMap.get(key); + if(task == null){ + continue; + } + + if(task instanceof PresenceAvailabilityTask){ + PresenceAvailabilityTask availabilityTask = (PresenceAvailabilityTask)task; + if(PhoneNumberUtils.compare(contact, availabilityTask.mContacts[0])){ + logger.debug("getTaskByContact contact=" + contact + + " task=" + availabilityTask); + return availabilityTask; + } + } + } + } + + logger.debug("getTaskByContact, contact=" + contact + " task=null"); + return null; + } +} + diff --git a/rcs/rcsservice/src/com/android/service/ims/presence/AlarmBroadcastReceiver.java b/rcs/rcsservice/src/com/android/service/ims/presence/AlarmBroadcastReceiver.java new file mode 100644 index 0000000..f02f9ce --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/presence/AlarmBroadcastReceiver.java @@ -0,0 +1,82 @@ +/* + * 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.presence; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.android.ims.internal.Logger; + +import com.android.service.ims.RcsStackAdaptor; +import com.android.service.ims.TaskManager; + +public class AlarmBroadcastReceiver extends BroadcastReceiver{ + private Logger logger = Logger.getLogger(this.getClass().getName()); + + private static final String ACTION_RETRY_ALARM = + RcsStackAdaptor.ACTION_RETRY_ALARM; + private static final String ACTION_TASK_TIMEOUT_ALARM = + PresenceCapabilityTask.ACTION_TASK_TIMEOUT_ALARM; + private static final String ACTION_RETRY_PUBLISH_ALARM = + PresencePublication.ACTION_RETRY_PUBLISH_ALARM; + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + logger.info("onReceive intent: " + action); + + // this could happen when there is a crash and the previous broadcasted intent + // coming when the PresenceService is launching. + RcsStackAdaptor rcsStackAdaptor = RcsStackAdaptor.getInstance(null); + if(rcsStackAdaptor == null){ + logger.debug("rcsStackAdaptor=null"); + return; + } + + if(ACTION_RETRY_ALARM.equals(action)) { + int times = intent.getIntExtra("times", -1); + rcsStackAdaptor.startInitThread(times); + }else if(ACTION_TASK_TIMEOUT_ALARM.equals(action)){ + int taskId = intent.getIntExtra("taskId", -1); + TaskManager.getDefault().onTimeout(taskId); + } else if(ACTION_RETRY_PUBLISH_ALARM.equals(action)) { + // default retry is for 888 + int sipCode = intent.getIntExtra("sipCode", 888); + PresencePublication publication = PresencePublication.getPresencePublication(); + if(publication != null) { + publication.retryPublish(); + } + } + else{ + logger.debug("not interest in intent=" + intent); + } + } +}; + diff --git a/rcs/rcsservice/src/com/android/service/ims/presence/PresenceAvailabilityTask.java b/rcs/rcsservice/src/com/android/service/ims/presence/PresenceAvailabilityTask.java new file mode 100644 index 0000000..99c698f --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/presence/PresenceAvailabilityTask.java @@ -0,0 +1,92 @@ +/* + * 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.presence; + +import java.util.Set; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; +import android.content.Context; +import android.content.Intent; +import android.app.PendingIntent; +import android.content.IntentFilter; +import android.os.RemoteException; +import android.app.AlarmManager; +import android.os.SystemClock; + +import com.android.ims.internal.uce.presence.PresCmdStatus; + +import com.android.ims.internal.Logger; +import com.android.ims.RcsManager.ResultCode; +import com.android.ims.RcsPresenceInfo; +import com.android.ims.IRcsPresenceListener; + +import com.android.service.ims.TaskManager; + +/** + * PresenceAvailabilityTask + */ +public class PresenceAvailabilityTask extends PresenceTask{ + /* + * The logger + */ + private Logger logger = Logger.getLogger(this.getClass().getName()); + + private long mCreateTimestamp = 0; + + // Time when get the notify. Used to check the 60s expires. + private long mNotifyTimeStamp = 0; + + public PresenceAvailabilityTask(int taskId, int cmdId, IRcsPresenceListener listener, + String[] contacts){ + super(taskId, cmdId, listener, contacts); + + mCreateTimestamp = System.currentTimeMillis(); + mNotifyTimeStamp = 0; + } + + public void updateNotifyTimestamp() { + mNotifyTimeStamp = System.currentTimeMillis(); + logger.debug("updateNotifyTimestamp mNotifyTimeStamp=" + mNotifyTimeStamp); + } + + public long getNotifyTimestamp() { + return mNotifyTimeStamp; + } + + public long getCreateTimestamp() { + return mCreateTimestamp; + } + + public String toString(){ + return super.toString() + + " mNotifyTimeStamp=" + mNotifyTimeStamp; + } +}; + diff --git a/rcs/rcsservice/src/com/android/service/ims/presence/PresenceBase.java b/rcs/rcsservice/src/com/android/service/ims/presence/PresenceBase.java new file mode 100644 index 0000000..5f1dfe5 --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/presence/PresenceBase.java @@ -0,0 +1,184 @@ +/* + * 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.presence; + +import java.lang.String; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.os.RemoteException; +import android.content.Intent; +import com.android.internal.telephony.TelephonyIntents; +import android.content.ComponentName; + +import com.android.ims.internal.uce.common.StatusCode; +import com.android.ims.internal.uce.common.StatusCode; +import com.android.ims.internal.uce.presence.PresCmdStatus; +import com.android.ims.internal.uce.presence.PresResInfo; +import com.android.ims.internal.uce.presence.PresSipResponse; + +import com.android.ims.RcsManager.ResultCode; +import com.android.ims.RcsPresence.PublishState; +import com.android.ims.RcsPresenceInfo; + +import com.android.ims.internal.Logger; +import com.android.service.ims.Task; +import com.android.service.ims.RcsUtils; +import com.android.service.ims.TaskManager; + +public abstract class PresenceBase{ + /* + * The logger + */ + static private Logger logger = Logger.getLogger("PresenceBase"); + + protected Context mContext = null; + + public PresenceBase() { + } + + protected void handleCallback(Task task, int resultCode, boolean forCmdStatus){ + if(task == null){ + logger.debug("task == null"); + return; + } + + if(task.mListener != null){ + try{ + if(resultCode >= ResultCode.SUCCESS){ + if(!forCmdStatus){ + task.mListener.onSuccess(task.mTaskId); + } + }else{ + task.mListener.onError(task.mTaskId, resultCode); + } + }catch(RemoteException e){ + logger.debug("Failed to send the status to client."); + } + } + + // remove task when error + // remove task when SIP response success. + // For list capability polling we will waiting for the terminated notify or timeout. + if(resultCode != ResultCode.SUCCESS){ + if(task instanceof PresencePublishTask){ + PresencePublishTask publishTask = (PresencePublishTask) task; + logger.debug("handleCallback for publishTask=" + publishTask); + if(resultCode == PublishState.PUBLISH_STATE_VOLTE_PROVISION_ERROR) { + // retry 3 times for "403 Not Authorized for Presence". + if(publishTask.getRetryCount() >= 3) { + //remove capability after try 3 times by PresencePolling + logger.debug("handleCallback remove task=" + task); + TaskManager.getDefault().removeTask(task.mTaskId); + } else { + // Continue retry + publishTask.setRetryCount(publishTask.getRetryCount() + 1); + } + } else { + logger.debug("handleCallback remove task=" + task); + TaskManager.getDefault().removeTask(task.mTaskId); + } + } else { + logger.debug("handleCallback remove task=" + task); + TaskManager.getDefault().removeTask(task.mTaskId); + } + }else{ + if(forCmdStatus || !forCmdStatus && (task instanceof PresenceCapabilityTask)){ + logger.debug("handleCallback remove task later"); + + //waiting for Notify from network + if(!forCmdStatus){ + ((PresenceCapabilityTask)task).setWaitingForNotify(true); + } + }else{ + if(!forCmdStatus && (task instanceof PresenceAvailabilityTask) && + (resultCode == ResultCode.SUCCESS)){ + // Availiablity, cache for 60s, remove it later. + logger.debug("handleCallback PresenceAvailabilityTask cache for 60s task=" + + task); + return; + } + + logger.debug("handleCallback remove task=" + task); + TaskManager.getDefault().removeTask(task.mTaskId); + } + } + } + + public void handleCmdStatus(PresCmdStatus pCmdStatus){ + if(pCmdStatus == null){ + logger.error("handleCallbackForCmdStatus pCmdStatus=null"); + return; + } + + Task task = TaskManager.getDefault().getTask(pCmdStatus.getUserData()); + int resultCode = RcsUtils.statusCodeToResultCode(pCmdStatus.getStatus().getStatusCode()); + if(task != null){ + task.mSipRequestId = pCmdStatus.getRequestId(); + task.mCmdStatus = resultCode; + TaskManager.getDefault().putTask(task.mTaskId, task); + } + + handleCallback(task, resultCode, true); + } + + protected void notifyDm() { + logger.debug("notifyDm"); + Intent intent = new Intent( + TelephonyIntents.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + + mContext.sendBroadcast(intent); + } + + protected boolean isInConfigList(int errorNo, String phrase, int configId) { + String inErrorString = ("" + errorNo).trim(); + + String[] errorArray = mContext.getResources().getStringArray(configId); + logger.debug("errorArray length=" + errorArray.length + " errorArray=" + errorArray); + for (String errorStr : errorArray) { + if (errorStr != null && errorStr.startsWith(inErrorString)) { + String errorPhrase = errorStr.substring(inErrorString.length()); + if(errorPhrase == null || errorPhrase.isEmpty()) { + return true; + } + + if(phrase == null || phrase.isEmpty()) { + return false; + } + + return phrase.toLowerCase().contains(errorPhrase.toLowerCase()); + } + } + return false; + } + + abstract public void handleSipResponse(PresSipResponse pSipResponse); +} + diff --git a/rcs/rcsservice/src/com/android/service/ims/presence/PresenceCapabilityTask.java b/rcs/rcsservice/src/com/android/service/ims/presence/PresenceCapabilityTask.java new file mode 100644 index 0000000..8dbc120 --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/presence/PresenceCapabilityTask.java @@ -0,0 +1,183 @@ +/* + * 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.presence; + +import java.util.Set; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; +import android.content.Context; +import android.content.Intent; +import android.app.PendingIntent; +import android.content.IntentFilter; +import android.os.RemoteException; +import android.app.AlarmManager; +import android.os.SystemClock; + +import com.android.ims.internal.uce.presence.PresCmdStatus; + +import com.android.ims.RcsManager.ResultCode; +import com.android.ims.RcsPresenceInfo; +import com.android.ims.IRcsPresenceListener; + +import com.android.ims.internal.Logger; +import com.android.service.ims.TaskManager; + +/** + * PresenceCapabilityTask + */ +public class PresenceCapabilityTask extends PresenceTask{ + /* + * The logger + */ + private Logger logger = Logger.getLogger(this.getClass().getName()); + + public static final String ACTION_TASK_TIMEOUT_ALARM = + "com.android.service.ims.presence.task.timeout"; + + private Context mContext = null; + + // The result code will be used for retry. + public int mResultCode; + + // The alarm manager. + static AlarmManager sAlarmManager = null; + PendingIntent mAlarmIntent = null; + boolean mTimerStarted = false; + + // it will be set to true after got sip response. + public boolean mWaitingForNotify; + + // The time when created the task. + private long mCreatedTimeStamp; + + private long mTimeout; + + public PresenceCapabilityTask(Context context, int taskId, int cmdId, + IRcsPresenceListener listener, String[] contacts, + long timeout){ + super(taskId, cmdId, listener, contacts); + mContext = context; + mWaitingForNotify = false; + + mCreatedTimeStamp = System.currentTimeMillis(); + mTimeout = timeout; + + if(mTimeout <=0){ + // The terminal notification may be received shortly after the time limit of + // the subscription due to network delays or retransmissions. + // Device shall wait for 3sec after the end of the subscription period in order to + // accept such notifications without returning spurious errors (e.g. SIP 481). + mTimeout = 36000; + } + + if(listener != null){ + startTimer(); + } //else it will be removed after got sip response. + } + + public String toString(){ + return super.toString() + + " mCreatedTimeStamp=" + mCreatedTimeStamp + + " mTimeout=" + mTimeout; + } + + private void startTimer(){ + if(mContext == null){ + logger.error("startTimer mContext is null"); + return; + } + + Intent intent = new Intent(ACTION_TASK_TIMEOUT_ALARM); + intent.setClass(mContext, AlarmBroadcastReceiver.class); + intent.putExtra("taskId", mTaskId); + PendingIntent mAlarmIntent = PendingIntent.getBroadcast(mContext, 0, intent, + PendingIntent.FLAG_ONE_SHOT); + + if(sAlarmManager == null){ + sAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); + } + + long triggerAt = SystemClock.elapsedRealtime() + mTimeout; + logger.debug("startTimer taskId=" + mTaskId + " mTimeout=" + mTimeout + + " triggerAt=" + triggerAt + " mAlarmIntent=" + mAlarmIntent); + sAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAt, mAlarmIntent); + mTimerStarted = true; + } + + public void cancelTimer(){ + if(mTimerStarted){ + logger.debug("cancelTimer, taskId=" + mTaskId); + if(mAlarmIntent != null && sAlarmManager != null) { + sAlarmManager.cancel(mAlarmIntent); + } + mTimerStarted = false; + } + } + + public void onTimeout(){ + logger.debug("onTimeout, taskId=" + mTaskId); + try{ + if(mListener != null){ + mListener.onTimeout(mTaskId); + } + }catch (RemoteException e){ + logger.error("RemoteException", e); + } + + TaskManager.getDefault().removeTask(mTaskId); + } + + public void setWaitingForNotify(boolean waitingForNotify){ + mWaitingForNotify = waitingForNotify; + } + + public boolean isWaitingForNotify(){ + return mWaitingForNotify; + } + + public void onTerminated(String reason){ + if(!mWaitingForNotify){ + logger.debug("onTerminated mWaitingForNotify is false. task=" + this); + return; + } + + cancelTimer(); + try{ + if(mListener != null){ + mListener.onFinish(mTaskId); + } + }catch (RemoteException e){ + logger.error("RemoteException", e); + } + + TaskManager.getDefault().removeTask(mTaskId); + } +}; + diff --git a/rcs/rcsservice/src/com/android/service/ims/presence/PresenceInfoParser.java b/rcs/rcsservice/src/com/android/service/ims/presence/PresenceInfoParser.java new file mode 100644 index 0000000..0e8f2d3 --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/presence/PresenceInfoParser.java @@ -0,0 +1,277 @@ +/* + * 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.presence; + +import java.lang.String; +import java.util.ArrayList; +import java.util.List; +import android.text.TextUtils; + +import com.android.ims.internal.uce.presence.PresTupleInfo; +import com.android.ims.internal.uce.presence.PresRlmiInfo; +import com.android.ims.internal.uce.presence.PresResInfo; +import com.android.ims.internal.uce.presence.PresResInstanceInfo; + +import com.android.ims.RcsManager.ResultCode; +import com.android.ims.RcsPresenceInfo; +import com.android.ims.RcsPresenceInfo.ServiceType; +import com.android.ims.RcsPresenceInfo.ServiceState; + +import com.android.ims.internal.Logger; + +public class PresenceInfoParser{ + /* + * The logger + */ + static private Logger logger = Logger.getLogger("PresenceInfoParser"); + + public PresenceInfoParser() { + } + + static public RcsPresenceInfo getPresenceInfoFromTuple(String pPresentityURI, + PresTupleInfo[] pTupleInfo){ + logger.debug("getPresenceInfoFromTuple: pPresentityURI=" + pPresentityURI + + " pTupleInfo=" + pTupleInfo); + + if(pPresentityURI == null){ + logger.error("pPresentityURI=" + pPresentityURI); + return null; + } + + String contactNumber = getPhoneFromUri(pPresentityURI); + // Now that we got notification if the content didn't indicate it is not Volte + // then it should be Volte enabled. So default to Volte enabled. + int volteStatus = RcsPresenceInfo.VolteStatus.VOLTE_ENABLED; + + int ipVoiceCallState = RcsPresenceInfo.ServiceState.UNKNOWN; + String ipVoiceCallServiceNumber = null; + // We use the timestamp which when we received it instead of the one in PDU + long ipVoiceCallTimestamp = System.currentTimeMillis(); + + int ipVideoCallState = RcsPresenceInfo.ServiceState.UNKNOWN; + String ipVideoCallServiceNumber = null; + long ipVideoCallTimestamp = System.currentTimeMillis(); + + if( pTupleInfo == null){ + logger.debug("pTupleInfo=null"); + return (new RcsPresenceInfo(contactNumber, volteStatus, + ipVoiceCallState, ipVoiceCallServiceNumber, ipVoiceCallTimestamp, + ipVideoCallState, ipVideoCallServiceNumber, ipVideoCallTimestamp)); + } + + for(int i = 0; i < pTupleInfo.length; i++){ + // Video call has high priority. If it supports video call it will support voice call. + String featureTag = pTupleInfo[i].getFeatureTag(); + logger.debug("getFeatureTag " + i + ": " + featureTag); + if(featureTag.equals( + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\";video") || + featureTag.equals("+g.gsma.rcs.telephony=\"cs,volte\";video")) { + logger.debug("Got Volte and VT enabled tuple"); + ipVoiceCallState = RcsPresenceInfo.ServiceState.ONLINE; + ipVideoCallState = RcsPresenceInfo.ServiceState.ONLINE; + + if(pTupleInfo[i].getContactUri() != null){ + ipVideoCallServiceNumber = getPhoneFromUri( + pTupleInfo[i].getContactUri().toString()); + } + } else if(featureTag.equals("+g.gsma.rcs.telephony=\"cs\";video")) { + logger.debug("Got Volte disabled but VT enabled tuple"); + ipVoiceCallState = RcsPresenceInfo.ServiceState.OFFLINE; + ipVideoCallState = RcsPresenceInfo.ServiceState.ONLINE; + + if(pTupleInfo[i].getContactUri() != null){ + ipVideoCallServiceNumber = getPhoneFromUri( + pTupleInfo[i].getContactUri().toString()); + } + }else{ + if(featureTag.equals( + "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.mmtel\"") || + featureTag.equals("+g.gsma.rcs.telephony=\"cs,volte\"")) { + + logger.debug("Got Volte only tuple"); + ipVoiceCallState = RcsPresenceInfo.ServiceState.ONLINE; + // "OR" for multiple tuples. + if(RcsPresenceInfo.ServiceState.UNKNOWN == ipVideoCallState) { + ipVideoCallState = RcsPresenceInfo.ServiceState.OFFLINE; + } + + if(pTupleInfo[i].getContactUri() != null){ + ipVoiceCallServiceNumber = getPhoneFromUri( + pTupleInfo[i].getContactUri().toString()); + } + }else{ + logger.debug("Ignoring feature tag: " + pTupleInfo[i].getFeatureTag()); + } + } + } + + RcsPresenceInfo retPresenceInfo = new RcsPresenceInfo(contactNumber,volteStatus, + ipVoiceCallState, ipVoiceCallServiceNumber, ipVoiceCallTimestamp, + ipVideoCallState, ipVideoCallServiceNumber, ipVideoCallTimestamp); + + logger.debug("getPresenceInfoFromTuple: " + retPresenceInfo); + + return retPresenceInfo; + } + + static private void addPresenceInfo(ArrayList<RcsPresenceInfo> presenceInfoList, + RcsPresenceInfo presenceInfo){ + logger.debug("addPresenceInfo presenceInfoList=" + presenceInfoList + + " presenceInfo=" + presenceInfo); + + if(presenceInfoList == null || presenceInfo == null){ + logger.debug("addPresenceInfo presenceInfoList=" + presenceInfoList + + " presenceInfo=" + presenceInfo); + return; + } + + for(int i=0; i< presenceInfoList.size(); i++){ + RcsPresenceInfo presenceInfoTmp = presenceInfoList.get(i); + if((presenceInfoTmp != null) && (presenceInfoTmp.getContactNumber() != null) && + presenceInfoTmp.getContactNumber().equalsIgnoreCase( + presenceInfo.getContactNumber())){ + // merge the information + presenceInfoList.set(i, new RcsPresenceInfo(presenceInfoTmp.getContactNumber(), + presenceInfoTmp.getVolteStatus(), + ((ServiceState.ONLINE == presenceInfo.getServiceState( + ServiceType.VOLTE_CALL)) || + (ServiceState.ONLINE == presenceInfoTmp.getServiceState( + ServiceType.VOLTE_CALL)))?ServiceState.ONLINE: + presenceInfoTmp.getServiceState(ServiceType.VOLTE_CALL), + presenceInfoTmp.getServiceContact(ServiceType.VOLTE_CALL), + presenceInfoTmp.getTimeStamp(ServiceType.VOLTE_CALL), + ((ServiceState.ONLINE == presenceInfo.getServiceState( + ServiceType.VT_CALL)) || + (ServiceState.ONLINE == presenceInfoTmp.getServiceState( + ServiceType.VT_CALL)))?ServiceState.ONLINE: + presenceInfoTmp.getServiceState(ServiceType.VT_CALL), + presenceInfoTmp.getServiceContact(ServiceType.VOLTE_CALL), + presenceInfoTmp.getTimeStamp(ServiceType.VOLTE_CALL))); + return; + } + } + + // didn't merge, so add the new one. + presenceInfoList.add(presenceInfo); + } + + static public RcsPresenceInfo[] getPresenceInfosFromPresenceRes( + PresRlmiInfo pRlmiInfo, PresResInfo[] pRcsPresenceInfo) { + if(pRcsPresenceInfo == null){ + logger.debug("getPresenceInfosFromPresenceRes pRcsPresenceInfo=null"); + return null; + } + + ArrayList<RcsPresenceInfo> presenceInfoList = new ArrayList<RcsPresenceInfo>(); + for(int i=0; i < pRcsPresenceInfo.length; i++ ) { + if(pRcsPresenceInfo[i].getInstanceInfo() == null){ + logger.error("invalid data getInstanceInfo = null"); + continue; + } + + String resUri = pRcsPresenceInfo[i].getResUri(); + if(resUri == null){ + logger.error("invalid data getResUri = null"); + continue; + } + + String contactNumber = getPhoneFromUri(resUri); + if(contactNumber == null){ + logger.error("invalid data contactNumber=null"); + continue; + } + + if(pRcsPresenceInfo[i].getInstanceInfo().getResInstanceState() == + PresResInstanceInfo.UCE_PRES_RES_INSTANCE_STATE_TERMINATED){ + logger.debug("instatance state is terminated"); + String reason = pRcsPresenceInfo[i].getInstanceInfo().getReason(); + if(reason != null){ + reason = reason.toLowerCase(); + // Noresource: 404. Device shall consider the resource as non-EAB capable + // Rejected: 403. Device shall consider the resource as non-EAB capable + // Deactivated: 408, 481, 603, other 4xx, 5xx 6xx. Ignore. + // Give Up: 480. Ignore. + // Probation: 503. Ignore. + if(reason.equals("rejected") || reason.equals("noresource")){ + RcsPresenceInfo presenceInfo = new RcsPresenceInfo(contactNumber, + RcsPresenceInfo.VolteStatus.VOLTE_DISABLED, + RcsPresenceInfo.ServiceState.OFFLINE, null, + System.currentTimeMillis(), + RcsPresenceInfo.ServiceState.OFFLINE, null, + System.currentTimeMillis()); + addPresenceInfo(presenceInfoList, presenceInfo); + logger.debug("reason=" + reason + " presenceInfo=" + presenceInfo); + continue; + } + } + } + + RcsPresenceInfo presenceInfo = getPresenceInfoFromTuple(resUri, + pRcsPresenceInfo[i].getInstanceInfo().getTupleInfo()); + if(presenceInfo != null){ + addPresenceInfo(presenceInfoList, presenceInfo); + }else{ + logger.debug("presenceInfo["+ i + "] = null"); + addPresenceInfo(presenceInfoList, getPresenceInfoFromTuple(resUri, null)); + } + } + + logger.debug("getPresenceInfoFromPresenceRes, presenceInfos:" + presenceInfoList); + if(presenceInfoList.size() == 0){ + return null; + } + + RcsPresenceInfo[] theArray = new RcsPresenceInfo[presenceInfoList.size()]; + return (RcsPresenceInfo[])presenceInfoList.toArray(theArray); + } + + static public String getPhoneFromUri(String uriValue) { + if(uriValue == null){ + return null; + } + + int startIndex = uriValue.indexOf(":", 0); + int endIndex = uriValue.indexOf("@", startIndex); + + logger.debug("getPhoneFromUri uriValue=" + uriValue + + " startIndex=" + startIndex + " endIndex=" + endIndex); + + String number = uriValue; + if(endIndex != -1){ + number = uriValue.substring(0, endIndex); + } + + if (startIndex != -1) { + return number = number.substring(startIndex + 1); + } + + logger.debug("number=" + number); + return number; + } +} diff --git a/rcs/rcsservice/src/com/android/service/ims/presence/PresencePublication.java b/rcs/rcsservice/src/com/android/service/ims/presence/PresencePublication.java new file mode 100644 index 0000000..05dcd7a --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/presence/PresencePublication.java @@ -0,0 +1,1227 @@ +/* + * 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.presence; + +import java.util.List; +import java.util.Arrays; + +import android.content.Context; +import android.content.Intent; +import com.android.internal.telephony.Phone; +import android.provider.Settings; +import android.os.SystemProperties; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.telephony.TelephonyManager; +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.telephony.TelephonyIntents; +import android.telecom.TelecomManager; +import android.content.IntentFilter; +import android.app.PendingIntent; +import android.app.AlarmManager; +import android.os.SystemClock; +import android.os.Message; +import android.os.Handler; + + +import com.android.ims.ImsManager; +import com.android.ims.ImsConnectionStateListener; +import com.android.ims.ImsServiceClass; +import com.android.ims.ImsException; +import android.telephony.SubscriptionManager; +import com.android.ims.ImsConfig; +import com.android.ims.ImsConfig.FeatureConstants; +import com.android.ims.ImsConfig.FeatureValueConstants; + +import com.android.service.ims.RcsSettingUtils; +import com.android.ims.RcsPresenceInfo; +import com.android.ims.IRcsPresenceListener; +import com.android.ims.RcsManager.ResultCode; +import com.android.ims.RcsPresence.PublishState; + +import com.android.ims.internal.Logger; +import com.android.service.ims.TaskManager; +import com.android.service.ims.Task; + +import com.android.ims.internal.uce.presence.PresPublishTriggerType; +import com.android.ims.internal.uce.presence.PresSipResponse; +import com.android.ims.internal.uce.common.StatusCode; +import com.android.ims.internal.uce.presence.PresCmdStatus; + +import com.android.service.ims.RcsStackAdaptor; + +import com.android.service.ims.R; + +public class PresencePublication extends PresenceBase { + private Logger logger = Logger.getLogger(this.getClass().getName()); + + private final Object mSyncObj = new Object(); + + final static String ACTION_IMS_FEATURE_AVAILABLE = + "com.android.service.ims.presence.ims-feature-status-changed"; + private static final int INVALID_SERVICE_ID = -1; + + boolean mMovedToIWLAN = false; + boolean mMovedToLTE = false; + boolean mVoPSEnabled = false; + + boolean mIsVolteAvailable = false; + boolean mIsVtAvailable = false; + boolean mIsVoWifiAvailable = false; + boolean mIsViWifiAvailable = false; + + // Queue for the pending PUBLISH request. Just need cache the last one. + volatile PublishRequest mPendingRequest = null; + volatile PublishRequest mPublishingRequest = null; + volatile PublishRequest mPublishedRequest = null; + + /* + * Message to notify for a publish + */ + public static final int MESSAGE_RCS_PUBLISH_REQUEST = 1; + + /** + * Publisher Error base + */ + public static final int PUBLISH_ERROR_CODE_START = ResultCode.SUBSCRIBER_ERROR_CODE_END; + + /** + * All publish errors not covered by specific errors + */ + public static final int PUBLISH_GENIRIC_FAILURE = PUBLISH_ERROR_CODE_START - 1; + + /** + * Responding for 403 - not authorized + */ + public static final int PUBLISH_NOT_AUTHORIZED_FOR_PRESENCE = PUBLISH_ERROR_CODE_START - 2; + + /** + * Responding for 404 error code. The subscriber is not provisioned. + * The Client should not send any EAB traffic after get this error. + */ + public static final int PUBLISH_NOT_PROVISIONED = PUBLISH_ERROR_CODE_START - 3; + + /** + * Responding for 200 - OK + */ + public static final int PUBLISH_SUCESS = ResultCode.SUCCESS; + + private RcsStackAdaptor mRcsStackAdaptor = null; + private PresenceSubscriber mSubscriber = null; + static private PresencePublication sPresencePublication = null; + private BroadcastReceiver mReceiver = null; + + private boolean mHasCachedTrigger = false; + private boolean mGotTriggerFromStack = false; + private boolean mDonotRetryUntilPowerCycle = false; + private boolean mSimLoaded = false; + private int mPreferredTtyMode = Phone.TTY_MODE_OFF; + + private boolean mImsRegistered = false; + private boolean mVtEnabled = false; + private boolean mDataEnabled = false; + + public class PublishType{ + public static final int PRES_PUBLISH_TRIGGER_DATA_CHANGED = 0; + // the lower layer should send trigger when enable volte + // the lower layer should unpublish when disable volte + // so only handle VT here. + public static final int PRES_PUBLISH_TRIGGER_VTCALL_CHANGED = 1; + public static final int PRES_PUBLISH_TRIGGER_CACHED_TRIGGER = 2; + public static final int PRES_PUBLISH_TRIGGER_TTY_ENABLE_STATUS = 3; + public static final int PRES_PUBLISH_TRIGGER_RETRY = 4; + public static final int PRES_PUBLISH_TRIGGER_FEATURE_AVAILABILITY_CHANGED = 5; + }; + + /** + * @param rcsStackAdaptor + * @param context + */ + public PresencePublication(RcsStackAdaptor rcsStackAdaptor, Context context) { + super(); + logger.debug("PresencePublication constrcuct"); + this.mRcsStackAdaptor = rcsStackAdaptor; + this.mContext = context; + + mVtEnabled = ImsManager.isVtEnabledByUser(mContext); + mDataEnabled = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.MOBILE_DATA, 1) == 1; + mPreferredTtyMode = Settings.Secure.getInt( + mContext.getContentResolver(), + Settings.Secure.PREFERRED_TTY_MODE, + Phone.TTY_MODE_OFF); + logger.debug("The current TTY mode is: " + mPreferredTtyMode); + + if(mRcsStackAdaptor != null){ + mRcsStackAdaptor.setPublishState( + SystemProperties.getInt("rcs.publish.status", + PublishState.PUBLISH_STATE_NOT_PUBLISHED)); + } + + mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + logger.print("onReceive intent=" + intent); + if(TelephonyIntents.ACTION_SIM_STATE_CHANGED.equalsIgnoreCase(intent.getAction())){ + String stateExtra = intent.getStringExtra( + IccCardConstants.INTENT_KEY_ICC_STATE); + logger.print("ACTION_SIM_STATE_CHANGED INTENT_KEY_ICC_STATE=" + stateExtra); + final TelephonyManager teleMgr = (TelephonyManager) context.getSystemService( + Context.TELEPHONY_SERVICE); + if(IccCardConstants.INTENT_VALUE_ICC_LOADED.equalsIgnoreCase(stateExtra)) { + new Thread(new Runnable() { + @Override + public void run() { + if(teleMgr == null){ + logger.error("teleMgr = null"); + return; + } + + int count = 0; + // retry 2 minutes to get the information from ISIM and RUIM + for(int i=0; i<60; i++) { + String[] myImpu = teleMgr.getIsimImpu(); + String myDomain = teleMgr.getIsimDomain(); + String line1Number = teleMgr.getLine1Number(); + logger.debug("myImpu=" + myImpu + " myDomain=" + myDomain + + " line1Number=" + line1Number); + if(line1Number != null && line1Number.length() != 0 || + myImpu != null && myImpu.length != 0 && + myDomain != null && myDomain.length() != 0){ + mSimLoaded = true; + // treate hot SIM hot swap as power on. + mDonotRetryUntilPowerCycle = false; + if(mHasCachedTrigger) { + invokePublish(PresencePublication.PublishType. + PRES_PUBLISH_TRIGGER_CACHED_TRIGGER); + } + break; + }else{ + try{ + Thread.sleep(2000);//retry 2 seconds later + }catch(InterruptedException e){ + } + } + } + } + }, "wait for ISIM and RUIM").start(); + }else if (IccCardConstants.INTENT_VALUE_ICC_ABSENT. + equalsIgnoreCase(stateExtra)) { + // pulled out the SIM, set it as the same as power on status: + logger.print("Pulled out SIM, set to PUBLISH_STATE_NOT_PUBLISHED"); + + // only reset when the SIM gets absent. + reset(); + mSimLoaded = false; + setPublishState( + PublishState.PUBLISH_STATE_NOT_PUBLISHED); + } + }else if(Intent.ACTION_AIRPLANE_MODE_CHANGED.equalsIgnoreCase(intent.getAction())){ + boolean airplaneMode = intent.getBooleanExtra("state", false); + if(airplaneMode){ + logger.print("Airplane mode, set to PUBLISH_STATE_NOT_PUBLISHED"); + reset(); + setPublishState( + PublishState.PUBLISH_STATE_NOT_PUBLISHED); + } + }else if(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED.equalsIgnoreCase( + intent.getAction())){ + int newPreferredTtyMode = intent.getIntExtra( + TelecomManager.EXTRA_TTY_PREFERRED_MODE, + TelecomManager.TTY_MODE_OFF); + newPreferredTtyMode = telecomTtyModeToPhoneMode(newPreferredTtyMode); + logger.debug("Tty mode changed from " + mPreferredTtyMode + + " to " + newPreferredTtyMode); + + boolean mIsTtyEnabled = isTtyEnabled(mPreferredTtyMode); + boolean isTtyEnabled = isTtyEnabled(newPreferredTtyMode); + mPreferredTtyMode = newPreferredTtyMode; + if (mIsTtyEnabled != isTtyEnabled) { + logger.print("ttyEnabled status changed from " + mIsTtyEnabled + + " to " + isTtyEnabled); + invokePublish(PresencePublication.PublishType. + PRES_PUBLISH_TRIGGER_TTY_ENABLE_STATUS); + } + } else if(ImsConfig.ACTION_IMS_FEATURE_CHANGED.equalsIgnoreCase( + intent.getAction())){ + handleProvisionChanged(); + } + } + }; + + IntentFilter statusFilter = new IntentFilter(); + statusFilter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + statusFilter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); + statusFilter.addAction(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED); + statusFilter.addAction(ImsConfig.ACTION_IMS_FEATURE_CHANGED); + mContext.registerReceiver(mReceiver, statusFilter); + + sPresencePublication = this; + } + + private boolean isIPVoiceSupported(boolean volteAvailable, boolean vtAvailable, + boolean voWifiAvailable, boolean viWifiAvailable) { + // volte and vowifi can be enabled separately + if(!ImsManager.isVolteEnabledByPlatform(mContext) && + !ImsManager.isWfcEnabledByPlatform(mContext)) { + logger.print("Disabled by platform, voiceSupported=false"); + return false; + } + + if(!ImsManager.isVolteProvisionedOnDevice(mContext) && + !RcsSettingUtils.isVowifiProvisioned(mContext)) { + logger.print("Wasn't provisioned, voiceSupported=false"); + return false; + } + + if(!ImsManager.isEnhanced4gLteModeSettingEnabledByUser(mContext) && + !ImsManager.isWfcEnabledByUser(mContext)){ + logger.print("User didn't enable volte or wfc, voiceSupported=false"); + return false; + } + + // for IWLAN. All WFC settings and provision should be fine if voWifiAvailable is true. + if(isOnIWLAN()) { + boolean voiceSupported=volteAvailable || voWifiAvailable; + logger.print("on IWLAN, voiceSupported=" + voiceSupported); + return voiceSupported; + } + + // for none-IWLAN + if(!isOnLTE()) { + logger.print("isOnLTE=false, voiceSupported=false"); + return false; + } + + if(!mVoPSEnabled) { + logger.print("mVoPSEnabled=false, voiceSupported=false"); + return false; + } + + logger.print("voiceSupported=true"); + return true; + } + + private boolean isIPVideoSupported(boolean volteAvailable, boolean vtAvailable, + boolean voWifiAvailable, boolean viWifiAvailable) { + // if volte or vt was disabled then the viwifi will be disabled as well. + if(!ImsManager.isVolteEnabledByPlatform(mContext) || + !ImsManager.isVtEnabledByPlatform(mContext)) { + logger.print("Disabled by platform, videoSupported=false"); + return false; + } + + if(!ImsManager.isVolteProvisionedOnDevice(mContext) || + !RcsSettingUtils.isLvcProvisioned(mContext)) { + logger.print("Not provisioned. videoSupported=false"); + return false; + } + + if(!ImsManager.isEnhanced4gLteModeSettingEnabledByUser(mContext) || + !mVtEnabled){ + logger.print("User disabled volte or vt, videoSupported=false"); + return false; + } + + if(isTtyOn()){ + logger.print("isTtyOn=true, videoSupported=false"); + return false; + } + + // for IWLAN, all WFC settings and provision should be fine if viWifiAvailable is true. + if(isOnIWLAN()) { + boolean videoSupported = vtAvailable || viWifiAvailable; + logger.print("on IWLAN, videoSupported=" + videoSupported); + return videoSupported; + } + + if(!isDataEnabled()) { + logger.print("isDataEnabled()=false, videoSupported=false"); + return false; + } + + if(!isOnLTE()) { + logger.print("isOnLTE=false, videoSupported=false"); + return false; + } + + if(!mVoPSEnabled) { + logger.print("mVoPSEnabled=false, videoSupported=false"); + return false; + } + + return true; + } + + public boolean isTtyOn() { + logger.debug("isTtyOn settingsTtyMode=" + mPreferredTtyMode); + return isTtyEnabled(mPreferredTtyMode); + } + + public void onImsConnected() { + mImsRegistered = true; + } + + public void onImsDisconnected() { + logger.debug("reset PUBLISH status for IMS had been disconnected"); + mImsRegistered = false; + reset(); + } + + private void reset() { + mIsVolteAvailable = false; + mIsVtAvailable = false; + mIsVoWifiAvailable = false; + mIsViWifiAvailable = false; + + synchronized(mSyncObj) { + mPendingRequest = null; //ignore the previous request + mPublishingRequest = null; + mPublishedRequest = null; + } + } + + public void handleImsServiceUp() { + reset(); + } + + public void handleImsServiceDown() { + } + + private void handleProvisionChanged() { + if(RcsSettingUtils.isEabProvisioned(mContext)) { + logger.debug("provisioned, set mDonotRetryUntilPowerCycle to false"); + mDonotRetryUntilPowerCycle = false; + if(mHasCachedTrigger) { + invokePublish(PresencePublication.PublishType.PRES_PUBLISH_TRIGGER_CACHED_TRIGGER); + } + } + } + + static public PresencePublication getPresencePublication() { + return sPresencePublication; + } + + public void setSubscriber(PresenceSubscriber subscriber) { + mSubscriber = subscriber; + } + + public boolean isDataEnabled() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.MOBILE_DATA, 1) == 1; + } + + public void onMobileDataChanged(boolean value){ + logger.print("onMobileDataChanged, mDataEnabled=" + mDataEnabled + " value=" + value); + if(mDataEnabled != value) { + mDataEnabled = value; + RcsSettingUtils.setMobileDataEnabled(mContext, mDataEnabled); + + invokePublish( + PresencePublication.PublishType.PRES_PUBLISH_TRIGGER_DATA_CHANGED); + } + } + + public void onVtEnabled(boolean enabled) { + logger.debug("onVtEnabled mVtEnabled=" + mVtEnabled + " enabled=" + enabled); + + if(mVtEnabled != enabled) { + mVtEnabled = enabled; + invokePublish(PresencePublication.PublishType. + PRES_PUBLISH_TRIGGER_VTCALL_CHANGED); + } + } + + /** + * @return return true if it had been PUBLISHED. + */ + public boolean isPublishedOrPublishing() { + long publishThreshold = RcsSettingUtils.getPublishThrottle(mContext); + + boolean publishing = false; + publishing = (mPublishingRequest != null) && + (System.currentTimeMillis() - mPublishingRequest.getTimestamp() + <= publishThreshold); + + return (getPublishState() == PublishState.PUBLISH_STATE_200_OK || publishing); + } + + /** + * @return the Publish State + */ + public int getPublishState() { + if(mRcsStackAdaptor == null){ + return PublishState.PUBLISH_STATE_NOT_PUBLISHED; + } + + return mRcsStackAdaptor.getPublishState(); + } + + /** + * @param mPublishState the publishState to set + */ + public void setPublishState(int publishState) { + if(mRcsStackAdaptor != null){ + mRcsStackAdaptor.setPublishState(publishState); + } + } + + public boolean getHasCachedTrigger(){ + return mHasCachedTrigger; + } + + // Tiggered by framework. + public int invokePublish(int trigger){ + int ret; + + // if the value is true then it will call stack to send the PUBLISH + // though the previous publish had the same capability + // for example: for retry PUBLISH. + boolean bForceToNetwork = true; + switch(trigger) + { + case PublishType.PRES_PUBLISH_TRIGGER_DATA_CHANGED: + { + logger.print("PRES_PUBLISH_TRIGGER_DATA_CHANGED"); + bForceToNetwork = false; + break; + } + case PublishType.PRES_PUBLISH_TRIGGER_VTCALL_CHANGED: + { + logger.print("PRES_PUBLISH_TRIGGER_VTCALL_CHANGED"); + // Didn't get featureCapabilityChanged sometimes. + bForceToNetwork = true; + break; + } + case PublishType.PRES_PUBLISH_TRIGGER_CACHED_TRIGGER: + { + logger.print("PRES_PUBLISH_TRIGGER_CACHED_TRIGGER"); + break; + } + case PublishType.PRES_PUBLISH_TRIGGER_TTY_ENABLE_STATUS: + { + logger.print("PRES_PUBLISH_TRIGGER_TTY_ENABLE_STATUS"); + bForceToNetwork = true; + + break; + } + case PublishType.PRES_PUBLISH_TRIGGER_FEATURE_AVAILABILITY_CHANGED: + { + bForceToNetwork = false; + logger.print("PRES_PUBLISH_TRIGGER_FEATURE_AVAILABILITY_CHANGED"); + break; + } + case PublishType.PRES_PUBLISH_TRIGGER_RETRY: + { + logger.print("PRES_PUBLISH_TRIGGER_RETRY"); + break; + } + default: + { + logger.print("Unknown publish trigger from AP"); + } + } + + if(mGotTriggerFromStack == false){ + // Waiting for RCS stack get ready. + logger.print("Didn't get trigger from stack yet, discard framework trigger."); + return ResultCode.ERROR_SERVICE_NOT_AVAILABLE; + } + + if (mDonotRetryUntilPowerCycle) { + logger.print("Don't publish until next power cycle"); + return ResultCode.SUCCESS; + } + + if(!mSimLoaded){ + //need to read some information from SIM to publish + logger.print("invokePublish cache the trigger since the SIM is not ready"); + mHasCachedTrigger = true; + return ResultCode.ERROR_SERVICE_NOT_AVAILABLE; + } + + //the provision status didn't be read from modem yet + if(!RcsSettingUtils.isEabProvisioned(mContext)) { + logger.print("invokePublish cache the trigger, not provision yet"); + mHasCachedTrigger = true; + return ResultCode.ERROR_SERVICE_NOT_AVAILABLE; + } + + PublishRequest publishRequest = new PublishRequest( + bForceToNetwork, System.currentTimeMillis()); + + requestPublication(publishRequest); + + return ResultCode.SUCCESS; + } + + public int invokePublish(PresPublishTriggerType val) { + int ret; + + mGotTriggerFromStack = true; + + // Always send the PUBLISH when we got the trigger from stack. + // This can avoid any issue when switch the phone between IWLAN and LTE. + boolean bForceToNetwork = true; + switch (val.getPublishTrigeerType()) + { + case PresPublishTriggerType.UCE_PRES_PUBLISH_TRIGGER_ETAG_EXPIRED: + { + logger.print("PUBLISH_TRIGGER_ETAG_EXPIRED"); + break; + } + case PresPublishTriggerType.UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_LTE_VOPS_DISABLED: + { + logger.print("PUBLISH_TRIGGER_MOVE_TO_LTE_VOPS_DISABLED"); + mMovedToLTE = true; + mVoPSEnabled = false; + mMovedToIWLAN = false; + + // onImsConnected could came later than this trigger. + mImsRegistered = true; + break; + } + case PresPublishTriggerType.UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_LTE_VOPS_ENABLED: + { + logger.print("PUBLISH_TRIGGER_MOVE_TO_LTE_VOPS_ENABLED"); + mMovedToLTE = true; + mVoPSEnabled = true; + mMovedToIWLAN = false; + + // onImsConnected could came later than this trigger. + mImsRegistered = true; + break; + } + case PresPublishTriggerType.UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_IWLAN: + { + logger.print("QRCS_PRES_PUBLISH_TRIGGER_MOVE_TO_IWLAN"); + mMovedToLTE = false; + mVoPSEnabled = false; + mMovedToIWLAN = true; + + // onImsConnected could came later than this trigger. + mImsRegistered = true; + break; + } + case PresPublishTriggerType.UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_EHRPD: + { + logger.print("PUBLISH_TRIGGER_MOVE_TO_EHRPD"); + mMovedToLTE = false; + mVoPSEnabled = false; + mMovedToIWLAN = false; + + // onImsConnected could came later than this trigger. + mImsRegistered = true; + break; + } + case PresPublishTriggerType.UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_HSPAPLUS: + { + logger.print("PUBLISH_TRIGGER_MOVE_TO_HSPAPLUS"); + mMovedToLTE = false; + mVoPSEnabled = false; + mMovedToIWLAN = false; + break; + } + case PresPublishTriggerType.UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_2G: + { + logger.print("PUBLISH_TRIGGER_MOVE_TO_2G"); + mMovedToLTE = false; + mVoPSEnabled = false; + mMovedToIWLAN = false; + break; + } + case PresPublishTriggerType.UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_3G: + { + logger.print("PUBLISH_TRIGGER_MOVE_TO_3G"); + mMovedToLTE = false; + mVoPSEnabled = false; + mMovedToIWLAN = false; + break; + } + default: + logger.print("Unknow Publish Trigger Type"); + } + + if (mDonotRetryUntilPowerCycle) { + logger.print("Don't publish until next power cycle"); + return ResultCode.SUCCESS; + } + + if(!mSimLoaded){ + //need to read some information from SIM to publish + logger.print("invokePublish cache the trigger since the SIM is not ready"); + mHasCachedTrigger = true; + return ResultCode.ERROR_SERVICE_NOT_AVAILABLE; + } + + //the provision status didn't be read from modem yet + if(!RcsSettingUtils.isEabProvisioned(mContext)) { + logger.print("invokePublish cache the trigger, not provision yet"); + mHasCachedTrigger = true; + return ResultCode.ERROR_SERVICE_NOT_AVAILABLE; + } + + PublishRequest publishRequest = new PublishRequest( + bForceToNetwork, System.currentTimeMillis()); + + requestPublication(publishRequest); + + return ResultCode.SUCCESS; + } + + public class PublishRequest { + private boolean mForceToNetwork = true; + private long mCurrentTime = 0; + private boolean mVolteCapable = false; + private boolean mVtCapable = false; + + PublishRequest(boolean bForceToNetwork, long currentTime) { + refreshPublishContent(); + mForceToNetwork = bForceToNetwork; + mCurrentTime = currentTime; + } + + public void refreshPublishContent() { + mVolteCapable = isIPVoiceSupported(mIsVolteAvailable, + mIsVtAvailable, + mIsVoWifiAvailable, + mIsViWifiAvailable); + mVtCapable = isIPVideoSupported(mIsVolteAvailable, + mIsVtAvailable, + mIsVoWifiAvailable, + mIsViWifiAvailable); + } + + public boolean getForceToNetwork() { + return mForceToNetwork; + } + + public void setForceToNetwork(boolean bForceToNetwork) { + mForceToNetwork = bForceToNetwork; + } + + public long getTimestamp() { + return mCurrentTime; + } + + public void setTimestamp(long currentTime) { + mCurrentTime = currentTime; + } + + public void setVolteCapable(boolean capable) { + mVolteCapable = capable; + } + + public void setVtCapable(boolean capable) { + mVtCapable = capable; + } + + public boolean getVolteCapable() { + return mVolteCapable; + } + + public boolean getVtCapable() { + return mVtCapable; + } + + public boolean hasSamePublishContent(PublishRequest request) { + if(request == null) { + logger.error("request == null"); + return false; + } + + return (mVolteCapable == request.getVolteCapable() && + mVtCapable == request.getVtCapable()); + } + + public String toString(){ + return "mForceToNetwork=" + mForceToNetwork + + " mCurrentTime=" + mCurrentTime + + " mVolteCapable=" + mVolteCapable + + " mVtCapable=" + mVtCapable; + } + } + + private Handler mMsgHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + + logger.debug( "Thread=" + Thread.currentThread().getName() + " received " + + msg); + if(msg == null){ + logger.error("msg=null"); + return; + } + + switch (msg.what) { + case MESSAGE_RCS_PUBLISH_REQUEST: + logger.debug("handleMessage msg=RCS_PUBLISH_REQUEST:"); + + PublishRequest publishRequest = (PublishRequest) msg.obj; + synchronized(mSyncObj) { + mPendingRequest = null; + } + doPublish(publishRequest); + break; + + default: + logger.debug("handleMessage unknown msg=" + msg.what); + } + } + }; + + private void requestPublication(PublishRequest publishRequest) { + // this is used to avoid the temp false feature change when switched between network. + if(publishRequest == null) { + logger.error("Invalid parameter publishRequest == null"); + return; + } + + long requestThrottle = 2000; + long currentTime = System.currentTimeMillis(); + synchronized(mSyncObj){ + // There is a PUBLISH will be done soon. Discard it + if((mPendingRequest != null) && currentTime - mPendingRequest.getTimestamp() + <= requestThrottle) { + logger.print("A publish is pending, update the pending request and discard this one"); + if(publishRequest.getForceToNetwork() && !mPendingRequest.getForceToNetwork()) { + mPendingRequest.setForceToNetwork(true); + } + mPendingRequest.setTimestamp(publishRequest.getTimestamp()); + return; + } + + mPendingRequest = publishRequest; + } + + Message publishMessage = mMsgHandler.obtainMessage( + MESSAGE_RCS_PUBLISH_REQUEST, mPendingRequest); + mMsgHandler.sendMessageDelayed(publishMessage, requestThrottle); + } + + private void doPublish(PublishRequest publishRequest) { + + if(publishRequest == null) { + logger.error("publishRequest == null"); + return; + } + + if (mRcsStackAdaptor == null) { + logger.error("mRcsStackAdaptor == null"); + return; + } + + if(!mImsRegistered) { + logger.debug("IMS wasn't registered"); + return; + } + + // Since we are doing a publish, don't need the retry any more. + if(mPendingRetry) { + mPendingRetry = false; + mAlarmManager.cancel(mRetryAlarmIntent); + } + + // always publish the latest status. + synchronized(mSyncObj) { + publishRequest.refreshPublishContent(); + } + + logger.print("publishRequest=" + publishRequest); + if(!publishRequest.getForceToNetwork() && isPublishedOrPublishing() && + (publishRequest.hasSamePublishContent(mPublishingRequest) || + (mPublishingRequest == null) && + publishRequest.hasSamePublishContent(mPublishedRequest)) && + (getPublishState() != PublishState.PUBLISH_STATE_NOT_PUBLISHED)) { + logger.print("Don't need publish since the capability didn't change publishRequest " + + publishRequest + " getPublishState()=" + getPublishState()); + return; + } + + // Don't publish too frequently. Checking the throttle timer. + if(isPublishedOrPublishing()) { + if(mPendingRetry) { + logger.print("Pending a retry"); + return; + } + + long publishThreshold = RcsSettingUtils.getPublishThrottle(mContext); + long passed = publishThreshold; + if(mPublishingRequest != null) { + passed = System.currentTimeMillis() - mPublishingRequest.getTimestamp(); + } else if(mPublishedRequest != null) { + passed = System.currentTimeMillis() - mPublishedRequest.getTimestamp(); + } + passed = passed>=0?passed:publishThreshold; + + long left = publishThreshold - passed; + left = left>120000?120000:left; // Don't throttle more then 2 mintues. + if(left > 0) { + // A publish is ongoing or published in 1 minute. + // Since the new publish has new status. So schedule + // the publish after the throttle timer. + scheduleRetryPublish(left); + return; + } + } + + // we need send PUBLISH once even the volte is off when power on the phone. + // That will tell other phone that it has no volte/vt capability. + if(!ImsManager.isEnhanced4gLteModeSettingEnabledByUser(mContext) && + getPublishState() != PublishState.PUBLISH_STATE_NOT_PUBLISHED) { + // volte was not enabled. + // or it is turnning off volte. lower layer should unpublish + reset(); + return; + } + + TelephonyManager teleMgr = (TelephonyManager) mContext.getSystemService( + Context.TELEPHONY_SERVICE); + if(teleMgr ==null) { + logger.debug("teleMgr=null"); + return; + } + + RcsPresenceInfo presenceInfo = new RcsPresenceInfo(teleMgr.getLine1Number(), + RcsPresenceInfo.VolteStatus.VOLTE_UNKNOWN, + publishRequest.getVolteCapable()?RcsPresenceInfo.ServiceState.ONLINE: + RcsPresenceInfo.ServiceState.OFFLINE, null, System.currentTimeMillis(), + publishRequest.getVtCapable()?RcsPresenceInfo.ServiceState.ONLINE: + RcsPresenceInfo.ServiceState.OFFLINE, null, + System.currentTimeMillis()); + + synchronized(mSyncObj) { + mPublishingRequest = publishRequest; + mPublishingRequest.setTimestamp(System.currentTimeMillis()); + } + + int ret = mRcsStackAdaptor.requestPublication(presenceInfo, null); + if(ret == ResultCode.ERROR_SERVICE_NOT_AVAILABLE){ + mHasCachedTrigger = true; + }else{ + //reset the cached trigger + mHasCachedTrigger = false; + } + } + + public void handleCmdStatus(PresCmdStatus cmdStatus) { + super.handleCmdStatus(cmdStatus); + } + + private PendingIntent mRetryAlarmIntent = null; + public static final String ACTION_RETRY_PUBLISH_ALARM = + "com.android.service.ims.presence.retry.publish"; + private AlarmManager mAlarmManager = null; + private BroadcastReceiver mRetryReceiver = null; + boolean mCancelRetry = true; + boolean mPendingRetry = false; + + private void scheduleRetryPublish(long timeSpan) { + logger.print("timeSpan=" + timeSpan + + " mPendingRetry=" + mPendingRetry + + " mCancelRetry=" + mCancelRetry); + + // avoid duplicated retry. + if(mPendingRetry) { + logger.debug("There was a retry already"); + return; + } + mPendingRetry = true; + mCancelRetry = false; + + Intent intent = new Intent(ACTION_RETRY_PUBLISH_ALARM); + intent.setClass(mContext, AlarmBroadcastReceiver.class); + mRetryAlarmIntent = PendingIntent.getBroadcast(mContext, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + + if(mAlarmManager == null) { + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + } + + mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + timeSpan, mRetryAlarmIntent); + } + + public void retryPublish() { + logger.print("mCancelRetry=" + mCancelRetry); + mPendingRetry = false; + + // Need some time to cancel it (1 minute for longest) + // Don't do it if it was canceled already. + if(mCancelRetry) { + return; + } + + invokePublish(PublishType.PRES_PUBLISH_TRIGGER_RETRY); + } + + public void handleSipResponse(PresSipResponse pSipResponse) { + logger.print( "Publish response code = " + pSipResponse.getSipResponseCode() + +"Publish response reason phrase = " + pSipResponse.getReasonPhrase()); + + synchronized(mSyncObj) { + mPublishedRequest = mPublishingRequest; + mPublishingRequest = null; + } + if(pSipResponse == null){ + logger.debug("handlePublishResponse pSipResponse = null"); + return; + } + int sipCode = pSipResponse.getSipResponseCode(); + + if(isInConfigList(sipCode, pSipResponse.getReasonPhrase(), + R.array.config_volte_provision_error_on_publish_response)) { + logger.print("volte provision error. sipCode=" + sipCode + " phrase=" + + pSipResponse.getReasonPhrase()); + setPublishState(PublishState.PUBLISH_STATE_VOLTE_PROVISION_ERROR); + mDonotRetryUntilPowerCycle = true; + + notifyDm(); + + return; + } + + if(isInConfigList(sipCode, pSipResponse.getReasonPhrase(), + R.array.config_rcs_provision_error_on_publish_response)) { + logger.print("rcs provision error.sipCode=" + sipCode + " phrase=" + + pSipResponse.getReasonPhrase()); + setPublishState(PublishState.PUBLISH_STATE_RCS_PROVISION_ERROR); + mDonotRetryUntilPowerCycle = true; + + return; + } + + switch (sipCode) { + case 999: + logger.debug("Publish ignored - No capability change"); + break; + case 200: + setPublishState(PublishState.PUBLISH_STATE_200_OK); + if(mSubscriber != null) { + mSubscriber.retryToGetAvailability(); + } + break; + + case 408: + setPublishState(PublishState.PUBLISH_STATE_REQUEST_TIMEOUT); + break; + + default: // Generic Failure + if ((sipCode < 100) || (sipCode > 699)) { + logger.debug("Ignore internal response code, sipCode=" + sipCode); + // internal error + // 0- PERMANENT ERROR: UI should not retry immediately + // 888- TEMP ERROR: UI Can retry immediately + // 999- NO CHANGE: No Publish needs to be sent + if(sipCode == 888) { + // retry per 2 minutes + scheduleRetryPublish(120000); + } else { + logger.debug("Ignore internal response code, sipCode=" + sipCode); + } + } else { + logger.debug( "Generic Failure"); + setPublishState(PublishState.PUBLISH_STATE_OTHER_ERROR); + + if ((sipCode>=400) && (sipCode <= 699)) { + // 4xx/5xx/6xx, No retry, no impact on subsequent publish + logger.debug( "No Retry in OEM"); + } + } + break; + } + + // Suppose the request ID had been set when IQPresListener_CMDStatus + Task task = TaskManager.getDefault().getTaskByRequestId( + pSipResponse.getRequestId()); + if(task != null){ + task.mSipResponseCode = pSipResponse.getSipResponseCode(); + task.mSipReasonPhrase = pSipResponse.getReasonPhrase(); + } + + handleCallback(task, getPublishState(), false); + } + + private static boolean isTtyEnabled(int mode) { + return Phone.TTY_MODE_OFF != mode; + } + + private static int telecomTtyModeToPhoneMode(int telecomMode) { + switch (telecomMode) { + case TelecomManager.TTY_MODE_FULL: + return Phone.TTY_MODE_FULL; + case TelecomManager.TTY_MODE_VCO: + return Phone.TTY_MODE_VCO; + case TelecomManager.TTY_MODE_HCO: + return Phone.TTY_MODE_HCO; + case TelecomManager.TTY_MODE_OFF: + default: + return Phone.TTY_MODE_OFF; + } + } + + public void finish() { + if (mReceiver != null) { + mContext.unregisterReceiver(mReceiver); + mReceiver = null; + } + } + + protected void finalize() throws Throwable { + finish(); + } + + private PendingIntent createIncomingCallPendingIntent() { + Intent intent = new Intent(ACTION_IMS_FEATURE_AVAILABLE); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + return PendingIntent.getBroadcast(mContext, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT); + } + + public void onFeatureCapabilityChanged(final int serviceClass, + final int[] enabledFeatures, final int[] disabledFeatures) { + logger.debug("onFeatureCapabilityChanged serviceClass="+serviceClass + +", enabledFeatures="+Arrays.toString(enabledFeatures) + +", disabledFeatures="+Arrays.toString(disabledFeatures)); + + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + onFeatureCapabilityChangedInternal(serviceClass, + enabledFeatures, disabledFeatures); + } + }, "onFeatureCapabilityChangedInternal thread"); + + thread.start(); + } + + synchronized private void onFeatureCapabilityChangedInternal(int serviceClass, + int[] enabledFeatures, int[] disabledFeatures) { + if (serviceClass == ImsServiceClass.MMTEL) { + boolean oldIsVolteAvailable = mIsVolteAvailable; + boolean oldIsVtAvailable = mIsVtAvailable; + boolean oldIsVoWifiAvailable = mIsVoWifiAvailable; + boolean oldIsViWifiAvailable = mIsViWifiAvailable; + + if(enabledFeatures[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE] == + ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE) { + mIsVolteAvailable = true; + } else if(enabledFeatures[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_LTE] == + ImsConfig.FeatureConstants.FEATURE_TYPE_UNKNOWN) { + mIsVolteAvailable = false; + } else { + logger.print("invalid value for FEATURE_TYPE_VOICE_OVER_LTE"); + } + + if(enabledFeatures[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_WIFI] == + ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_WIFI) { + mIsVoWifiAvailable = true; + } else if(enabledFeatures[ImsConfig.FeatureConstants.FEATURE_TYPE_VOICE_OVER_WIFI] == + ImsConfig.FeatureConstants.FEATURE_TYPE_UNKNOWN) { + mIsVoWifiAvailable = false; + } else { + logger.print("invalid value for FEATURE_TYPE_VOICE_OVER_WIFI"); + } + + if (enabledFeatures[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE] == + ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE) { + mIsVtAvailable = true; + } else if (enabledFeatures[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_LTE] == + ImsConfig.FeatureConstants.FEATURE_TYPE_UNKNOWN) { + mIsVtAvailable = false; + } else { + logger.print("invalid value for FEATURE_TYPE_VIDEO_OVER_LTE"); + } + + if(enabledFeatures[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_WIFI] == + ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_WIFI) { + mIsViWifiAvailable = true; + } else if(enabledFeatures[ImsConfig.FeatureConstants.FEATURE_TYPE_VIDEO_OVER_WIFI] == + ImsConfig.FeatureConstants.FEATURE_TYPE_UNKNOWN) { + mIsViWifiAvailable = false; + } else { + logger.print("invalid value for FEATURE_TYPE_VIDEO_OVER_WIFI"); + } + + logger.print("mIsVolteAvailable=" + mIsVolteAvailable + + " mIsVoWifiAvailable=" + mIsVoWifiAvailable + + " mIsVtAvailable=" + mIsVtAvailable + + " mIsViWifiAvailable=" + mIsViWifiAvailable + + " oldIsVolteAvailable=" + oldIsVolteAvailable + + " oldIsVoWifiAvailable=" + oldIsVoWifiAvailable + + " oldIsVtAvailable=" + oldIsVtAvailable + + " oldIsViWifiAvailable=" + oldIsViWifiAvailable); + + if(oldIsVolteAvailable != mIsVolteAvailable || + oldIsVtAvailable != mIsVtAvailable || + oldIsVoWifiAvailable != mIsVoWifiAvailable || + oldIsViWifiAvailable != mIsViWifiAvailable) { + if(mGotTriggerFromStack) { + if((Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) != 0) && !mIsVoWifiAvailable && + !mIsViWifiAvailable) { + logger.print("Airplane mode was on and no vowifi and viwifi." + + " Don't need publish. Stack will unpublish"); + return; + } + + if(isOnIWLAN()) { + // will check duplicated PUBLISH in requestPublication by invokePublish + invokePublish(PresencePublication.PublishType. + PRES_PUBLISH_TRIGGER_FEATURE_AVAILABILITY_CHANGED); + } + } else { + mHasCachedTrigger = true; + } + } + } + } + + private boolean isOnLTE() { + TelephonyManager teleMgr = (TelephonyManager) mContext.getSystemService( + Context.TELEPHONY_SERVICE); + int networkType = teleMgr.getDataNetworkType(); + logger.debug("mMovedToLTE=" + mMovedToLTE + " networkType=" + networkType); + + // Had reported LTE by trigger and still have DATA. + return mMovedToLTE && (networkType != TelephonyManager.NETWORK_TYPE_UNKNOWN); + } + + private boolean isOnIWLAN() { + TelephonyManager teleMgr = (TelephonyManager) mContext.getSystemService( + Context.TELEPHONY_SERVICE); + int networkType = teleMgr.getDataNetworkType(); + logger.debug("mMovedToIWLAN=" + mMovedToIWLAN + " networkType=" + networkType); + + // Had reported IWLAN by trigger and still have DATA. + return mMovedToIWLAN && (networkType != TelephonyManager.NETWORK_TYPE_UNKNOWN); + } + +} diff --git a/rcs/rcsservice/src/com/android/service/ims/presence/PresencePublishTask.java b/rcs/rcsservice/src/com/android/service/ims/presence/PresencePublishTask.java new file mode 100644 index 0000000..361488e --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/presence/PresencePublishTask.java @@ -0,0 +1,90 @@ +/* + * 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.presence; + +import java.util.Set; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; +import android.content.Context; +import android.content.Intent; +import android.app.PendingIntent; +import android.content.IntentFilter; +import android.os.RemoteException; +import android.app.AlarmManager; +import android.os.SystemClock; + +import com.android.ims.internal.uce.presence.PresCmdStatus; + +import com.android.ims.internal.Logger; +import com.android.ims.RcsManager.ResultCode; +import com.android.ims.RcsPresenceInfo; +import com.android.ims.IRcsPresenceListener; + +import com.android.service.ims.TaskManager; + +/** + * PresencePublishTask + */ +public class PresencePublishTask extends PresenceTask{ + /* + * The logger + */ + private Logger logger = Logger.getLogger(this.getClass().getName()); + + private long mCreateTimestamp = 0; + + private int mRetryCount = 0; + + public PresencePublishTask(int taskId, int cmdId, IRcsPresenceListener listener, + String[] contacts){ + super(taskId, cmdId, listener, contacts); + + mCreateTimestamp = System.currentTimeMillis(); + } + + public long getCreateTimestamp() { + return mCreateTimestamp; + } + + public int getRetryCount() { + return mRetryCount; + } + + public void setRetryCount(int retryCount) { + mRetryCount = retryCount; + } + + public String toString(){ + return super.toString() + + " mCreateTimestamp=" + mCreateTimestamp + + " mRetryCount=" + mRetryCount; + } +}; + diff --git a/rcs/rcsservice/src/com/android/service/ims/presence/PresenceSubscriber.java b/rcs/rcsservice/src/com/android/service/ims/presence/PresenceSubscriber.java new file mode 100644 index 0000000..60a783b --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/presence/PresenceSubscriber.java @@ -0,0 +1,628 @@ +/* + * 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.presence; + +import java.util.List; +import java.util.ArrayList; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.Semaphore; +import android.content.ContentValues; +import android.text.TextUtils; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import com.android.internal.telephony.TelephonyIntents; +import android.os.HandlerThread; +import android.os.RemoteException; +import android.telephony.TelephonyManager; +import android.database.Cursor; + +import java.lang.String; +import android.content.Context; +import android.util.Log; + +import com.android.ims.internal.uce.presence.PresSipResponse; +import com.android.ims.internal.uce.common.StatusCode; +import com.android.ims.internal.uce.common.StatusCode; +import com.android.ims.internal.uce.presence.PresSubscriptionState; +import com.android.ims.internal.uce.presence.PresCmdStatus; +import com.android.ims.internal.uce.presence.PresResInfo; +import com.android.ims.internal.uce.presence.PresRlmiInfo; +import com.android.ims.internal.uce.presence.PresTupleInfo; + +import com.android.ims.RcsPresenceInfo; +import com.android.ims.RcsPresence; +import com.android.ims.IRcsPresenceListener; +import com.android.ims.RcsManager.ResultCode; +import com.android.ims.RcsPresence.PublishState; + +import com.android.ims.internal.Logger; +import com.android.ims.internal.ContactNumberUtils; +import com.android.service.ims.TaskManager; +import com.android.service.ims.Task; +import com.android.service.ims.RcsStackAdaptor; +import com.android.service.ims.RcsUtils; +import com.android.service.ims.RcsSettingUtils; + +import com.android.service.ims.R; + +public class PresenceSubscriber extends PresenceBase{ + /* + * The logger + */ + private Logger logger = Logger.getLogger(this.getClass().getName()); + + private RcsStackAdaptor mRcsStackAdaptor = null; + + private static PresenceSubscriber sSubsriber = null; + + private String mAvailabilityRetryNumber = null; + + /* + * Constructor + */ + public PresenceSubscriber(RcsStackAdaptor rcsStackAdaptor, Context context){ + mRcsStackAdaptor = rcsStackAdaptor; + mContext = context; + } + + private String numberToUriString(String number){ + String formatedContact = number; + if(!formatedContact.startsWith("sip:") && !formatedContact.startsWith("tel:")){ + String domain = TelephonyManager.getDefault().getIsimDomain(); + logger.debug("domain=" + domain); + if(domain == null || domain.length() ==0){ + formatedContact = "tel:" + formatedContact; + }else{ + formatedContact = "sip:" + formatedContact + "@" + domain; + } + } + + logger.print("numberToUriString formatedContact=" + formatedContact); + return formatedContact; + } + + private String numberToTelString(String number){ + String formatedContact = number; + if(!formatedContact.startsWith("sip:") && !formatedContact.startsWith("tel:")){ + formatedContact = "tel:" + formatedContact; + } + + logger.print("numberToTelString formatedContact=" + formatedContact); + return formatedContact; + } + + public int requestCapability(List<String> contactsNumber, + IRcsPresenceListener listener){ + logger.debug("requestCapability contactsNumber=" + contactsNumber); + + int ret = mRcsStackAdaptor.checkStackAndPublish(); + if(ret < ResultCode.SUCCESS){ + logger.error("requestCapability ret=" + ret); + return ret; + } + + if(contactsNumber == null || contactsNumber.size() ==0){ + ret = ResultCode.SUBSCRIBE_INVALID_PARAM; + return ret; + } + + logger.debug("check contact size ..."); + if(contactsNumber.size() > RcsSettingUtils.getMaxNumbersInRCL(mContext)){ + ret = ResultCode.SUBSCRIBE_TOO_LARGE; + logger.error("requestCapability contctNumber size=" + contactsNumber.size()); + return ret; + } + + String[] formatedNumbers = ContactNumberUtils.getDefault().format(contactsNumber); + ret = ContactNumberUtils.getDefault().validate(formatedNumbers); + if(ret != ContactNumberUtils.NUMBER_VALID){ + logger.error("requestCapability ret=" + ret); + return ret; + } + + String[] formatedContacts = new String[formatedNumbers.length]; + for(int i=0; i<formatedContacts.length; i++){ + formatedContacts[i] = numberToTelString(formatedNumbers[i]); + } + // In ms + long timeout = RcsSettingUtils.getCapabPollListSubExp(mContext) * 1000; + timeout += RcsSettingUtils.getSIPT1Timer(mContext); + + // The terminal notification may be received shortly after the time limit of + // the subscription due to network delays or retransmissions. + // Device shall wait for 3sec after the end of the subscription period in order to + // accept such notifications without returning spurious errors (e.g. SIP 481) + timeout += 3000; + + logger.print("add to task manager, formatedNumbers=" + + RcsUtils.toContactString(formatedNumbers)); + int taskId = TaskManager.getDefault().addCapabilityTask(mContext, formatedNumbers, + listener, timeout); + logger.print("taskId=" + taskId); + + ret = mRcsStackAdaptor.requestCapability(formatedContacts, taskId); + if(ret < ResultCode.SUCCESS){ + logger.error("requestCapability ret=" + ret + " remove taskId=" + taskId); + TaskManager.getDefault().removeTask(taskId); + } + + ret = taskId; + + return ret; + } + + public int requestAvailability(String contactNumber, IRcsPresenceListener listener, + boolean forceToNetwork){ + logger.print("requestAvailability contactNumber=" + contactNumber + + " forceToNetwork=" + forceToNetwork); + + String formatedContact = ContactNumberUtils.getDefault().format(contactNumber); + int ret = ContactNumberUtils.getDefault().validate(formatedContact); + if(ret != ContactNumberUtils.NUMBER_VALID){ + return ret; + } + + if(!forceToNetwork){ + logger.debug("check if we can use the value in cache"); + int availabilityExpire = RcsSettingUtils.getAvailabilityCacheExpiration(mContext); + availabilityExpire = availabilityExpire>0?availabilityExpire*1000: + 60*1000; // by default is 60s + logger.print("requestAvailability availabilityExpire=" + availabilityExpire); + + TaskManager.getDefault().clearTimeoutAvailabilityTask(availabilityExpire); + + Task task = TaskManager.getDefault().getAvailabilityTaskByContact(formatedContact); + if(task != null && task instanceof PresenceAvailabilityTask) { + PresenceAvailabilityTask availabilityTask = (PresenceAvailabilityTask)task; + if(availabilityTask.getNotifyTimestamp() == 0) { + // The previous one didn't get response yet. + logger.print("requestAvailability: the request is pending in queue"); + return ResultCode.SUBSCRIBE_ALREADY_IN_QUEUE; + }else { + // not expire yet. Can use the previous value. + logger.print("requestAvailability: the prevous valuedoesn't be expired yet"); + return ResultCode.SUBSCRIBE_TOO_FREQUENTLY; + } + } + } + + boolean isFtSupported = false; // hard code to not support FT at present. + boolean isChatSupported = false; // hard code to not support chat at present. + // Only poll/fetch capability/availability on LTE + if(((TelephonyManager.getDefault().getNetworkType() != TelephonyManager.NETWORK_TYPE_LTE) + && !isFtSupported && !isChatSupported)){ + logger.error("requestAvailability return ERROR_SERVICE_NOT_AVAILABLE" + + " for it is not LTE network"); + return ResultCode.ERROR_SERVICE_NOT_AVAILABLE; + } + + ret = mRcsStackAdaptor.checkStackAndPublish(); + if(ret < ResultCode.SUCCESS){ + logger.error("requestAvailability=" + ret); + return ret; + } + + // user number format in TaskManager. + int taskId = TaskManager.getDefault().addAvailabilityTask(formatedContact, listener); + + // Change it to URI format. + formatedContact = numberToUriString(formatedContact); + + logger.print("addAvailabilityTask formatedContact=" + formatedContact); + + ret = mRcsStackAdaptor.requestAvailability(formatedContact, taskId); + if(ret < ResultCode.SUCCESS){ + logger.error("requestAvailability ret=" + ret + " remove taskId=" + taskId); + TaskManager.getDefault().removeTask(taskId); + } + + ret = taskId; + + return ret; + } + + private int translateResponse403(PresSipResponse pSipResponse){ + String reasonPhrase = pSipResponse.getReasonPhrase(); + if(reasonPhrase == null){ + // No retry. The PS provisioning has not occurred correctly. UX Decision to show errror. + return ResultCode.SUBSCRIBE_GENIRIC_FAILURE; + } + + reasonPhrase = reasonPhrase.toLowerCase(); + if(reasonPhrase.contains("user not registered")){ + // Register to IMS then retry the single resource subscription if capability polling. + // availability fetch: no retry. ignore the availability and allow LVC? (PLM decision) + return ResultCode.SUBSCRIBE_NOT_REGISTERED; + } + + if(reasonPhrase.contains("not authorized for presence")){ + // No retry. + return ResultCode.SUBSCRIBE_NOT_AUTHORIZED_FOR_PRESENCE; + } + + // unknown phrase: handle it as the same as no phrase + return ResultCode.SUBSCRIBE_FORBIDDEN; + } + + private int translateResponseCode(PresSipResponse pSipResponse){ + // pSipResponse should not be null. + logger.debug("translateResponseCode getSipResponseCode=" + + pSipResponse.getSipResponseCode()); + int ret = ResultCode.SUBSCRIBE_GENIRIC_FAILURE; + + int sipCode = pSipResponse.getSipResponseCode(); + if(sipCode < 100 || sipCode > 699){ + logger.debug("internal error code sipCode=" + sipCode); + ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR; //it is internal issue. ignore it. + return ret; + } + + switch(sipCode){ + case 200: + ret = ResultCode.SUCCESS; + break; + + case 403: + ret = translateResponse403(pSipResponse); + break; + + case 404: + // Target MDN is not provisioned for VoLTE or it is not known as VzW IMS subscriber + // Device shall not retry. Device shall remove the VoLTE status of the target MDN + // and update UI + ret = ResultCode.SUBSCRIBE_NOT_FOUND; + break; + + case 408: + // Request Timeout + // Device shall retry with exponential back-off + ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR; + break; + + case 413: + // Too Large. + // Application need shrink the size of request contact list and resend the request + ret = ResultCode.SUBSCRIBE_TOO_LARGE; + break; + + case 423: + // Interval Too Short. Requested expiry interval too short and server rejects it + // Device shall re-attempt subscription after changing the expiration interval in + // the Expires header field to be equal to or greater than the expiration interval + // within the Min-Expires header field of the 423 response + ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR; + break; + + case 500: + // 500 Server Internal Error + // capability polling: exponential back-off retry (same rule as resource list) + // availability fetch: no retry. ignore the availability and allow LVC + // (PLM decision) + ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR; + break; + + case 503: + // capability polling: exponential back-off retry (same rule as resource list) + // availability fetch: no retry. ignore the availability and allow LVC? + // (PLM decision) + ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR; + break; + + // capability polling: Device shall retry with exponential back-off + // Availability Fetch: device shall ignore the error and shall not retry + case 603: + ret = ResultCode.SUBSCRIBE_TEMPORARY_ERROR; + break; + + default: + // Other 4xx/5xx/6xx + // Device shall not retry + ret = ResultCode.SUBSCRIBE_GENIRIC_FAILURE; + } + + logger.debug("translateResponseCode ret=" + ret); + return ret; + } + + public void handleSipResponse(PresSipResponse pSipResponse){ + if(pSipResponse == null){ + logger.debug("handleSipResponse pSipResponse = null"); + return; + } + + int sipCode = pSipResponse.getSipResponseCode(); + String phrase = pSipResponse.getReasonPhrase(); + if(isInConfigList(sipCode, phrase, + R.array.config_volte_provision_error_on_subscribe_response)) { + logger.print("volte provision sipCode=" + sipCode + " phrase=" + phrase); + mRcsStackAdaptor.setPublishState(PublishState.PUBLISH_STATE_VOLTE_PROVISION_ERROR); + + notifyDm(); + } else if(isInConfigList(sipCode, phrase, + R.array.config_rcs_provision_error_on_subscribe_response)) { + logger.print("rcs provision sipCode=" + sipCode + " phrase=" + phrase); + mRcsStackAdaptor.setPublishState(PublishState.PUBLISH_STATE_RCS_PROVISION_ERROR); + } + + int errorCode = translateResponseCode(pSipResponse); + logger.print("handleSipResponse errorCode=" + errorCode); + + if(errorCode == ResultCode.SUBSCRIBE_NOT_REGISTERED){ + logger.debug("setPublishState to unknown" + + " for subscribe error 403 not registered"); + mRcsStackAdaptor.setPublishState( + PublishState.PUBLISH_STATE_OTHER_ERROR); + } + + if(errorCode == ResultCode.SUBSCRIBE_NOT_AUTHORIZED_FOR_PRESENCE) { + logger.debug("ResultCode.SUBSCRIBE_NOT_AUTHORIZED_FOR_PRESENCE"); + } + + if(errorCode == ResultCode.SUBSCRIBE_FORBIDDEN){ + logger.debug("ResultCode.SUBSCRIBE_FORBIDDEN"); + } + + // Suppose the request ID had been set when IQPresListener_CMDStatus + Task task = TaskManager.getDefault().getTaskByRequestId( + pSipResponse.getRequestId()); + logger.debug("handleSipResponse task=" + task); + if(task != null){ + task.mSipResponseCode = sipCode; + task.mSipReasonPhrase = phrase; + TaskManager.getDefault().putTask(task.mTaskId, task); + } + + if(errorCode == ResultCode.SUBSCRIBE_NOT_REGISTERED && + task != null && task.mCmdId == TaskManager.TASK_TYPE_GET_AVAILABILITY) { + String[] contacts = ((PresenceTask)task).mContacts; + if(contacts != null && contacts.length>0){ + mAvailabilityRetryNumber = contacts[0]; + } + logger.debug("retry to get availability for " + mAvailabilityRetryNumber); + } + + // 404 error for single contact only as per requirement + // need handle 404 for multiple contacts as per CV 3.24. + if(errorCode == ResultCode.SUBSCRIBE_NOT_FOUND && + task != null && ((PresenceTask)task).mContacts != null) { + String[] contacts = ((PresenceTask)task).mContacts; + ArrayList<RcsPresenceInfo> presenceInfoList = new ArrayList<RcsPresenceInfo>(); + + for(int i=0; i<contacts.length; i++){ + logger.debug("404 error: task.mContacts[" + i + "]=" + contacts[i]); + if(TextUtils.isEmpty(contacts[i])){ + continue; + } + + RcsPresenceInfo presenceInfo = new RcsPresenceInfo(contacts[i], + RcsPresenceInfo.VolteStatus.VOLTE_DISABLED, + RcsPresenceInfo.ServiceState.OFFLINE, null, System.currentTimeMillis(), + RcsPresenceInfo.ServiceState.OFFLINE, null, System.currentTimeMillis()); + presenceInfoList.add(presenceInfo); + } + + // Notify presence information changed. + logger.debug("notify presence changed for 404 error"); + Intent intent = new Intent(RcsPresence.ACTION_PRESENCE_CHANGED); + intent.putParcelableArrayListExtra(RcsPresence.EXTRA_PRESENCE_INFO_LIST, + presenceInfoList); + intent.putExtra("updateLastTimestamp", true); + launchPersistService(intent); + } else if(errorCode == ResultCode.SUBSCRIBE_GENIRIC_FAILURE) { + updateAvailabilityToUnknown(task); + } + + handleCallback(task, errorCode, false); + } + + private void launchPersistService(Intent intent) { + ComponentName component = new ComponentName("com.android.service.ims.presence", + "com.android.service.ims.presence.PersistService"); + intent.setComponent(component); + mContext.startService(intent); + } + + public void retryToGetAvailability() { + logger.debug("retryToGetAvailability, number=" + mAvailabilityRetryNumber); + if(mAvailabilityRetryNumber == null){ + return; + } + requestAvailability(mAvailabilityRetryNumber, null, true); + //retry one time only + mAvailabilityRetryNumber = null; + } + + public void updatePresence(String pPresentityUri, PresTupleInfo[] pTupleInfo){ + if(mContext == null){ + logger.error("updatePresence mContext == null"); + return; + } + + RcsPresenceInfo rcsPresenceInfo = PresenceInfoParser.getPresenceInfoFromTuple( + pPresentityUri, pTupleInfo); + if(rcsPresenceInfo == null || TextUtils.isEmpty( + rcsPresenceInfo.getContactNumber())){ + logger.error("rcsPresenceInfo is null or " + + "TextUtils.isEmpty(rcsPresenceInfo.getContactNumber()"); + return; + } + + ArrayList<RcsPresenceInfo> rcsPresenceInfoList = new ArrayList<RcsPresenceInfo>(); + rcsPresenceInfoList.add(rcsPresenceInfo); + + // For single contact number we got 1 NOTIFY only. So regard it as terminated. + TaskManager.getDefault().onTerminated(rcsPresenceInfo.getContactNumber()); + + PresenceAvailabilityTask availabilityTask = TaskManager.getDefault(). + getAvailabilityTaskByContact(rcsPresenceInfo.getContactNumber()); + if(availabilityTask != null){ + availabilityTask.updateNotifyTimestamp(); + } + + // Notify presence information changed. + Intent intent = new Intent(RcsPresence.ACTION_PRESENCE_CHANGED); + intent.putParcelableArrayListExtra(RcsPresence.EXTRA_PRESENCE_INFO_LIST, + rcsPresenceInfoList); + intent.putExtra("updateLastTimestamp", true); + launchPersistService(intent); + } + + public void updatePresences(PresRlmiInfo pRlmiInfo, PresResInfo[] pRcsPresenceInfo) { + if(mContext == null){ + logger.error("updatePresences: mContext == null"); + return; + } + + RcsPresenceInfo[] rcsPresenceInfos = PresenceInfoParser. + getPresenceInfosFromPresenceRes(pRlmiInfo, pRcsPresenceInfo); + if(rcsPresenceInfos == null){ + logger.error("updatePresences: rcsPresenceInfos == null"); + return; + } + + ArrayList<RcsPresenceInfo> rcsPresenceInfoList = new ArrayList<RcsPresenceInfo>(); + + for (int i=0; i < rcsPresenceInfos.length; i++) { + RcsPresenceInfo rcsPresenceInfo = rcsPresenceInfos[i]; + if(rcsPresenceInfo != null && !TextUtils.isEmpty(rcsPresenceInfo.getContactNumber())){ + logger.debug("rcsPresenceInfo=" + rcsPresenceInfo); + + rcsPresenceInfoList.add(rcsPresenceInfo); + } + } + + boolean isTerminated = false; + if(pRlmiInfo.getPresSubscriptionState() != null){ + if(pRlmiInfo.getPresSubscriptionState().getPresSubscriptionStateValue() == + PresSubscriptionState.UCE_PRES_SUBSCRIPTION_STATE_TERMINATED){ + isTerminated = true; + } + } + + if(isTerminated){ + TaskManager.getDefault().onTerminated(pRlmiInfo.getRequestId(), + pRlmiInfo.getSubscriptionTerminatedReason()); + } + + if (rcsPresenceInfoList.size() > 0) { + // Notify presence changed + Intent intent = new Intent(RcsPresence.ACTION_PRESENCE_CHANGED); + intent.putParcelableArrayListExtra(RcsPresence.EXTRA_PRESENCE_INFO_LIST, + rcsPresenceInfoList); + logger.debug("broadcast ACTION_PRESENCE_CHANGED, rcsPresenceInfo=" + + rcsPresenceInfoList); + intent.putExtra("updateLastTimestamp", true); + launchPersistService(intent); + } + } + + public void handleCmdStatus(PresCmdStatus pCmdStatus){ + if(pCmdStatus == null){ + logger.error("handleCallbackForCmdStatus pCmdStatus=null"); + return; + } + + + Task taskTmp = TaskManager.getDefault().getTask(pCmdStatus.getUserData()); + int resultCode = RcsUtils.statusCodeToResultCode(pCmdStatus.getStatus().getStatusCode()); + logger.print("handleCmdStatus resultCode=" + resultCode); + PresenceTask task = null; + if(taskTmp != null && (taskTmp instanceof PresenceTask)){ + task = (PresenceTask)taskTmp; + task.mSipRequestId = pCmdStatus.getRequestId(); + task.mCmdStatus = resultCode; + TaskManager.getDefault().putTask(task.mTaskId, task); + + // handle error as the same as temporary network error + // set availability to false, keep old capability + if(resultCode != ResultCode.SUCCESS && task.mContacts != null){ + updateAvailabilityToUnknown(task); + } + } + + handleCallback(task, resultCode, true); + } + + private void updateAvailabilityToUnknown(Task inTask){ + //only used for serviceState is offline or unknown. + if(mContext == null){ + logger.error("updateAvailabilityToUnknown mContext=null"); + return; + } + + if(inTask == null){ + logger.error("updateAvailabilityToUnknown task=null"); + return; + } + + if(!(inTask instanceof PresenceTask)){ + logger.error("updateAvailabilityToUnknown not PresencTask"); + return; + } + + PresenceTask task = (PresenceTask)inTask; + + if(task.mContacts == null || task.mContacts.length ==0){ + logger.error("updateAvailabilityToUnknown no contacts"); + return; + } + + ArrayList<RcsPresenceInfo> presenceInfoList = new ArrayList<RcsPresenceInfo>(); + for(int i = 0; i< task.mContacts.length; i++){ + if(task.mContacts[i] == null || task.mContacts[i].length() == 0){ + continue; + } + + RcsPresenceInfo presenceInfo = new RcsPresenceInfo( + PresenceInfoParser.getPhoneFromUri(task.mContacts[i]), + RcsPresenceInfo.VolteStatus.VOLTE_UNKNOWN, + RcsPresenceInfo.ServiceState.UNKNOWN, null, System.currentTimeMillis(), + RcsPresenceInfo.ServiceState.UNKNOWN, null, System.currentTimeMillis()); + presenceInfoList.add(presenceInfo); + } + + if(presenceInfoList.size() > 0) { + // Notify presence information changed. + logger.debug("notify presence changed for cmd error"); + Intent intent = new Intent(RcsPresence.ACTION_PRESENCE_CHANGED); + intent.putParcelableArrayListExtra(RcsPresence.EXTRA_PRESENCE_INFO_LIST, + presenceInfoList); + + // don't update timestamp so it can be subscribed soon. + intent.putExtra("updateLastTimestamp", false); + launchPersistService(intent); + } + } +} + diff --git a/rcs/rcsservice/src/com/android/service/ims/presence/PresenceTask.java b/rcs/rcsservice/src/com/android/service/ims/presence/PresenceTask.java new file mode 100644 index 0000000..8ff74cd --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/presence/PresenceTask.java @@ -0,0 +1,76 @@ +/* + * 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.presence; + +import java.util.Set; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; +import android.os.RemoteException; + +import com.android.ims.internal.uce.presence.PresCmdStatus; + +import com.android.ims.internal.Logger; +import com.android.ims.RcsManager.ResultCode; +import com.android.ims.RcsPresenceInfo; +import com.android.ims.IRcsPresenceListener; + +import com.android.service.ims.Task; +import com.android.service.ims.RcsUtils; + +/** + * PresenceTask + */ +public class PresenceTask extends Task{ + /* + * The logger + */ + private Logger logger = Logger.getLogger(this.getClass().getName()); + + // filled before send the request to stack + public String[] mContacts; + + public PresenceTask(int taskId, int cmdId, IRcsPresenceListener listener, String[] contacts){ + super(taskId, cmdId, listener); + + mContacts = contacts; + mListener = listener; + } + + public String toString(){ + return "PresenceTask: mTaskId=" + mTaskId + + " mCmdId=" + mCmdId + + " mContacts=" + RcsUtils.toContactString(mContacts) + + " mCmdStatus=" + mCmdStatus + + " mSipRequestId=" + mSipRequestId + + " mSipResponseCode=" + mSipResponseCode + + " mSipReasonPhrase=" + mSipReasonPhrase; + } +}; + diff --git a/rcs/rcsservice/src/com/android/service/ims/presence/StackListener.java b/rcs/rcsservice/src/com/android/service/ims/presence/StackListener.java new file mode 100644 index 0000000..5abc19f --- /dev/null +++ b/rcs/rcsservice/src/com/android/service/ims/presence/StackListener.java @@ -0,0 +1,535 @@ +/* + * 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.presence; + +import java.util.ArrayList; +import java.util.List; + +import android.content.ServiceConnection; +import android.annotation.SuppressLint; +import android.content.Intent; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Log; +import android.os.Parcel; + +import com.android.ims.internal.uce.presence.IPresenceListener; +import com.android.ims.internal.uce.presence.PresCmdId; +import com.android.ims.internal.uce.presence.PresCmdStatus; +import com.android.ims.internal.uce.presence.PresPublishTriggerType; +import com.android.ims.internal.uce.presence.PresResInfo; +import com.android.ims.internal.uce.presence.PresRlmiInfo; +import com.android.ims.internal.uce.presence.PresSipResponse; +import com.android.ims.internal.uce.presence.PresTupleInfo; +import com.android.ims.internal.uce.common.StatusCode; +import com.android.ims.internal.uce.common.StatusCode; + +import com.android.ims.RcsManager; +import com.android.ims.RcsManager.ResultCode; +import com.android.ims.RcsPresence; +import com.android.ims.RcsPresenceInfo; +import com.android.ims.IRcsPresenceListener; + +import com.android.ims.internal.Logger; +import com.android.service.ims.TaskManager; +import com.android.service.ims.Task; +import com.android.service.ims.RcsStackAdaptor; +import com.android.service.ims.LauncherUtils; + +public class StackListener extends Handler{ + /* + * The logger + */ + private Logger logger = Logger.getLogger(this.getClass().getName()); + + Context mContext; + private PresencePublication mPresencePublication = null; + private PresenceSubscriber mPresenceSubscriber = null; + + // RCS stack notify the AP to publish the presence. + private static final short PRESENCE_IMS_UNSOL_PUBLISH_TRIGGER = 1; + // PUBLISH CMD status changed + private static final short PRESENCE_IMS_UNSOL_PUBLISH_CMDSTATUS = 2; + // Received the SIP response for publish + private static final short PRESENCE_IMS_UNSOL_PUBLISH_SIPRESPONSE = 3; + // Received the presence for single contact + private static final short PRESENCE_IMS_UNSOL_NOTIFY_UPDATE = 4; + // Received the presence for contacts. + private static final short PRESENCE_IMS_UNSOL_NOTIFY_LIST_UPDATE = 5; + // Received the CMD status for capability/availability request + private static final short PRESENCE_IMS_UNSOL_NOTIFY_UPDATE_CMDSTATUS = 6; + // Received the SIP response for capability/availability request + private static final short PRESENCE_IMS_UNSOL_NOTIFY_UPDATE_SIPRESPONSE = 7; + + private final Object mSyncObj = new Object(); + + public StackListener(Context context, Looper looper) { + super(looper); + mContext = context; + } + + public void setPresencePublication(PresencePublication presencePublication){ + mPresencePublication = presencePublication; + } + + public void setPresenceSubscriber(PresenceSubscriber presenceSubscriber){ + mPresenceSubscriber = presenceSubscriber; + } + + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + + logger.debug( "Thread=" + Thread.currentThread().getName() + " received " + + msg); + if(msg == null){ + logger.error("msg=null"); + return; + } + + switch (msg.what) { + // RCS stack notify the AP to publish the presence. + case PRESENCE_IMS_UNSOL_PUBLISH_TRIGGER: + { + PresPublishTriggerType val = (PresPublishTriggerType) msg.obj; + if(mPresencePublication == null || val == null){ + logger.error("mPresencePublication=" + mPresencePublication + " val=" + val); + return; + } + + mPresencePublication.invokePublish(val); + break; + } + + // RCS stack tell AP that the CMD status changed. + case PRESENCE_IMS_UNSOL_PUBLISH_CMDSTATUS: + { + PresCmdStatus pCmdStatus = (PresCmdStatus) msg.obj; + if(mPresencePublication == null || pCmdStatus == null){ + logger.error("mPresencePublication=" + mPresencePublication + + " pCmdStatus=" + pCmdStatus); + return; + } + + mPresencePublication.handleCmdStatus(pCmdStatus); + } + break; + + // RCS stack tells AP that the CMD status changed. + case PRESENCE_IMS_UNSOL_NOTIFY_UPDATE_CMDSTATUS: + { + PresCmdStatus pCmdStatus = (PresCmdStatus) msg.obj; + if(mPresenceSubscriber == null || pCmdStatus == null){ + logger.error("mPresenceSubcriber=" + mPresenceSubscriber + + " pCmdStatus=" + pCmdStatus); + return; + } + + mPresenceSubscriber.handleCmdStatus(pCmdStatus); + break; + } + + // RCS stack tells AP that the SIP response has been received. + case PRESENCE_IMS_UNSOL_PUBLISH_SIPRESPONSE: + { + PresSipResponse pSipResponse = (PresSipResponse) msg.obj; + if(mPresencePublication == null || pSipResponse == null){ + logger.error("mPresencePublication=" + mPresencePublication + + "pSipResponse=" +pSipResponse); + return; + } + + mPresencePublication.handleSipResponse(pSipResponse); + break; + } + + // RCS stack tells AP that the SIP response has been received. + case PRESENCE_IMS_UNSOL_NOTIFY_UPDATE_SIPRESPONSE: + { + PresSipResponse pSipResponse = (PresSipResponse) msg.obj; + if(mPresenceSubscriber == null || pSipResponse == null){ + logger.error("mPresenceSubscriber=" + mPresenceSubscriber + + " pSipResponse=" + pSipResponse); + return; + } + + mPresenceSubscriber.handleSipResponse(pSipResponse); + break; + } + + // RCS stack tells AP that the presence data has been received. + case PRESENCE_IMS_UNSOL_NOTIFY_UPDATE: + { + NotifyData notifyData = (NotifyData) msg.obj; + if(mPresenceSubscriber == null || notifyData == null){ + logger.error("mPresenceSubscriber=" + mPresenceSubscriber + + " notifyData=" + notifyData); + return; + } + + mPresenceSubscriber.updatePresence(notifyData.getUri(), + notifyData.getTupleInfo()); + break; + } + + case PRESENCE_IMS_UNSOL_NOTIFY_LIST_UPDATE: + { + NotifyListData notifyListData = (NotifyListData) msg.obj; + logger.debug("Received PRESENCE_IMS_UNSOL_NOTIFY_LIST_UPDATE"); + if(mPresenceSubscriber==null || notifyListData == null){ + logger.error("mPresenceSubscriber=" + mPresenceSubscriber + + " notifyListData=" + notifyListData); + return; + } + + mPresenceSubscriber.updatePresences(notifyListData.getRlmiInfo(), + notifyListData.getResInfo()); + break; + } + + default: + logger.debug("Unknown mesg " + msg.what + " recieved."); + } + } + + public class NotifyData{ + private String mUri; + private PresTupleInfo[] mTupleInfo; + + NotifyData(){ + mUri = null; + mTupleInfo = null; + } + + NotifyData(String uri, PresTupleInfo[] pTupleInfo){ + mUri = uri; + mTupleInfo = pTupleInfo; + } + + public String getUri(){ + return mUri; + } + + public PresTupleInfo[] getTupleInfo(){ + return mTupleInfo; + } + } + + public class NotifyListData{ + private PresRlmiInfo mRlmiInfo; + private PresResInfo[] mResInfo; + + NotifyListData(){ + mRlmiInfo = null; + mResInfo = null; + } + + NotifyListData(PresRlmiInfo pRlmiInfo, PresResInfo[] pResInfo){ + mRlmiInfo = pRlmiInfo; + mResInfo = pResInfo; + } + + public PresRlmiInfo getRlmiInfo(){ + return mRlmiInfo; + } + + public PresResInfo[] getResInfo(){ + return mResInfo; + } + } + + public IPresenceListener mPresenceListener = new IPresenceListener.Stub() { + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException{ + try{ + return super.onTransact(code, data, reply, flags); + } catch (RemoteException e) { + Log.w("ListenerHandler", "Unexpected remote exception", e); + e.printStackTrace(); + throw e; + } + } + + public void getVersionCb(String pVersion) { + logger.debug("pVersion=" + pVersion); + } + + public void sipResponseReceived(PresSipResponse pSipResponse) throws RemoteException { + synchronized(mSyncObj){ + if(pSipResponse == null){ + logger.error("ISipResponseReceived pSipResponse=null"); + return; + } + + logger.debug("pSipResponse.getCmdId() "+ + pSipResponse.getCmdId().getCmdId()); + logger.debug("getReasonPhrase() "+pSipResponse.getReasonPhrase()); + logger.debug("getsRequestID() "+pSipResponse.getRequestId()); + logger.debug("getsSipResponseCode() "+pSipResponse.getSipResponseCode()); + + switch (pSipResponse.getCmdId().getCmdId()) { + case PresCmdId.UCE_PRES_CMD_PUBLISHMYCAP: + { + Message updateMesgSipPub = StackListener.this.obtainMessage( + PRESENCE_IMS_UNSOL_PUBLISH_SIPRESPONSE, + pSipResponse); + StackListener.this.sendMessage(updateMesgSipPub); + break; + } + + case PresCmdId.UCE_PRES_CMD_GETCONTACTCAP: + case PresCmdId.UCE_PRES_CMD_GETCONTACTLISTCAP: + { + Message updateMesgSipPub = StackListener.this.obtainMessage( + PRESENCE_IMS_UNSOL_NOTIFY_UPDATE_SIPRESPONSE, + pSipResponse); + StackListener.this.sendMessage(updateMesgSipPub); + + break; + } + + case PresCmdId.UCE_PRES_CMD_SETNEWFEATURETAG: + { + logger.debug("UCE_PRES_CMD_SETNEWFEATURETAG, doesn't care it"); + break; + } + + default: + logger.debug("CMD ID for unknown value=" + + pSipResponse.getCmdId().getCmdId()); + } + } + } + + public void serviceUnAvailable(StatusCode statusCode) throws RemoteException { + synchronized(mSyncObj){ + if(statusCode == null){ + logger.error("statusCode=null"); + }else{ + logger.debug("IServiceUnAvailable statusCode " + + statusCode.getStatusCode()); + } + logger.debug("QPresListener_ServiceUnAvailable"); + + RcsStackAdaptor rcsStackAdaptor = RcsStackAdaptor.getInstance(null); + if (rcsStackAdaptor != null) { + rcsStackAdaptor.setImsEnableState(false); + } + + Intent intent = new Intent(RcsManager.ACTION_RCS_SERVICE_UNAVAILABLE); + mContext.sendBroadcast(intent, + "com.android.ims.rcs.permission.STATUS_CHANGED"); + } + } + + public void serviceAvailable(StatusCode statusCode) throws RemoteException { + synchronized(mSyncObj){ + if(statusCode == null){ + logger.error("statusCode=null"); + }else{ + logger.debug("IServiceAvailable statusCode " + + statusCode.getStatusCode()); + } + + logger.debug("QPresListener_ServiceAvailable"); + + RcsStackAdaptor rcsStackAdaptor = RcsStackAdaptor.getInstance(null); + if (rcsStackAdaptor != null) { + rcsStackAdaptor.setImsEnableState(true); + } + + // Handle the cached trigger which got from stack + if(mPresencePublication != null && mPresencePublication.getHasCachedTrigger()){ + logger.debug("publish for cached trigger"); + + mPresencePublication.invokePublish( + PresencePublication.PublishType.PRES_PUBLISH_TRIGGER_CACHED_TRIGGER); + } + + Intent intent = new Intent(RcsManager.ACTION_RCS_SERVICE_AVAILABLE); + mContext.sendBroadcast(intent, + "com.android.ims.rcs.permission.STATUS_CHANGED"); + } + } + + public void publishTriggering(PresPublishTriggerType publishTrigger) + throws RemoteException { + if(publishTrigger == null){ + logger.error("publishTrigger=null"); + }else{ + logger.debug("getPublishTrigeerType() "+ + publishTrigger.getPublishTrigeerType()); + } + logger.debug("ListenerHandler : PublishTriggering"); + + Message publishTrigerMsg = StackListener.this.obtainMessage( + PRESENCE_IMS_UNSOL_PUBLISH_TRIGGER, publishTrigger); + StackListener.this.sendMessage(publishTrigerMsg); + } + + public void listCapInfoReceived(PresRlmiInfo pRlmiInfo, PresResInfo[] pResInfo) + throws RemoteException { + if(pRlmiInfo == null || pResInfo == null){ + logger.error("pRlmiInfo=" + pRlmiInfo + " pResInfo=" + pResInfo); + }else{ + logger.debug("pRlmiInfo.getListName "+pRlmiInfo.getListName()); + logger.debug("pRlmiInfo.isFullState "+pRlmiInfo.isFullState()); + logger.debug("pRlmiInfo.getUri "+pRlmiInfo.getUri()); + logger.debug("pRlmiInfo.getVersion "+pRlmiInfo.getVersion()); + logger.debug("pRlmiInfo.getSubscriptionTerminatedReason " + + pRlmiInfo.getSubscriptionTerminatedReason()); + logger.debug("pRlmiInfo.getPresSubscriptionState " + + pRlmiInfo.getPresSubscriptionState()); + logger.debug("pRlmiInfo.getRequestID=" + pRlmiInfo.getRequestId()); + for(int i=0; i < pResInfo.length; i++ ){ + if(pResInfo[i] == null){ + logger.debug("ignoring, pResInfo[" + i + "]=null"); + continue; + } + + logger.debug(".getDisplayName() "+pResInfo[i].getDisplayName()); + logger.debug("getResUri() "+pResInfo[i].getResUri()); + if(pResInfo[i].getInstanceInfo() != null){ + logger.debug("getInstanceInfo().getPresentityUri() "+ + pResInfo[i].getInstanceInfo().getPresentityUri()); + logger.debug("getInstanceInfo().getResId() "+ + pResInfo[i].getInstanceInfo().getResId()); + logger.debug("getInstanceInfo().getsReason() "+ + pResInfo[i].getInstanceInfo().getReason()); + logger.debug("getInstanceInfo().getResInstanceState() "+ + pResInfo[i].getInstanceInfo().getResInstanceState()); + if(pResInfo[i].getInstanceInfo().getTupleInfo() == null){ + logger.debug("pResInfo[" + i +"].getInstanceInfo().getTupleInfo()=null"); + continue; + } + + logger.debug("getTupleInfo().length "+ + pResInfo[i].getInstanceInfo().getTupleInfo().length); + if(pResInfo[i].getInstanceInfo().getTupleInfo() != null){ + for(int j = 0; j < pResInfo[i].getInstanceInfo().getTupleInfo().length; + j++) + { + if(pResInfo[i].getInstanceInfo().getTupleInfo() == null){ + logger.debug("ignoring, pResInfo[" + i + + "].getInstanceInfo().getTupleInfo()[" +j + "]"); + continue; + } + + logger.debug("getFeatureTag "+ + pResInfo[i].getInstanceInfo().getTupleInfo()[j]. + getFeatureTag()); + logger.debug("getsContactUri "+ + pResInfo[i].getInstanceInfo().getTupleInfo()[j]. + getContactUri()); + logger.debug("getsTimestamp "+ + pResInfo[i].getInstanceInfo().getTupleInfo()[j]. + getTimestamp()); + } + } + } + } + } + + Message notifyListReceivedMsg = StackListener.this.obtainMessage( + PRESENCE_IMS_UNSOL_NOTIFY_LIST_UPDATE, + new NotifyListData(pRlmiInfo, pResInfo)); + logger.debug("Send PRESENCE_IMS_UNSOL_NOTIFY_LIST_UPDATE"); + + StackListener.this.sendMessage(notifyListReceivedMsg); + } + + public void capInfoReceived(String presentityURI, PresTupleInfo[] pTupleInfo) + throws RemoteException { + logger.debug("ListenerHandler : CapInfoReceived"); + if(presentityURI == null || presentityURI == null){ + logger.error("presentityURI=null or presentityURI=null"); + return; + } + + logger.debug("ListenerHandler : CapInfoReceived : presentityURI "+ presentityURI); + + Message notifyReceivedMsg = StackListener.this.obtainMessage( + PRESENCE_IMS_UNSOL_NOTIFY_UPDATE, + new NotifyData(presentityURI, pTupleInfo)); + StackListener.this.sendMessage(notifyReceivedMsg); + } + + public void cmdStatus(PresCmdStatus pCmdStatus) throws RemoteException { + synchronized(mSyncObj){ + if(pCmdStatus == null || pCmdStatus.getCmdId() == null){ + logger.debug( "ICMDStatus error, pCmdStatus="+ pCmdStatus); + return; + } + + logger.debug("ListenerHandler : CMDStatus"); + logger.debug("ListenerHandler : CMDStatus : pCmdStatus.getRequestID() "+ + pCmdStatus.getRequestId()); + logger.debug("ListenerHandler : CMDStatus : pCmdStatus.getUserData() "+ + pCmdStatus.getUserData()); + logger.debug("ListenerHandler : CMDStatus : pCmdStatus.getCmdId() "+ + pCmdStatus.getCmdId().getCmdId()); + if(pCmdStatus.getStatus() != null){ + logger.debug("ListenerHandler : CMDStatus : pCmdStatus.getStatus() "+ + pCmdStatus.getStatus().getStatusCode()); + } + + switch (pCmdStatus.getCmdId().getCmdId()) { + case PresCmdId.UCE_PRES_CMD_PUBLISHMYCAP: + Message publishCmdMsg = StackListener.this.obtainMessage( + PRESENCE_IMS_UNSOL_PUBLISH_CMDSTATUS, + pCmdStatus); + StackListener.this.sendMessage(publishCmdMsg); + break; + + case PresCmdId.UCE_PRES_CMD_GETCONTACTCAP: + case PresCmdId.UCE_PRES_CMD_GETCONTACTLISTCAP: + Message notifyUpdateCmdMsg = StackListener.this.obtainMessage( + PRESENCE_IMS_UNSOL_NOTIFY_UPDATE_CMDSTATUS, + pCmdStatus); + StackListener.this.sendMessage(notifyUpdateCmdMsg); + break; + + case PresCmdId.UCE_PRES_CMD_SETNEWFEATURETAG: + logger.debug("UCE_PRES_CMD_SETNEWFEATURETAG: app does not care it"); + break; + + default: + logger.debug("CMD ID for unknown value=" + + pCmdStatus.getCmdId().getCmdId()); + } + } + } + }; +} + |