/* * Copyright (C) 2017 The Android Open Source Project * * 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.googlecode.android_scripting.service; import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import com.google.common.base.Preconditions; import com.googlecode.android_scripting.BaseApplication; import com.googlecode.android_scripting.ForegroundService; import com.googlecode.android_scripting.IntentBuilders; import com.googlecode.android_scripting.NotificationIdFactory; import com.googlecode.android_scripting.R; import com.googlecode.android_scripting.activity.TriggerManager; import com.googlecode.android_scripting.event.Event; import com.googlecode.android_scripting.event.EventObserver; import com.googlecode.android_scripting.facade.EventFacade; import com.googlecode.android_scripting.facade.FacadeConfiguration; import com.googlecode.android_scripting.facade.FacadeManager; import com.googlecode.android_scripting.trigger.EventGenerationControllingObserver; import com.googlecode.android_scripting.trigger.Trigger; import com.googlecode.android_scripting.trigger.TriggerRepository; import com.googlecode.android_scripting.trigger.TriggerRepository.TriggerRepositoryObserver; /** * The trigger service takes care of installing triggers serialized to the preference storage. * *

* The service also installs an alarm that keeps it running, unless the user force-quits the * service. * *

* When no triggers are installed the service shuts down silently as to not consume resources * unnecessarily. * */ public class TriggerService extends ForegroundService { private static final String CHANNEL_ID = "trigger_service_channel"; private static final int NOTIFICATION_ID = NotificationIdFactory.create(); private static final long PING_MILLIS = 10 * 1000 * 60; private final IBinder mBinder; private TriggerRepository mTriggerRepository; private FacadeManager mFacadeManager; private EventFacade mEventFacade; public class LocalBinder extends Binder { public TriggerService getService() { return TriggerService.this; } } public TriggerService() { super(NOTIFICATION_ID); mBinder = new LocalBinder(); } @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public void onCreate() { super.onCreate(); mFacadeManager = new FacadeManager(FacadeConfiguration.getSdkLevel(), this, null, FacadeConfiguration.getFacadeClasses()); mEventFacade = mFacadeManager.getReceiver(EventFacade.class); mTriggerRepository = ((BaseApplication) getApplication()).getTriggerRepository(); mTriggerRepository.bootstrapObserver(new RepositoryObserver()); mTriggerRepository.bootstrapObserver(new EventGenerationControllingObserver(mFacadeManager)); installAlarm(); } @Override public void onStart(Intent intent, int startId) { if (mTriggerRepository.isEmpty()) { stopSelfResult(startId); return; } } protected void createNotificationChannel() { NotificationManager notificationManager = getNotificationManager(); CharSequence name = getString(R.string.notification_channel_name); String description = getString(R.string.notification_channel_description); int importance = NotificationManager.IMPORTANCE_DEFAULT; NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); channel.setDescription(description); channel.enableLights(false); channel.enableVibration(false); notificationManager.createNotificationChannel(channel); } /** Returns the notification to display whenever the service is running. */ @Override protected Notification createNotification() { createNotificationChannel(); Intent notificationIntent = new Intent(this, TriggerManager.class); Notification.Builder builder = new Notification.Builder(this, CHANNEL_ID); builder.setSmallIcon(R.drawable.sl4a_logo_48) .setTicker("SL4A Trigger Service started.") .setWhen(System.currentTimeMillis()) .setContentTitle("SL4A Trigger Service") .setContentText("Tap to view triggers") .setContentIntent(PendingIntent.getActivity(this, 0, notificationIntent, 0)); Notification notification = builder.build(); notification.flags = Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT; return notification; } private class TriggerEventObserver implements EventObserver { private final Trigger mTrigger; public TriggerEventObserver(Trigger trigger) { mTrigger = trigger; } @Override public void onEventReceived(Event event) { mTrigger.handleEvent(event, TriggerService.this); } } private class RepositoryObserver implements TriggerRepositoryObserver { int mTriggerCount = 0; @Override public void onPut(Trigger trigger) { mTriggerCount++; mEventFacade.addNamedEventObserver(trigger.getEventName(), new TriggerEventObserver(trigger)); } @Override public void onRemove(Trigger trigger) { Preconditions.checkArgument(mTriggerCount > 0); // TODO(damonkohler): Tear down EventObserver associated with trigger. if (--mTriggerCount == 0) { // TODO(damonkohler): Use stopSelfResult() which would require tracking startId. stopSelf(); } } } private void installAlarm() { AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + PING_MILLIS, PING_MILLIS, IntentBuilders.buildTriggerServicePendingIntent(this)); } private void uninstallAlarm() { AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); alarmManager.cancel(IntentBuilders.buildTriggerServicePendingIntent(this)); } @Override public void onDestroy() { super.onDestroy(); uninstallAlarm(); } }