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}