com.github.nutomic.pegasus.LocationService.java Source code

Java tutorial

Introduction

Here is the source code for com.github.nutomic.pegasus.LocationService.java

Source

/*
 * Copyright (C) 2012 Felix Ableitner
 *
 * 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.
 */

package com.github.nutomic.pegasus;

import java.util.Set;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.media.AudioManager;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemClock;
import android.support.v4.app.NotificationCompat;
import android.telephony.CellLocation;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.telephony.cdma.CdmaCellLocation;
import android.telephony.gsm.GsmCellLocation;
import android.util.Log;

import com.github.nutomic.pegasus.activities.AreaList;
import com.github.nutomic.pegasus.content.AreaColumns;
import com.github.nutomic.pegasus.content.CellColumns;
import com.github.nutomic.pegasus.content.CellLogColumns;
import com.github.nutomic.pegasus.content.Database;
import com.github.nutomic.pegasus.content.ProfileColumns;

/**
 * Changes the sound profile when a different network cell is entered.
 * 
 * @author Felix Ableitner
 * 
 */
public class LocationService extends Service {

    /**
     * Start learning an area, pass time interval in seconds as data. Must
     * also set MESSAGE_LEARN_AREA.
     */
    public static final String MESSAGE_LEARN_INTERVAL = "learn_interval";

    /**
     * Start learning an area, pass the id of the area to learn. Must also 
     * set MESSAGE_LEARN_INTERVAL.
     */
    public static final String MESSAGE_LEARN_AREA = "learn_area";

    /** Areas or profiles updated, reapply current profile. */
    public static final String MESSAGE_UPDATE = "update";

    private static final int NOTIFICATION_ID = 1;

    private static final String TAG = "LocationService";

    private static final int CELL_NO_SIGNAL = -1;

    /** Database ID of the current area. */
    private volatile long mCurrentArea = Database.ROW_NONE;

    private volatile int mCurrentCell = CELL_NO_SIGNAL;

    /** The database ID of the area to assign new cells to. */
    private volatile long mLearnArea = Database.ROW_NONE;

    /** Time limit after which to stop learning mLearnArea. */
    private volatile long mLearnUntil = 0;

    private Notification mNotification = null;

    private CellListener mCellListener;

    private class CellListener extends PhoneStateListener {

        /**
         *  Network type as TelephonyManager.PHONE_TYPE_CDMA or 
         *  TelephonyManager.PHONE_TYPE_GSM.
         */
        private final int mNetworkType;

        /**
         * Set the network type on start as callback is only called on changes.
         */
        CellListener(int networkType) {
            mNetworkType = networkType;
        }

        /**
         * Read the cell id, add it to the database if it is entered 
         * for the first time, log it, apply the associated sound profile.
         */
        @Override
        public void onCellLocationChanged(final CellLocation location) {
            super.onCellLocationChanged(location);

            new Thread(new Runnable() {

                @Override
                public void run() {
                    int cell = CELL_NO_SIGNAL;

                    if (mNetworkType == TelephonyManager.PHONE_TYPE_CDMA) {
                        CdmaCellLocation l = (CdmaCellLocation) location;
                        cell = l.getBaseStationId();
                    } else if (mNetworkType == TelephonyManager.PHONE_TYPE_GSM) {
                        GsmCellLocation l = (GsmCellLocation) location;
                        cell = l.getCid();
                    }

                    // Ignore no signal.
                    if (cell == CELL_NO_SIGNAL) {
                        Log.i(TAG, "Lost signal, igoring");
                        return;
                    }

                    Log.i(TAG, "Switch to cell " + Integer.toString(cell));

                    final Database db = Database.getInstance(LocationService.this);

                    // Get cell ID if cell exists.
                    Cursor c = db.getReadableDatabase().query(
                            CellColumns.TABLE_NAME + " as c, " + AreaColumns.TABLE_NAME + " as a",
                            new String[] { "c." + CellColumns._ID + " as cell_id",
                                    "a." + AreaColumns._ID + " as area_id" },
                            "a." + AreaColumns._ID + " = c." + CellColumns.AREA_ID + " " + "AND "
                                    + CellColumns.CELL_ID + " = ? " + "AND " + CellColumns.CELL_TYPE + " = ?",
                            new String[] { Long.toString(cell), Integer.toString(mNetworkType) }, null, null, null);
                    long cellRow = Database.ROW_NONE;
                    long newArea = Database.ROW_NONE;

                    // If cell is in database, select and update if necessary.
                    if (c.moveToFirst()) {
                        // Get the values.
                        cellRow = c.getLong(c.getColumnIndex("cell_id"));
                        newArea = c.getLong(c.getColumnIndex("area_id"));
                        // Update the cell if we are learning an area.
                        if (SystemClock.elapsedRealtime() <= mLearnUntil) {
                            ContentValues cv = new ContentValues();
                            cv.put(CellColumns.AREA_ID, mLearnArea);
                            db.getWritableDatabase().update(CellColumns.TABLE_NAME, cv, CellColumns._ID + " = ?",
                                    new String[] { Long.toString(cellRow) });
                            newArea = mLearnArea;
                        }
                    }
                    // Create cell if it does not exist.
                    else {
                        // Check if we are still learning, if not use default area.
                        newArea = (SystemClock.elapsedRealtime() <= mLearnUntil) ? mLearnArea
                                : AreaColumns.AREA_DEFAULT;

                        ContentValues cv = new ContentValues();
                        cv.put(CellColumns.AREA_ID, newArea);
                        cv.put(CellColumns.CELL_ID, cell);
                        cv.put(CellColumns.CELL_TYPE, mNetworkType);
                        cellRow = db.getWritableDatabase().insert(CellColumns.TABLE_NAME, null, cv);
                    }

                    // Only apply profile if we weren't in the same area before.
                    if (mCurrentArea != newArea) {
                        mCurrentArea = newArea;
                        applyProfile(cell);
                    }

                    // Log cell.
                    ContentValues cv = new ContentValues();
                    cv.put(CellLogColumns.CELL_ID, cellRow);
                    cv.put(CellLogColumns.TIMESTAMP, System.currentTimeMillis());
                    db.getWritableDatabase().insert(CellLogColumns.TABLE_NAME, null, cv);

                    mCurrentCell = cell;
                }
            }).start();
        }

        /**
         * Applies the profile for the current area.
         */
        private void applyProfile(final int cell) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    final Database db = Database.getInstance(LocationService.this);

                    // Get area name and ID of the associated profile.
                    Cursor c = db.getReadableDatabase().query(
                            AreaColumns.TABLE_NAME + " as a, " + CellColumns.TABLE_NAME + " as c",
                            new String[] { "a." + AreaColumns.PROFILE_ID, "a." + AreaColumns.NAME,
                                    "a." + AreaColumns.WIFI_ENABLED, "a." + AreaColumns.BLUETOOTH_ENABLED },
                            "a." + AreaColumns._ID + " = c." + CellColumns.AREA_ID + " AND " + "c."
                                    + CellColumns.CELL_ID + " = ? AND " + "c." + CellColumns.CELL_TYPE + " = ?",
                            new String[] { Integer.toString(cell), Integer.toString(mNetworkType) }, null, null,
                            null);

                    long profileId = Database.ROW_NONE;
                    String areaName;

                    if (c.moveToFirst()) {
                        profileId = c.getLong(c.getColumnIndex(AreaColumns.PROFILE_ID));
                        areaName = c.getString(c.getColumnIndex(AreaColumns.NAME));

                        WifiManager wm = (WifiManager) LocationService.this.getSystemService(Context.WIFI_SERVICE);
                        wm.setWifiEnabled(
                                (c.getInt(c.getColumnIndex(AreaColumns.WIFI_ENABLED)) == 1) ? true : false);

                        BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter();
                        if (bt != null) {
                            if ((c.getInt(c.getColumnIndex(AreaColumns.BLUETOOTH_ENABLED)) == 1)) {
                                bt.enable();
                            } else {
                                bt.disable();
                            }
                        }
                    } else {
                        areaName = getResources().getString(R.string.locationservice_area_unknown);
                    }

                    c = db.getReadableDatabase().query(ProfileColumns.TABLE_NAME,
                            new String[] { ProfileColumns.NAME, ProfileColumns.RINGTONE_VOLUME,
                                    ProfileColumns.NOTIFICATION_VOLUME, ProfileColumns.MEDIA_VOLUME,
                                    ProfileColumns.ALARM_VOLUME, ProfileColumns.RINGER_MODE },
                            "_id = ?", new String[] { Long.toString(profileId) }, null, null, null);

                    // Apply profile if there is one and set the name for the notification.
                    String profileName;
                    if (c.moveToFirst()) {
                        AudioManager am = (AudioManager) LocationService.this
                                .getSystemService(Context.AUDIO_SERVICE);

                        // Value smaller zero means the volume should not change.
                        if (c.getInt(c.getColumnIndex(ProfileColumns.RINGTONE_VOLUME)) >= 0) {
                            am.setStreamVolume(AudioManager.STREAM_RING,
                                    c.getInt(c.getColumnIndex(ProfileColumns.RINGTONE_VOLUME)),
                                    AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
                        }

                        if (c.getInt(c.getColumnIndex(ProfileColumns.NOTIFICATION_VOLUME)) >= 0) {
                            am.setStreamVolume(AudioManager.STREAM_NOTIFICATION,
                                    c.getInt(c.getColumnIndex(ProfileColumns.NOTIFICATION_VOLUME)),
                                    AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
                        }

                        if (c.getInt(c.getColumnIndex(ProfileColumns.MEDIA_VOLUME)) >= 0) {
                            am.setStreamVolume(AudioManager.STREAM_MUSIC,
                                    c.getInt(c.getColumnIndex(ProfileColumns.MEDIA_VOLUME)),
                                    AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
                        }

                        if (c.getInt(c.getColumnIndex(ProfileColumns.ALARM_VOLUME)) >= 0) {
                            am.setStreamVolume(AudioManager.STREAM_ALARM,
                                    c.getInt(c.getColumnIndex(ProfileColumns.ALARM_VOLUME)),
                                    AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
                        }

                        if (c.getInt(
                                c.getColumnIndex(ProfileColumns.RINGER_MODE)) != ProfileColumns.RINGER_MODE_KEEP) {
                            am.setRingerMode(c.getInt(c.getColumnIndex(ProfileColumns.RINGER_MODE)));
                        }

                        profileName = c.getString(c.getColumnIndex(ProfileColumns.NAME));
                    } else {
                        profileName = getResources().getString(R.string.arealist_profile_none);
                    }

                    Log.i(TAG, "Apply profile " + profileName + " (in area " + areaName + ")");
                    showNotification(areaName, profileName);
                }
            }).start();
        }
    }

    /**
     * Convenience method for sending an Intent with MESSAGE_UPDATE 
     * to the service.
     * 
     * @param context Application context.
     */
    public static void sendUpdateIntent(Context context) {
        Intent i = new Intent(context, LocationService.class);
        // Value is unused.
        i.putExtra(MESSAGE_UPDATE, 0);
        context.startService(i);
    }

    /**
     * Register CellListener and show Notification.
     */
    @Override
    public void onCreate() {
        super.onCreate();
        TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
        mCellListener = new CellListener(tm.getPhoneType());
        tm.listen(mCellListener, PhoneStateListener.LISTEN_CELL_LOCATION);
        // Force update.
        mCellListener.onCellLocationChanged(tm.getCellLocation());
    }

    /**
     * Show Notification displaying area and profile.
     * 
     * @param area Currently active area.
     * @param profile Currently active profile.
     */
    private void showNotification(String area, String profile) {
        mNotification = new NotificationCompat.Builder(this).setContentTitle(area).setContentText(profile)
                .setSmallIcon(R.drawable.ic_launcher).setAutoCancel(false).setOngoing(true)
                .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, AreaList.class), 0)).build();

        startForeground(NOTIFICATION_ID, mNotification);
    }

    /**
     * Receive start Intent, start learning an area or check if the 
     * sound profile for the current area has changed.
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent != null) {
            Bundle extras = intent.getExtras();
            if (extras != null) {
                Set<String> keys = extras.keySet();
                if (keys.contains(MESSAGE_LEARN_AREA)) {
                    // Set the area to learn now and the learn duration.
                    mLearnUntil = SystemClock.elapsedRealtime() + extras.getLong(MESSAGE_LEARN_INTERVAL);
                    mLearnArea = extras.getLong(MESSAGE_LEARN_AREA);
                }
                if (keys.contains(MESSAGE_UPDATE)) {
                    // Profile/area mappings have changed, reapply profile.
                    mCellListener.applyProfile(mCurrentCell);
                }
            }
        }
        return START_STICKY;
    }

    /**
     * Cannot bind.
     */
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

}