19f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li/* 29f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Copyright (C) 2016 Google Inc. 39f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * 49f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Licensed under the Apache License, Version 2.0 (the "License"); you may not 59f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * use this file except in compliance with the License. You may obtain a copy of 69f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * the License at 79f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * 89f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * http://www.apache.org/licenses/LICENSE-2.0 99f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * 109f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Unless required by applicable law or agreed to in writing, software 119f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 129f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 139f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * License for the specific language governing permissions and limitations under 149f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * the License. 159f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */ 169f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 179f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Lipackage com.googlecode.android_scripting.trigger; 189f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 199f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.content.Context; 209f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.content.Intent; 219f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.content.SharedPreferences; 229f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.preference.PreferenceManager; 239f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 249f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.google.common.collect.ArrayListMultimap; 259f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.google.common.collect.Multimap; 269f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.google.common.collect.Multimaps; 279f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.googlecode.android_scripting.IntentBuilders; 289f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.googlecode.android_scripting.Log; 299f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 309f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.io.ByteArrayInputStream; 319f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.io.ByteArrayOutputStream; 329f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.io.IOException; 339f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.io.ObjectInputStream; 349f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.io.ObjectOutputStream; 359f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.util.Map.Entry; 369f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.util.concurrent.CopyOnWriteArrayList; 379f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 389f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport org.apache.commons.codec.binary.Base64Codec; 399f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 409f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li/** 419f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * A repository maintaining all currently scheduled triggers. This includes, for example, alarms or 429f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * observers of arriving text messages etc. This class is responsible for serializing the list of 439f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * triggers to the shared preferences store, and retrieving it from there. 449f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * 459f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * @author Felix Arends (felix.arends@gmail.com) 469f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * @author Damon Kohler (damonkohler@gmail.com) 479f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */ 489f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Lipublic class TriggerRepository { 499f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** 509f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * The list of triggers is serialized to the shared preferences entry with this name. 519f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */ 529f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private static final String TRIGGERS_PREF_KEY = "TRIGGERS"; 539f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 549f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private final SharedPreferences mPreferences; 559f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private final Context mContext; 569f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 579f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** 589f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * An interface for objects that are notified when a trigger is added to the repository. 599f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */ 609f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public interface TriggerRepositoryObserver { 619f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** 629f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Invoked just before the trigger is added to the repository. 639f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * 649f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * @param trigger 659f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * The trigger about to be added to the repository. 669f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */ 679f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li void onPut(Trigger trigger); 689f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 699f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** 709f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Invoked just after the trigger has been removed from the repository. 719f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * 729f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * @param trigger 739f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * The trigger that has just been removed from the repository. 749f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */ 759f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li void onRemove(Trigger trigger); 769f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 779f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 789f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private final Multimap<String, Trigger> mTriggers; 799f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private final CopyOnWriteArrayList<TriggerRepositoryObserver> mTriggerObservers = 809f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li new CopyOnWriteArrayList<TriggerRepositoryObserver>(); 819f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 829f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public TriggerRepository(Context context) { 839f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li mContext = context; 849f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li mPreferences = PreferenceManager.getDefaultSharedPreferences(context); 859f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li String triggers = mPreferences.getString(TRIGGERS_PREF_KEY, null); 869f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li mTriggers = deserializeTriggersFromString(triggers); 879f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 889f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 899f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Returns a list of all triggers. The list is unmodifiable. */ 909f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public synchronized Multimap<String, Trigger> getAllTriggers() { 919f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li return Multimaps.unmodifiableMultimap(mTriggers); 929f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 939f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 949f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** 959f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Adds a new trigger to the repository. 969f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * 979f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * @param trigger 989f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * the {@link Trigger} to add 999f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */ 1009f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public synchronized void put(Trigger trigger) { 1019f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li notifyOnAdd(trigger); 1029f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li mTriggers.put(trigger.getEventName(), trigger); 1039f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li storeTriggers(); 1049f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li ensureTriggerServiceRunning(); 1059f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1069f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1079f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Removes a specific {@link Trigger}. */ 1089f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public synchronized void remove(final Trigger trigger) { 1099f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li mTriggers.get(trigger.getEventName()).remove(trigger); 1109f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li storeTriggers(); 1119f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li notifyOnRemove(trigger); 1129f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1139f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1149f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Ensures that the {@link TriggerService} is running */ 1159f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private void ensureTriggerServiceRunning() { 1169f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li Intent startTriggerServiceIntent = IntentBuilders.buildTriggerServiceIntent(); 1179f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li mContext.startService(startTriggerServiceIntent); 1189f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1199f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1209f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Notify all {@link TriggerRepositoryObserver}s that a {@link Trigger} was added. */ 1219f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private void notifyOnAdd(Trigger trigger) { 1229f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li for (TriggerRepositoryObserver observer : mTriggerObservers) { 1239f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li observer.onPut(trigger); 1249f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1259f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1269f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1279f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Notify all {@link TriggerRepositoryObserver}s that a {@link Trigger} was removed. */ 1289f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private void notifyOnRemove(Trigger trigger) { 1299f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li for (TriggerRepositoryObserver observer : mTriggerObservers) { 1309f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li observer.onRemove(trigger); 1319f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1329f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1339f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1349f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Writes the list of triggers to the shared preferences. */ 1359f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private synchronized void storeTriggers() { 1369f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li SharedPreferences.Editor editor = mPreferences.edit(); 1379f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li final String triggerValue = serializeTriggersToString(mTriggers); 1389f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li if (triggerValue != null) { 1399f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li editor.putString(TRIGGERS_PREF_KEY, triggerValue); 1409f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1419f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li editor.commit(); 1429f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1439f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1449f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Deserializes the {@link Multimap} of {@link Trigger}s from a base 64 encoded string. */ 1459f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li @SuppressWarnings("unchecked") 1469f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private Multimap<String, Trigger> deserializeTriggersFromString(String triggers) { 1479f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li if (triggers == null) { 1489f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li return ArrayListMultimap.<String, Trigger> create(); 1499f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1509f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li try { 1519f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li final ByteArrayInputStream inputStream = 1529f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li new ByteArrayInputStream(Base64Codec.decodeBase64(triggers.getBytes())); 1539f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li final ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); 1549f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li return (Multimap<String, Trigger>) objectInputStream.readObject(); 1559f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } catch (Exception e) { 1569f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li Log.e(e); 1579f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1589f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li return ArrayListMultimap.<String, Trigger> create(); 1599f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1609f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1619f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Serializes the list of triggers to a Base64 encoded string. */ 1629f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private String serializeTriggersToString(Multimap<String, Trigger> triggers) { 1639f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li try { 1649f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 1659f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li final ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); 1669f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li objectOutputStream.writeObject(triggers); 1679f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li return new String(Base64Codec.encodeBase64(outputStream.toByteArray())); 1689f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } catch (IOException e) { 1699f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li Log.e(e); 1709f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li return null; 1719f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1729f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1739f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1749f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Returns {@code true} iff the list of triggers is empty. */ 1759f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public synchronized boolean isEmpty() { 1769f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li return mTriggers.isEmpty(); 1779f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1789f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1799f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Adds a {@link TriggerRepositoryObserver}. */ 1809f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public void addObserver(TriggerRepositoryObserver observer) { 1819f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li mTriggerObservers.add(observer); 1829f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1839f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1849f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** 1859f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Adds the given {@link TriggerRepositoryObserver} and invokes 1869f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * {@link TriggerRepositoryObserver#onPut} for all existing triggers. 1879f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * 1889f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * @param observer 1899f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * The observer to add. 1909f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */ 1919f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public synchronized void bootstrapObserver(TriggerRepositoryObserver observer) { 1929f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li addObserver(observer); 1939f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li for (Entry<String, Trigger> trigger : mTriggers.entries()) { 1949f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li observer.onPut(trigger.getValue()); 1959f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1969f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1979f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1989f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** 1999f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Removes a {@link TriggerRepositoryObserver}. 2009f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */ 2019f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public void removeObserver(TriggerRepositoryObserver observer) { 2029f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li mTriggerObservers.remove(observer); 2039f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 2049f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li}