/*
Copyright 2012-2013, Polyvi Inc. (http://polyvi.github.io/openxface)
This program is distributed under the terms of the GNU General Public License.
This file is part of xFace.
xFace is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
xFace is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with xFace. If not, see <http://www.gnu.org/licenses/>.
*/
package com.polyvi.xface.extension.push;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.NotificationIQ;
import org.jivesoftware.smack.NotificationIQProvider;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Registration;
import org.jivesoftware.smack.provider.ProviderManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.provider.Settings;
/**
* 该类主要实现了客户端对服务器端的连接操作
*/
public class XConnectionManager {
/** 程序客户端名 */
private static final String XFACE_CLIENT_NAME = "XfaceApolloClient";
/** 保存信息的文件名 */
private static final String SHARED_PREFERENCE_NAME = "push_preferences";
/** 登录服务器账号的保存名 */
private static final String USERNAME = "USERNAME";
/** 登陆服务器密码的保存名 */
private static final String PASSWORD = "PASSWORD";
/** 收到通知的xml文件的元素名 */
private static final String ELEMENT_NAME = "notification";
/** 收到服务器发过来的通知的xml文件的xmlns的名字,这样才能区分 */
private static final String NAME_SPACE = "androidpn:iq:notification";
/** 分隔符 */
private static final String SEPARATE_SIGN = "_";
/** 客户端用户名 */
private static final String KEY_USERNAME = "username";
/** 客户端密码 */
private static final String KEY_PASSWORD = "password";
/** 连接错误类型 */
private static final String INVALID_CREDENTIALS_ERROR_CODE = "401";
/** service */
private XNotificationService mNotificationService;
private SharedPreferences mSharedPrefs;
private XMPPConnection mConnection;
/** 客户端用户名 */
private String mUsername;
/** 客户端密码 */
private String mPassword;
/** 连接监听 */
private ConnectionListener mConnectionListener;
private ExecutorService mExecutorService;
public XConnectionManager(XNotificationService notificationService) {
init(notificationService);
}
/**
* 初始化操作
* @param notificationService service
*/
private void init(XNotificationService notificationService) {
mSharedPrefs = notificationService.getSharedPreferences(
SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE);
mNotificationService = notificationService;
mUsername = mSharedPrefs.getString(USERNAME, "");
mPassword = mSharedPrefs.getString(PASSWORD, "");
mExecutorService = Executors.newSingleThreadExecutor();
}
/**
* 对连接出错监听的初始化以及实现重连
*/
private void initConnectionListener() {
if (null == mConnectionListener) {
mConnectionListener = new ConnectionListener() {
@Override
public void connectionClosedOnError(Exception arg0) {
if (mConnection != null && mConnection.isConnected()) {
mConnection.disconnect();
}
startReconnection();
}
@Override
public void reconnectingIn(int arg0) {
}
@Override
public void reconnectionFailed(Exception arg0) {
}
@Override
public void reconnectionSuccessful() {
}
@Override
public void connectionClosed() {
}
};
}
}
/**
* 连接服务器
*/
public void connect() {
Runnable connectRunnable = new Runnable() {
@Override
public void run() {
initConnect();
register();
login();
}
};
mExecutorService.submit(connectRunnable);
}
/**
* 通过主机名和端口号去对服务器进行连接和一些初始化
*/
private void initConnect() {
if (!isConnected()) {
String connectionHost = mNotificationService.getHost();
int connectionPort = Integer.parseInt(mNotificationService
.getPort());
ConnectionConfiguration connConfig = new ConnectionConfiguration(
connectionHost, connectionPort);
connConfig.setSecurityMode(SecurityMode.required);
connConfig.setSASLAuthenticationEnabled(false);
connConfig.setCompressionEnabled(false);
mConnection = new XMPPConnection(connConfig);
try {
mConnection.connect();
ProviderManager.getInstance().addIQProvider(ELEMENT_NAME,
NAME_SPACE, new NotificationIQProvider());
} catch (XMPPException e) {
startReconnection();
}
}
}
/**
* 首次连接的时候需要去注册账号,将账号保存,然后去执行注册
*/
private void register() {
if (!isRegistered()) {
// 第一次登陆以uuid和报名作为用户名,包名作为密码,这样就能很好的区别
// 因为uuid可以区别设备而包名可以区别应用。这样用户名就不会有相同的存在,
// 这样通知就能准确的发送到要发送指定的设备和应用之上。
mUsername = createUserName();
mPassword = mNotificationService.getPackageName();
Registration registration = new Registration();
PacketFilter packetFilter = new AndFilter(new PacketIDFilter(
registration.getPacketID()), new PacketTypeFilter(IQ.class));
PacketListener packetListener = new PacketListener() {
public void processPacket(Packet packet) {
if (packet instanceof IQ) {
IQ response = (IQ) packet;
if (response.getType() == IQ.Type.RESULT) {
Editor editor = mSharedPrefs.edit();
editor.putString(USERNAME, mUsername);
editor.putString(PASSWORD, mPassword);
editor.commit();
}
}
}
};
mConnection.addPacketListener(packetListener, packetFilter);
registration.setType(IQ.Type.SET);
registration.addAttribute(KEY_USERNAME, mUsername);
registration.addAttribute(KEY_PASSWORD, mPassword);
mConnection.sendPacket(registration);
}
}
/**
* 通过用户名密码去登陆服务器,连接服务器之后在设定时间内断开连接然后又去重连,这样就实现了循环断开连接
*/
private void login() {
if (!isAuthenticated()) {
try {
mConnection.login(mUsername, mPassword, XFACE_CLIENT_NAME);
initConnectionListener();
mConnection.addConnectionListener(mConnectionListener);
PacketFilter packetFilter = new PacketTypeFilter(
NotificationIQ.class);
mConnection.addPacketListener(
mNotificationService.getNotificationPacketListener(),
packetFilter);
} catch (XMPPException e) {
String errorMessage = e.getMessage();
if (errorMessage != null
&& errorMessage
.contains(INVALID_CREDENTIALS_ERROR_CODE)) {
deleteAccount();
startReconnection();
return;
}
startReconnection();
} catch (Exception e) {
startReconnection();
}
}
}
/**
* 断开服务器
*/
public void disconnect() {
if (isConnected()) {
mConnection.removePacketListener(mNotificationService
.getNotificationPacketListener());
mConnection.disconnect();
}
}
/**
* 重连服务器
*/
private void startReconnection() {
connect();
}
/**
* 当401错误发生去执行删除账号
*/
private void deleteAccount() {
Editor editor = mSharedPrefs.edit();
editor.remove(USERNAME);
editor.remove(PASSWORD);
editor.commit();
}
/**
* 判断是否对服务器进行连接
* @return true:已经连接,false:未连接
*/
private boolean isConnected() {
return mConnection != null && mConnection.isConnected();
}
/**
* 判断是否登陆服务器
* @return true:已经登陆,false:未登录
*/
private boolean isAuthenticated() {
return isConnected()&& mConnection.isAuthenticated();
}
/**
* 判断是否注册用户名和密码
* @return true:已经注册,false:未注册
*/
private boolean isRegistered() {
return mSharedPrefs.contains(USERNAME)
&& mSharedPrefs.contains(PASSWORD);
}
/**
* 创建用户登陆的用户名
* @return 用户名
*/
private String createUserName() {
return Settings.Secure.getString(
mNotificationService.getContentResolver(),
android.provider.Settings.Secure.ANDROID_ID)
+ SEPARATE_SIGN
+ mNotificationService.getPackageName();
}
}