1 /* 2 * Copyright (C) 2016 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.googlecode.android_scripting.activity; 18 19 import android.app.AlarmManager; 20 import android.app.Notification; 21 import android.app.PendingIntent; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.os.Binder; 25 import android.os.IBinder; 26 27 import com.google.common.base.Preconditions; 28 import com.googlecode.android_scripting.BaseApplication; 29 import com.googlecode.android_scripting.ForegroundService; 30 import com.googlecode.android_scripting.IntentBuilders; 31 import com.googlecode.android_scripting.NotificationIdFactory; 32 import com.googlecode.android_scripting.R; 33 import com.googlecode.android_scripting.event.Event; 34 import com.googlecode.android_scripting.event.EventObserver; 35 import com.googlecode.android_scripting.facade.EventFacade; 36 import com.googlecode.android_scripting.facade.FacadeConfiguration; 37 import com.googlecode.android_scripting.facade.FacadeManager; 38 import com.googlecode.android_scripting.trigger.EventGenerationControllingObserver; 39 import com.googlecode.android_scripting.trigger.Trigger; 40 import com.googlecode.android_scripting.trigger.TriggerRepository; 41 import com.googlecode.android_scripting.trigger.TriggerRepository.TriggerRepositoryObserver; 42 43 /** 44 * The trigger service takes care of installing triggers serialized to the preference storage. 45 * 46 * <p> 47 * The service also installs an alarm that keeps it running, unless the user force-quits the 48 * service. 49 * 50 * <p> 51 * When no triggers are installed the service shuts down silently as to not consume resources 52 * unnecessarily. 53 * 54 */ 55 public class TriggerService extends ForegroundService { 56 private static final int NOTIFICATION_ID = NotificationIdFactory.create(); 57 private static final long PING_MILLIS = 10 * 1000 * 60; 58 59 private final IBinder mBinder; 60 private TriggerRepository mTriggerRepository; 61 private FacadeManager mFacadeManager; 62 private EventFacade mEventFacade; 63 64 public class LocalBinder extends Binder { 65 public TriggerService getService() { 66 return TriggerService.this; 67 } 68 } 69 70 public TriggerService() { 71 super(NOTIFICATION_ID); 72 mBinder = new LocalBinder(); 73 } 74 75 @Override 76 public IBinder onBind(Intent intent) { 77 return mBinder; 78 } 79 80 @Override 81 public void onCreate() { 82 super.onCreate(); 83 84 mFacadeManager = 85 new FacadeManager(FacadeConfiguration.getSdkLevel(), this, null, 86 FacadeConfiguration.getFacadeClasses()); 87 mEventFacade = mFacadeManager.getReceiver(EventFacade.class); 88 89 mTriggerRepository = ((BaseApplication) getApplication()).getTriggerRepository(); 90 mTriggerRepository.bootstrapObserver(new RepositoryObserver()); 91 mTriggerRepository.bootstrapObserver(new EventGenerationControllingObserver(mFacadeManager)); 92 installAlarm(); 93 } 94 95 @Override 96 public void onStart(Intent intent, int startId) { 97 if (mTriggerRepository.isEmpty()) { 98 stopSelfResult(startId); 99 return; 100 } 101 } 102 103 /** Returns the notification to display whenever the service is running. */ 104 @Override 105 protected Notification createNotification() { 106 Intent notificationIntent = new Intent(this, TriggerManager.class); 107 Notification.Builder builder = new Notification.Builder(this); 108 builder.setSmallIcon(R.drawable.sl4a_logo_48) 109 .setTicker("SL4A Trigger Service started.") 110 .setWhen(System.currentTimeMillis()) 111 .setContentTitle("SL4A Trigger Service") 112 .setContentText("Tap to view triggers") 113 .setContentIntent(PendingIntent.getActivity(this, 0, notificationIntent, 0)); 114 Notification notification = builder.build(); 115 notification.flags = Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT; 116 return notification; 117 } 118 119 private class TriggerEventObserver implements EventObserver { 120 private final Trigger mTrigger; 121 122 public TriggerEventObserver(Trigger trigger) { 123 mTrigger = trigger; 124 } 125 126 @Override 127 public void onEventReceived(Event event) { 128 mTrigger.handleEvent(event, TriggerService.this); 129 } 130 } 131 132 private class RepositoryObserver implements TriggerRepositoryObserver { 133 int mTriggerCount = 0; 134 135 @Override 136 public void onPut(Trigger trigger) { 137 mTriggerCount++; 138 mEventFacade.addNamedEventObserver(trigger.getEventName(), new TriggerEventObserver(trigger)); 139 } 140 141 @Override 142 public void onRemove(Trigger trigger) { 143 Preconditions.checkArgument(mTriggerCount > 0); 144 // TODO(damonkohler): Tear down EventObserver associated with trigger. 145 if (--mTriggerCount == 0) { 146 // TODO(damonkohler): Use stopSelfResult() which would require tracking startId. 147 stopSelf(); 148 } 149 } 150 } 151 152 private void installAlarm() { 153 AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 154 alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + PING_MILLIS, 155 PING_MILLIS, IntentBuilders.buildTriggerServicePendingIntent(this)); 156 } 157 158 private void uninstallAlarm() { 159 AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 160 alarmManager.cancel(IntentBuilders.buildTriggerServicePendingIntent(this)); 161 } 162 163 @Override 164 public void onDestroy() { 165 super.onDestroy(); 166 uninstallAlarm(); 167 } 168 } 169