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