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