blob: 02f9caea8ef9221609727988dd8899e7ebd3dfa7 [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.app;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import android.app.Activity;
import android.app.Application;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.Broadcaster;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import com.android.im.IConnectionCreationListener;
import com.android.im.IImConnection;
import com.android.im.IRemoteImService;
import com.android.im.R;
import com.android.im.app.adapter.ConnectionListenerAdapter;
import com.android.im.engine.ImConnection;
import com.android.im.engine.ImErrorInfo;
import com.android.im.plugin.BrandingResourceIDs;
import com.android.im.plugin.ImPlugin;
import com.android.im.plugin.ImPluginInfo;
import com.android.im.provider.Imps;
import com.android.im.service.ImServiceConstants;
public class ImApp extends Application {
public static final String LOG_TAG = "ImApp";
public static final String EXTRA_INTENT_SEND_TO_USER = "Send2_U";
public static final String EXTRA_INTENT_PASSWORD = "password";
public static final String IMPS_CATEGORY = "com.android.im.IMPS_CATEGORY";
private static ImApp sImApp;
IRemoteImService mImService;
HashMap<Long, IImConnection> mConnections;
MyConnListener mConnectionListener;
HashMap<Long, ProviderDef> mProviders;
Broadcaster mBroadcaster;
/** A queue of messages that are waiting to be sent when service is connected.*/
ArrayList<Message> mQueue = new ArrayList<Message>();
/** A flag indicates that we have called to start the service.*/
private boolean mServiceStarted;
private Context mApplicationContext;
private Resources mPrivateResources;
private HashMap<String, BrandingResources> mBrandingResources;
private BrandingResources mDefaultBrandingResources;
public static final int EVENT_SERVICE_CONNECTED = 100;
public static final int EVENT_CONNECTION_CREATED = 150;
public static final int EVENT_CONNECTION_LOGGING_IN = 200;
public static final int EVENT_CONNECTION_LOGGED_IN = 201;
public static final int EVENT_CONNECTION_LOGGING_OUT = 202;
public static final int EVENT_CONNECTION_DISCONNECTED = 203;
public static final int EVENT_CONNECTION_SUSPENDED = 204;
public static final int EVENT_USER_PRESENCE_UPDATED = 300;
public static final int EVENT_UPDATE_USER_PRESENCE_ERROR = 301;
private static final String[] PROVIDER_PROJECTION = {
Imps.Provider._ID,
Imps.Provider.NAME,
Imps.Provider.FULLNAME,
Imps.Provider.SIGNUP_URL,
};
private static final String[] ACCOUNT_PROJECTION = {
Imps.Account._ID,
Imps.Account.PROVIDER,
Imps.Account.NAME,
Imps.Account.USERNAME,
Imps.Account.PASSWORD,
};
static final void log(String log) {
Log.d(LOG_TAG, log);
}
public static ImApp getApplication(Activity activity) {
// TODO should this be synchronized?
if (sImApp == null) {
initialize(activity);
}
return sImApp;
}
/**
* Initialize performs the manual ImApp instantiation and initialization. When the
* ImApp is started first in the process, the ImApp public constructor should be called,
* and sImApp initialized. So calling initialize() later should have no effect. However,
* if another application runs in the same process and is started first, the ImApp
* application object won't be instantiated, and we need to call initialize() manually to
* instantiate and initialize it.
*/
private static void initialize(Activity activity) {
// construct the TalkApp manually and call onCreate().
sImApp = new ImApp();
sImApp.mApplicationContext = activity.getApplication();
sImApp.mPrivateResources = activity.getResources();
sImApp.onCreate();
}
@Override
public Resources getResources() {
if (mApplicationContext == this) {
return super.getResources();
}
return mPrivateResources;
}
@Override
public ContentResolver getContentResolver() {
if (mApplicationContext == this) {
return super.getContentResolver();
}
return mApplicationContext.getContentResolver();
}
public ImApp() {
super();
mConnections = new HashMap<Long, IImConnection>();
mApplicationContext = this;
sImApp = this;
}
@Override
public void onCreate() {
super.onCreate();
mBroadcaster = new Broadcaster();
loadDefaultBrandingRes();
}
@Override
public void onTerminate() {
stopImServiceIfInactive();
if (mImService != null) {
try {
mImService.removeConnectionCreatedListener(mConnCreationListener);
} catch (RemoteException e) {
Log.w(LOG_TAG, "failed to remove ConnectionCreatedListener");
}
}
super.onTerminate();
}
public synchronized void startImServiceIfNeed() {
if(!mServiceStarted) {
if(Log.isLoggable(LOG_TAG, Log.DEBUG)) log("start ImService");
Intent serviceIntent = new Intent();
serviceIntent.setComponent(ImServiceConstants.IM_SERVICE_COMPONENT);
mApplicationContext.startService(serviceIntent);
mApplicationContext.bindService(serviceIntent, mImServiceConn, Context.BIND_AUTO_CREATE);
mServiceStarted = true;
mConnectionListener = new MyConnListener(new Handler());
}
}
public synchronized void stopImServiceIfInactive() {
boolean hasActiveConnection = true;
synchronized (mConnections) {
hasActiveConnection = !mConnections.isEmpty();
}
if (!hasActiveConnection && mServiceStarted) {
if (Log.isLoggable(LOG_TAG, Log.DEBUG))
log("stop ImService because there's no active connections");
if(mImService != null) {
mApplicationContext.unbindService(mImServiceConn);
mImService = null;
}
Intent intent = new Intent();
intent.setComponent(ImServiceConstants.IM_SERVICE_COMPONENT);
mApplicationContext.stopService(intent);
mServiceStarted = false;
}
}
private ServiceConnection mImServiceConn = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
if(Log.isLoggable(LOG_TAG, Log.DEBUG))
log("service connected");
mImService = IRemoteImService.Stub.asInterface(service);
fetchActiveConnections();
synchronized (mQueue) {
for (Message msg : mQueue) {
msg.sendToTarget();
}
mQueue.clear();
}
Message msg = Message.obtain(null, EVENT_SERVICE_CONNECTED);
mBroadcaster.broadcast(msg);
}
public void onServiceDisconnected(ComponentName className) {
if(Log.isLoggable(LOG_TAG, Log.DEBUG))
log("service disconnected");
mConnections.clear();
mImService = null;
}
};
public boolean serviceConnected() {
return mImService != null;
}
public boolean isBackgroundDataEnabled() {
ConnectivityManager manager =
(ConnectivityManager) mApplicationContext.getSystemService(CONNECTIVITY_SERVICE);
return manager.getBackgroundDataSetting();
}
public static long insertOrUpdateAccount(ContentResolver cr,
long providerId, String userName, String pw) {
String selection = Imps.Account.PROVIDER + "=? AND " + Imps.Account.USERNAME + "=?";
String[] selectionArgs = {Long.toString(providerId), userName };
Cursor c = cr.query(Imps.Account.CONTENT_URI, ACCOUNT_PROJECTION,
selection, selectionArgs, null);
if (c != null && c.moveToFirst()) {
// Update the password
c.updateString(c.getColumnIndexOrThrow(Imps.Account.PASSWORD), pw);
c.commitUpdates();
long id = c.getLong(c.getColumnIndexOrThrow(Imps.Account._ID));
c.close();
return id;
} else {
ContentValues values = new ContentValues(4);
values.put(Imps.Account.PROVIDER, providerId);
values.put(Imps.Account.NAME, userName);
values.put(Imps.Account.USERNAME, userName);
values.put(Imps.Account.PASSWORD, pw);
Uri result = cr.insert(Imps.Account.CONTENT_URI, values);
return ContentUris.parseId(result);
}
}
private void loadImProviderSettings() {
if (mProviders != null) {
return;
}
mProviders = new HashMap<Long, ProviderDef>();
ContentResolver cr = getContentResolver();
String selectionArgs[] = new String[1];
selectionArgs[0] = ImApp.IMPS_CATEGORY;
Cursor c = cr.query(Imps.Provider.CONTENT_URI, PROVIDER_PROJECTION,
Imps.Provider.CATEGORY+"=?", selectionArgs, null);
if (c == null) {
return;
}
try {
while (c.moveToNext()) {
long id = c.getLong(0);
String providerName = c.getString(1);
String fullName = c.getString(2);
String signUpUrl = c.getString(3);
mProviders.put(id, new ProviderDef(id, providerName, fullName, signUpUrl));
}
} finally {
c.close();
}
}
private void loadDefaultBrandingRes() {
HashMap<Integer, Integer> resMapping = new HashMap<Integer, Integer>();
resMapping.put(BrandingResourceIDs.DRAWABLE_LOGO, R.drawable.imlogo_s);
resMapping.put(BrandingResourceIDs.DRAWABLE_PRESENCE_ONLINE,
android.R.drawable.presence_online);
resMapping.put(BrandingResourceIDs.DRAWABLE_PRESENCE_AWAY,
android.R.drawable.presence_away);
resMapping.put(BrandingResourceIDs.DRAWABLE_PRESENCE_BUSY,
android.R.drawable.presence_busy);
resMapping.put(BrandingResourceIDs.DRAWABLE_PRESENCE_INVISIBLE,
android.R.drawable.presence_invisible);
resMapping.put(BrandingResourceIDs.DRAWABLE_PRESENCE_OFFLINE,
android.R.drawable.presence_offline);
resMapping.put(BrandingResourceIDs.DRAWABLE_READ_CHAT,
R.drawable.status_chat);
resMapping.put(BrandingResourceIDs.DRAWABLE_UNREAD_CHAT,
R.drawable.status_chat_new);
resMapping.put(BrandingResourceIDs.DRAWABLE_BLOCK,
R.drawable.ic_im_block);
resMapping.put(BrandingResourceIDs.STRING_ARRAY_SMILEY_NAMES,
R.array.default_smiley_names);
resMapping.put(BrandingResourceIDs.STRING_ARRAY_SMILEY_TEXTS,
R.array.default_smiley_texts);
resMapping.put(BrandingResourceIDs.STRING_PRESENCE_AVAILABLE,
R.string.presence_available);
resMapping.put(BrandingResourceIDs.STRING_PRESENCE_BUSY,
R.string.presence_busy);
resMapping.put(BrandingResourceIDs.STRING_PRESENCE_AWAY,
R.string.presence_away);
resMapping.put(BrandingResourceIDs.STRING_PRESENCE_IDLE,
R.string.presence_idle);
resMapping.put(BrandingResourceIDs.STRING_PRESENCE_OFFLINE,
R.string.presence_offline);
resMapping.put(BrandingResourceIDs.STRING_PRESENCE_INVISIBLE,
R.string.presence_invisible);
resMapping.put(BrandingResourceIDs.STRING_LABEL_USERNAME,
R.string.label_username);
resMapping.put(BrandingResourceIDs.STRING_ONGOING_CONVERSATION,
R.string.ongoing_conversation);
resMapping.put(BrandingResourceIDs.STRING_ADD_CONTACT_TITLE,
R.string.add_contact_title);
resMapping.put(BrandingResourceIDs.STRING_LABEL_INPUT_CONTACT,
R.string.input_contact_label);
resMapping.put(BrandingResourceIDs.STRING_BUTTON_ADD_CONTACT,
R.string.invite_label);
resMapping.put(BrandingResourceIDs.STRING_CONTACT_INFO_TITLE,
R.string.contact_profile_title);
resMapping.put(BrandingResourceIDs.STRING_MENU_ADD_CONTACT,
R.string.menu_add_contact);
resMapping.put(BrandingResourceIDs.STRING_MENU_BLOCK_CONTACT,
R.string.menu_block_contact);
resMapping.put(BrandingResourceIDs.STRING_MENU_CONTACT_LIST,
R.string.menu_view_contact_list);
resMapping.put(BrandingResourceIDs.STRING_MENU_DELETE_CONTACT,
R.string.menu_remove_contact);
resMapping.put(BrandingResourceIDs.STRING_MENU_END_CHAT,
R.string.menu_end_conversation);
resMapping.put(BrandingResourceIDs.STRING_MENU_INSERT_SMILEY,
R.string.menu_insert_smiley);
resMapping.put(BrandingResourceIDs.STRING_MENU_START_CHAT,
R.string.menu_start_chat);
resMapping.put(BrandingResourceIDs.STRING_MENU_VIEW_PROFILE,
R.string.menu_view_profile);
resMapping.put(BrandingResourceIDs.STRING_MENU_SWITCH_CHATS,
R.string.menu_switch_chats);
resMapping.put(BrandingResourceIDs.STRING_TOAST_CHECK_AUTO_SIGN_IN,
R.string.check_auto_sign_in);
resMapping.put(BrandingResourceIDs.STRING_LABEL_SIGN_UP,
R.string.sign_up);
mDefaultBrandingResources = new BrandingResources(this, resMapping,
null /* default res */);
}
private void loadThirdPartyResources() {
ImPluginHelper helper = ImPluginHelper.getInstance(this);
helper.loadAvaiablePlugins();
ArrayList<ImPlugin> pluginList = helper.getPluginObjects();
ArrayList<ImPluginInfo> infoList = helper.getPluginsInfo();
int N = pluginList.size();
PackageManager pm = getPackageManager();
for (int i = 0; i < N; i++) {
ImPlugin plugin = pluginList.get(i);
ImPluginInfo pluginInfo = infoList.get(i);
try {
Resources packageRes = pm.getResourcesForApplication(pluginInfo.mPackageName);
Map<Integer, Integer> resMap = plugin.getResourceMap();
int[] smileyIcons = plugin.getSmileyIconIds();
BrandingResources res = new BrandingResources(packageRes, resMap,
smileyIcons, mDefaultBrandingResources);
mBrandingResources.put(pluginInfo.mProviderName, res);
} catch (NameNotFoundException e) {
Log.e(LOG_TAG, "Failed to load third party resources.", e);
}
}
}
public long getProviderId(String name) {
loadImProviderSettings();
for (ProviderDef provider: mProviders.values()) {
if(provider.mName.equals(name)) {
return provider.mId;
}
}
return -1;
}
public ProviderDef getProvider(long id) {
loadImProviderSettings();
return mProviders.get(id);
}
public List<ProviderDef> getProviders() {
loadImProviderSettings();
ArrayList<ProviderDef> result = new ArrayList<ProviderDef>();
result.addAll(mProviders.values());
return result;
}
public BrandingResources getBrandingResource(long providerId) {
ProviderDef provider = getProvider(providerId);
if (provider == null) {
return mDefaultBrandingResources;
}
if (mBrandingResources == null) {
mBrandingResources = new HashMap<String, BrandingResources>();
loadThirdPartyResources();
}
BrandingResources res = mBrandingResources.get(provider.mName);
return res == null ? mDefaultBrandingResources : res;
}
public IImConnection createConnection(long providerId) throws RemoteException {
if (mImService == null) {
// Service hasn't been connected or has died.
return null;
}
IImConnection conn = getConnection(providerId);
if (conn == null) {
conn = mImService.createConnection(providerId);
}
return conn;
}
IImConnection getConnection(long providerId) {
synchronized (mConnections) {
return mConnections.get(providerId);
}
}
public IImConnection getConnectionByAccount(long accountId) {
synchronized (mConnections) {
for (IImConnection conn : mConnections.values()) {
try {
if (conn.getAccountId() == accountId) {
return conn;
}
} catch (RemoteException e) {
// No server!
}
}
return null;
}
}
public List<IImConnection> getActiveConnections() {
synchronized (mConnections) {
ArrayList<IImConnection> result = new ArrayList<IImConnection>();
result.addAll(mConnections.values());
return result;
}
}
public void callWhenServiceConnected(Handler target, Runnable callback) {
Message msg = Message.obtain(target, callback);
if (serviceConnected()) {
msg.sendToTarget();
} else {
startImServiceIfNeed();
synchronized (mQueue) {
mQueue.add(msg);
}
}
}
public void removePendingCall(Handler target) {
synchronized (mQueue) {
Iterator<Message> iter = mQueue.iterator();
while (iter.hasNext()) {
Message msg = iter.next();
if (msg.getTarget() == target) {
iter.remove();
}
}
}
}
public void registerForBroadcastEvent(int what, Handler target) {
mBroadcaster.request(what, target, what);
}
public void unregisterForBroadcastEvent(int what, Handler target) {
mBroadcaster.cancelRequest(what, target, what);
}
public void registerForConnEvents(Handler handler) {
mBroadcaster.request(EVENT_CONNECTION_CREATED, handler,
EVENT_CONNECTION_CREATED);
mBroadcaster.request(EVENT_CONNECTION_LOGGING_IN, handler,
EVENT_CONNECTION_LOGGING_IN);
mBroadcaster.request(EVENT_CONNECTION_LOGGED_IN, handler,
EVENT_CONNECTION_LOGGED_IN);
mBroadcaster.request(EVENT_CONNECTION_LOGGING_OUT, handler,
EVENT_CONNECTION_LOGGING_OUT);
mBroadcaster.request(EVENT_CONNECTION_SUSPENDED, handler,
EVENT_CONNECTION_SUSPENDED);
mBroadcaster.request(EVENT_CONNECTION_DISCONNECTED, handler,
EVENT_CONNECTION_DISCONNECTED);
mBroadcaster.request(EVENT_USER_PRESENCE_UPDATED, handler,
EVENT_USER_PRESENCE_UPDATED);
mBroadcaster.request(EVENT_UPDATE_USER_PRESENCE_ERROR, handler,
EVENT_UPDATE_USER_PRESENCE_ERROR);
}
public void unregisterForConnEvents(Handler handler) {
mBroadcaster.cancelRequest(EVENT_CONNECTION_CREATED, handler,
EVENT_CONNECTION_CREATED);
mBroadcaster.cancelRequest(EVENT_CONNECTION_LOGGING_IN, handler,
EVENT_CONNECTION_LOGGING_IN);
mBroadcaster.cancelRequest(EVENT_CONNECTION_LOGGED_IN, handler,
EVENT_CONNECTION_LOGGED_IN);
mBroadcaster.cancelRequest(EVENT_CONNECTION_LOGGING_OUT, handler,
EVENT_CONNECTION_LOGGING_OUT);
mBroadcaster.cancelRequest(EVENT_CONNECTION_SUSPENDED, handler,
EVENT_CONNECTION_SUSPENDED);
mBroadcaster.cancelRequest(EVENT_CONNECTION_DISCONNECTED, handler,
EVENT_CONNECTION_DISCONNECTED);
mBroadcaster.cancelRequest(EVENT_USER_PRESENCE_UPDATED, handler,
EVENT_USER_PRESENCE_UPDATED);
mBroadcaster.cancelRequest(EVENT_UPDATE_USER_PRESENCE_ERROR, handler,
EVENT_UPDATE_USER_PRESENCE_ERROR);
}
void broadcastConnEvent(int what, long providerId, ImErrorInfo error) {
if(Log.isLoggable(LOG_TAG, Log.DEBUG)){
log("broadcasting connection event " + what + ", provider id " + providerId);
}
android.os.Message msg = android.os.Message.obtain(
null,
what,
(int)(providerId >> 32), (int)providerId,
error);
mBroadcaster.broadcast(msg);
}
public void dismissNotifications(long providerId) {
if (mImService != null) {
try {
mImService.dismissNotifications(providerId);
} catch (RemoteException e) {
}
}
}
public void dismissChatNotification(long providerId, String username) {
if (mImService != null) {
try {
mImService.dismissChatNotification(providerId, username);
} catch (RemoteException e) {
}
}
}
private void fetchActiveConnections() {
try {
// register the listener before fetch so that we won't miss any connection.
mImService.addConnectionCreatedListener(mConnCreationListener);
synchronized (mConnections) {
for(IBinder binder: (List<IBinder>) mImService.getActiveConnections()) {
IImConnection conn = IImConnection.Stub.asInterface(binder);
long providerId = conn.getProviderId();
if (!mConnections.containsKey(providerId)) {
mConnections.put(providerId, conn);
conn.registerConnectionListener(mConnectionListener);
}
}
}
} catch (RemoteException e) {
Log.e(LOG_TAG, "fetching active connections", e);
}
}
private final IConnectionCreationListener mConnCreationListener
= new IConnectionCreationListener.Stub() {
public void onConnectionCreated(IImConnection conn)
throws RemoteException {
long providerId = conn.getProviderId();
synchronized (mConnections) {
if (!mConnections.containsKey(providerId)) {
mConnections.put(providerId, conn);
conn.registerConnectionListener(mConnectionListener);
}
}
broadcastConnEvent(EVENT_CONNECTION_CREATED, providerId, null);
}
};
private final class MyConnListener extends ConnectionListenerAdapter {
public MyConnListener(Handler handler) {
super(handler);
}
@Override
public void onConnectionStateChange(IImConnection conn, int state,
ImErrorInfo error) {
if(Log.isLoggable(LOG_TAG, Log.DEBUG)){
log("onConnectionStateChange(" + state + ", " + error + ")");
}
try {
int what = -1;
long providerId = conn.getProviderId();
switch (state) {
case ImConnection.LOGGED_IN:
what = EVENT_CONNECTION_LOGGED_IN;
break;
case ImConnection.LOGGING_IN:
what = EVENT_CONNECTION_LOGGING_IN;
break;
case ImConnection.LOGGING_OUT:
what = EVENT_CONNECTION_LOGGING_OUT;
synchronized (mConnections) {
mConnections.remove(providerId);
}
break;
case ImConnection.DISCONNECTED:
what = EVENT_CONNECTION_DISCONNECTED;
synchronized (mConnections) {
mConnections.remove(providerId);
}
// stop the service if there isn't an active connection anymore.
stopImServiceIfInactive();
break;
case ImConnection.SUSPENDED:
what = EVENT_CONNECTION_SUSPENDED;
break;
}
if (what != -1) {
broadcastConnEvent(what, providerId, error);
}
} catch (RemoteException e) {
Log.e(LOG_TAG, "onConnectionStateChange", e);
}
}
@Override
public void onUpdateSelfPresenceError(IImConnection connection,
ImErrorInfo error) {
if(Log.isLoggable(LOG_TAG, Log.DEBUG)){
log("onUpdateUserPresenceError(" + error + ")");
}
try {
long providerId = connection.getProviderId();
broadcastConnEvent(EVENT_UPDATE_USER_PRESENCE_ERROR, providerId,
error);
} catch (RemoteException e) {
Log.e(LOG_TAG, "onUpdateUserPresenceError", e);
}
}
@Override
public void onSelfPresenceUpdated(IImConnection connection) {
if(Log.isLoggable(LOG_TAG, Log.DEBUG)) log("onUserPresenceUpdated");
try {
long providerId = connection.getProviderId();
broadcastConnEvent(EVENT_USER_PRESENCE_UPDATED, providerId,
null);
} catch (RemoteException e) {
Log.e(LOG_TAG, "onUserPresenceUpdated", e);
}
}
}
}