Added RCS service.
Gao Bing [Tue, 26 Jan 2016 01:02:10 +0000 (17:02 -0800)]
    1> PUBLISH
    2> SUBSCRIBE
    3> Periodically Capability polling

Bug: 23222011
Change-Id: I3160dfcadd85bb55e2c3033128a6766a005b68b0

69 files changed:
Android.mk
rcs/Android.mk [new file with mode: 0644]
rcs/presencepolling/Android.mk [new file with mode: 0644]
rcs/presencepolling/AndroidManifest.xml [new file with mode: 0644]
rcs/presencepolling/res/values/config.xml [new file with mode: 0644]
rcs/presencepolling/res/values/strings.xml [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/AccountUtil.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/AlarmBroadcastReceiver.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/CapabilityPolling.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/ContactDbUtil.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/Contacts.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/DatabaseContentProvider.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/DeviceBoot.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/DeviceShutdown.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/EABContactManager.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/EABDbUtil.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/EABProvider.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/EABService.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/InvalidDBException.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/LauncherUtils.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/PersistService.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/PollingAction.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/PollingService.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/PollingTask.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/PollingsQueue.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/PresenceBroadcastReceiver.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/PresenceContact.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/PresencePreferences.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/PresenceSetting.java [new file with mode: 0644]
rcs/presencepolling/src/com/android/service/ims/presence/SharedPrefUtil.java [new file with mode: 0644]
rcs/rcsmanager/Android.mk [new file with mode: 0644]
rcs/rcsmanager/AndroidManifest.xml [new file with mode: 0644]
rcs/rcsmanager/CleanSpec.mk [new file with mode: 0644]
rcs/rcsmanager/com.android.ims.rcsmanager.xml [new file with mode: 0644]
rcs/rcsmanager/src/java/com/android/ims/IRcsPresenceListener.aidl [new file with mode: 0644]
rcs/rcsmanager/src/java/com/android/ims/RcsException.java [new file with mode: 0644]
rcs/rcsmanager/src/java/com/android/ims/RcsManager.java [new file with mode: 0644]
rcs/rcsmanager/src/java/com/android/ims/RcsPresence.java [new file with mode: 0644]
rcs/rcsmanager/src/java/com/android/ims/RcsPresenceInfo.aidl [new file with mode: 0644]
rcs/rcsmanager/src/java/com/android/ims/RcsPresenceInfo.java [new file with mode: 0644]
rcs/rcsmanager/src/java/com/android/ims/internal/ContactNumberUtils.java [new file with mode: 0644]
rcs/rcsmanager/src/java/com/android/ims/internal/EABContract.java [new file with mode: 0644]
rcs/rcsmanager/src/java/com/android/ims/internal/IRcsPresence.aidl [new file with mode: 0644]
rcs/rcsmanager/src/java/com/android/ims/internal/IRcsService.aidl [new file with mode: 0644]
rcs/rcsmanager/src/java/com/android/ims/internal/Logger.java [new file with mode: 0644]
rcs/rcsservice/Android.mk [new file with mode: 0644]
rcs/rcsservice/AndroidManifest.xml [new file with mode: 0644]
rcs/rcsservice/res/values/config.xml [new file with mode: 0644]
rcs/rcsservice/res/values/strings.xml [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/DeviceShutdown.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/LauncherUtils.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/RcsService.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/RcsServiceApp.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/RcsSettingUtils.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/RcsStackAdaptor.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/RcsUtils.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/Task.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/TaskManager.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/presence/AlarmBroadcastReceiver.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/presence/PresenceAvailabilityTask.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/presence/PresenceBase.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/presence/PresenceCapabilityTask.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/presence/PresenceInfoParser.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/presence/PresencePublication.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/presence/PresencePublishTask.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/presence/PresenceSubscriber.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/presence/PresenceTask.java [new file with mode: 0644]
rcs/rcsservice/src/com/android/service/ims/presence/StackListener.java [new file with mode: 0644]
src/java/com/android/ims/ImsConfig.java

index ee92ea6..2242958 100644 (file)
@@ -26,3 +26,5 @@ LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE := ims-common
 
 include $(BUILD_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/rcs/Android.mk b/rcs/Android.mk
new file mode 100644 (file)
index 0000000..7f1f7eb
--- /dev/null
@@ -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 (file)
index 0000000..f1f1471
--- /dev/null
@@ -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 (file)
index 0000000..12db21a
--- /dev/null
@@ -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 (file)
index 0000000..5d083a7
--- /dev/null
@@ -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 (file)
index 0000000..053aca0
--- /dev/null
@@ -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 (file)
index 0000000..0139f86
--- /dev/null
@@ -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 (file)
index 0000000..1e5c0ef
--- /dev/null
@@ -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 (file)
index 0000000..5d3e12f
--- /dev/null
@@ -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 (file)
index 0000000..e4f4a21
--- /dev/null
@@ -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 (file)
index 0000000..cbad25a
--- /dev/null
@@ -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 (file)
index 0000000..0ae9bc9
--- /dev/null
@@ -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 (file)
index 0000000..096d342
--- /dev/null
@@ -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 (file)
index 0000000..193fd09
--- /dev/null
@@ -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 (file)
index 0000000..cefe50b
--- /dev/null
@@ -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 (file)
index 0000000..8e851dc
--- /dev/null
@@ -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 (file)
index 0000000..651b1cd
--- /dev/null
@@ -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 (file)
index 0000000..e79293c
--- /dev/null
@@ -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 (file)
index 0000000..1e7fdeb
--- /dev/null
@@ -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 (file)
index 0000000..8362142
--- /dev/null
@@ -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 (file)
index 0000000..bbd9cee
--- /dev/null
@@ -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 (file)
index 0000000..fc297ae
--- /dev/null
@@ -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 (file)
index 0000000..d6e4410
--- /dev/null
@@ -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 (file)
index 0000000..69c4018
--- /dev/null
@@ -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 (file)
index 0000000..b833de7
--- /dev/null
@@ -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 (file)
index 0000000..6d716bd
--- /dev/null
@@ -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 (file)
index 0000000..a9bd8a5
--- /dev/null
@@ -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 (file)
index 0000000..b777c5c
--- /dev/null
@@ -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 (file)
index 0000000..9452237
--- /dev/null
@@ -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 (file)
index 0000000..9e9a31e
--- /dev/null
@@ -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 (file)
index 0000000..93452c5
--- /dev/null
@@ -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 (file)
index 0000000..630c170
--- /dev/null
@@ -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 (file)
index 0000000..c104bc4
--- /dev/null
@@ -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 (file)
index 0000000..0737f5d
--- /dev/null
@@ -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 --g