All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.avos.avoscloud.PushService Maven / Gradle / Ivy

The newest version!
package com.avos.avoscloud;

import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.net.ConnectivityManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;

import com.alibaba.fastjson.JSON;
import com.avos.avoscloud.im.v2.AVIMClient;
import com.avos.avoscloud.im.v2.AVIMMessage;
import com.avos.avoscloud.im.v2.AVIMMessageOption;
import com.avos.avoscloud.im.v2.AVIMOptions;
import com.avos.avoscloud.im.v2.Conversation;
import com.avos.avoscloud.im.v2.Conversation.AVIMOperation;
import com.avos.avospush.push.*;
import com.avos.avospush.session.CommandPacket;

import org.java_websocket.framing.CloseFrame;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

/**
 * 

* A service to listen for push notifications. This operates in the same process as the parent * application. To use this class, the PushService must be registered. Add this XML right before the * tag in your AndroidManifest.xml: *

*

*

 *    
 *    
 *        
 *            
 *            
 *        
 *    
 * 
*

* Next, you must ensure your app has the permissions needed to show a notification. Make sure that * these permissions are present in your AndroidManifest.xml, typically immediately before the * tag: *

*

*

 *    
 *    
 *    
 * 
*

* Once push notifications are configured in the manifest, you can subscribe to a push channel by * calling *

*

*

 * PushService.subscribe(context, "the_channel_name", YourActivity.class);
 * 
*

* When the client receives a push message, a notification will appear in the system tray. When the * user taps the notification, they will enter the application through a new instance of * YourActivity. *

*/ public class PushService extends Service { private static final String LOGTAG = PushService.class.getName(); private static AVPushConnectionManager sPushConnectionManager; private static Object connecting = new Object(); private volatile static boolean isStarted = false; // 是否需要唤醒其他同样使用 LeanCloud 服务的 app,此变量用于缓存结果,避免无意义调用 private static boolean isNeedNotifyApplication = true; /** * 是否自动唤醒 PushService */ private static boolean isAutoWakeUp = true; static String DefaultChannelId = ""; AVConnectivityReceiver connectivityReceiver; AVShutdownReceiver shutdownReceiver; private Timer cleanupTimer = new Timer(); /** * Client code should not call onCreate directly. */ @Override public void onCreate() { LogUtil.log.d(LOGTAG, "On Create"); super.onCreate(); sPushConnectionManager = AVPushConnectionManager.getInstance(this); connectivityReceiver = new AVConnectivityReceiver(new AVConnectivityListener() { private volatile boolean connectEstablished = false; @Override public void onMobile(Context context) { LogUtil.log.d(LOGTAG, "Connectivity resumed with Mobile"); connectEstablished = true; sPushConnectionManager.initConnection(); } @Override public void onWifi(Context context) { LogUtil.log.d(LOGTAG, "Connectivity resumed with Wifi"); connectEstablished = true; sPushConnectionManager.initConnection(); } public void onOtherConnected(Context context) { LogUtil.log.d(LOGTAG, "Connectivity resumed with Others"); connectEstablished = true; sPushConnectionManager.initConnection(); } @Override public void onNotConnected(Context context) { if(!connectEstablished) { LogUtil.log.d(LOGTAG, "Connectivity isn't established yet."); return; } LogUtil.log.d(LOGTAG, "Connectivity broken"); connectEstablished = false; if (AVIMOptions.getGlobalOptions().isResetConnectionWhileBroken()) { cleanupTimer.schedule(new TimerTask() { @Override public void run() { if (!connectEstablished) { LogUtil.log.d(LOGTAG, "Connection cleanup now."); sPushConnectionManager.cleanupSocketConnection(CloseFrame.ABNORMAL_CLOSE, "Connectivity broken"); } else { LogUtil.log.d(LOGTAG, "Connection has been resumed"); } } }, 3000); } } }); registerReceiver(connectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); shutdownReceiver = new AVShutdownReceiver(new AVShutdownListener() { @Override public void onShutdown(Context context) { sPushConnectionManager.cleanupSocketConnection(); } }); registerReceiver(shutdownReceiver, new IntentFilter(Intent.ACTION_SHUTDOWN)); isStarted = true; } @TargetApi(Build.VERSION_CODES.ECLAIR) @Override public int onStartCommand(final Intent intent, int flags, int startId) { notifyOtherApplication(null != intent ? intent.getAction() : null); // Here to get intent from SessionManager if (AVUtils.isConnected(this) && isPushConnectionBroken()) { synchronized (connecting) { try { AVInstallation installation = AVInstallation.getCurrentInstallation(); final String installationId = installation.getInstallationId(); if (installation.isDirty()) { installation.saveInBackground(); } LogUtil.log.d(LOGTAG, "Start to connect to push server with installationId " + installationId); sPushConnectionManager .initConnection(new AVCallback() { @Override protected void internalDone0(Object o, AVException exception) { if (exception == null) { processIMRequests(intent); } else { reportRouterConnectionException(intent, exception); } } }); } catch (Exception e) { LogUtil.log .e(LOGTAG, "Exception when init connection, looks like you have not called AVOSCloud.initialized yet", e); e.printStackTrace(); } } } else { processIMRequests(intent); } // http://developer.android.com/reference/android/app/Service.html#START_STICKY return START_STICKY; } private void reportRouterConnectionException(Intent intent, AVException e) { if (intent != null && Conversation.AV_CONVERSATION_INTENT_ACTION.equalsIgnoreCase(intent.getAction())) { int operationCode = intent.getExtras().getInt(Conversation.INTENT_KEY_OPERATION); String clientId = intent.getExtras().getString(Conversation.INTENT_KEY_CLIENT); String conversationId = intent.getExtras().getString(Conversation.INTENT_KEY_CONVERSATION); int requestId = intent.getExtras().getInt(Conversation.INTENT_KEY_REQUESTID); BroadcastUtil.sendIMLocalBroadcast(clientId, conversationId, requestId, e, AVIMOperation.getAVIMOperation(operationCode)); } } private void processLiveQueryEventFromClient(Intent intent) { String subscribeId = intent.getExtras().getString("id"); int requestId = intent.getExtras().getInt(Conversation.INTENT_KEY_REQUESTID); sPushConnectionManager.sendLiveQueryLoginCmd(subscribeId, requestId); } private void processIMRequests(Intent intent) { if (null != intent) { if (Conversation.AV_CONVERSATION_INTENT_ACTION.equalsIgnoreCase(intent.getAction())) { processConversationEventsFromClient(intent); } if (AVLiveQuery.ACTION_LIVE_QUERY_LOGIN.equalsIgnoreCase(intent.getAction())) { processLiveQueryEventFromClient(intent); } if (Conversation.AV_CONVERSATION_PARCEL_ACTION.equalsIgnoreCase(intent.getAction())) { processConversationParcelEventFromClient(intent); } } } /** * Client code should not call onDestroy directly. */ @Override public void onDestroy() { LogUtil.log.d(LOGTAG, "On Destroy"); if (sPushConnectionManager != null) { sPushConnectionManager.stop(); } unregisterReceiver(connectivityReceiver); unregisterReceiver(shutdownReceiver); isStarted = false; if (isAutoWakeUp && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) { // Let's try to wake PushService again try { Intent i = new Intent(AVOSCloud.applicationContext, PushService.class); i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startService(i); } catch (Exception ex) { // i have tried my best. LogUtil.log.e("failed to start PushService. cause: " + ex.getMessage()); } } super.onDestroy(); } /** * onBind should not be called directly. */ @Override public IBinder onBind(Intent intent) { LogUtil.log.d(LOGTAG, "On bind"); return null; } private static void startServiceIfRequired(Context context, final java.lang.Class cls) { if (isStarted) { return; } if (context == null) { LogUtil.log.d(LOGTAG, "context is null"); return; } if (!AVUtils.checkPermission(context, "android.permission.INTERNET")) { LogUtil.log .e(LOGTAG, "Please add in your AndroidManifest file"); return; } if (!AVUtils.isConnected(context)) { LogUtil.log.d(LOGTAG, "No network available now"); return; } if (!AVUtils.isPushServiceAvailable(context, PushService.class)) { LogUtil.log .e(LOGTAG, "Please add in your AndroidManifest file"); return; } startService(context, cls); } private static synchronized void startService(Context context, final java.lang.Class cls) { final Context finalContext = context; new Thread(new Runnable() { @Override public void run() { LogUtil.log.d(LOGTAG, "Start service"); try { Intent intent = new Intent(finalContext, PushService.class); intent.putExtra(AVConstants.AV_PUSH_SERVICE_APPLICATION_ID, AVOSCloud.applicationId); if (cls != null) { intent.putExtra(AVConstants.AV_PUSH_SERVICE_DEFAULT_CALLBACK, cls.getName()); } finalContext.startService(intent); } catch (Exception ex) { // i have tried my best. LogUtil.log.e("failed to start PushService. cause: " + ex.getMessage()); } } }).start(); } /** * Helper function to subscribe to push notifications with the default application icon. * * @param context This is used to access local storage to cache the subscription, so it must * currently be a viable context. * @param channel A string identifier that determines which messages will cause a push * notification to be sent to this client. The channel name must start with a letter and * contain only letters, numbers, dashes, and underscores. * @param cls This should be a subclass of Activity. An instance of this Activity is started when * the user responds to this push notification. If you are not sure what to use here, * just * use your application's main Activity subclass. */ public static synchronized void subscribe(android.content.Context context, java.lang.String channel, java.lang.Class cls) { startServiceIfRequired(context, cls); final String finalChannel = channel; AVInstallation.getCurrentInstallation().addUnique("channels", finalChannel); _installationSaveHandler.sendMessage(Message.obtain()); if (cls != null) { AVNotificationManager manager = AVNotificationManager.getInstance(); manager.addDefaultPushCallback(channel, cls.getName()); // set default push callback if it's not exist yet if (manager.getDefaultPushCallback(AVOSCloud.applicationId) == null) { manager.addDefaultPushCallback(AVOSCloud.applicationId, cls.getName()); } } } /** * 设置推送消息的Icon图标,如果没有设置,默认使用您配置里的应用图标。 * * @param icon A resource ID in the application's package of the drawble to use. * @since 1.4.4 */ public static void setNotificationIcon(int icon) { AVNotificationManager.getInstance().setNotificationIcon(icon); } /** * Provides a default Activity class to handle pushes. Setting a default allows your program to * handle pushes that aren't registered with a subscribe call. This can happen when your * application changes its subscriptions directly through the AVInstallation or via push-to-query. * * @param context This is used to access local storage to cache the subscription, so it must * currently be a viable context. * @param cls This should be a subclass of Activity. An instance of this Activity is started when * the user responds to this push notification. If you are not sure what to use here, * just * use your application's main Activity subclass. */ public static void setDefaultPushCallback(android.content.Context context, java.lang.Class cls) { startServiceIfRequired(context, cls); AVNotificationManager.getInstance().addDefaultPushCallback(AVOSCloud.applicationId, cls.getName()); } /** * Cancels a previous call to subscribe. If the user is not subscribed to this channel, this is a * no-op. This call does not require internet access. It returns without blocking * * @param context A currently viable Context. * @param channel The string defining the channel to unsubscribe from. */ public static synchronized void unsubscribe(android.content.Context context, java.lang.String channel) { if (channel == null) { return; } AVNotificationManager.getInstance().removeDefaultPushCallback(channel); final java.lang.String finalChannel = channel; if (AVUtils.isBlankString(AVInstallation.getCurrentInstallation().getObjectId())) { AVInstallation.getCurrentInstallation().saveInBackground(new SaveCallback() { @Override public void done(AVException e) { if (e == null) { AVInstallation.getCurrentInstallation().removeAll("channels", Arrays.asList(finalChannel)); _installationSaveHandler.sendMessage(Message.obtain()); } else { if (AVOSCloud.showInternalDebugLog()) { e.printStackTrace(); } } } }); } else { AVInstallation.getCurrentInstallation().removeAll("channels", Arrays.asList(finalChannel)); _installationSaveHandler.sendMessage(Message.obtain()); } } static synchronized void sendData(CommandPacket packet) { if (sPushConnectionManager != null) { sPushConnectionManager.sendData(packet); } } /* * https://groups.google.com/forum/#!topic/android-developers/H-DSQ4-tiac * @see android.app.Service#onTaskRemoved(android.content.Intent) */ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) @Override public void onTaskRemoved(Intent rootIntent) { if (AVOSCloud.showInternalDebugLog()) { LogUtil.log.d("try to restart service on task Removed"); } if (isAutoWakeUp) { Intent restartServiceIntent = new Intent(getApplicationContext(), this.getClass()); restartServiceIntent.setPackage(getPackageName()); PendingIntent restartServicePendingIntent = PendingIntent.getService(getApplicationContext(), 1, restartServiceIntent, PendingIntent.FLAG_UPDATE_CURRENT); AlarmManager alarmService = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE); alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 500, restartServicePendingIntent); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { super.onTaskRemoved(rootIntent); } } // 这个方法只是获取当前是否连接的状态 protected static boolean isPushConnectionBroken() { return (sPushConnectionManager == null || (sPushConnectionManager != null && !sPushConnectionManager .isConnectedToPushServer())); } private void processConversationParcelEventFromClient(Intent intent) { int operationCode = intent.getExtras().getInt(Conversation.INTENT_KEY_OPERATION); String clientId = intent.getExtras().getString(Conversation.INTENT_KEY_CLIENT); String conversationId = intent.getExtras().getString(Conversation.INTENT_KEY_CONVERSATION); int convType = intent.getExtras().getInt(Conversation.INTENT_KEY_CONV_TYPE, Conversation.CONV_TYPE_NORMAL); int requestId = intent.getExtras().getInt(Conversation.INTENT_KEY_REQUESTID); AVIMOperation operation = AVIMOperation.getAVIMOperation(operationCode); AVSession session = sPushConnectionManager.getOrCreateSession(clientId); PushServiceParcel parcel = intent.getExtras().getParcelable(Conversation.INTENT_KEY_DATA); switch (operation) { case CONVERSATION_RECALL_MESSAGE: case CONVERSATION_UPDATE_MESSAGE: if (!AVUtils.isBlankString(conversationId)) { AVConversationHolder conversation = session.getConversationHolder(conversationId, convType); if (null != conversation) { conversation.patchMessage(parcel, operation, requestId); } else { LogUtil.log.d("can't find out conversation with id:" + conversationId); } } else { LogUtil.log.d("conversationId is mandatory for MessageRecall or MessageUpdate."); } break; } } private void processConversationEventsFromClient(Intent intent) { int operationCode = intent.getExtras().getInt(Conversation.INTENT_KEY_OPERATION); String clientId = intent.getExtras().getString(Conversation.INTENT_KEY_CLIENT); String conversationId = intent.getExtras().getString(Conversation.INTENT_KEY_CONVERSATION); int convType = intent.getExtras().getInt(Conversation.INTENT_KEY_CONV_TYPE, Conversation.CONV_TYPE_NORMAL); int requestId = intent.getExtras().getInt(Conversation.INTENT_KEY_REQUESTID); AVSession session = sPushConnectionManager.getOrCreateSession(clientId); Map params = null; AVIMOperation operation = AVIMOperation.getAVIMOperation(operationCode); if (operation != AVIMOperation.CONVERSATION_SEND_MESSAGE) { String intentData = intent.getExtras().getString(Conversation.INTENT_KEY_DATA); if (!AVUtils.isBlankString(intentData)) { params = JSON.parseObject(intentData, Map.class); } } // 先检查一下session的状态,但是消息记录查询和conversation查询由于支持缓存则跳过 if (operation != AVIMOperation.CLIENT_OPEN && operation != AVIMOperation.CONVERSATION_MESSAGE_QUERY && operation != AVIMOperation.CONVERSATION_QUERY && operation != AVIMOperation.CLIENT_REFRESH_TOKEN) { AVException connectionException = session.checkSessionStatus(); if (connectionException != null) { BroadcastUtil.sendIMLocalBroadcast(clientId, conversationId, requestId, connectionException, operation); return; } } switch (operation) { case CLIENT_OPEN: AVIMClientParcel parcel = intent.getExtras().getParcelable(Conversation.INTENT_KEY_CLIENT_PARCEL); session.open(parcel, requestId); break; case CLIENT_REFRESH_TOKEN: session.renewRealtimeSesionToken(requestId); break; case CLIENT_DISCONNECT: session.close(requestId); break; case CLIENT_ONLINE_QUERY: List idList = (List) params.get(Conversation.PARAM_ONLINE_CLIENTS); session.queryOnlinePeers(idList, requestId); break; case CONVERSATION_CREATION: List memberList = (List) params.get(Conversation.PARAM_CONVERSATION_MEMBER); Map attribute = null; if (params.containsKey(Conversation.PARAM_CONVERSATION_ATTRIBUTE)) { attribute = (Map) params.get(Conversation.PARAM_CONVERSATION_ATTRIBUTE); } boolean isTransient = false; if (params.containsKey(Conversation.PARAM_CONVERSATION_ISTRANSIENT)) { isTransient = (Boolean) params.get(Conversation.PARAM_CONVERSATION_ISTRANSIENT); } boolean isUnique = (Boolean) params.get(Conversation.PARAM_CONVERSATION_ISUNIQUE); boolean isSystem = false; if (params.containsKey(Conversation.PARAM_CONVERSATION_ISSYSTEM)) { isSystem = (Boolean) params.get(Conversation.PARAM_CONVERSATION_ISSYSTEM); } boolean isTemp = false; if (params.containsKey(Conversation.PARAM_CONVERSATION_ISTEMPORARY)) { isTemp = (Boolean) params.get(Conversation.PARAM_CONVERSATION_ISTEMPORARY); } int tempTTL = 86400*3; if (params.containsKey(Conversation.PARAM_CONVERSATION_TEMPORARY_TTL)) { tempTTL = (Integer) params.get(Conversation.PARAM_CONVERSATION_TEMPORARY_TTL); } session.createConversation(memberList, attribute, isTransient, isUnique, isTemp, tempTTL, isSystem, requestId); break; case CONVERSATION_QUERY: session.conversationQuery(params, requestId); break; case CONVERSATION_SEND_MESSAGE: if (!AVUtils.isBlankString(conversationId)) { AVConversationHolder conversation = session.getConversationHolder(conversationId, convType); if (null != conversation) { AVIMMessage message = intent.getExtras().getParcelable(Conversation.INTENT_KEY_DATA); AVIMMessageOption messageOption = null; if (intent.getExtras().containsKey(Conversation.INTENT_KEY_MESSAGE_OPTION)) { messageOption = intent.getExtras().getParcelable(Conversation.INTENT_KEY_MESSAGE_OPTION); } else { messageOption = new AVIMMessageOption(); } message.setFrom(clientId); conversation.sendMessage(message, requestId, messageOption); } } break; case CLIENT_STATUS: processSessionConnectionStatus(session, requestId); break; case CONVERSATION_PROMOTE_MEMBER: case CONVERSATION_MUTE_MEMBER: case CONVERSATION_UNMUTE_MEMBER: case CONVERSATION_BLOCK_MEMBER: case CONVERSATION_UNBLOCK_MEMBER: case CONVERSATION_MUTED_MEMBER_QUERY: case CONVERSATION_BLOCKED_MEMBER_QUERY: default: if (AVUtils.isBlankString(conversationId)) { LogUtil.log.e("conversation id is null during promoting MemberInfo"); } else { AVConversationHolder internalConversation = session.getConversationHolder(conversationId, convType); if (null == internalConversation) { LogUtil.log.w("not found target conversation with id=" + conversationId); } else { internalConversation.processConversationCommandFromClient(operation, params, requestId); } } break; } } private void processSessionConnectionStatus(AVSession session, int requestId) { AVIMClient.AVIMClientStatus status = AVIMClient.AVIMClientStatus.AVIMClientStatusNone; if (session.sessionOpened.get() && session.sessionPaused.get()) { status = AVIMClient.AVIMClientStatus.AVIMClientStatusPaused; } else if (session.sessionOpened.get()) { status = AVIMClient.AVIMClientStatus.AVIMClientStatusOpened; } Bundle bundle = new Bundle(); bundle.putInt(Conversation.callbackClientStatus, status.getCode()); BroadcastUtil.sendIMLocalBroadcast(session.getSelfPeerId(), null, requestId, bundle, AVIMOperation.CLIENT_STATUS); } /** * 唤醒其他同样使用 LeanCloud 服务的 app * 必须要在 AndroidManifest PushService 注册时主动添加 exported = true 才会开启此功能 * 只需要在每次 app 启动时唤醒其他 app * @param action */ private void notifyOtherApplication(final String action) { if (isNeedNotifyApplication && !NotifyUtil.SERVICE_RESTART_ACTION.equals(action)) { // 每次 app 启动只需要唤醒一次就行了 isNeedNotifyApplication = false; try { ServiceInfo info = getApplicationContext().getPackageManager().getServiceInfo( new ComponentName(getApplicationContext(), PushService.class), 0); if(info.exported) { NotifyUtil.notifyHandler.sendEmptyMessage(NotifyUtil.SERVICE_RESTART); } } catch (PackageManager.NameNotFoundException e) { } } } /** * Set whether to automatically wake up PushService * @param isAutoWakeUp the default value is true */ public static void setAutoWakeUp(boolean isAutoWakeUp) { PushService.isAutoWakeUp = isAutoWakeUp; } /** * Set default channel for Android Oreo or newer version * Notice: it isn"t necessary to invoke this method for any Android version before Oreo. * * @param context context * @param channelId default channel id. */ @TargetApi(Build.VERSION_CODES.O) public static void setDefaultChannelId(Context context, String channelId) { DefaultChannelId = channelId; if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) { // do nothing for Android versions before Ore return; } try { NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); CharSequence name = context.getPackageName(); String description = "PushNotification"; int importance = NotificationManager.IMPORTANCE_DEFAULT; android.app.NotificationChannel channel = new android.app.NotificationChannel(channelId, name, importance); channel.setDescription(description); notificationManager.createNotificationChannel(channel); } catch (Exception ex) { LogUtil.log.w("failed to create NotificationChannel, then perhaps PushNotification doesn't work well on Android O and newer version."); } } private static Handler _installationSaveHandler = new Handler(Looper.getMainLooper()) { public void handleMessage(Message m) { AVInstallation.getCurrentInstallation().saveInBackground(new SaveCallback() { @Override public void done(AVException e) { if (e != null && "already has one request sending".equals(e.getMessage())) { _installationSaveHandler.removeMessages(0); Message m = Message.obtain(); m.what = 0; _installationSaveHandler.sendMessageDelayed(m, 2000); } } }); } }; }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy