• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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@gmail.com)
46  * @author Damon Kohler (damonkohler@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      */
onPut(Trigger trigger)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      */
onRemove(Trigger trigger)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 
TriggerRepository(Context context)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. */
getAllTriggers()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    */
put(Trigger trigger)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}. */
remove(final Trigger 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 */
ensureTriggerServiceRunning()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. */
notifyOnAdd(Trigger trigger)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. */
notifyOnRemove(Trigger trigger)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. */
storeTriggers()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")
deserializeTriggersFromString(String triggers)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. */
serializeTriggersToString(Multimap<String, Trigger> triggers)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. */
isEmpty()175   public synchronized boolean isEmpty() {
176     return mTriggers.isEmpty();
177   }
178 
179   /** Adds a {@link TriggerRepositoryObserver}. */
addObserver(TriggerRepositoryObserver observer)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    */
bootstrapObserver(TriggerRepositoryObserver observer)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    */
removeObserver(TriggerRepositoryObserver observer)201   public void removeObserver(TriggerRepositoryObserver observer) {
202     mTriggerObservers.remove(observer);
203   }
204 }