/* * Copyright (C) 2016 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.googlecode.android_scripting.trigger; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.preference.PreferenceManager; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import com.googlecode.android_scripting.IntentBuilders; import com.googlecode.android_scripting.Log; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Map.Entry; import java.util.concurrent.CopyOnWriteArrayList; import org.apache.commons.codec.binary.Base64Codec; /** * A repository maintaining all currently scheduled triggers. This includes, for example, alarms or * observers of arriving text messages etc. This class is responsible for serializing the list of * triggers to the shared preferences store, and retrieving it from there. * * @author Felix Arends (felix.arends@gmail.com) * @author Damon Kohler (damonkohler@gmail.com) */ public class TriggerRepository { /** * The list of triggers is serialized to the shared preferences entry with this name. */ private static final String TRIGGERS_PREF_KEY = "TRIGGERS"; private final SharedPreferences mPreferences; private final Context mContext; /** * An interface for objects that are notified when a trigger is added to the repository. */ public interface TriggerRepositoryObserver { /** * Invoked just before the trigger is added to the repository. * * @param trigger * The trigger about to be added to the repository. */ void onPut(Trigger trigger); /** * Invoked just after the trigger has been removed from the repository. * * @param trigger * The trigger that has just been removed from the repository. */ void onRemove(Trigger trigger); } private final Multimap mTriggers; private final CopyOnWriteArrayList mTriggerObservers = new CopyOnWriteArrayList(); public TriggerRepository(Context context) { mContext = context; mPreferences = PreferenceManager.getDefaultSharedPreferences(context); String triggers = mPreferences.getString(TRIGGERS_PREF_KEY, null); mTriggers = deserializeTriggersFromString(triggers); } /** Returns a list of all triggers. The list is unmodifiable. */ public synchronized Multimap getAllTriggers() { return Multimaps.unmodifiableMultimap(mTriggers); } /** * Adds a new trigger to the repository. * * @param trigger * the {@link Trigger} to add */ public synchronized void put(Trigger trigger) { notifyOnAdd(trigger); mTriggers.put(trigger.getEventName(), trigger); storeTriggers(); ensureTriggerServiceRunning(); } /** Removes a specific {@link Trigger}. */ public synchronized void remove(final Trigger trigger) { mTriggers.get(trigger.getEventName()).remove(trigger); storeTriggers(); notifyOnRemove(trigger); } /** Ensures that the {@link TriggerService} is running */ private void ensureTriggerServiceRunning() { Intent startTriggerServiceIntent = IntentBuilders.buildTriggerServiceIntent(); mContext.startService(startTriggerServiceIntent); } /** Notify all {@link TriggerRepositoryObserver}s that a {@link Trigger} was added. */ private void notifyOnAdd(Trigger trigger) { for (TriggerRepositoryObserver observer : mTriggerObservers) { observer.onPut(trigger); } } /** Notify all {@link TriggerRepositoryObserver}s that a {@link Trigger} was removed. */ private void notifyOnRemove(Trigger trigger) { for (TriggerRepositoryObserver observer : mTriggerObservers) { observer.onRemove(trigger); } } /** Writes the list of triggers to the shared preferences. */ private synchronized void storeTriggers() { SharedPreferences.Editor editor = mPreferences.edit(); final String triggerValue = serializeTriggersToString(mTriggers); if (triggerValue != null) { editor.putString(TRIGGERS_PREF_KEY, triggerValue); } editor.commit(); } /** Deserializes the {@link Multimap} of {@link Trigger}s from a base 64 encoded string. */ @SuppressWarnings("unchecked") private Multimap deserializeTriggersFromString(String triggers) { if (triggers == null) { return ArrayListMultimap. create(); } try { final ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Codec.decodeBase64(triggers.getBytes())); final ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); return (Multimap) objectInputStream.readObject(); } catch (Exception e) { Log.e(e); } return ArrayListMultimap. create(); } /** Serializes the list of triggers to a Base64 encoded string. */ private String serializeTriggersToString(Multimap triggers) { try { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); objectOutputStream.writeObject(triggers); return new String(Base64Codec.encodeBase64(outputStream.toByteArray())); } catch (IOException e) { Log.e(e); return null; } } /** Returns {@code true} iff the list of triggers is empty. */ public synchronized boolean isEmpty() { return mTriggers.isEmpty(); } /** Adds a {@link TriggerRepositoryObserver}. */ public void addObserver(TriggerRepositoryObserver observer) { mTriggerObservers.add(observer); } /** * Adds the given {@link TriggerRepositoryObserver} and invokes * {@link TriggerRepositoryObserver#onPut} for all existing triggers. * * @param observer * The observer to add. */ public synchronized void bootstrapObserver(TriggerRepositoryObserver observer) { addObserver(observer); for (Entry trigger : mTriggers.entries()) { observer.onPut(trigger.getValue()); } } /** * Removes a {@link TriggerRepositoryObserver}. */ public void removeObserver(TriggerRepositoryObserver observer) { mTriggerObservers.remove(observer); } }