Home | History | Annotate | Download | only in trigger
      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.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  */
     46 public class TriggerRepository {
     47   /**
     48    * The list of triggers is serialized to the shared preferences entry with this name.
     49    */
     50   private static final String TRIGGERS_PREF_KEY = "TRIGGERS";
     51 
     52   private final SharedPreferences mPreferences;
     53   private final Context mContext;
     54 
     55   /**
     56    * An interface for objects that are notified when a trigger is added to the repository.
     57    */
     58   public interface TriggerRepositoryObserver {
     59     /**
     60      * Invoked just before the trigger is added to the repository.
     61      *
     62      * @param trigger
     63      *          The trigger about to be added to the repository.
     64      */
     65     void onPut(Trigger trigger);
     66 
     67     /**
     68      * Invoked just after the trigger has been removed from the repository.
     69      *
     70      * @param trigger
     71      *          The trigger that has just been removed from the repository.
     72      */
     73     void onRemove(Trigger trigger);
     74   }
     75 
     76   private final Multimap<String, Trigger> mTriggers;
     77   private final CopyOnWriteArrayList<TriggerRepositoryObserver> mTriggerObservers =
     78       new CopyOnWriteArrayList<TriggerRepositoryObserver>();
     79 
     80   public TriggerRepository(Context context) {
     81     mContext = context;
     82     mPreferences = PreferenceManager.getDefaultSharedPreferences(context);
     83     String triggers = mPreferences.getString(TRIGGERS_PREF_KEY, null);
     84     mTriggers = deserializeTriggersFromString(triggers);
     85   }
     86 
     87   /** Returns a list of all triggers. The list is unmodifiable. */
     88   public synchronized Multimap<String, Trigger> getAllTriggers() {
     89     return Multimaps.unmodifiableMultimap(mTriggers);
     90   }
     91 
     92   /**
     93    * Adds a new trigger to the repository.
     94    *
     95    * @param trigger
     96    *          the {@link Trigger} to add
     97    */
     98   public synchronized void put(Trigger trigger) {
     99     notifyOnAdd(trigger);
    100     mTriggers.put(trigger.getEventName(), trigger);
    101     storeTriggers();
    102     ensureTriggerServiceRunning();
    103   }
    104 
    105   /** Removes a specific {@link Trigger}. */
    106   public synchronized void remove(final Trigger trigger) {
    107     mTriggers.get(trigger.getEventName()).remove(trigger);
    108     storeTriggers();
    109     notifyOnRemove(trigger);
    110   }
    111 
    112   /** Ensures that the {@link TriggerService} is running */
    113   private void ensureTriggerServiceRunning() {
    114     Intent startTriggerServiceIntent = IntentBuilders.buildTriggerServiceIntent();
    115     mContext.startService(startTriggerServiceIntent);
    116   }
    117 
    118   /** Notify all {@link TriggerRepositoryObserver}s that a {@link Trigger} was added. */
    119   private void notifyOnAdd(Trigger trigger) {
    120     for (TriggerRepositoryObserver observer : mTriggerObservers) {
    121       observer.onPut(trigger);
    122     }
    123   }
    124 
    125   /** Notify all {@link TriggerRepositoryObserver}s that a {@link Trigger} was removed. */
    126   private void notifyOnRemove(Trigger trigger) {
    127     for (TriggerRepositoryObserver observer : mTriggerObservers) {
    128       observer.onRemove(trigger);
    129     }
    130   }
    131 
    132   /** Writes the list of triggers to the shared preferences. */
    133   private synchronized void storeTriggers() {
    134     SharedPreferences.Editor editor = mPreferences.edit();
    135     final String triggerValue = serializeTriggersToString(mTriggers);
    136     if (triggerValue != null) {
    137       editor.putString(TRIGGERS_PREF_KEY, triggerValue);
    138     }
    139     editor.commit();
    140   }
    141 
    142   /** Deserializes the {@link Multimap} of {@link Trigger}s from a base 64 encoded string. */
    143   @SuppressWarnings("unchecked")
    144   private Multimap<String, Trigger> deserializeTriggersFromString(String triggers) {
    145     if (triggers == null) {
    146       return ArrayListMultimap.<String, Trigger> create();
    147     }
    148     try {
    149       final ByteArrayInputStream inputStream =
    150           new ByteArrayInputStream(Base64Codec.decodeBase64(triggers.getBytes()));
    151       final ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
    152       return (Multimap<String, Trigger>) objectInputStream.readObject();
    153     } catch (Exception e) {
    154       Log.e(e);
    155     }
    156     return ArrayListMultimap.<String, Trigger> create();
    157   }
    158 
    159   /** Serializes the list of triggers to a Base64 encoded string. */
    160   private String serializeTriggersToString(Multimap<String, Trigger> triggers) {
    161     try {
    162       final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    163       final ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
    164       objectOutputStream.writeObject(triggers);
    165       return new String(Base64Codec.encodeBase64(outputStream.toByteArray()));
    166     } catch (IOException e) {
    167       Log.e(e);
    168       return null;
    169     }
    170   }
    171 
    172   /** Returns {@code true} iff the list of triggers is empty. */
    173   public synchronized boolean isEmpty() {
    174     return mTriggers.isEmpty();
    175   }
    176 
    177   /** Adds a {@link TriggerRepositoryObserver}. */
    178   public void addObserver(TriggerRepositoryObserver observer) {
    179     mTriggerObservers.add(observer);
    180   }
    181 
    182   /**
    183    * Adds the given {@link TriggerRepositoryObserver} and invokes
    184    * {@link TriggerRepositoryObserver#onPut} for all existing triggers.
    185    *
    186    * @param observer
    187    *          The observer to add.
    188    */
    189   public synchronized void bootstrapObserver(TriggerRepositoryObserver observer) {
    190     addObserver(observer);
    191     for (Entry<String, Trigger> trigger : mTriggers.entries()) {
    192       observer.onPut(trigger.getValue());
    193     }
    194   }
    195 
    196   /**
    197    * Removes a {@link TriggerRepositoryObserver}.
    198    */
    199   public void removeObserver(TriggerRepositoryObserver observer) {
    200     mTriggerObservers.remove(observer);
    201   }
    202 }