Home | History | Annotate | Download | only in trigger
      1 /*
      2  * Copyright (C) 2016 Google Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * 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, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.googlecode.android_scripting.trigger;
     18 
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.content.SharedPreferences;
     22 import android.preference.PreferenceManager;
     23 
     24 import com.google.common.collect.ArrayListMultimap;
     25 import com.google.common.collect.Multimap;
     26 import com.google.common.collect.Multimaps;
     27 import com.googlecode.android_scripting.IntentBuilders;
     28 import com.googlecode.android_scripting.Log;
     29 
     30 import java.io.ByteArrayInputStream;
     31 import java.io.ByteArrayOutputStream;
     32 import java.io.IOException;
     33 import java.io.ObjectInputStream;
     34 import java.io.ObjectOutputStream;
     35 import java.util.Map.Entry;
     36 import java.util.concurrent.CopyOnWriteArrayList;
     37 
     38 import org.apache.commons.codec.binary.Base64Codec;
     39 
     40 /**
     41  * A repository maintaining all currently scheduled triggers. This includes, for example, alarms or
     42  * observers of arriving text messages etc. This class is responsible for serializing the list of
     43  * triggers to the shared preferences store, and retrieving it from there.
     44  *
     45  * @author Felix Arends (felix.arends (at) gmail.com)
     46  * @author Damon Kohler (damonkohler (at) gmail.com)
     47  */
     48 public class TriggerRepository {
     49   /**
     50    * The list of triggers is serialized to the shared preferences entry with this name.
     51    */
     52   private static final String TRIGGERS_PREF_KEY = "TRIGGERS";
     53 
     54   private final SharedPreferences mPreferences;
     55   private final Context mContext;
     56 
     57   /**
     58    * An interface for objects that are notified when a trigger is added to the repository.
     59    */
     60   public interface TriggerRepositoryObserver {
     61     /**
     62      * Invoked just before the trigger is added to the repository.
     63      *
     64      * @param trigger
     65      *          The trigger about to be added to the repository.
     66      */
     67     void onPut(Trigger trigger);
     68 
     69     /**
     70      * Invoked just after the trigger has been removed from the repository.
     71      *
     72      * @param trigger
     73      *          The trigger that has just been removed from the repository.
     74      */
     75     void onRemove(Trigger trigger);
     76   }
     77 
     78   private final Multimap<String, Trigger> mTriggers;
     79   private final CopyOnWriteArrayList<TriggerRepositoryObserver> mTriggerObservers =
     80       new CopyOnWriteArrayList<TriggerRepositoryObserver>();
     81 
     82   public TriggerRepository(Context context) {
     83     mContext = context;
     84     mPreferences = PreferenceManager.getDefaultSharedPreferences(context);
     85     String triggers = mPreferences.getString(TRIGGERS_PREF_KEY, null);
     86     mTriggers = deserializeTriggersFromString(triggers);
     87   }
     88 
     89   /** Returns a list of all triggers. The list is unmodifiable. */
     90   public synchronized Multimap<String, Trigger> getAllTriggers() {
     91     return Multimaps.unmodifiableMultimap(mTriggers);
     92   }
     93 
     94   /**
     95    * Adds a new trigger to the repository.
     96    *
     97    * @param trigger
     98    *          the {@link Trigger} to add
     99    */
    100   public synchronized void put(Trigger trigger) {
    101     notifyOnAdd(trigger);
    102     mTriggers.put(trigger.getEventName(), trigger);
    103     storeTriggers();
    104     ensureTriggerServiceRunning();
    105   }
    106 
    107   /** Removes a specific {@link Trigger}. */
    108   public synchronized void remove(final Trigger trigger) {
    109     mTriggers.get(trigger.getEventName()).remove(trigger);
    110     storeTriggers();
    111     notifyOnRemove(trigger);
    112   }
    113 
    114   /** Ensures that the {@link TriggerService} is running */
    115   private void ensureTriggerServiceRunning() {
    116     Intent startTriggerServiceIntent = IntentBuilders.buildTriggerServiceIntent();
    117     mContext.startService(startTriggerServiceIntent);
    118   }
    119 
    120   /** Notify all {@link TriggerRepositoryObserver}s that a {@link Trigger} was added. */
    121   private void notifyOnAdd(Trigger trigger) {
    122     for (TriggerRepositoryObserver observer : mTriggerObservers) {
    123       observer.onPut(trigger);
    124     }
    125   }
    126 
    127   /** Notify all {@link TriggerRepositoryObserver}s that a {@link Trigger} was removed. */
    128   private void notifyOnRemove(Trigger trigger) {
    129     for (TriggerRepositoryObserver observer : mTriggerObservers) {
    130       observer.onRemove(trigger);
    131     }
    132   }
    133 
    134   /** Writes the list of triggers to the shared preferences. */
    135   private synchronized void storeTriggers() {
    136     SharedPreferences.Editor editor = mPreferences.edit();
    137     final String triggerValue = serializeTriggersToString(mTriggers);
    138     if (triggerValue != null) {
    139       editor.putString(TRIGGERS_PREF_KEY, triggerValue);
    140     }
    141     editor.commit();
    142   }
    143 
    144   /** Deserializes the {@link Multimap} of {@link Trigger}s from a base 64 encoded string. */
    145   @SuppressWarnings("unchecked")
    146   private Multimap<String, Trigger> deserializeTriggersFromString(String triggers) {
    147     if (triggers == null) {
    148       return ArrayListMultimap.<String, Trigger> create();
    149     }
    150     try {
    151       final ByteArrayInputStream inputStream =
    152           new ByteArrayInputStream(Base64Codec.decodeBase64(triggers.getBytes()));
    153       final ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
    154       return (Multimap<String, Trigger>) objectInputStream.readObject();
    155     } catch (Exception e) {
    156       Log.e(e);
    157     }
    158     return ArrayListMultimap.<String, Trigger> create();
    159   }
    160 
    161   /** Serializes the list of triggers to a Base64 encoded string. */
    162   private String serializeTriggersToString(Multimap<String, Trigger> triggers) {
    163     try {
    164       final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    165       final ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
    166       objectOutputStream.writeObject(triggers);
    167       return new String(Base64Codec.encodeBase64(outputStream.toByteArray()));
    168     } catch (IOException e) {
    169       Log.e(e);
    170       return null;
    171     }
    172   }
    173 
    174   /** Returns {@code true} iff the list of triggers is empty. */
    175   public synchronized boolean isEmpty() {
    176     return mTriggers.isEmpty();
    177   }
    178 
    179   /** Adds a {@link TriggerRepositoryObserver}. */
    180   public void addObserver(TriggerRepositoryObserver observer) {
    181     mTriggerObservers.add(observer);
    182   }
    183 
    184   /**
    185    * Adds the given {@link TriggerRepositoryObserver} and invokes
    186    * {@link TriggerRepositoryObserver#onPut} for all existing triggers.
    187    *
    188    * @param observer
    189    *          The observer to add.
    190    */
    191   public synchronized void bootstrapObserver(TriggerRepositoryObserver observer) {
    192     addObserver(observer);
    193     for (Entry<String, Trigger> trigger : mTriggers.entries()) {
    194       observer.onPut(trigger.getValue());
    195     }
    196   }
    197 
    198   /**
    199    * Removes a {@link TriggerRepositoryObserver}.
    200    */
    201   public void removeObserver(TriggerRepositoryObserver observer) {
    202     mTriggerObservers.remove(observer);
    203   }
    204 }