Commit 51eedd96 authored by akwizgran's avatar akwizgran

Initial commit: test various types of alarm.

parents
*.iml
.gradle
/local.properties
/.idea
.DS_Store
/build
/captures
.externalNativeBuild
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "org.briarproject.snooze"
minSdkVersion 14
targetSdkVersion 23
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:23.2.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
}
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /home/someone/Android/Sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.briarproject.snooze">
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:name=".SnoozeApplication"
android:allowBackup="false"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".SnoozeActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service
android:name=".SnoozeService"
android:exported="false">
<intent-filter>
<action android:name="org.briarproject.snooze.SnoozeService"/>
</intent-filter>
</service>
</application>
</manifest>
\ No newline at end of file
package org.briarproject.snooze;
import android.content.Context;
import android.content.Intent;
import static org.briarproject.snooze.Constants.ACTION_ALARM;
import static org.briarproject.snooze.Constants.EXTRA_ALARM_TYPE;
abstract class AbstractAlarm implements Alarm {
final Context appContext;
AbstractAlarm(Context appContext) {
this.appContext = appContext;
}
Intent getAlarmIntent() {
Intent i = new Intent(appContext, SnoozeService.class);
i.setAction(ACTION_ALARM);
i.putExtra(EXTRA_ALARM_TYPE, getAlarmType().name());
return i;
}
}
package org.briarproject.snooze;
import java.util.concurrent.TimeUnit;
interface Alarm {
AlarmType getAlarmType();
void setAlarm(long delay, TimeUnit unit);
void cancelAlarm();
}
package org.briarproject.snooze;
import android.content.Context;
class AlarmFactory {
static Alarm createAlarm(AlarmType alarmType, Context appContext) {
switch (alarmType) {
case TIMER:
return new TimerAlarm(appContext);
case SCHEDULED_EXECUTOR_SERVICE:
return new ScheduledExecutorServiceAlarm(appContext);
case RTC:
return new AndroidAlarm(appContext, false);
case RTC_WAKEUP:
return new AndroidAlarm(appContext, true);
case RTC_WHEN_IDLE:
return new AndroidIdleAlarm(appContext, false);
case RTC_WAKEUP_WHEN_IDLE:
return new AndroidIdleAlarm(appContext, true);
case ALARM_CLOCK:
return new AndroidClockAlarm(appContext);
default:
throw new UnsupportedOperationException();
}
}
}
package org.briarproject.snooze;
enum AlarmType {
TIMER,
SCHEDULED_EXECUTOR_SERVICE,
RTC,
RTC_WAKEUP,
ALARM_CLOCK,
RTC_WHEN_IDLE,
RTC_WAKEUP_WHEN_IDLE
}
package org.briarproject.snooze;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.os.Build;
import java.util.concurrent.TimeUnit;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.content.Context.ALARM_SERVICE;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.snooze.AlarmType.RTC;
import static org.briarproject.snooze.AlarmType.RTC_WAKEUP;
class AndroidAlarm extends AbstractAlarm {
final boolean wakeup;
AndroidAlarm(Context appContext, boolean wakeup) {
super(appContext);
this.wakeup = wakeup;
}
@Override
public AlarmType getAlarmType() {
return wakeup ? RTC_WAKEUP : RTC;
}
int getAndroidAlarmType() {
return wakeup ? AlarmManager.RTC_WAKEUP : AlarmManager.RTC;
}
long getRealTimeMillis(long delay, TimeUnit unit) {
return System.currentTimeMillis() + MILLISECONDS.convert(delay, unit);
}
AlarmManager getAlarmManager() {
return (AlarmManager) appContext.getSystemService(ALARM_SERVICE);
}
PendingIntent getPendingIntent() {
return PendingIntent.getService(appContext, 0, getAlarmIntent(), FLAG_CANCEL_CURRENT);
}
@Override
public void setAlarm(long delay, TimeUnit unit) {
int type = getAndroidAlarmType();
long millis = getRealTimeMillis(delay, unit);
PendingIntent pi = getPendingIntent();
if (Build.VERSION.SDK_INT >= 19) getAlarmManager().setExact(type, millis, pi);
else getAlarmManager().set(type, millis, pi);
}
@Override
public void cancelAlarm() {
getAlarmManager().cancel(getPendingIntent());
}
}
package org.briarproject.snooze;
import android.app.AlarmManager.AlarmClockInfo;
import android.app.PendingIntent;
import android.content.Context;
import android.os.Build;
import java.util.concurrent.TimeUnit;
import static org.briarproject.snooze.AlarmType.ALARM_CLOCK;
class AndroidClockAlarm extends AndroidAlarm {
AndroidClockAlarm(Context appContext) {
super(appContext, false);
}
@Override
public AlarmType getAlarmType() {
return ALARM_CLOCK;
}
@Override
public void setAlarm(long delay, TimeUnit unit) {
if (Build.VERSION.SDK_INT >= 21) {
long millis = getRealTimeMillis(delay, unit);
PendingIntent pi = getPendingIntent();
getAlarmManager().setAlarmClock(new AlarmClockInfo(millis, pi), pi);
} else {
throw new UnsupportedOperationException();
}
}
}
package org.briarproject.snooze;
import android.app.PendingIntent;
import android.content.Context;
import android.os.Build;
import java.util.concurrent.TimeUnit;
import static org.briarproject.snooze.AlarmType.RTC_WAKEUP_WHEN_IDLE;
import static org.briarproject.snooze.AlarmType.RTC_WHEN_IDLE;
class AndroidIdleAlarm extends AndroidAlarm {
AndroidIdleAlarm(Context appContext, boolean wakeup) {
super(appContext, wakeup);
}
@Override
public AlarmType getAlarmType() {
return wakeup ? RTC_WAKEUP_WHEN_IDLE : RTC_WHEN_IDLE;
}
@Override
public void setAlarm(long delay, TimeUnit unit) {
if (Build.VERSION.SDK_INT >= 23) {
int type = getAndroidAlarmType();
long millis = getRealTimeMillis(delay, unit);
PendingIntent pi = getPendingIntent();
getAlarmManager().setExactAndAllowWhileIdle(type, millis, pi);
} else {
throw new UnsupportedOperationException();
}
}
}
package org.briarproject.snooze;
interface Constants {
String ACTION_START = "org.briarproject.snooze.ACTION_START";
String ACTION_ALARM = "org.briarproject.snooze.ACTION_ALARM";
String EXTRA_FOREGROUND_SERVICE = "org.briarproject.snooze.EXTRA_FOREGROUND_SERVICE";
String EXTRA_WAKE_LOCK = "org.briarproject.snooze.EXTRA_WAKE_LOCK";
String EXTRA_ALARM_TYPE = "org.briarproject.snooze.EXTRA_ALARM_TYPE";
int ALARM_INTERVAL_MS = 5 * 1000;
}
package org.briarproject.snooze;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import static java.util.Locale.US;
class Logger {
private final Handler handler;
private final String tag;
private final List<LogListener> listeners = new CopyOnWriteArrayList<>();
private final StringBuffer messages = new StringBuffer();
private final DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss", US);
Logger(Context ctx, String tag) {
handler = new Handler(ctx.getMainLooper());
this.tag = tag;
}
String addListener(LogListener listener) {
listeners.add(listener);
return messages.toString();
}
void removeListener(LogListener listener) {
listeners.remove(listener);
}
void log(final String message) {
handler.post(new Runnable() {
@Override
public void run() {
Log.i(tag, message);
String formatted = dateFormat.format(new Date()) + " " + message + "\n";
messages.append(formatted);
for (LogListener listener : listeners) listener.log(formatted);
}
});
}
interface LogListener {
void log(String message);
}
}
package org.briarproject.snooze;
import android.content.Context;
import android.support.annotation.NonNull;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import static org.briarproject.snooze.AlarmType.SCHEDULED_EXECUTOR_SERVICE;
class ScheduledExecutorServiceAlarm extends AbstractAlarm {
private static final ThreadFactory THREAD_FACTORY = new ThreadFactory() {
@Override
public Thread newThread(@NonNull Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
};
private ScheduledExecutorService scheduledExecutorService = null; // Locking: this
ScheduledExecutorServiceAlarm(Context appContext) {
super(appContext);
}
@Override
public AlarmType getAlarmType() {
return SCHEDULED_EXECUTOR_SERVICE;
}
@Override
public synchronized void setAlarm(long delay, TimeUnit unit) {
if (scheduledExecutorService == null)
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(THREAD_FACTORY);
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
appContext.startService(getAlarmIntent());
}
}, delay, unit);
}
@Override
public synchronized void cancelAlarm() {
if (scheduledExecutorService == null) throw new IllegalStateException();
scheduledExecutorService.shutdownNow();
scheduledExecutorService = null;
}
}
package org.briarproject.snooze;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.ScrollView;
import android.widget.Spinner;
import android.widget.Switch;
import android.widget.TextView;
import org.briarproject.snooze.Logger.LogListener;
import static android.view.View.FOCUS_DOWN;
import static org.briarproject.snooze.AlarmType.TIMER;
import static org.briarproject.snooze.Constants.ACTION_START;
import static org.briarproject.snooze.Constants.EXTRA_ALARM_TYPE;
import static org.briarproject.snooze.Constants.EXTRA_FOREGROUND_SERVICE;
import static org.briarproject.snooze.Constants.EXTRA_WAKE_LOCK;
public class SnoozeActivity extends AppCompatActivity implements LogListener,
OnItemSelectedListener {
private ScrollView outputScrollView;
private TextView outputTextView;
private AlarmType alarmType = TIMER;
private volatile Logger log = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
log = ((SnoozeApplication) getApplication()).getLogger();
log.log("Activity created");
setContentView(R.layout.activity_snooze);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
R.array.alarm_types, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Spinner alarmTypeSpinner = (Spinner) findViewById(R.id.alarmTypeSpinner);
alarmTypeSpinner.setAdapter(adapter);
alarmTypeSpinner.setOnItemSelectedListener(this);
outputScrollView = (ScrollView) findViewById(R.id.outputScrollView);
outputTextView = (TextView) findViewById(R.id.outputTextView);
outputTextView.setText(log.addListener(this));
}
@Override
protected void onDestroy() {
super.onDestroy();
log.log("Activity destroyed");
log.removeListener(this);
}
@Override
public void log(String message) {
outputTextView.append(message);
outputScrollView.post(new Runnable() {
@Override
public void run() {
outputScrollView.fullScroll(FOCUS_DOWN);
}
});
}
public void onClickStartServiceButton(View v) {
boolean foreground = ((Switch) findViewById(R.id.foregroundServiceSwitch)).isChecked();
boolean wakeLock = ((Switch) findViewById(R.id.permanentWakeLockSwitch)).isChecked();
Intent i = new Intent(this, SnoozeService.class);
i.setAction(ACTION_START);
i.putExtra(EXTRA_FOREGROUND_SERVICE, foreground);
i.putExtra(EXTRA_WAKE_LOCK, wakeLock);
i.putExtra(EXTRA_ALARM_TYPE, alarmType.name());
startService(i);
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
alarmType = AlarmType.values()[position];
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
}
package org.briarproject.snooze;
import android.app.Application;
public class SnoozeApplication extends Application {
private volatile Logger log = null;
@Override
public void onCreate() {
super.onCreate();
log = new Logger(this, SnoozeApplication.class.getSimpleName());
log.log("Application created");
}
Logger getLogger() {
return log;
}
}
package org.briarproject.snooze;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.PowerManager;
import android.support.v7.app.NotificationCompat;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.snooze.Constants.ACTION_ALARM;
import static org.briarproject.snooze.Constants.ACTION_START;
import static org.briarproject.snooze.Constants.ALARM_INTERVAL_MS;
import static org.briarproject.snooze.Constants.EXTRA_ALARM_TYPE;
import static org.briarproject.snooze.Constants.EXTRA_FOREGROUND_SERVICE;
import static org.briarproject.snooze.Constants.EXTRA_WAKE_LOCK;
public class SnoozeService extends Service {
private final Binder binder = new Binder();
private boolean foreground = false;
private PowerManager.WakeLock wakeLock = null;
private Alarm alarm = null;
private volatile Logger log = null;
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void onCreate() {
super.onCreate();
log = ((SnoozeApplication) getApplication()).getLogger();
log.log("Service created");
}
@Override
public int onStartCommand(Intent i, int flags, int startId) {
if (ACTION_START.equals(i.getAction())) {
boolean newForeground = i.getBooleanExtra(EXTRA_FOREGROUND_SERVICE, false);
boolean newWakeLock = i.getBooleanExtra(EXTRA_WAKE_LOCK, false);
AlarmType newAlarmType = AlarmType.valueOf(i.getStringExtra(EXTRA_ALARM_TYPE));
log.log("Setting alarm " + newAlarmType);
if (newForeground && !foreground) startForegroundService();
else if (!newForeground && foreground) stopForegroundService();
if (newWakeLock && wakeLock == null) acquireWakeLock();
else if (!newWakeLock && wakeLock != null) releaseWakeLock();
setAlarm(newAlarmType);
} else if (ACTION_ALARM.equals(i.getAction())) {
AlarmType alarmType = AlarmType.valueOf(i.getStringExtra(EXTRA_ALARM_TYPE));
log.log("Alarm " + alarmType);
setAlarm();
}
return START_NOT_STICKY; // Don't restart automatically if killed
}
@Override
public void onDestroy() {
super.onDestroy();
log.log("Service destroyed");
if (foreground) stopForegroundService();
if (wakeLock != null) releaseWakeLock();
}
@Override
public void onLowMemory() {
super.onLowMemory();
log.log("Low memory");
}
private void startForegroundService() {
if (foreground) throw new IllegalStateException();
log.log("Starting foreground service");
NotificationCompat.Builder b = new NotificationCompat.Builder(this);
b.setSmallIcon(R.drawable.ongoing_notification_icon);
b.setContentTitle(getText(R.string.ongoing_notification_title));
b.setContentText(getText(R.string.ongoing_notification_text));
b.setWhen(0); // Don't show the time
b.setOngoing(true);
Intent i = new Intent(this, SnoozeActivity.class);
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
b.setContentIntent(PendingIntent.getActivity(this, 0, i, 0));
startForeground(1, b.build());
foreground = true;
}
private void stopForegroundService() {
if (!foreground) throw new IllegalStateException();
log.log("Stopping foreground service");
stopForeground(true);
foreground = false;
}