2015-03-22 13:32:51 +00:00
|
|
|
/*
|
2015-10-03 20:47:05 +00:00
|
|
|
* Copyright 2013-2015 microG Project Team
|
2015-03-22 13:32:51 +00:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2015-04-02 21:46:47 +00:00
|
|
|
package org.microg.gms.gcm;
|
2015-03-22 13:32:51 +00:00
|
|
|
|
2015-08-04 11:05:47 +00:00
|
|
|
import android.app.AlarmManager;
|
2015-03-22 13:32:51 +00:00
|
|
|
import android.app.IntentService;
|
2015-08-04 11:05:47 +00:00
|
|
|
import android.app.PendingIntent;
|
2015-03-22 13:32:51 +00:00
|
|
|
import android.content.Context;
|
|
|
|
import android.content.Intent;
|
|
|
|
import android.content.SharedPreferences;
|
2015-08-04 11:05:47 +00:00
|
|
|
import android.os.Handler;
|
|
|
|
import android.os.Looper;
|
|
|
|
import android.os.PowerManager;
|
|
|
|
import android.os.SystemClock;
|
2015-08-17 21:28:01 +00:00
|
|
|
import android.support.v4.content.WakefulBroadcastReceiver;
|
2015-03-22 13:32:51 +00:00
|
|
|
import android.util.Log;
|
|
|
|
|
|
|
|
import com.squareup.wire.Message;
|
|
|
|
|
|
|
|
import org.microg.gms.checkin.LastCheckinInfo;
|
2015-04-02 21:46:47 +00:00
|
|
|
import org.microg.gms.gcm.mcs.AppData;
|
|
|
|
import org.microg.gms.gcm.mcs.Close;
|
|
|
|
import org.microg.gms.gcm.mcs.DataMessageStanza;
|
|
|
|
import org.microg.gms.gcm.mcs.HeartbeatAck;
|
|
|
|
import org.microg.gms.gcm.mcs.HeartbeatPing;
|
|
|
|
import org.microg.gms.gcm.mcs.LoginRequest;
|
|
|
|
import org.microg.gms.gcm.mcs.LoginResponse;
|
|
|
|
import org.microg.gms.gcm.mcs.Setting;
|
2015-03-22 13:32:51 +00:00
|
|
|
|
|
|
|
import java.net.Socket;
|
|
|
|
import java.util.Arrays;
|
2015-03-23 01:14:07 +00:00
|
|
|
import java.util.Collections;
|
2015-03-22 13:32:51 +00:00
|
|
|
|
|
|
|
import javax.net.ssl.SSLContext;
|
|
|
|
|
|
|
|
import static android.os.Build.VERSION.SDK_INT;
|
2015-08-04 11:05:47 +00:00
|
|
|
import static org.microg.gms.gcm.Constants.MSG_CONNECT;
|
|
|
|
import static org.microg.gms.gcm.Constants.MSG_HEARTBEAT;
|
|
|
|
import static org.microg.gms.gcm.Constants.MSG_INPUT;
|
|
|
|
import static org.microg.gms.gcm.Constants.MSG_INPUT_ERROR;
|
|
|
|
import static org.microg.gms.gcm.Constants.MSG_OUTPUT;
|
|
|
|
import static org.microg.gms.gcm.Constants.MSG_OUTPUT_ERROR;
|
|
|
|
import static org.microg.gms.gcm.Constants.MSG_OUTPUT_READY;
|
|
|
|
import static org.microg.gms.gcm.Constants.MSG_TEARDOWN;
|
2015-03-22 13:32:51 +00:00
|
|
|
|
2015-08-04 11:05:47 +00:00
|
|
|
public class McsService extends IntentService implements Handler.Callback {
|
2015-03-22 13:32:51 +00:00
|
|
|
private static final String TAG = "GmsGcmMcsSvc";
|
2015-04-02 21:46:47 +00:00
|
|
|
|
2015-08-04 11:05:47 +00:00
|
|
|
public static String ACTION_CONNECT = "org.microg.gms.gcm.mcs.CONNECT";
|
|
|
|
public static String ACTION_HEARTBEAT = "org.microg.gms.gcm.mcs.HEARTBEAT";
|
|
|
|
|
2015-03-22 13:32:51 +00:00
|
|
|
public static final String PREFERENCES_NAME = "mcs";
|
2015-04-02 21:46:47 +00:00
|
|
|
public static final String PREF_LAST_PERSISTENT_ID = "last_persistent_id";
|
2015-03-22 13:32:51 +00:00
|
|
|
|
|
|
|
public static final String SELF_CATEGORY = "com.google.android.gsf.gtalkservice";
|
|
|
|
public static final String IDLE_NOTIFICATION = "IdleNotification";
|
|
|
|
public static final String FROM_FIELD = "gcm@android.com";
|
2015-04-02 21:46:47 +00:00
|
|
|
|
2015-08-04 11:05:47 +00:00
|
|
|
public static final String SERVICE_HOST = "mtalk.google.com";
|
|
|
|
public static final int SERVICE_PORT = 5228;
|
|
|
|
|
2015-03-23 01:14:07 +00:00
|
|
|
public static final int HEARTBEAT_MS = 60000;
|
2015-03-22 13:32:51 +00:00
|
|
|
|
2015-08-04 11:05:47 +00:00
|
|
|
private static Socket sslSocket;
|
|
|
|
private static McsInputStream inputStream;
|
|
|
|
private static McsOutputStream outputStream;
|
|
|
|
|
|
|
|
private PendingIntent heartbeatIntent;
|
|
|
|
|
|
|
|
private static MainThread mainThread;
|
|
|
|
private static Handler mainHandler;
|
|
|
|
|
|
|
|
private AlarmManager alarmManager;
|
|
|
|
private PowerManager powerManager;
|
|
|
|
private static PowerManager.WakeLock wakeLock;
|
2015-03-22 13:32:51 +00:00
|
|
|
|
2015-10-03 20:47:05 +00:00
|
|
|
private static long currentDelay = 0;
|
2015-08-17 21:28:01 +00:00
|
|
|
|
|
|
|
private Intent connectIntent;
|
|
|
|
|
2015-03-22 13:32:51 +00:00
|
|
|
public McsService() {
|
|
|
|
super(TAG);
|
|
|
|
}
|
|
|
|
|
2015-08-04 11:05:47 +00:00
|
|
|
private class MainThread extends Thread {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
Looper.prepare();
|
2015-08-17 21:28:01 +00:00
|
|
|
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "mcs");
|
|
|
|
wakeLock.setReferenceCounted(false);
|
|
|
|
synchronized (McsService.class) {
|
|
|
|
mainHandler = new Handler(Looper.myLooper(), McsService.this);
|
|
|
|
if (connectIntent != null) {
|
|
|
|
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_CONNECT, connectIntent));
|
|
|
|
WakefulBroadcastReceiver.completeWakefulIntent(connectIntent);
|
|
|
|
}
|
|
|
|
}
|
2015-08-04 11:05:47 +00:00
|
|
|
Looper.loop();
|
|
|
|
}
|
2015-07-04 11:47:27 +00:00
|
|
|
}
|
|
|
|
|
2015-03-22 13:32:51 +00:00
|
|
|
@Override
|
2015-08-04 11:05:47 +00:00
|
|
|
public void onCreate() {
|
|
|
|
super.onCreate();
|
|
|
|
heartbeatIntent = PendingIntent.getService(this, 0, new Intent(ACTION_HEARTBEAT, null, this, McsService.class), 0);
|
|
|
|
alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
|
|
|
|
powerManager = (PowerManager) getSystemService(POWER_SERVICE);
|
2015-08-17 21:28:01 +00:00
|
|
|
synchronized (McsService.class) {
|
|
|
|
if (mainThread == null) {
|
|
|
|
mainThread = new MainThread();
|
|
|
|
mainThread.start();
|
|
|
|
}
|
|
|
|
}
|
2015-03-22 13:32:51 +00:00
|
|
|
}
|
|
|
|
|
2015-08-17 21:28:01 +00:00
|
|
|
public synchronized static boolean isConnected() {
|
2015-08-04 11:05:47 +00:00
|
|
|
return inputStream != null && inputStream.isAlive() && outputStream != null && outputStream.isAlive();
|
2015-04-02 21:46:47 +00:00
|
|
|
}
|
|
|
|
|
2015-10-03 20:47:05 +00:00
|
|
|
public synchronized static long getCurrentDelay() {
|
|
|
|
long delay = currentDelay == 0 ? 5000 : currentDelay;
|
|
|
|
if (currentDelay < 60000) currentDelay += 5000;
|
|
|
|
if (currentDelay >= 60000 && currentDelay < 60000) currentDelay += 60000;
|
|
|
|
return delay;
|
|
|
|
}
|
|
|
|
|
|
|
|
public synchronized static void resetCurrentDelay() {
|
|
|
|
currentDelay = 0;
|
|
|
|
}
|
|
|
|
|
2015-08-04 11:05:47 +00:00
|
|
|
@Override
|
|
|
|
protected void onHandleIntent(Intent intent) {
|
2015-08-17 21:28:01 +00:00
|
|
|
synchronized (McsService.class) {
|
|
|
|
if (mainHandler != null) {
|
|
|
|
wakeLock.acquire();
|
|
|
|
if (ACTION_CONNECT.equals(intent.getAction())) {
|
|
|
|
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_CONNECT, intent));
|
|
|
|
} else if (ACTION_HEARTBEAT.equals(intent.getAction())) {
|
|
|
|
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_HEARTBEAT, intent));
|
|
|
|
}
|
|
|
|
WakefulBroadcastReceiver.completeWakefulIntent(intent);
|
|
|
|
} else if (connectIntent == null) {
|
|
|
|
connectIntent = intent;
|
|
|
|
} else {
|
|
|
|
WakefulBroadcastReceiver.completeWakefulIntent(intent);
|
2015-03-23 01:14:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-04 11:05:47 +00:00
|
|
|
private synchronized void connect() {
|
2015-03-22 13:32:51 +00:00
|
|
|
try {
|
|
|
|
Log.d(TAG, "Starting MCS connection...");
|
2015-04-02 21:46:47 +00:00
|
|
|
Socket socket = new Socket(SERVICE_HOST, SERVICE_PORT);
|
2015-03-22 13:32:51 +00:00
|
|
|
Log.d(TAG, "Connected to " + SERVICE_HOST + ":" + SERVICE_PORT);
|
2015-08-04 11:05:47 +00:00
|
|
|
sslSocket = SSLContext.getDefault().getSocketFactory().createSocket(socket, SERVICE_HOST, SERVICE_PORT, true);
|
2015-03-22 13:32:51 +00:00
|
|
|
Log.d(TAG, "Activated SSL with " + SERVICE_HOST + ":" + SERVICE_PORT);
|
2015-08-04 11:05:47 +00:00
|
|
|
inputStream = new McsInputStream(sslSocket.getInputStream(), mainHandler);
|
|
|
|
outputStream = new McsOutputStream(sslSocket.getOutputStream(), mainHandler);
|
|
|
|
inputStream.start();
|
|
|
|
outputStream.start();
|
2015-08-17 21:28:01 +00:00
|
|
|
|
|
|
|
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), HEARTBEAT_MS, heartbeatIntent);
|
2015-03-22 13:32:51 +00:00
|
|
|
} catch (Exception e) {
|
2015-08-04 11:05:47 +00:00
|
|
|
Log.w(TAG, "Exception while connecting!", e);
|
|
|
|
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_TEARDOWN, e));
|
2015-03-22 13:32:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-04 11:05:47 +00:00
|
|
|
private void handleClose(Close close) {
|
|
|
|
throw new RuntimeException("Server requested close!");
|
2015-03-22 13:32:51 +00:00
|
|
|
}
|
|
|
|
|
2015-08-04 11:05:47 +00:00
|
|
|
private void handleLoginResponse(LoginResponse loginResponse) {
|
2015-03-22 13:32:51 +00:00
|
|
|
if (loginResponse.error == null) {
|
2015-08-04 11:05:47 +00:00
|
|
|
getSharedPreferences().edit().putString(PREF_LAST_PERSISTENT_ID, "").apply();
|
2015-03-22 13:32:51 +00:00
|
|
|
Log.d(TAG, "Logged in");
|
2015-08-04 11:05:47 +00:00
|
|
|
wakeLock.release();
|
2015-03-22 13:32:51 +00:00
|
|
|
} else {
|
2015-08-04 11:05:47 +00:00
|
|
|
throw new RuntimeException("Could not login: " + loginResponse.error);
|
2015-03-23 01:14:07 +00:00
|
|
|
}
|
2015-03-22 13:32:51 +00:00
|
|
|
}
|
|
|
|
|
2015-08-04 11:05:47 +00:00
|
|
|
private void handleCloudMessage(DataMessageStanza message) {
|
2015-03-22 13:32:51 +00:00
|
|
|
if (message.persistent_id != null) {
|
2015-04-03 22:21:47 +00:00
|
|
|
String old = getSharedPreferences().getString(PREF_LAST_PERSISTENT_ID, "");
|
|
|
|
if (!old.isEmpty()) {
|
2015-03-22 13:32:51 +00:00
|
|
|
old += "|";
|
|
|
|
}
|
|
|
|
getSharedPreferences().edit()
|
|
|
|
.putString(PREF_LAST_PERSISTENT_ID, old + message.persistent_id).apply();
|
|
|
|
}
|
|
|
|
if (SELF_CATEGORY.equals(message.category)) {
|
|
|
|
handleSelfMessage(message);
|
|
|
|
} else {
|
|
|
|
handleAppMessage(message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-08-04 11:05:47 +00:00
|
|
|
private void handleHearbeatPing(HeartbeatPing ping) {
|
2015-03-22 13:32:51 +00:00
|
|
|
HeartbeatAck.Builder ack = new HeartbeatAck.Builder().status(ping.status);
|
|
|
|
if (inputStream.newStreamIdAvailable()) {
|
|
|
|
ack.last_stream_id_received(inputStream.getStreamId());
|
|
|
|
}
|
2015-08-04 11:05:47 +00:00
|
|
|
send(ack.build());
|
2015-03-22 13:32:51 +00:00
|
|
|
}
|
|
|
|
|
2015-08-04 11:05:47 +00:00
|
|
|
private void handleHeartbeatAck(HeartbeatAck ack) {
|
|
|
|
wakeLock.release();
|
|
|
|
}
|
|
|
|
|
|
|
|
private LoginRequest buildLoginRequest() {
|
|
|
|
LastCheckinInfo info = LastCheckinInfo.read(this);
|
2015-03-22 13:32:51 +00:00
|
|
|
return new LoginRequest.Builder()
|
|
|
|
.adaptive_heartbeat(false)
|
|
|
|
.auth_service(LoginRequest.AuthService.ANDROID_ID)
|
|
|
|
.auth_token(Long.toString(info.securityToken))
|
|
|
|
.id("android-" + SDK_INT)
|
|
|
|
.domain("mcs.android.com")
|
|
|
|
.device_id("android-" + Long.toHexString(info.androidId))
|
|
|
|
.network_type(1)
|
|
|
|
.resource(Long.toString(info.androidId))
|
|
|
|
.user(Long.toString(info.androidId))
|
|
|
|
.use_rmq2(true)
|
2015-04-02 21:46:47 +00:00
|
|
|
.setting(Collections.singletonList(new Setting("new_vc", "1")))
|
2015-03-22 13:32:51 +00:00
|
|
|
.received_persistent_id(Arrays.asList(getSharedPreferences().getString(PREF_LAST_PERSISTENT_ID, "").split("\\|")))
|
|
|
|
.build();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void handleAppMessage(DataMessageStanza msg) {
|
|
|
|
Intent intent = new Intent();
|
|
|
|
intent.setAction("com.google.android.c2dm.intent.RECEIVE");
|
|
|
|
intent.addCategory(msg.category);
|
|
|
|
for (AppData appData : msg.app_data) {
|
|
|
|
intent.putExtra(appData.key, appData.value);
|
|
|
|
}
|
|
|
|
sendOrderedBroadcast(intent, msg.category + ".permission.C2D_MESSAGE");
|
|
|
|
}
|
|
|
|
|
2015-08-04 11:05:47 +00:00
|
|
|
private void handleSelfMessage(DataMessageStanza msg) {
|
2015-03-22 13:32:51 +00:00
|
|
|
for (AppData appData : msg.app_data) {
|
|
|
|
if (IDLE_NOTIFICATION.equals(appData.key)) {
|
|
|
|
DataMessageStanza.Builder msgResponse = new DataMessageStanza.Builder()
|
|
|
|
.from(FROM_FIELD)
|
|
|
|
.sent(System.currentTimeMillis() / 1000)
|
|
|
|
.ttl(0)
|
|
|
|
.category(SELF_CATEGORY)
|
2015-03-23 01:14:07 +00:00
|
|
|
.app_data(Collections.singletonList(new AppData(IDLE_NOTIFICATION, "false")));
|
2015-03-22 13:32:51 +00:00
|
|
|
if (inputStream.newStreamIdAvailable()) {
|
|
|
|
msgResponse.last_stream_id_received(inputStream.getStreamId());
|
|
|
|
}
|
2015-08-04 11:05:47 +00:00
|
|
|
send(msgResponse.build());
|
2015-03-22 13:32:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private SharedPreferences getSharedPreferences() {
|
|
|
|
return getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
|
|
|
|
}
|
2015-08-04 11:05:47 +00:00
|
|
|
|
|
|
|
private void send(Message message) {
|
|
|
|
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_OUTPUT, message));
|
|
|
|
}
|
|
|
|
|
|
|
|
private void sendOutputStream(int what, Object obj) {
|
|
|
|
McsOutputStream os = outputStream;
|
|
|
|
if (os != null) {
|
|
|
|
Handler outputHandler = os.getHandler();
|
|
|
|
if (outputHandler != null)
|
|
|
|
outputHandler.dispatchMessage(outputHandler.obtainMessage(what, obj));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean handleMessage(android.os.Message msg) {
|
|
|
|
switch (msg.what) {
|
|
|
|
case MSG_INPUT:
|
|
|
|
Log.d(TAG, "Incoming message: " + msg.obj);
|
2015-08-17 21:28:01 +00:00
|
|
|
handleInput((Message) msg.obj);
|
2015-08-04 11:05:47 +00:00
|
|
|
return true;
|
|
|
|
case MSG_OUTPUT:
|
|
|
|
Log.d(TAG, "Outgoing message: " + msg.obj);
|
|
|
|
sendOutputStream(MSG_OUTPUT, msg.obj);
|
|
|
|
return true;
|
|
|
|
case MSG_INPUT_ERROR:
|
|
|
|
case MSG_OUTPUT_ERROR:
|
|
|
|
Log.d(TAG, "I/O error: " + msg.obj);
|
|
|
|
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_TEARDOWN, msg.obj));
|
|
|
|
return true;
|
|
|
|
case MSG_TEARDOWN:
|
|
|
|
Log.d(TAG, "Teardown initiated, reason: " + msg.obj);
|
|
|
|
handleTeardown(msg);
|
|
|
|
return true;
|
|
|
|
case MSG_CONNECT:
|
|
|
|
Log.d(TAG, "Connect initiated, reason: " + msg.obj);
|
|
|
|
if (!isConnected()) {
|
|
|
|
connect();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
case MSG_HEARTBEAT:
|
|
|
|
Log.d(TAG, "Heartbeat initiated, reason: " + msg.obj);
|
|
|
|
if (isConnected()) {
|
|
|
|
HeartbeatPing.Builder ping = new HeartbeatPing.Builder();
|
|
|
|
if (inputStream.newStreamIdAvailable()) {
|
|
|
|
ping.last_stream_id_received(inputStream.getStreamId());
|
|
|
|
}
|
|
|
|
send(ping.build());
|
|
|
|
} else {
|
|
|
|
Log.d(TAG, "Ignoring heartbeat, not connected!");
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
case MSG_OUTPUT_READY:
|
|
|
|
Log.d(TAG, "Sending login request...");
|
|
|
|
send(buildLoginRequest());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
Log.w(TAG, "Unknown message: " + msg);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void handleInput(Message message) {
|
|
|
|
try {
|
|
|
|
if (message instanceof DataMessageStanza) {
|
|
|
|
handleCloudMessage((DataMessageStanza) message);
|
|
|
|
} else if (message instanceof HeartbeatPing) {
|
|
|
|
handleHearbeatPing((HeartbeatPing) message);
|
|
|
|
} else if (message instanceof Close) {
|
|
|
|
handleClose((Close) message);
|
|
|
|
} else if (message instanceof LoginResponse) {
|
|
|
|
handleLoginResponse((LoginResponse) message);
|
|
|
|
} else if (message instanceof HeartbeatAck) {
|
|
|
|
handleHeartbeatAck((HeartbeatAck) message);
|
|
|
|
} else {
|
|
|
|
Log.w(TAG, "Unknown message: " + message);
|
|
|
|
}
|
2015-10-03 20:47:05 +00:00
|
|
|
resetCurrentDelay();
|
2015-08-04 11:05:47 +00:00
|
|
|
} catch (Exception e) {
|
|
|
|
mainHandler.dispatchMessage(mainHandler.obtainMessage(MSG_TEARDOWN, e));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void handleTeardown(android.os.Message msg) {
|
|
|
|
sendOutputStream(MSG_TEARDOWN, msg.obj);
|
|
|
|
if (inputStream != null) {
|
|
|
|
inputStream.close();
|
2015-08-17 21:28:01 +00:00
|
|
|
inputStream = null;
|
2015-08-04 11:05:47 +00:00
|
|
|
}
|
|
|
|
try {
|
|
|
|
sslSocket.close();
|
|
|
|
} catch (Exception ignored) {
|
|
|
|
}
|
2015-10-03 20:47:05 +00:00
|
|
|
if (currentDelay == 0) {
|
2015-08-17 21:28:01 +00:00
|
|
|
sendBroadcast(new Intent("org.microg.gms.gcm.RECONNECT"), "org.microg.gms.STATUS_BROADCAST");
|
|
|
|
} else {
|
2015-10-03 20:47:05 +00:00
|
|
|
alarmManager.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + getCurrentDelay(),
|
|
|
|
PendingIntent.getBroadcast(this, 1, new Intent("org.microg.gms.gcm.RECONNECT", null, this, TriggerReceiver.class), 0));
|
2015-08-17 21:28:01 +00:00
|
|
|
}
|
2015-08-04 11:05:47 +00:00
|
|
|
alarmManager.cancel(heartbeatIntent);
|
2015-08-17 21:28:01 +00:00
|
|
|
if (wakeLock != null) {
|
|
|
|
wakeLock.release();
|
|
|
|
}
|
2015-08-04 11:05:47 +00:00
|
|
|
}
|
2015-03-22 13:32:51 +00:00
|
|
|
}
|