org.kegbot.app.service.CheckinService.java Source code

Java tutorial

Introduction

Here is the source code for org.kegbot.app.service.CheckinService.java

Source

/*
 * Copyright 2012 Mike Wakerly <opensource@hoho.com>.
 *
 * This file is part of the Kegtab package from the Kegbot project. For
 * more information on Kegtab or Kegbot, see <http://kegbot.org/>.
 *
 * Kegtab 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, version 2.
 *
 * Kegtab 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 Kegtab. If not, see <http://www.gnu.org/licenses/>.
 */
package org.kegbot.app.service;

import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.util.EntityUtils;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.kegbot.app.R;
import org.kegbot.app.build.BuildInfo;
import org.kegbot.app.util.DeviceId;
import org.kegbot.app.util.PreferenceHelper;
import org.kegbot.app.util.Utils;

import android.app.AlarmManager;
import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Build;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
import android.util.Log;

import com.google.common.collect.Lists;

/**
 * Checkin service: pings kegbot servers for version/support information.
 *
 * @author mike wakerly (opensource@hoho.com)
 */
public class CheckinService extends IntentService {

    private static final String TAG = CheckinService.class.getSimpleName();
    private static final String CHECKIN_URL = "https://kegbot.org/checkin/";
    static final String CHECKIN_ACTION = "org.kegbot.app.CHECKIN";

    private static final long CHECKIN_INTERVAL_MILLIS = AlarmManager.INTERVAL_HALF_DAY;

    private static final int CHECKIN_NOTIFICATION_ID = 100;

    /**
     * Normal checkin response status; nothing to do.
     */
    static final String STATUS_OK = "ok";

    private PreferenceHelper mPrefsHelper;

    private int mKegbotVersion = -1;
    private String mDeviceId;
    private PendingIntent mPendingIntent;
    private WakeLock mWakeLock;

    public CheckinService() {
        super("CheckinService");
    }

    @Override
    public void onCreate() {
        super.onCreate();

        mPrefsHelper = new PreferenceHelper(this);

        final PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "kbcheckin");

        mDeviceId = DeviceId.getDeviceId(this);
        try {
            final PackageInfo pinfo = getPackageManager().getPackageInfo(getPackageName(), 0);
            mKegbotVersion = pinfo.versionCode;
        } catch (NameNotFoundException e) {
            Log.w(TAG, "Could not look up own package info.");
        }

        resetCheckinStateIfNeeded();

        registerAlarm();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        acquireWakeLock();
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        try {
            final String action = intent.getAction();
            if (CHECKIN_ACTION.equals(action)) {
                doCheckin();
            } else if (resetCheckinStateIfNeeded()) {
                doCheckin();
            }
        } finally {
            releaseWakeLock();
        }
    }

    @Override
    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
        super.dump(fd, writer, args);
        writer.println(String.format("Last checkin attempt: %s", new Date(mPrefsHelper.getLastCheckinAttempt())));
        writer.println(String.format("Last checkin success: %s", new Date(mPrefsHelper.getLastCheckinSuccess())));
    }

    private void registerAlarm() {
        unregisterAlarm();
        Log.d(TAG, "Registering alarm.");
        final Intent intent = getCheckinIntent(this);
        mPendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
        final AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
        final long nextCheckin = SystemClock.elapsedRealtime() + CHECKIN_INTERVAL_MILLIS;
        alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextCheckin, CHECKIN_INTERVAL_MILLIS,
                mPendingIntent);
    }

    private void unregisterAlarm() {
        if (mPendingIntent != null) {
            Log.d(TAG, "Unregistering alarm.");
            final AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
            alarmManager.cancel(mPendingIntent);
            mPendingIntent = null;
        }
    }

    private void acquireWakeLock() {
        synchronized (mWakeLock) {
            if (!mWakeLock.isHeld()) {
                Log.d(TAG, "Acquiring wake lock.");
                mWakeLock.acquire();
            }
        }
    }

    private void releaseWakeLock() {
        synchronized (mWakeLock) {
            if (mWakeLock.isHeld()) {
                Log.d(TAG, "Releasing wake lock.");
                mWakeLock.release();
            }
        }
    }

    private void doCheckin() {
        Log.d(TAG, "Performing checkin: " + CHECKIN_URL);
        final long now = System.currentTimeMillis();
        mPrefsHelper.setLastCheckinAttempt(now);

        final HttpClient client = new DefaultHttpClient();
        final HttpPost request = new HttpPost(CHECKIN_URL);
        final HttpParams requestParams = new BasicHttpParams();

        HttpProtocolParams.setUserAgent(requestParams, Utils.getUserAgent());
        request.setParams(requestParams);

        try {
            List<NameValuePair> params = Lists.newArrayList();
            params.add(new BasicNameValuePair("device_id", mDeviceId));
            params.add(new BasicNameValuePair("android_version", Build.VERSION.SDK));
            params.add(new BasicNameValuePair("android_device", Build.DEVICE));
            params.add(new BasicNameValuePair("app_package", getPackageName()));
            params.add(new BasicNameValuePair("app_version", String.valueOf(mKegbotVersion)));
            params.add(new BasicNameValuePair("app_date", BuildInfo.BUILD_DATE_HUMAN));
            params.add(new BasicNameValuePair("gcm_reg_id", mPrefsHelper.getGcmRegistrationId()));
            request.setEntity(new UrlEncodedFormEntity(params));

            final HttpResponse response = client.execute(request);
            Log.d(TAG, "Checkin complete");
            final String responseBody = EntityUtils.toString(response.getEntity());
            final ObjectMapper mapper = new ObjectMapper();
            final JsonNode rootNode = mapper.readValue(responseBody, JsonNode.class);
            if (response.getStatusLine().getStatusCode() == 200) {
                mPrefsHelper.setLastCheckinSuccess(now);
                mPrefsHelper.setIsRegistered(true);
                processLastCheckinResponse(rootNode);
            }
        } catch (IOException e) {
            Log.d(TAG, "Checkin failed: " + e);
        }
    }

    /**
     * Processes the checkin response message.
     */
    private void processLastCheckinResponse(JsonNode response) {
        Log.d(TAG, "Checkin response: " + response);

        NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

        final JsonNode statusNode = response.get("status");
        if (statusNode == null || !statusNode.isTextual()) {
            Log.d(TAG, "Invalid checkin response: no status.");
            return;
        }

        final String status = statusNode.getTextValue();
        if (STATUS_OK.equals(status)) {
            Log.d(TAG, "Checkin status: " + status);
        } else {
            Log.d(TAG, "Invalid checkin response: unknown status: " + status);
            return;
        }

        boolean updateNeeded = false;
        final JsonNode updateNeededNode = response.get("update_needed");
        if (updateNeededNode != null && updateNeededNode.isBoolean() && updateNeededNode.getBooleanValue()) {
            updateNeeded = true;
        }

        boolean updateRequired = false;
        final JsonNode updateRequiredNode = response.get("update_required");
        if (updateRequiredNode != null && updateRequiredNode.isBoolean() && updateRequiredNode.getBooleanValue()) {
            updateRequired = true;
        }

        mPrefsHelper.setLastCheckinStatus(status);
        mPrefsHelper.setUpdateNeeded(updateNeeded);
        mPrefsHelper.setUpdateRequired(updateRequired);

        if (updateNeeded) {
            Intent notificationIntent = new Intent(Intent.ACTION_VIEW);
            notificationIntent.setData(Uri.parse("market://details?id=org.kegbot.app"));
            PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

            int titleRes = updateRequired ? R.string.checkin_update_required_title
                    : R.string.checkin_update_available_title;

            Notification noti = new Notification.Builder(this)
                    .setSmallIcon(updateRequired ? R.drawable.icon_warning : R.drawable.icon_download)
                    .setContentTitle(getString(titleRes))
                    .setContentText(getString(R.string.checkin_update_description)).setContentIntent(contentIntent)
                    .setOngoing(true).setOnlyAlertOnce(true).getNotification();

            Log.d(TAG, "Posting notification.");
            nm.notify(CHECKIN_NOTIFICATION_ID, noti);
        } else {
            nm.cancel(CHECKIN_NOTIFICATION_ID);
        }
    }

    /**
     * @return {@code true} if state was reset.
     */
    private boolean resetCheckinStateIfNeeded() {
        if (mPrefsHelper.getLastCheckinVersion() == mKegbotVersion) {
            return false;
        }
        mPrefsHelper.setUpdateNeeded(false);
        mPrefsHelper.setUpdateRequired(false);
        mPrefsHelper.setLastCheckinAttempt(Long.MIN_VALUE);
        mPrefsHelper.setLastCheckinSuccess(Long.MIN_VALUE);
        mPrefsHelper.setLastCheckinStatus("unknown");
        return true;
    }

    private static Intent getCheckinIntent(Context context) {
        final Intent intent = new Intent(CHECKIN_ACTION);
        return intent;
    }

    public static void requestImmediateCheckin(Context context) {
        final Intent intent = getCheckinIntent(context);
        context.sendBroadcast(intent);
    }

    public static void startCheckinService(Context context) {
        final Intent intent = new Intent(context, CheckinService.class);
        context.startService(intent);
    }

}