/** * Copyright (c) 2011, SOCIETIES Consortium (WATERFORD INSTITUTE OF TECHNOLOGY (TSSG), HERIOT-WATT UNIVERSITY (HWU), SOLUTA.NET * (SN), GERMAN AEROSPACE CENTRE (Deutsches Zentrum fuer Luft- und Raumfahrt e.V.) (DLR), Zavod za varnostne tehnologije * informacijske družbe in elektronsko poslovanje (SETCCE), INSTITUTE OF COMMUNICATION AND COMPUTER SYSTEMS (ICCS), LAKE * COMMUNICATIONS (LAKE), INTEL PERFORMANCE LEARNING SOLUTIONS LTD (INTEL), PORTUGAL TELECOM INOVAÇÃO, SA (PTIN), IBM Corp., * INSTITUT TELECOM (ITSUD), AMITEC DIACHYTI EFYIA PLIROFORIKI KAI EPIKINONIES ETERIA PERIORISMENIS EFTHINIS (AMITEC), TELECOM * ITALIA S.p.a.(TI), TRIALOG (TRIALOG), Stiftelsen SINTEF (SINTEF), NEC EUROPE LTD (NEC)) * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following * conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.societies.android.platform.useragent.feedback; import android.app.Notification; import android.app.Service; import android.content.Intent; import android.os.Binder; import android.os.IBinder; import android.util.Log; import org.jivesoftware.smack.packet.IQ; import org.societies.android.api.comms.IMethodCallback; import org.societies.android.api.comms.xmpp.*; import org.societies.android.platform.androidutils.AndroidNotifier; import org.societies.android.platform.comms.helper.ClientCommunicationMgr; import org.societies.android.platform.useragent.feedback.guis.NotificationHistoryPopup; import org.societies.android.platform.useragent.feedback.model.NotificationHistoryItem; import org.societies.api.identity.INetworkNode; import org.societies.api.identity.InvalidFormatException; import org.societies.api.internal.schema.useragent.feedback.HistoryRequestType; import org.societies.api.internal.schema.useragent.feedback.UserFeedbackAccessControlEvent; import org.societies.api.internal.schema.useragent.feedback.UserFeedbackHistoryRequest; import org.societies.api.internal.schema.useragent.feedback.UserFeedbackPrivacyNegotiationEvent; import org.societies.api.schema.useragent.feedback.UserFeedbackBean; import java.util.*; public class EventHistory extends Service { private static EventHistory singleton; public static EventHistory getInstance() { return singleton; } public class LocalBinder extends Binder { public EventHistory getService() { // Return this instance of EventHistory so clients can call public methods return EventHistory.this; } } private class BindMethodCallback implements IMethodCallback { private final String LOG_TAG = BindMethodCallback.class.getCanonicalName(); @Override public void returnAction(boolean resultFlag) { Log.i(LOG_TAG + ".methodCallback", "returnAction(boolean)"); // NB: If we need to register more names for use in other components, we should either union the two lists or register twice clientCommunicationMgr.register( ELEMENT_NAMES, NAMESPACES, PACKAGES, registerMethodCallback); } @Override public void returnAction(String result) { Log.i(LOG_TAG + ".methodCallback", "returnAction(String)"); } @Override public void returnException(String result) { Log.i(LOG_TAG + ".methodCallback", "returnException(String)"); } } private class RegisterMethodCallback implements IMethodCallback { private final String LOG_TAG = RegisterMethodCallback.class.getCanonicalName(); @Override public void returnAction(boolean resultFlag) { Log.i(LOG_TAG + ".methodCallback", "returnAction(boolean)"); historyRepository.loadNotifications(DEFAULT_FETCH_COUNT); } @Override public void returnAction(String result) { Log.i(LOG_TAG + ".methodCallback", "returnAction(String)"); } @Override public void returnException(String result) { Log.i(LOG_TAG + ".methodCallback", "returnException(String)"); } } private class NotificationHistoryRepository implements ICommCallback { private final String LOG_TAG = NotificationHistoryRepository.class.getCanonicalName(); public void loadNotifications(int howMany) { try { if (clientCommunicationMgr == null) { Log.e(LOG_TAG, "commsManager was null when trying to list previous"); return; } if (clientCommunicationMgr.getIdManager() == null) { Log.e(LOG_TAG, "commsManager.getIdManager() was null when trying to list previous"); return; } Log.d(LOG_TAG, "listPrevious(int)"); INetworkNode cloudNode = clientCommunicationMgr.getIdManager().getCloudNode(); UserFeedbackHistoryRequest bean = new UserFeedbackHistoryRequest(); bean.setRequestType(HistoryRequestType.OUTSTANDING); bean.setHowMany(howMany); // bean.setSinceWhen(new Date()); // will be ignored, but must not be null Stanza stanza = new Stanza(cloudNode); String id = UUID.randomUUID().toString(); stanza.setId(id); Log.d(LOG_TAG, "Sending loadNotifications IQ..."); clientCommunicationMgr.sendIQ(stanza, IQ.Type.GET, bean, this); Log.d(LOG_TAG, "loadNotifications IQ sent"); } catch (InvalidFormatException e) { Log.e(LOG_TAG, "Error listing previous notification history items", e); } catch (CommunicationException e) { Log.e(LOG_TAG, "Error listing previous notification history items", e); } catch (Exception e) { Log.e(LOG_TAG, "Error listing previous notification history items", e); } } @Override public List<String> getXMLNamespaces() { return NAMESPACES; } @Override public List<String> getJavaPackages() { return PACKAGES; } @Override public void receiveResult(Stanza stanza, Object payload) { Log.d(LOG_TAG, String.format("receiveResult() \nStanza=%s\nPayload=%s", stanza != null ? stanza.toString() : "null", payload != null ? payload.toString() : "null")); if (payload == null) { Log.w(LOG_TAG, "Received null payload in receiveResult()"); return; } UserFeedbackHistoryRequest request = (UserFeedbackHistoryRequest) payload; Log.i(LOG_TAG, String.format("Received: %s UF beans, %s PPNs, %s ACs", request.getUserFeedbackBean() == null ? "null" : request.getUserFeedbackBean().size(), request.getUserFeedbackPrivacyNegotiationEvent() == null ? "null" : request.getUserFeedbackPrivacyNegotiationEvent().size(), request.getUserFeedbackAccessControlEvent() == null ? "null" : request.getUserFeedbackAccessControlEvent().size())); List<NotificationHistoryItem> historyItems = new ArrayList<NotificationHistoryItem>(); // wrap the beans in NotificationHistoryItem objects for (UserFeedbackBean bean : request.getUserFeedbackBean()) { NotificationHistoryItem item = new NotificationHistoryItem(bean.getRequestId(), // bean.getRequestDate(), new Date(), bean); historyItems.add(item); } for (UserFeedbackPrivacyNegotiationEvent bean : request.getUserFeedbackPrivacyNegotiationEvent()) { NotificationHistoryItem item = new NotificationHistoryItem(bean.getRequestId(), // bean.getRequestDate(), new Date(), bean); historyItems.add(item); } for (UserFeedbackAccessControlEvent bean : request.getUserFeedbackAccessControlEvent()) { NotificationHistoryItem item = new NotificationHistoryItem(bean.getRequestId(), // bean.getRequestDate(), new Date(), bean); historyItems.add(item); } EventHistory.this.replaceCacheWithList(historyItems); EventHistory.this.showHistoryItemsPopup(); } @Override public void receiveError(Stanza stanza, XMPPError error) { Log.d(LOG_TAG, String.format("receiveError() \nStanza=%s\nerror=%s", stanza != null ? stanza.toString() : "null", error != null ? error.getStanzaErrorString() : "null")); } @Override public void receiveInfo(Stanza stanza, String node, XMPPInfo info) { Log.d(LOG_TAG, String.format("receiveInfo() \nStanza=%s\nNode=%s\nInfo=%s", stanza != null ? stanza.toString() : "null", node != null ? node : "null", info != null ? info.toString() : "null")); } @Override public void receiveItems(Stanza stanza, String node, List<String> items) { Log.d(LOG_TAG, String.format("receiveInfo() \nStanza=%s\nNode=%s\nitems=%s", stanza != null ? stanza.toString() : "null", node != null ? node : "null", items != null ? Arrays.toString(items.toArray()) : "null")); } @Override public void receiveMessage(Stanza stanza, Object payload) { Log.d(LOG_TAG, String.format("receiveMessage() \nStanza=%s\nPayload=%s", stanza != null ? stanza.toString() : "null", payload != null ? payload.toString() : "null")); } } private static final String LOG_TAG = EventHistory.class.getCanonicalName(); public static final int DEFAULT_FETCH_COUNT = 50; // public static final int REQUEST_TIMEOUT = 10000; public static final List<String> ELEMENT_NAMES = Collections.unmodifiableList( Arrays.asList("userFeedbackHistoryRequest")); public static final List<String> NAMESPACES = Collections.unmodifiableList( Arrays.asList("http://societies.org/api/schema/useragent/feedback", "http://societies.org/api/internal/schema/useragent/feedback")); public static final List<String> PACKAGES = Collections.unmodifiableList( Arrays.asList("org.societies.api.schema.useragent.feedback", "org.societies.api.internal.schema.useragent.feedback")); // Binder given to clients private final IBinder serviceBinder = new LocalBinder(); private final BindMethodCallback bindMethodCallback = new BindMethodCallback(); private final RegisterMethodCallback registerMethodCallback = new RegisterMethodCallback(); private NotificationHistoryRepository historyRepository; private ClientCommunicationMgr clientCommunicationMgr; // NB: to avoid deadlocks, always synchronise on historyItems, not on itemIDs private final List<NotificationHistoryItem> historyItems = new ArrayList<NotificationHistoryItem>(); private final Set<String> itemIDs = new HashSet<String>(); @Override public IBinder onBind(Intent intent) { return serviceBinder; } @Override public void onCreate() { Log.i(LOG_TAG, "onCreate()"); super.onCreate(); EventHistory.singleton = this; clientCommunicationMgr = new ClientCommunicationMgr(getApplicationContext(), true); clientCommunicationMgr.bindCommsService(bindMethodCallback); historyRepository = new NotificationHistoryRepository(); } @Override public void onDestroy() { Log.i(LOG_TAG, "onDestroy()"); super.onDestroy(); // NB: If we need to register more names for use in other components, we should either union the two lists or register twice clientCommunicationMgr.unregister(ELEMENT_NAMES, NAMESPACES, registerMethodCallback); clientCommunicationMgr.unbindCommsService(); } private void replaceCacheWithList(List<NotificationHistoryItem> storedItems) { Log.i(LOG_TAG, String.format("replaceCacheWithList() with %s items", storedItems != null ? storedItems.size() : "0")); synchronized (historyItems) { historyItems.clear(); itemIDs.clear(); if (storedItems != null) { for (NotificationHistoryItem item : storedItems) { historyItems.add(item); itemIDs.add(item.getUuid()); } } } } private void showHistoryItemsPopup() { if (historyItems.isEmpty()) return; // pop up a notification //CREATE INTENT FOR LAUNCHING ACTIVITY Intent intent = new Intent(this.getApplicationContext(), NotificationHistoryPopup.class); // intent.putExtra(UserFeedbackActivityIntentExtra.USERFEEDBACK_NODES, (Parcelable) ufBean); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //CREATE ANDROID NOTIFICATION int notifierFlags[] = new int[1]; notifierFlags[0] = Notification.FLAG_AUTO_CANCEL; AndroidNotifier notifier = new AndroidNotifier(this.getApplicationContext(), Notification.DEFAULT_SOUND, notifierFlags); notifier.notifyMessage( "Click to answer outstanding requests", "Outstanding Requests", NotificationHistoryPopup.class, intent, historyItems.size() + " outstanding requests"); } public void addIncomingEvent(UserFeedbackBean uf) { synchronized (historyItems) { if (itemIDs.contains(uf.getRequestId())) { Log.w(LOG_TAG, "UF event ID " + uf.getRequestId() + " already in cache - ignoring"); return; } NotificationHistoryItem newItem = new NotificationHistoryItem( uf.getRequestId(), new Date(), uf ); historyItems.add(newItem); itemIDs.add(newItem.getUuid()); } } public void addIncomingEvent(UserFeedbackPrivacyNegotiationEvent ppn) { synchronized (historyItems) { if (itemIDs.contains(ppn.getRequestId())) { Log.w(LOG_TAG, "PPN event ID " + ppn.getRequestId() + " already in cache - ignoring"); return; } NotificationHistoryItem newItem = new NotificationHistoryItem( ppn.getRequestId(), new Date(), ppn ); historyItems.add(newItem); itemIDs.add(newItem.getUuid()); } } public List<NotificationHistoryItem> getHistoryItemsList() { return Collections.unmodifiableList(historyItems); } }