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