Home | History | Annotate | Download | only in service
      1 /*
      2  * Copyright (C) 2007-2008 Esmertec AG.
      3  * Copyright (C) 2007-2008 The Android Open Source Project
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.im.service;
     19 
     20 import java.lang.reflect.InvocationTargetException;
     21 import java.lang.reflect.Method;
     22 import java.util.ArrayList;
     23 import java.util.HashMap;
     24 import java.util.List;
     25 import java.util.Map;
     26 import java.util.Vector;
     27 
     28 import android.app.Service;
     29 import android.content.BroadcastReceiver;
     30 import android.content.ContentResolver;
     31 import android.content.Context;
     32 import android.content.Intent;
     33 import android.content.IntentFilter;
     34 import android.database.Cursor;
     35 import android.net.ConnectivityManager;
     36 import android.net.Uri;
     37 import android.net.NetworkInfo;
     38 import android.os.Bundle;
     39 import android.os.RemoteCallbackList;
     40 import android.os.RemoteException;
     41 import android.os.Handler;
     42 import android.os.IBinder;
     43 import android.os.Message;
     44 import android.os.SystemProperties;
     45 import android.telephony.TelephonyManager;
     46 import android.text.TextUtils;
     47 import android.util.Log;
     48 import android.widget.Toast;
     49 
     50 import com.android.common.NetworkConnectivityListener;
     51 import com.android.common.NetworkConnectivityListener.State;
     52 import com.android.im.IConnectionCreationListener;
     53 import com.android.im.IImConnection;
     54 import com.android.im.IRemoteImService;
     55 import com.android.im.app.ImPluginHelper;
     56 import com.android.im.engine.ConnectionFactory;
     57 import com.android.im.engine.ImConnection;
     58 import com.android.im.engine.ImException;
     59 import com.android.im.imps.ImpsConnectionConfig;
     60 import com.android.im.plugin.ImConfigNames;
     61 import com.android.im.plugin.ImPluginInfo;
     62 import com.android.im.plugin.ImpsConfigNames;
     63 import com.android.im.provider.Imps;
     64 
     65 
     66 public class RemoteImService extends Service {
     67 
     68     private static final String[] ACCOUNT_PROJECTION = {
     69         Imps.Account._ID,
     70         Imps.Account.PROVIDER,
     71         Imps.Account.USERNAME,
     72         Imps.Account.PASSWORD,
     73     };
     74     private static final int ACCOUNT_ID_COLUMN = 0;
     75     private static final int ACCOUNT_PROVIDER_COLUMN = 1;
     76     private static final int ACCOUNT_USERNAME_COLUMN = 2;
     77     private static final int ACCOUNT_PASSOWRD_COLUMN = 3;
     78 
     79     static final String TAG = "ImService";
     80 
     81     private static final int EVENT_SHOW_TOAST = 100;
     82     private static final int EVENT_NETWORK_STATE_CHANGED = 200;
     83 
     84     private StatusBarNotifier mStatusBarNotifier;
     85     private Handler mServiceHandler;
     86     NetworkConnectivityListener mNetworkConnectivityListener;
     87     private int mNetworkType;
     88     private boolean mNeedCheckAutoLogin;
     89 
     90     private boolean mBackgroundDataEnabled;
     91 
     92     private SettingsMonitor mSettingsMonitor;
     93 
     94     private ImPluginHelper mPluginHelper;
     95     Vector<ImConnectionAdapter> mConnections;
     96     final RemoteCallbackList<IConnectionCreationListener> mRemoteListeners
     97             = new RemoteCallbackList<IConnectionCreationListener>();
     98 
     99 
    100     public RemoteImService() {
    101         mConnections = new Vector<ImConnectionAdapter>();
    102     }
    103 
    104     @Override
    105     public void onCreate() {
    106         Log.d(TAG, "ImService started");
    107         mStatusBarNotifier = new StatusBarNotifier(this);
    108         mServiceHandler = new ServiceHandler();
    109         mNetworkConnectivityListener = new NetworkConnectivityListener();
    110         mNetworkConnectivityListener.registerHandler(mServiceHandler, EVENT_NETWORK_STATE_CHANGED);
    111         mNetworkConnectivityListener.startListening(this);
    112 
    113         mSettingsMonitor = new SettingsMonitor();
    114 
    115         IntentFilter intentFilter = new IntentFilter();
    116         intentFilter.addAction(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
    117         registerReceiver(mSettingsMonitor, intentFilter);
    118 
    119         ConnectivityManager manager
    120             = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
    121         setBackgroundData(manager.getBackgroundDataSetting());
    122 
    123         mPluginHelper = ImPluginHelper.getInstance(this);
    124         mPluginHelper.loadAvaiablePlugins();
    125         AndroidSystemService.getInstance().initialize(this);
    126     }
    127 
    128     @Override
    129     public int onStartCommand(Intent intent, int flags, int startId) {
    130         if (intent != null) {
    131             mNeedCheckAutoLogin = intent.getBooleanExtra(ImServiceConstants.EXTRA_CHECK_AUTO_LOGIN, false);
    132 
    133             Log.d(TAG, "ImService.onStart, checkAutoLogin=" + mNeedCheckAutoLogin);
    134 
    135             // Check and login accounts if network is ready, otherwise it's checked
    136             // when the network becomes available.
    137             if (mNeedCheckAutoLogin &&
    138                     mNetworkConnectivityListener.getState() == State.CONNECTED) {
    139                 mNeedCheckAutoLogin = false;
    140                 autoLogin();
    141             }
    142         }
    143         return super.onStartCommand(intent, flags, startId);
    144     }
    145 
    146     private void autoLogin() {
    147         Log.d(TAG, "Scaning accounts and login automatically");
    148 
    149         ContentResolver resolver = getContentResolver();
    150 
    151         String where = Imps.Account.KEEP_SIGNED_IN + "=1 AND " + Imps.Account.ACTIVE + "=1";
    152         Cursor cursor = resolver.query(Imps.Account.CONTENT_URI,
    153                 ACCOUNT_PROJECTION, where, null, null);
    154         if (cursor == null) {
    155             Log.w(TAG, "Can't query account!");
    156             return;
    157         }
    158         while (cursor.moveToNext()) {
    159             long accountId = cursor.getLong(ACCOUNT_ID_COLUMN);
    160             long providerId = cursor.getLong(ACCOUNT_PROVIDER_COLUMN);
    161             String username = cursor.getString(ACCOUNT_USERNAME_COLUMN);
    162             String password = cursor.getString(ACCOUNT_PASSOWRD_COLUMN);
    163 
    164             IImConnection conn = createConnection(providerId);
    165 
    166             try {
    167                 conn.login(accountId, username, password, true);
    168             } catch (RemoteException e) {
    169                 Log.w(TAG, "Logging error while automatically login!");
    170             }
    171         }
    172         cursor.close();
    173     }
    174 
    175     private Map<String, String> loadProviderSettings(long providerId) {
    176         ContentResolver cr = getContentResolver();
    177         Map<String, String> settings = Imps.ProviderSettings.queryProviderSettings(cr, providerId);
    178 
    179         NetworkInfo networkInfo = mNetworkConnectivityListener.getNetworkInfo();
    180         // Insert a fake msisdn on emulator. We don't need this on device
    181         // because the mobile network will take care of it.
    182         if ("1".equals(SystemProperties.get("ro.kernel.qemu"))) {
    183             settings.put(ImpsConfigNames.MSISDN, "15555218135");
    184         } else if (networkInfo != null
    185                 && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
    186             if (!TextUtils.isEmpty(settings.get(ImpsConfigNames.SMS_ADDR))) {
    187                 // Send authentication through sms if SMS data channel is
    188                 // supported and WiFi is used.
    189                 settings.put(ImpsConfigNames.SMS_AUTH, "true");
    190                 settings.put(ImpsConfigNames.SECURE_LOGIN, "false");
    191             } else {
    192                 // Wi-Fi network won't insert a MSISDN, we should get from the SIM
    193                 // card. Assume we can always get the correct MSISDN from SIM, otherwise,
    194                 // the sign in would fail and an error message should be shown to warn
    195                 // the user to contact their operator.
    196                 String msisdn = TelephonyManager.getDefault().getLine1Number();
    197                 if (TextUtils.isEmpty(msisdn)) {
    198                     Log.w(TAG, "Can not read MSISDN from SIM, use a fake one."
    199                          + " SMS related feature won't work.");
    200                     msisdn = "15555218135";
    201                 }
    202                 settings.put(ImpsConfigNames.MSISDN, msisdn);
    203             }
    204         }
    205         return settings;
    206     }
    207 
    208     @Override
    209     public void onDestroy() {
    210         Log.w(TAG, "ImService stopped.");
    211         for (ImConnectionAdapter conn : mConnections) {
    212             conn.logout();
    213         }
    214 
    215         AndroidSystemService.getInstance().shutdown();
    216 
    217         mNetworkConnectivityListener.unregisterHandler(mServiceHandler);
    218         mNetworkConnectivityListener.stopListening();
    219         mNetworkConnectivityListener = null;
    220 
    221         unregisterReceiver(mSettingsMonitor);
    222     }
    223 
    224     @Override
    225     public IBinder onBind(Intent intent) {
    226         return mBinder;
    227     }
    228 
    229     public void showToast(CharSequence text, int duration) {
    230         Message msg = Message.obtain(mServiceHandler, EVENT_SHOW_TOAST, duration, 0, text);
    231         msg.sendToTarget();
    232     }
    233 
    234     public StatusBarNotifier getStatusBarNotifier() {
    235         return mStatusBarNotifier;
    236     }
    237 
    238     public void scheduleReconnect(long delay) {
    239         if (!isNetworkAvailable()) {
    240             // Don't schedule reconnect if no network available. We will try to
    241             // reconnect when network state become CONNECTED.
    242             return;
    243         }
    244         mServiceHandler.postDelayed(new Runnable() {
    245             public void run() {
    246                 reestablishConnections();
    247             }
    248         }, delay);
    249     }
    250 
    251     IImConnection createConnection(long providerId) {
    252         Map<String, String> settings = loadProviderSettings(providerId);
    253         String protocol = settings.get(ImConfigNames.PROTOCOL_NAME);
    254         if(!"IMPS".equals(protocol)) {
    255             Log.e(TAG, "Unsupported protocol: " + protocol);
    256             return null;
    257         }
    258         ImpsConnectionConfig config = new ImpsConnectionConfig(settings);
    259         ConnectionFactory factory = ConnectionFactory.getInstance();
    260         try {
    261             ImConnection conn = factory.createConnection(config);
    262             ImConnectionAdapter result = new ImConnectionAdapter(providerId,
    263                     conn, this);
    264             mConnections.add(result);
    265 
    266             final int N = mRemoteListeners.beginBroadcast();
    267             for (int i = 0; i < N; i++) {
    268                 IConnectionCreationListener listener = mRemoteListeners.getBroadcastItem(i);
    269                 try {
    270                     listener.onConnectionCreated(result);
    271                 } catch (RemoteException e) {
    272                     // The RemoteCallbackList will take care of removing the
    273                     // dead listeners.
    274                 }
    275             }
    276             mRemoteListeners.finishBroadcast();
    277             return result;
    278         } catch (ImException e) {
    279             Log.e(TAG, "Error creating connection", e);
    280             return null;
    281         }
    282     }
    283 
    284     void removeConnection(IImConnection connection) {
    285         mConnections.remove(connection);
    286     }
    287 
    288     private boolean isNetworkAvailable() {
    289         return mNetworkConnectivityListener.getState() == State.CONNECTED;
    290     }
    291 
    292     private boolean isBackgroundDataEnabled() {
    293         return mBackgroundDataEnabled;
    294     }
    295 
    296     private void setBackgroundData(boolean flag) {
    297         mBackgroundDataEnabled = flag;
    298     }
    299 
    300     void handleBackgroundDataSettingChange(){
    301         if (!isBackgroundDataEnabled()) {
    302             for (ImConnectionAdapter conn : mConnections) {
    303                 conn.logout();
    304             }
    305         }
    306     }
    307 
    308     void networkStateChanged() {
    309         if (mNetworkConnectivityListener == null) {
    310             return;
    311         }
    312         NetworkInfo networkInfo = mNetworkConnectivityListener.getNetworkInfo();
    313         NetworkInfo.State state = networkInfo.getState();
    314 
    315         Log.d(TAG, "networkStateChanged:" + state);
    316 
    317         int oldType = mNetworkType;
    318         mNetworkType = networkInfo.getType();
    319 
    320         // Notify the connection that network type has changed. Note that this
    321         // only work for connected connections, we need to reestablish if it's
    322         // suspended.
    323         if (mNetworkType != oldType
    324                 && isNetworkAvailable()) {
    325             for (ImConnectionAdapter conn : mConnections) {
    326                 conn.networkTypeChanged();
    327             }
    328         }
    329 
    330         switch (state) {
    331             case CONNECTED:
    332                 if (mNeedCheckAutoLogin) {
    333                     mNeedCheckAutoLogin = false;
    334                     autoLogin();
    335                     break;
    336                 }
    337                 reestablishConnections();
    338                 break;
    339 
    340             case DISCONNECTED:
    341                 if (!isNetworkAvailable()) {
    342                     suspendConnections();
    343                 }
    344                 break;
    345         }
    346     }
    347 
    348     // package private for inner class access
    349     void reestablishConnections() {
    350         if (!isNetworkAvailable()) {
    351             return;
    352         }
    353 
    354         for (ImConnectionAdapter conn : mConnections) {
    355             int connState = conn.getState();
    356             if (connState == ImConnection.SUSPENDED) {
    357                 conn.reestablishSession();
    358             }
    359         }
    360     }
    361 
    362     private void suspendConnections() {
    363         for (ImConnectionAdapter conn : mConnections) {
    364             if (conn.getState() != ImConnection.LOGGED_IN) {
    365                 continue;
    366             }
    367             conn.suspend();
    368         }
    369     }
    370 
    371     private final IRemoteImService.Stub mBinder = new IRemoteImService.Stub() {
    372 
    373         public List<ImPluginInfo> getAllPlugins() {
    374             return new ArrayList<ImPluginInfo>(mPluginHelper.getPluginsInfo());
    375         }
    376 
    377         public void addConnectionCreatedListener(IConnectionCreationListener listener) {
    378             if (listener != null) {
    379                 mRemoteListeners.register(listener);
    380             }
    381         }
    382 
    383         public void removeConnectionCreatedListener(IConnectionCreationListener listener) {
    384             if (listener != null) {
    385                 mRemoteListeners.unregister(listener);
    386             }
    387         }
    388 
    389         public IImConnection createConnection(long providerId) {
    390             return RemoteImService.this.createConnection(providerId);
    391         }
    392 
    393         public List getActiveConnections() {
    394             ArrayList<IBinder> result = new ArrayList<IBinder>(mConnections.size());
    395             for(IImConnection conn : mConnections) {
    396                 result.add(conn.asBinder());
    397             }
    398             return result;
    399         }
    400 
    401         public void dismissNotifications(long providerId) {
    402             mStatusBarNotifier.dismissNotifications(providerId);
    403         }
    404 
    405         public void dismissChatNotification(long providerId, String username) {
    406             mStatusBarNotifier.dismissChatNotification(providerId, username);
    407         }
    408     };
    409 
    410     private final class SettingsMonitor extends BroadcastReceiver {
    411         @Override
    412         public void onReceive(Context context, Intent intent) {
    413             String action = intent.getAction();
    414 
    415             if (ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED.equals(action)) {
    416                 ConnectivityManager manager =
    417                     (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
    418                 setBackgroundData(manager.getBackgroundDataSetting());
    419                 handleBackgroundDataSettingChange();
    420             }
    421         }
    422     }
    423 
    424     private final class ServiceHandler extends Handler {
    425         public ServiceHandler() {
    426         }
    427 
    428         @Override
    429         public void handleMessage(Message msg) {
    430             switch (msg.what) {
    431                 case EVENT_SHOW_TOAST:
    432                     Toast.makeText(RemoteImService.this,
    433                             (CharSequence) msg.obj, msg.arg1).show();
    434                     break;
    435 
    436                 case EVENT_NETWORK_STATE_CHANGED:
    437                     networkStateChanged();
    438                     break;
    439 
    440                 default:
    441             }
    442         }
    443     }
    444 }
    445