summaryrefslogtreecommitdiff
path: root/rcs
diff options
context:
space:
mode:
authorPavel Zhamaitsiak <pavelz@google.com>2016-05-02 13:52:24 -0700
committerPavel Zhamaitsiak <pavelz@google.com>2016-05-02 13:52:24 -0700
commitac76c57e7a49d4f1431c751db3c810467a60ca48 (patch)
tree8a20ee971de05913567cde774b74e0021d12f9d1 /rcs
parentcec5ed8b43d8fa5d93faa2ba954bb4992d954e94 (diff)
downloadims-ac76c57e7a49d4f1431c751db3c810467a60ca48.tar.gz
Move rcs from frameworks/opt/net/ims to external/ims
Bug: 27811089 Change-Id: I4ac4948096373f1dfe119d21c7d0870ccdb061db
Diffstat (limited to 'rcs')
-rw-r--r--rcs/Android.mk17
-rw-r--r--rcs/presencepolling/Android.mk44
-rw-r--r--rcs/presencepolling/AndroidManifest.xml118
-rw-r--r--rcs/presencepolling/res/values/config.xml37
-rw-r--r--rcs/presencepolling/res/values/strings.xml39
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/AccountUtil.java105
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/AlarmBroadcastReceiver.java78
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/CapabilityPolling.java845
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/ContactDbUtil.java220
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/Contacts.java297
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/DatabaseContentProvider.java434
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/DeviceBoot.java81
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/DeviceShutdown.java54
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/EABContactManager.java932
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/EABDbUtil.java428
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/EABProvider.java453
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/EABService.java1142
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/InvalidDBException.java41
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/LauncherUtils.java82
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/PersistService.java215
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/PollingAction.java209
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/PollingService.java118
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/PollingTask.java256
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/PollingsQueue.java246
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/PresenceBroadcastReceiver.java56
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/PresenceContact.java106
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/PresencePreferences.java136
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/PresenceSetting.java185
-rw-r--r--rcs/presencepolling/src/com/android/service/ims/presence/SharedPrefUtil.java197
-rw-r--r--rcs/rcsmanager/Android.mk54
-rw-r--r--rcs/rcsmanager/AndroidManifest.xml37
-rw-r--r--rcs/rcsmanager/CleanSpec.mk60
-rw-r--r--rcs/rcsmanager/com.android.ims.rcsmanager.xml33
-rw-r--r--rcs/rcsmanager/src/java/com/android/ims/IRcsPresenceListener.aidl97
-rw-r--r--rcs/rcsmanager/src/java/com/android/ims/RcsException.java64
-rw-r--r--rcs/rcsmanager/src/java/com/android/ims/RcsManager.java384
-rw-r--r--rcs/rcsmanager/src/java/com/android/ims/RcsPresence.java305
-rw-r--r--rcs/rcsmanager/src/java/com/android/ims/RcsPresenceInfo.aidl34
-rw-r--r--rcs/rcsmanager/src/java/com/android/ims/RcsPresenceInfo.java354
-rw-r--r--rcs/rcsmanager/src/java/com/android/ims/internal/ContactNumberUtils.java343
-rw-r--r--rcs/rcsmanager/src/java/com/android/ims/internal/EABContract.java254
-rw-r--r--rcs/rcsmanager/src/java/com/android/ims/internal/IRcsPresence.aidl123
-rw-r--r--rcs/rcsmanager/src/java/com/android/ims/internal/IRcsService.aidl48
-rw-r--r--rcs/rcsmanager/src/java/com/android/ims/internal/Logger.java239
-rw-r--r--rcs/rcsservice/Android.mk47
-rw-r--r--rcs/rcsservice/AndroidManifest.xml90
-rw-r--r--rcs/rcsservice/res/values/config.xml48
-rw-r--r--rcs/rcsservice/res/values/strings.xml34
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/DeviceShutdown.java54
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/LauncherUtils.java75
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/RcsService.java403
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/RcsServiceApp.java46
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/RcsSettingUtils.java255
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/RcsStackAdaptor.java710
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/RcsUtils.java109
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/Task.java104
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/TaskManager.java394
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/presence/AlarmBroadcastReceiver.java82
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/presence/PresenceAvailabilityTask.java92
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/presence/PresenceBase.java184
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/presence/PresenceCapabilityTask.java183
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/presence/PresenceInfoParser.java277
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/presence/PresencePublication.java1227
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/presence/PresencePublishTask.java90
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/presence/PresenceSubscriber.java628
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/presence/PresenceTask.java76
-rw-r--r--rcs/rcsservice/src/com/android/service/ims/presence/StackListener.java535
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());
+ }
+ }
+ }
+ };
+}
+