blob: 2ccc9742685208e265f95804a1a55c84deb0f557 [file] [log] [blame]
/*
* Copyright (C) 2007-2008 Esmertec AG.
* Copyright (C) 2007-2008 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.im.service;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.SystemProperties;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.android.common.NetworkConnectivityListener;
import com.android.common.NetworkConnectivityListener.State;
import com.android.im.IConnectionCreationListener;
import com.android.im.IImConnection;
import com.android.im.IRemoteImService;
import com.android.im.app.ImPluginHelper;
import com.android.im.engine.ConnectionFactory;
import com.android.im.engine.ImConnection;
import com.android.im.engine.ImException;
import com.android.im.imps.ImpsConnectionConfig;
import com.android.im.plugin.ImConfigNames;
import com.android.im.plugin.ImPluginInfo;
import com.android.im.plugin.ImpsConfigNames;
import com.android.im.provider.Imps;
public class RemoteImService extends Service {
private static final String[] ACCOUNT_PROJECTION = {
Imps.Account._ID,
Imps.Account.PROVIDER,
Imps.Account.USERNAME,
Imps.Account.PASSWORD,
};
private static final int ACCOUNT_ID_COLUMN = 0;
private static final int ACCOUNT_PROVIDER_COLUMN = 1;
private static final int ACCOUNT_USERNAME_COLUMN = 2;
private static final int ACCOUNT_PASSOWRD_COLUMN = 3;
static final String TAG = "ImService";
private static final int EVENT_SHOW_TOAST = 100;
private static final int EVENT_NETWORK_STATE_CHANGED = 200;
private StatusBarNotifier mStatusBarNotifier;
private Handler mServiceHandler;
NetworkConnectivityListener mNetworkConnectivityListener;
private int mNetworkType;
private boolean mNeedCheckAutoLogin;
private boolean mBackgroundDataEnabled;
private SettingsMonitor mSettingsMonitor;
private ImPluginHelper mPluginHelper;
Vector<ImConnectionAdapter> mConnections;
final RemoteCallbackList<IConnectionCreationListener> mRemoteListeners
= new RemoteCallbackList<IConnectionCreationListener>();
public RemoteImService() {
mConnections = new Vector<ImConnectionAdapter>();
}
@Override
public void onCreate() {
Log.d(TAG, "ImService started");
mStatusBarNotifier = new StatusBarNotifier(this);
mServiceHandler = new ServiceHandler();
mNetworkConnectivityListener = new NetworkConnectivityListener();
mNetworkConnectivityListener.registerHandler(mServiceHandler, EVENT_NETWORK_STATE_CHANGED);
mNetworkConnectivityListener.startListening(this);
mSettingsMonitor = new SettingsMonitor();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
registerReceiver(mSettingsMonitor, intentFilter);
ConnectivityManager manager
= (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
setBackgroundData(manager.getBackgroundDataSetting());
mPluginHelper = ImPluginHelper.getInstance(this);
mPluginHelper.loadAvaiablePlugins();
AndroidSystemService.getInstance().initialize(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
mNeedCheckAutoLogin = intent.getBooleanExtra(ImServiceConstants.EXTRA_CHECK_AUTO_LOGIN, false);
Log.d(TAG, "ImService.onStart, checkAutoLogin=" + mNeedCheckAutoLogin);
// Check and login accounts if network is ready, otherwise it's checked
// when the network becomes available.
if (mNeedCheckAutoLogin &&
mNetworkConnectivityListener.getState() == State.CONNECTED) {
mNeedCheckAutoLogin = false;
autoLogin();
}
}
return super.onStartCommand(intent, flags, startId);
}
private void autoLogin() {
Log.d(TAG, "Scaning accounts and login automatically");
ContentResolver resolver = getContentResolver();
String where = Imps.Account.KEEP_SIGNED_IN + "=1 AND " + Imps.Account.ACTIVE + "=1";
Cursor cursor = resolver.query(Imps.Account.CONTENT_URI,
ACCOUNT_PROJECTION, where, null, null);
if (cursor == null) {
Log.w(TAG, "Can't query account!");
return;
}
while (cursor.moveToNext()) {
long accountId = cursor.getLong(ACCOUNT_ID_COLUMN);
long providerId = cursor.getLong(ACCOUNT_PROVIDER_COLUMN);
String username = cursor.getString(ACCOUNT_USERNAME_COLUMN);
String password = cursor.getString(ACCOUNT_PASSOWRD_COLUMN);
IImConnection conn = createConnection(providerId);
try {
conn.login(accountId, username, password, true);
} catch (RemoteException e) {
Log.w(TAG, "Logging error while automatically login!");
}
}
cursor.close();
}
private Map<String, String> loadProviderSettings(long providerId) {
ContentResolver cr = getContentResolver();
Map<String, String> settings = Imps.ProviderSettings.queryProviderSettings(cr, providerId);
NetworkInfo networkInfo = mNetworkConnectivityListener.getNetworkInfo();
// Insert a fake msisdn on emulator. We don't need this on device
// because the mobile network will take care of it.
if ("1".equals(SystemProperties.get("ro.kernel.qemu"))) {
settings.put(ImpsConfigNames.MSISDN, "15555218135");
} else if (networkInfo != null
&& networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
if (!TextUtils.isEmpty(settings.get(ImpsConfigNames.SMS_ADDR))) {
// Send authentication through sms if SMS data channel is
// supported and WiFi is used.
settings.put(ImpsConfigNames.SMS_AUTH, "true");
settings.put(ImpsConfigNames.SECURE_LOGIN, "false");
} else {
// Wi-Fi network won't insert a MSISDN, we should get from the SIM
// card. Assume we can always get the correct MSISDN from SIM, otherwise,
// the sign in would fail and an error message should be shown to warn
// the user to contact their operator.
String msisdn = TelephonyManager.getDefault().getLine1Number();
if (TextUtils.isEmpty(msisdn)) {
Log.w(TAG, "Can not read MSISDN from SIM, use a fake one."
+ " SMS related feature won't work.");
msisdn = "15555218135";
}
settings.put(ImpsConfigNames.MSISDN, msisdn);
}
}
return settings;
}
@Override
public void onDestroy() {
Log.w(TAG, "ImService stopped.");
for (ImConnectionAdapter conn : mConnections) {
conn.logout();
}
AndroidSystemService.getInstance().shutdown();
mNetworkConnectivityListener.unregisterHandler(mServiceHandler);
mNetworkConnectivityListener.stopListening();
mNetworkConnectivityListener = null;
unregisterReceiver(mSettingsMonitor);
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public void showToast(CharSequence text, int duration) {
Message msg = Message.obtain(mServiceHandler, EVENT_SHOW_TOAST, duration, 0, text);
msg.sendToTarget();
}
public StatusBarNotifier getStatusBarNotifier() {
return mStatusBarNotifier;
}
public void scheduleReconnect(long delay) {
if (!isNetworkAvailable()) {
// Don't schedule reconnect if no network available. We will try to
// reconnect when network state become CONNECTED.
return;
}
mServiceHandler.postDelayed(new Runnable() {
public void run() {
reestablishConnections();
}
}, delay);
}
IImConnection createConnection(long providerId) {
Map<String, String> settings = loadProviderSettings(providerId);
String protocol = settings.get(ImConfigNames.PROTOCOL_NAME);
if(!"IMPS".equals(protocol)) {
Log.e(TAG, "Unsupported protocol: " + protocol);
return null;
}
ImpsConnectionConfig config = new ImpsConnectionConfig(settings);
ConnectionFactory factory = ConnectionFactory.getInstance();
try {
ImConnection conn = factory.createConnection(config);
ImConnectionAdapter result = new ImConnectionAdapter(providerId,
conn, this);
mConnections.add(result);
final int N = mRemoteListeners.beginBroadcast();
for (int i = 0; i < N; i++) {
IConnectionCreationListener listener = mRemoteListeners.getBroadcastItem(i);
try {
listener.onConnectionCreated(result);
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing the
// dead listeners.
}
}
mRemoteListeners.finishBroadcast();
return result;
} catch (ImException e) {
Log.e(TAG, "Error creating connection", e);
return null;
}
}
void removeConnection(IImConnection connection) {
mConnections.remove(connection);
}
private boolean isNetworkAvailable() {
return mNetworkConnectivityListener.getState() == State.CONNECTED;
}
private boolean isBackgroundDataEnabled() {
return mBackgroundDataEnabled;
}
private void setBackgroundData(boolean flag) {
mBackgroundDataEnabled = flag;
}
void handleBackgroundDataSettingChange(){
if (!isBackgroundDataEnabled()) {
for (ImConnectionAdapter conn : mConnections) {
conn.logout();
}
}
}
void networkStateChanged() {
if (mNetworkConnectivityListener == null) {
return;
}
NetworkInfo networkInfo = mNetworkConnectivityListener.getNetworkInfo();
NetworkInfo.State state = networkInfo.getState();
Log.d(TAG, "networkStateChanged:" + state);
int oldType = mNetworkType;
mNetworkType = networkInfo.getType();
// Notify the connection that network type has changed. Note that this
// only work for connected connections, we need to reestablish if it's
// suspended.
if (mNetworkType != oldType
&& isNetworkAvailable()) {
for (ImConnectionAdapter conn : mConnections) {
conn.networkTypeChanged();
}
}
switch (state) {
case CONNECTED:
if (mNeedCheckAutoLogin) {
mNeedCheckAutoLogin = false;
autoLogin();
break;
}
reestablishConnections();
break;
case DISCONNECTED:
if (!isNetworkAvailable()) {
suspendConnections();
}
break;
}
}
// package private for inner class access
void reestablishConnections() {
if (!isNetworkAvailable()) {
return;
}
for (ImConnectionAdapter conn : mConnections) {
int connState = conn.getState();
if (connState == ImConnection.SUSPENDED) {
conn.reestablishSession();
}
}
}
private void suspendConnections() {
for (ImConnectionAdapter conn : mConnections) {
if (conn.getState() != ImConnection.LOGGED_IN) {
continue;
}
conn.suspend();
}
}
private final IRemoteImService.Stub mBinder = new IRemoteImService.Stub() {
public List<ImPluginInfo> getAllPlugins() {
return new ArrayList<ImPluginInfo>(mPluginHelper.getPluginsInfo());
}
public void addConnectionCreatedListener(IConnectionCreationListener listener) {
if (listener != null) {
mRemoteListeners.register(listener);
}
}
public void removeConnectionCreatedListener(IConnectionCreationListener listener) {
if (listener != null) {
mRemoteListeners.unregister(listener);
}
}
public IImConnection createConnection(long providerId) {
return RemoteImService.this.createConnection(providerId);
}
public List getActiveConnections() {
ArrayList<IBinder> result = new ArrayList<IBinder>(mConnections.size());
for(IImConnection conn : mConnections) {
result.add(conn.asBinder());
}
return result;
}
public void dismissNotifications(long providerId) {
mStatusBarNotifier.dismissNotifications(providerId);
}
public void dismissChatNotification(long providerId, String username) {
mStatusBarNotifier.dismissChatNotification(providerId, username);
}
};
private final class SettingsMonitor extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED.equals(action)) {
ConnectivityManager manager =
(ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
setBackgroundData(manager.getBackgroundDataSetting());
handleBackgroundDataSettingChange();
}
}
}
private final class ServiceHandler extends Handler {
public ServiceHandler() {
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_SHOW_TOAST:
Toast.makeText(RemoteImService.this,
(CharSequence) msg.obj, msg.arg1).show();
break;
case EVENT_NETWORK_STATE_CHANGED:
networkStateChanged();
break;
default:
}
}
}
}