19f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li/* 2f04335f899f2cce69f843692a3cb9cec229683c2tturney * Copyright (C) 2017 The Android Open Source Project 39f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * 4f04335f899f2cce69f843692a3cb9cec229683c2tturney * Licensed under the Apache License, Version 2.0 (the "License"); 5f04335f899f2cce69f843692a3cb9cec229683c2tturney * you may not use this file except in compliance with the License. 6f04335f899f2cce69f843692a3cb9cec229683c2tturney * You may obtain a copy of the License at 79f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * 8f04335f899f2cce69f843692a3cb9cec229683c2tturney * http://www.apache.org/licenses/LICENSE-2.0 99f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * 109f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Unless required by applicable law or agreed to in writing, software 11f04335f899f2cce69f843692a3cb9cec229683c2tturney * distributed under the License is distributed on an "AS IS" BASIS, 12f04335f899f2cce69f843692a3cb9cec229683c2tturney * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13f04335f899f2cce69f843692a3cb9cec229683c2tturney * See the License for the specific language governing permissions and 14f04335f899f2cce69f843692a3cb9cec229683c2tturney * limitations under 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 */ 469f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Lipublic class TriggerRepository { 479f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** 489f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * The list of triggers is serialized to the shared preferences entry with this name. 499f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */ 509f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private static final String TRIGGERS_PREF_KEY = "TRIGGERS"; 519f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 529f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private final SharedPreferences mPreferences; 539f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private final Context mContext; 549f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 559f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** 569f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * An interface for objects that are notified when a trigger is added to the repository. 579f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */ 589f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public interface TriggerRepositoryObserver { 599f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** 609f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Invoked just before the trigger is added to the repository. 619f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * 629f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * @param trigger 639f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * The trigger about to be added to the repository. 649f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */ 659f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li void onPut(Trigger trigger); 669f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 679f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** 689f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Invoked just after the trigger has been removed from the repository. 699f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * 709f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * @param trigger 719f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * The trigger that has just been removed from the repository. 729f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */ 739f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li void onRemove(Trigger trigger); 749f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 759f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 769f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private final Multimap<String, Trigger> mTriggers; 779f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private final CopyOnWriteArrayList<TriggerRepositoryObserver> mTriggerObservers = 789f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li new CopyOnWriteArrayList<TriggerRepositoryObserver>(); 799f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 809f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public TriggerRepository(Context context) { 819f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li mContext = context; 829f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li mPreferences = PreferenceManager.getDefaultSharedPreferences(context); 839f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li String triggers = mPreferences.getString(TRIGGERS_PREF_KEY, null); 849f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li mTriggers = deserializeTriggersFromString(triggers); 859f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 869f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 879f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Returns a list of all triggers. The list is unmodifiable. */ 889f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public synchronized Multimap<String, Trigger> getAllTriggers() { 899f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li return Multimaps.unmodifiableMultimap(mTriggers); 909f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 919f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 929f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** 939f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Adds a new trigger to the repository. 949f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * 959f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * @param trigger 969f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * the {@link Trigger} to add 979f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */ 989f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public synchronized void put(Trigger trigger) { 999f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li notifyOnAdd(trigger); 1009f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li mTriggers.put(trigger.getEventName(), trigger); 1019f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li storeTriggers(); 1029f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li ensureTriggerServiceRunning(); 1039f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1049f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1059f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Removes a specific {@link Trigger}. */ 1069f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public synchronized void remove(final Trigger trigger) { 1079f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li mTriggers.get(trigger.getEventName()).remove(trigger); 1089f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li storeTriggers(); 1099f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li notifyOnRemove(trigger); 1109f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1119f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1129f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Ensures that the {@link TriggerService} is running */ 1139f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private void ensureTriggerServiceRunning() { 1149f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li Intent startTriggerServiceIntent = IntentBuilders.buildTriggerServiceIntent(); 1159f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li mContext.startService(startTriggerServiceIntent); 1169f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1179f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1189f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Notify all {@link TriggerRepositoryObserver}s that a {@link Trigger} was added. */ 1199f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private void notifyOnAdd(Trigger trigger) { 1209f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li for (TriggerRepositoryObserver observer : mTriggerObservers) { 1219f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li observer.onPut(trigger); 1229f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1239f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1249f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1259f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Notify all {@link TriggerRepositoryObserver}s that a {@link Trigger} was removed. */ 1269f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private void notifyOnRemove(Trigger trigger) { 1279f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li for (TriggerRepositoryObserver observer : mTriggerObservers) { 1289f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li observer.onRemove(trigger); 1299f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1309f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1319f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1329f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Writes the list of triggers to the shared preferences. */ 1339f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private synchronized void storeTriggers() { 1349f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li SharedPreferences.Editor editor = mPreferences.edit(); 1359f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li final String triggerValue = serializeTriggersToString(mTriggers); 1369f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li if (triggerValue != null) { 1379f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li editor.putString(TRIGGERS_PREF_KEY, triggerValue); 1389f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1399f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li editor.commit(); 1409f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1419f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1429f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Deserializes the {@link Multimap} of {@link Trigger}s from a base 64 encoded string. */ 1439f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li @SuppressWarnings("unchecked") 1449f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private Multimap<String, Trigger> deserializeTriggersFromString(String triggers) { 1459f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li if (triggers == null) { 1469f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li return ArrayListMultimap.<String, Trigger> create(); 1479f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1489f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li try { 1499f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li final ByteArrayInputStream inputStream = 1509f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li new ByteArrayInputStream(Base64Codec.decodeBase64(triggers.getBytes())); 1519f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li final ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); 1529f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li return (Multimap<String, Trigger>) objectInputStream.readObject(); 1539f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } catch (Exception e) { 1549f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li Log.e(e); 1559f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1569f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li return ArrayListMultimap.<String, Trigger> create(); 1579f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1589f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1599f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Serializes the list of triggers to a Base64 encoded string. */ 1609f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li private String serializeTriggersToString(Multimap<String, Trigger> triggers) { 1619f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li try { 1629f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 1639f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li final ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); 1649f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li objectOutputStream.writeObject(triggers); 1659f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li return new String(Base64Codec.encodeBase64(outputStream.toByteArray())); 1669f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } catch (IOException e) { 1679f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li Log.e(e); 1689f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li return null; 1699f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1709f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1719f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1729f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Returns {@code true} iff the list of triggers is empty. */ 1739f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public synchronized boolean isEmpty() { 1749f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li return mTriggers.isEmpty(); 1759f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1769f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1779f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** Adds a {@link TriggerRepositoryObserver}. */ 1789f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public void addObserver(TriggerRepositoryObserver observer) { 1799f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li mTriggerObservers.add(observer); 1809f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1819f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1829f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** 1839f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Adds the given {@link TriggerRepositoryObserver} and invokes 1849f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * {@link TriggerRepositoryObserver#onPut} for all existing triggers. 1859f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * 1869f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * @param observer 1879f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * The observer to add. 1889f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */ 1899f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public synchronized void bootstrapObserver(TriggerRepositoryObserver observer) { 1909f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li addObserver(observer); 1919f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li for (Entry<String, Trigger> trigger : mTriggers.entries()) { 1929f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li observer.onPut(trigger.getValue()); 1939f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1949f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 1959f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li 1969f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li /** 1979f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Removes a {@link TriggerRepositoryObserver}. 1989f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */ 1999f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li public void removeObserver(TriggerRepositoryObserver observer) { 2009f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li mTriggerObservers.remove(observer); 2019f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li } 2029f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li}