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, 0)); 131 Notification notification = builder.build(); 132 notification.flags = Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT; 133 return notification; 134 } 135 136 private class TriggerEventObserver implements EventObserver { 137 private final Trigger mTrigger; 138 TriggerEventObserver(Trigger trigger)139 public TriggerEventObserver(Trigger trigger) { 140 mTrigger = trigger; 141 } 142 143 @Override onEventReceived(Event event)144 public void onEventReceived(Event event) { 145 mTrigger.handleEvent(event, TriggerService.this); 146 } 147 } 148 149 private class RepositoryObserver implements TriggerRepositoryObserver { 150 int mTriggerCount = 0; 151 152 @Override onPut(Trigger trigger)153 public void onPut(Trigger trigger) { 154 mTriggerCount++; 155 mEventFacade.addNamedEventObserver(trigger.getEventName(), new TriggerEventObserver(trigger)); 156 } 157 158 @Override onRemove(Trigger trigger)159 public void onRemove(Trigger trigger) { 160 Preconditions.checkArgument(mTriggerCount > 0); 161 // TODO(damonkohler): Tear down EventObserver associated with trigger. 162 if (--mTriggerCount == 0) { 163 // TODO(damonkohler): Use stopSelfResult() which would require tracking startId. 164 stopSelf(); 165 } 166 } 167 } 168 installAlarm()169 private void installAlarm() { 170 AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 171 alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + PING_MILLIS, 172 PING_MILLIS, IntentBuilders.buildTriggerServicePendingIntent(this)); 173 } 174 uninstallAlarm()175 private void uninstallAlarm() { 176 AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 177 alarmManager.cancel(IntentBuilders.buildTriggerServicePendingIntent(this)); 178 } 179 180 @Override onDestroy()181 public void onDestroy() { 182 super.onDestroy(); 183 uninstallAlarm(); 184 } 185 } 186