blob: 6d3eabf314b3f6d2322093afcb2412e48ee52a87 [file] [log] [blame]
/*
* Copyright (C) 2020 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.
*/
package com.android.ims.rcs.uce.eab;
import static android.telephony.ims.RcsContactUceCapability.CAPABILITY_MECHANISM_OPTIONS;
import static android.telephony.ims.RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE;
import android.app.AlarmManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.Telephony;
import android.telephony.CarrierConfigManager;
import android.telephony.ims.ImsManager;
import android.telephony.ims.ImsRcsManager;
import android.telephony.ims.RcsContactUceCapability;
import android.telephony.ims.aidl.IRcsUceControllerCallback;
import android.util.Log;
import com.android.ims.rcs.uce.UceController;
import java.util.ArrayList;
import java.util.List;
public final class EabBulkCapabilityUpdater {
private final String TAG = this.getClass().getSimpleName();
private static final Uri USER_EAB_SETTING = Uri.withAppendedPath(Telephony.SimInfo.CONTENT_URI,
Telephony.SimInfo.COLUMN_IMS_RCS_UCE_ENABLED);
private static final int NUM_SECS_IN_DAY = 86400;
private final int mSubId;
private final Context mContext;
private final Handler mHandler;
private final AlarmManager.OnAlarmListener mCapabilityExpiredListener;
private final ContactChangedListener mContactProviderListener;
private final EabSettingsListener mEabSettingListener;
private final BroadcastReceiver mCarrierConfigChangedListener;
private final EabControllerImpl mEabControllerImpl;
private final EabContactSyncController mEabContactSyncController;
private UceController.UceControllerCallback mUceControllerCallback;
private List<Uri> mRefreshContactList;
private boolean mIsContactProviderListenerRegistered = false;
private boolean mIsEabSettingListenerRegistered = false;
private boolean mIsCarrierConfigListenerRegistered = false;
private boolean mIsCarrierConfigEnabled = false;
/**
* Listen capability expired intent. Only registered when
* {@link CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} has enabled bulk
* capability exchange.
*/
private class CapabilityExpiredListener implements AlarmManager.OnAlarmListener {
@Override
public void onAlarm() {
Log.d(TAG, "Capability expired.");
try {
List<Uri> expiredContactList = getExpiredContactList();
if (expiredContactList.size() > 0) {
mUceControllerCallback.refreshCapabilities(
getExpiredContactList(),
mRcsUceControllerCallback);
} else {
Log.d(TAG, "expiredContactList is empty.");
}
} catch (RemoteException e) {
Log.e(TAG, "CapabilityExpiredListener RemoteException", e);
}
}
}
/**
* Listen contact provider change. Only registered when
* {@link CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} has enabled bulk
* capability exchange.
*/
private class ContactChangedListener extends ContentObserver {
public ContactChangedListener(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
Log.d(TAG, "Contact changed");
syncContactAndRefreshCapabilities();
}
}
/**
* Listen EAB settings change. Only registered when
* {@link CarrierConfigManager.Ims#KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL} has enabled bulk
* capability exchange.
*/
private class EabSettingsListener extends ContentObserver {
public EabSettingsListener(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
boolean isUserEnableUce = isUserEnableUce();
Log.d(TAG, "EAB user setting changed: " + isUserEnableUce);
if (isUserEnableUce) {
mHandler.post(new SyncContactRunnable());
} else {
unRegisterContactProviderListener();
cancelTimeAlert(mContext);
}
}
}
/**
* Listen carrier config changed to prevent this instance created before carrier config loaded.
*/
private class CarrierConfigChangedListener extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
boolean isSupportBulkCapabilityExchange = getBooleanCarrierConfig(
CarrierConfigManager.Ims.KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, mSubId);
Log.d(TAG, "Carrier config changed. "
+ "isCarrierConfigEnabled: " + mIsCarrierConfigEnabled
+ ", isSupportBulkCapabilityExchange: " + isSupportBulkCapabilityExchange);
if (!mIsCarrierConfigEnabled && isSupportBulkCapabilityExchange) {
enableBulkCapability();
updateExpiredTimeAlert();
mIsCarrierConfigEnabled = true;
} else if (mIsCarrierConfigEnabled && !isSupportBulkCapabilityExchange) {
onDestroy();
}
}
}
private IRcsUceControllerCallback mRcsUceControllerCallback = new IRcsUceControllerCallback() {
@Override
public void onCapabilitiesReceived(List<RcsContactUceCapability> contactCapabilities) {
Log.d(TAG, "onCapabilitiesReceived");
mEabControllerImpl.saveCapabilities(contactCapabilities);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete");
}
@Override
public void onError(int errorCode, long retryAfterMilliseconds) {
Log.d(TAG, "Refresh capabilities failed. Error code: " + errorCode
+ ", retryAfterMilliseconds: " + retryAfterMilliseconds);
if (retryAfterMilliseconds != 0) {
mHandler.postDelayed(new retryRunnable(), retryAfterMilliseconds);
}
}
@Override
public IBinder asBinder() {
return null;
}
};
private class SyncContactRunnable implements Runnable {
@Override
public void run() {
Log.d(TAG, "Sync contact from contact provider");
syncContactAndRefreshCapabilities();
registerContactProviderListener();
registerEabUserSettingsListener();
}
}
/**
* Re-refresh capability if error happened.
*/
private class retryRunnable implements Runnable {
@Override
public void run() {
Log.d(TAG, "Retry refreshCapabilities()");
try {
mUceControllerCallback.refreshCapabilities(
mRefreshContactList, mRcsUceControllerCallback);
} catch (RemoteException e) {
Log.e(TAG, "refreshCapabilities RemoteException" , e);
}
}
}
public EabBulkCapabilityUpdater(Context context,
int subId,
EabControllerImpl eabControllerImpl,
EabContactSyncController eabContactSyncController,
UceController.UceControllerCallback uceControllerCallback,
Handler handler) {
mContext = context;
mSubId = subId;
mEabControllerImpl = eabControllerImpl;
mEabContactSyncController = eabContactSyncController;
mUceControllerCallback = uceControllerCallback;
mHandler = handler;
mContactProviderListener = new ContactChangedListener(mHandler);
mEabSettingListener = new EabSettingsListener(mHandler);
mCapabilityExpiredListener = new CapabilityExpiredListener();
mCarrierConfigChangedListener = new CarrierConfigChangedListener();
Log.d(TAG, "create EabBulkCapabilityUpdater() subId: " + mSubId);
enableBulkCapability();
updateExpiredTimeAlert();
}
private void enableBulkCapability() {
boolean isUserEnableUce = isUserEnableUce();
boolean isSupportBulkCapabilityExchange = getBooleanCarrierConfig(
CarrierConfigManager.Ims.KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, mSubId);
Log.d(TAG, "isUserEnableUce: " + isUserEnableUce
+ ", isSupportBulkCapabilityExchange: " + isSupportBulkCapabilityExchange);
if (isUserEnableUce && isSupportBulkCapabilityExchange) {
mHandler.post(new SyncContactRunnable());
mIsCarrierConfigEnabled = true;
} else if (!isUserEnableUce && isSupportBulkCapabilityExchange) {
registerEabUserSettingsListener();
mIsCarrierConfigEnabled = false;
} else {
registerCarrierConfigChanged();
Log.d(TAG, "Not support bulk capability exchange.");
}
}
private void syncContactAndRefreshCapabilities() {
mRefreshContactList = mEabContactSyncController.syncContactToEabProvider(mContext);
Log.d(TAG, "refresh contacts number: " + mRefreshContactList.size());
if (mUceControllerCallback == null) {
Log.d(TAG, "mUceControllerCallback is null.");
return;
}
try {
if (mRefreshContactList.size() > 0) {
mUceControllerCallback.refreshCapabilities(
mRefreshContactList, mRcsUceControllerCallback);
}
} catch (RemoteException e) {
Log.e(TAG, "mUceControllerCallback RemoteException.", e);
}
}
protected void updateExpiredTimeAlert() {
boolean isUserEnableUce = isUserEnableUce();
boolean isSupportBulkCapabilityExchange = getBooleanCarrierConfig(
CarrierConfigManager.Ims.KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, mSubId);
Log.d(TAG, " updateExpiredTimeAlert(), isUserEnableUce: " + isUserEnableUce
+ ", isSupportBulkCapabilityExchange: " + isSupportBulkCapabilityExchange);
if (isUserEnableUce && isSupportBulkCapabilityExchange) {
long expiredTimestamp = getLeastExpiredTimestamp();
if (expiredTimestamp == Long.MAX_VALUE) {
Log.d(TAG, "Can't find min timestamp in eab provider");
return;
}
expiredTimestamp += mEabControllerImpl.getCapabilityCacheExpiration(mSubId);
Log.d(TAG, "set time alert at " + expiredTimestamp);
cancelTimeAlert(mContext);
setTimeAlert(mContext, expiredTimestamp);
}
}
private long getLeastExpiredTimestamp() {
String selection = "("
// Query presence timestamp
+ EabProvider.EabCommonColumns.MECHANISM + "=" + CAPABILITY_MECHANISM_PRESENCE
+ " AND "
+ EabProvider.PresenceTupleColumns.REQUEST_TIMESTAMP + " IS NOT NULL) "
// Query options timestamp
+ " OR " + "(" + EabProvider.EabCommonColumns.MECHANISM + "="
+ CAPABILITY_MECHANISM_OPTIONS + " AND "
+ EabProvider.OptionsColumns.REQUEST_TIMESTAMP + " IS NOT NULL) "
// filter by sub id
+ " AND " + EabProvider.EabCommonColumns.SUBSCRIPTION_ID + "=" + mSubId
// filter the contact that not come from contact provider
+ " AND " + EabProvider.ContactColumns.RAW_CONTACT_ID + " IS NOT NULL "
+ " AND " + EabProvider.ContactColumns.DATA_ID + " IS NOT NULL ";
long minTimestamp = Long.MAX_VALUE;
Cursor result = mContext.getContentResolver().query(EabProvider.ALL_DATA_URI, null,
selection,
null, null);
if (result != null) {
while (result.moveToNext()) {
int mechanism = result.getInt(
result.getColumnIndex(EabProvider.EabCommonColumns.MECHANISM));
long timestamp;
if (mechanism == CAPABILITY_MECHANISM_PRESENCE) {
timestamp = result.getLong(result.getColumnIndex(
EabProvider.PresenceTupleColumns.REQUEST_TIMESTAMP));
} else {
timestamp = result.getLong(result.getColumnIndex(
EabProvider.OptionsColumns.REQUEST_TIMESTAMP));
}
if (timestamp < minTimestamp) {
minTimestamp = timestamp;
}
}
result.close();
} else {
Log.d(TAG, "getLeastExpiredTimestamp() cursor is null");
}
return minTimestamp;
}
private void setTimeAlert(Context context, long wakeupTimeMs) {
AlarmManager am = context.getSystemService(AlarmManager.class);
// To prevent all devices from sending requests to the server at the same time, add a jitter
// time (0 sec ~ 2 days) randomly.
int jitterTimeSec = (int) (Math.random() * (NUM_SECS_IN_DAY * 2));
Log.d(TAG, " setTimeAlert: " + wakeupTimeMs + ", jitterTimeSec: " + jitterTimeSec);
am.set(AlarmManager.RTC_WAKEUP,
(wakeupTimeMs * 1000) + jitterTimeSec,
TAG,
mCapabilityExpiredListener,
mHandler);
}
private void cancelTimeAlert(Context context) {
Log.d(TAG, "cancelTimeAlert.");
AlarmManager am = context.getSystemService(AlarmManager.class);
am.cancel(mCapabilityExpiredListener);
}
private boolean getBooleanCarrierConfig(String key, int subId) {
CarrierConfigManager mConfigManager = mContext.getSystemService(CarrierConfigManager.class);
PersistableBundle b = null;
if (mConfigManager != null) {
b = mConfigManager.getConfigForSubId(subId);
}
if (b != null) {
return b.getBoolean(key);
} else {
Log.w(TAG, "getConfigForSubId(subId) is null. Return the default value of " + key);
return CarrierConfigManager.getDefaultConfig().getBoolean(key);
}
}
private boolean isUserEnableUce() {
ImsManager manager = mContext.getSystemService(ImsManager.class);
if (manager == null) {
Log.e(TAG, "ImsManager is null");
return false;
}
try {
ImsRcsManager rcsManager = manager.getImsRcsManager(mSubId);
return (rcsManager != null) && rcsManager.getUceAdapter().isUceSettingEnabled();
} catch (Exception e) {
Log.e(TAG, "hasUserEnabledUce: exception = " + e.getMessage());
}
return false;
}
private List<Uri> getExpiredContactList() {
List<Uri> refreshList = new ArrayList<>();
long expiredTime = (System.currentTimeMillis() / 1000)
+ mEabControllerImpl.getCapabilityCacheExpiration(mSubId);
String selection = "("
+ EabProvider.EabCommonColumns.MECHANISM + "=" + CAPABILITY_MECHANISM_PRESENCE
+ " AND " + EabProvider.PresenceTupleColumns.REQUEST_TIMESTAMP + "<"
+ expiredTime + ")";
selection += " OR " + "(" + EabProvider.EabCommonColumns.MECHANISM + "="
+ CAPABILITY_MECHANISM_OPTIONS + " AND "
+ EabProvider.OptionsColumns.REQUEST_TIMESTAMP + "<" + expiredTime + ")";
Cursor result = mContext.getContentResolver().query(EabProvider.ALL_DATA_URI, null,
selection,
null, null);
while (result.moveToNext()) {
String phoneNumber = result.getString(
result.getColumnIndex(EabProvider.ContactColumns.PHONE_NUMBER));
refreshList.add(Uri.parse(phoneNumber));
}
result.close();
return refreshList;
}
protected void onDestroy() {
Log.d(TAG, "onDestroy");
cancelTimeAlert(mContext);
unRegisterContactProviderListener();
unRegisterEabUserSettings();
unRegisterCarrierConfigChanged();
}
private void registerContactProviderListener() {
Log.d(TAG, "registerContactProviderListener");
mIsContactProviderListenerRegistered = true;
mContext.getContentResolver().registerContentObserver(
ContactsContract.Contacts.CONTENT_URI,
true,
mContactProviderListener);
}
private void registerEabUserSettingsListener() {
Log.d(TAG, "registerEabUserSettingsListener");
mIsEabSettingListenerRegistered = true;
mContext.getContentResolver().registerContentObserver(
USER_EAB_SETTING,
true,
mEabSettingListener);
}
private void registerCarrierConfigChanged() {
Log.d(TAG, "registerCarrierConfigChanged");
mIsCarrierConfigListenerRegistered = true;
IntentFilter FILTER_CARRIER_CONFIG_CHANGED =
new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
mContext.registerReceiver(mCarrierConfigChangedListener, FILTER_CARRIER_CONFIG_CHANGED);
}
private void unRegisterContactProviderListener() {
Log.d(TAG, "unRegisterContactProviderListener");
if (mIsContactProviderListenerRegistered) {
mIsContactProviderListenerRegistered = false;
mContext.getContentResolver().unregisterContentObserver(mContactProviderListener);
}
}
private void unRegisterEabUserSettings() {
Log.d(TAG, "unRegisterEabUserSettings");
if (mIsEabSettingListenerRegistered) {
mIsEabSettingListenerRegistered = false;
mContext.getContentResolver().unregisterContentObserver(mEabSettingListener);
}
}
private void unRegisterCarrierConfigChanged() {
Log.d(TAG, "unregisterCarrierConfigChanged");
if (mIsCarrierConfigListenerRegistered) {
mIsCarrierConfigListenerRegistered = false;
mContext.unregisterReceiver(mCarrierConfigChangedListener);
}
}
public void setUceRequestCallback(UceController.UceControllerCallback uceControllerCallback) {
mUceControllerCallback = uceControllerCallback;
}
}