/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.framework.proximity;

import it.polimi.spf.framework.SPF;
import it.polimi.spf.framework.security.ContactRequest;
import it.polimi.spf.shared.model.InvocationRequest;
import it.polimi.spf.shared.model.InvocationResponse;
import it.polimi.spf.shared.model.ProfileFieldContainer;
import it.polimi.spf.shared.model.SPFActionSendNotification;
import it.polimi.spf.shared.model.SPFActivity;

/**
 * Remote instance of SPF found by the middleware. Offers method to interact
 * according to the SPF model. Each middleware defines its own subclass
 * according to its features.
 *
 * @author darioarchetti
 */
public abstract class SPFRemoteInstance {

    /**
     * Dispatches a {@link SPFActivity} to the remote instance
     *
     * @param activity - the activity to dispatch
     * @return an {@link InvocationResponse} with the result of the invocation
     */
    public abstract InvocationResponse sendActivity(SPFActivity activity);

    /**
     * Retrieves the values of a defined set of {@link ProfileField} from the
     * profile of the remote instance. The value of a field will be returned
     * only if visible to the current instance.
     *
     * @param fieldIdentifiers - the list of identifiers of the profile fields to retrieve
     * @param appIdentifier    - the identifier of the app that created the request.
     * @return a {@link ProfileFieldContainer} with all the retrieved values.
     */
    public final ProfileFieldContainer getProfileBulk(String[] fieldIdentifiers, String appIdentifier) {
        if (fieldIdentifiers == null || appIdentifier == null) {
            throw new NullPointerException();
        }

        String identifierList = FieldContainerMarshaller.marshallIdentifierList(fieldIdentifiers);
        String token = SPF.get().getSecurityMonitor().getPersonRegistry().getTokenFor(getUniqueIdentifier());

        String containerString = getProfileBulk(token, identifierList, appIdentifier);
        return FieldContainerMarshaller.unmarshallContainer(containerString);
    }

    /**
     * Sends a contact request to the remote SPF instance.
     *
     * @param request - the {@link ContactRequest} to send.
     */
    public abstract void sendContactRequest(ContactRequest request);

    /**
     * Sends a notification to the remote SPF instance.
     *
     * @param uniqueIdentifier - the identifier of the sender.
     * @param action           - the notification action.
     */
    public final void sendNotification(String uniqueIdentifier, SPFActionSendNotification action) {
        if (uniqueIdentifier == null || action == null) {
            throw new NullPointerException();
        }

        String actionJSON = action.toJSON();
        sendNotification(uniqueIdentifier, actionJSON);
    }

    /**
     * Provides the unique identifier of the remote SPF instance.
     *
     * @return the unique identifier
     */
    public abstract String getUniqueIdentifier();

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#toString()
     */
    @Override
    public final String toString() {
        return getClass().getSimpleName() + ":" + getUniqueIdentifier();
    }

    // Abstract method for subclasses to provide middleware-dependent
    // dispatching of messages for higher level methods.

    /**
     * Dispatches an {@link InvocationRequest} to the remote instance of SPF.
     *
     * @param request - the request to dispatch
     * @return - an {@link InvocationResponse} containing the result of the
     * invocation
     */
    public abstract InvocationResponse executeService(InvocationRequest request);

    protected abstract String getProfileBulk(String token, String identifierList, String appIdentifier);

    protected abstract void sendNotification(String senderIdentifier, String action);
}


/**
 * The MIT License
 *
 * Copyright (C) 2015 Asterios Raptis
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package de.alpharogroup.xml;

import java.util.HashMap;
import java.util.Map;

/**
 * The abstract Class XmlTransformation that transform a subclass as xml string.
 */
public abstract class XmlTransformation
{

	/**
	 * To xml.
	 *
	 * @return the string
	 */
	public String toXml()
	{
		final String lqSimpleName = this.getClass().getSimpleName().toLowerCase();
		final Map<String, Class<?>> aliases = new HashMap<>();
		aliases.put(lqSimpleName, this.getClass());
		return ObjectToXmlExtensions.toXmlWithXStream(this, aliases);
	}
}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 * Copyright 2015 Stefano Cappa
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.app;

import android.app.Application;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.RemoteViews;

import it.polimi.spf.framework.ExceptionLogger;
import it.polimi.spf.framework.SPF;
import it.polimi.spf.framework.SPFContext;
import it.polimi.spf.wfdadapter.WFDMiddlewareAdapter;

/**
 * {@link Application} subclass that serves two main purposes:
 *

 *
Initializing SPF singleton with a context reference

 *
Local broadcaster for application-wide events

 *

 */
public class SPFApp extends Application {
    private static final String TAG = SPFApp.class.getSimpleName();
    private static final String STOPSPF = "it.polimi.spf.app.stop";

    private RemoteViews contentView;
    private Notification notification;

    @Override
    public void onCreate() {
        super.onCreate();

        this.initNotification();

        // Initialize SPF
        // Use this line to initialize SPF on Wi-Fi Direct
        SPFContext.initialize(this, 0, true, WFDMiddlewareAdapter.FACTORY);

        SPFContext.get().setAppRegistrationHandler(new PopupAppRegistrationHandler());

        SPFContext.get().setServiceNotification(notification);

        // Set exception logger to log uncaught exceptions
        ExceptionLogger.installAsDefault(this);
    }

    private void initNotification() {
        //Intent for the stop button in the notification layout
        Intent stopSpf = new Intent(STOPSPF);
        PendingIntent pendingIntentStop = PendingIntent.getBroadcast(this, 0, stopSpf, 0);

        //intent for the click inside the notification area
        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent pIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);

        //set my custom contentView with a layout
        this.contentView = new RemoteViews(this.getApplicationContext().getPackageName(), R.layout.notification_layout);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        builder.setContentIntent(pIntent);
        builder.setTicker(getResources().getString(R.string.notification_ticker));
        builder.setSmallIcon(R.drawable.ic_launcher);
        builder.setAutoCancel(true);

        //------------------------------------------------------------------------------------
        // when you click on buttons in a notification, the actions will be implemented in
        // SPSService in the Framework's module.
        //add the onclickpendingintent to the button
        this.contentView.setOnClickPendingIntent(R.id.stopSpfButton, pendingIntentStop);
        //------------------------------------------------------------------------------------

        //build the notification
        this.notification = builder.build();

        // Set data in the RemoteViews programmatically
        this.setContentViewWithMinimalElements();

        /* Workaround: Need to set the content view here directly on the notification.
         * NotificationCompatBuilder contains a bug that prevents this from working on platform
         * versions HoneyComb.
         * See https://code.google.com/p/android/issues/detail?id=30495
         */
        notification.contentView = contentView;
    }

    public void initSPF(int goIntent, boolean isAutonomous) {
        // Initialize SPF
        // Use this line to initialize SPF on Wi-Fi Direct
        SPF.get().disconnect();

        SPFContext.initializeForcedNoSingleton(this, goIntent, isAutonomous, WFDMiddlewareAdapter.FACTORY);
        SPFContext.get().setAppRegistrationHandler(new PopupAppRegistrationHandler());

        SPFContext.get().setServiceNotification(notification);

        // Set exception logger to log uncaught exceptions
        ExceptionLogger.installAsDefault(this);
    }

    public void updateIdentifier(int goIntent) {
        Log.d(TAG, "Called updateIdentifier with goIntent: " + goIntent);
        SPF.get().updateIdentifier(goIntent);
    }

    /**
     * Method to set the contentview with Title, Text and Image.
     */
    private void setContentViewWithMinimalElements() {
        // Set data in the RemoteViews programmatically
        contentView.setImageViewResource(R.id.imageView, R.drawable.ic_launcher);
        contentView.setTextViewText(R.id.title_text_notification, getResources().getString(R.string.notification_title));
        contentView.setTextViewText(R.id.message_notification, getResources().getString(R.string.notification_text));
    }
}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 * Copyright 2015 Stefano Cappa
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.app.fragments.profile;

import java.text.ParseException;
import java.util.Date;
import java.util.List;

import it.polimi.spf.app.R;
import it.polimi.spf.framework.security.DefaultCircles;
import it.polimi.spf.shared.model.ProfileField;
import android.content.Context;
import android.text.format.DateFormat;
import android.util.Log;
import android.util.SparseArray;

/**
 * Helper class to retrieve friendly name of fields and circles from resources
 * and to convert the value of {@link ProfileField} to strings that can be
 * displayed in views
 *
 * @author darioarchetti
 *
 */
public class Helper {

	public class InvalidValueException extends Exception {

		private static final long serialVersionUID = 4448039112806219926L;

	}

	private static final String TAG = "Helper";
	private static final String PROFILE_FIELD_PREFIX = "profile_field_";
	private static final String CIRCLE_PREFIX = "circle_";
	private static final SparseArray<String> sCache = new SparseArray<String>();

	private Converter<String> mStringConverter = new StringConverter();
	private Converter<Date> mDateConverter = new DateConverter();
	private Converter<String[]> mStringArrayConverter = new StringArrayConverter();

	private Context mContext;

	public Helper(Context context) {
		this.mContext = context;
	}

	/**
	 * Provides the localized friendly name of a field.
	 * @param field - the field whose name to retrieve
	 * @return the friendly name of the field
	 */
	public String getFriendlyNameOfField(ProfileField<?> field) {
		String name = getStringFromResource(PROFILE_FIELD_PREFIX + field.getIdentifier());
		if(name == null){
			return field.getIdentifier();
		}

		return name;
	}

	private String getStringFromResource(String resourceName){
		int id;

		try {
			id = (Integer) R.string.class.getField(resourceName).get(null);
		} catch (IllegalAccessException e) {
			Log.w(TAG, "Cannot retrieve string id for " + resourceName, e);
			return null;
		} catch (IllegalArgumentException e) {
			Log.w(TAG, "Cannot retrieve string id for " + resourceName, e);
			return null;
		} catch (NoSuchFieldException e) {
			Log.w(TAG, "Cannot retrieve string id for " + resourceName, e);
			return null;
		}

		String val = sCache.get(id);

		if (val == null) {
			val = mContext.getResources().getString(id);
			sCache.put(id, val);
		}

		return val;
	}

	public String convertToFriendlyString(ProfileField<?> field, Object value) {
		Class<?> fieldClass = field.getFieldClass();
		if (fieldClass == String.class) {
			return mStringConverter.toString((String) value);
		} else if (fieldClass == Date.class) {
			return mDateConverter.toString((Date) value);
		} else if (fieldClass == String[].class) {
			return mStringArrayConverter.toString((String[]) value);
		} else {
			throw new IllegalArgumentException("Type " + fieldClass.getSimpleName() + " not supported.");
		}
	}

	@SuppressWarnings("unchecked")
	public <E> E convertFromFriendlyString(ProfileField<E> field, String value) throws InvalidValueException {
		Class<?> fieldClass = field.getFieldClass();
		if (fieldClass == String.class) {
			return (E) mStringConverter.fromString(value);
		} else if (fieldClass == Date.class) {
			return (E) mDateConverter.fromString(value);
		} else if (fieldClass == String[].class) {
			return (E) mStringArrayConverter.fromString(value);
		} else {
			throw new IllegalArgumentException("Type " + fieldClass.getSimpleName() + " not supported.");
		}
	}

	public String getFriendlyNameOfCircle(String circle) {
		if (!DefaultCircles.isDefault(circle)) {
			return circle;
		}

		String name = getStringFromResource(CIRCLE_PREFIX + circle);
		if(name == null){
			return circle;
		}

		return name;
	}

	public String sumUpCircles(List<String> circles) {
		if (circles == null || circles.isEmpty()) {
			return getFriendlyNameOfCircle(DefaultCircles.PRIVATE);
		}

		if (circles.contains(DefaultCircles.PUBLIC)) {
			return getFriendlyNameOfCircle(DefaultCircles.PUBLIC);
		}

		if (circles.contains(DefaultCircles.ALL_CIRCLE)) {
			return getFriendlyNameOfCircle(DefaultCircles.ALL_CIRCLE);
		}

		return circles.size() + " circles";
	}

	private interface Converter<E> {
		E fromString(String value) throws InvalidValueException;

		String toString(E value);
	}

	private class StringConverter implements Converter<String> {

		@Override
		public String fromString(String value) {
			return value;
		}

		@Override
		public String toString(String value) {
			return value;
		}

	}

	private class DateConverter implements Converter<Date> {

		@Override
		public Date fromString(String value) throws InvalidValueException {
			try {
				return value == null ? null : DateFormat.getDateFormat(mContext).parse(value);
			} catch (ParseException e) {
				throw new InvalidValueException();
			}
		}

		@Override
		public String toString(Date value) {
			return value == null ? null : DateFormat.getDateFormat(mContext).format(value);
		}

	}

	public class StringArrayConverter implements Converter<String[]> {

		@Override
		public String[] fromString(String value) throws InvalidValueException {
			return value.split("\n");
		}

		@Override
		public String toString(String[] value) {
			if (value == null) {
				return null;
			}

			int i = 0;
			StringBuilder b = new StringBuilder();

			while (i < value.length) {
				b.append(value[i]);
				i++;

				if (i < value.length) {
					b.append("\n");
				}
			}

			return b.toString();
		}

	}

}


/*
 * Copyright 2015 Stefano Cappa
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package it.polimi.spf.app.fragments.groupinfo;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.TextView;

import com.mikepenz.fontawesome_typeface_library.FontAwesome;
import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.iconics.view.IconicsImageView;

import butterknife.Bind;
import butterknife.ButterKnife;
import it.polimi.spf.app.R;

public class ServicesRecyclerViewAdapter extends RecyclerView.Adapter<ServicesRecyclerViewAdapter.ViewHolder> {
    private ItemClickListener itemClickListener;
    private final Context context;

    public ServicesRecyclerViewAdapter(@NonNull ItemClickListener itemClickListener, @NonNull Context context) {
        this.itemClickListener = itemClickListener;
        this.context = context;
        setHasStableIds(true);
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        private final View parent;

        @Bind(R.id.logoImageView)
        IconicsImageView logo;
        @Bind(R.id.nameTextView)
        TextView nameTextView;
        @Bind(R.id.addressTextView)
        TextView addressTextView;
        @Bind(R.id.identifierTextView)
        TextView identifierTextView;

        public ViewHolder(View view) {
            super(view);
            this.parent = view;
            ButterKnife.bind(this, view);
        }

        public void setOnClickListener(OnClickListener listener) {
            parent.setOnClickListener(listener);
        }
    }

    // Create new views (invoked by the layout manager)
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        // Create a new view.
        View v = LayoutInflater.from(viewGroup.getContext())
                .inflate(R.layout.service_card_view, viewGroup, false);
        return new ViewHolder(v);
    }

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(ViewHolder viewHolder, final int position) {
        final DeviceGuiElement device = ServicesGuiList.get().getServices().get(position);

        if (device.getName() == null || device.getName().equals("")) {
            viewHolder.nameTextView.setText("Name not found, please wait...");
        } else {
            viewHolder.nameTextView.setText(device.getName());
        }

        viewHolder.addressTextView.setText(device.getAddress());
        viewHolder.identifierTextView.setText(device.getIdentifier());
        viewHolder.logo.setImageDrawable(new IconicsDrawable(context)
                .icon(FontAwesome.Icon.faw_android)
                .color(context.getResources().getColor(R.color.red))
                .sizeDp(30));

        viewHolder.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                itemClickListener.serviceItemClicked(device);
            }
        });
    }

    @Override
    public int getItemCount() {
        return ServicesGuiList.get().getServices().size();
    }

    public interface ItemClickListener {
        void serviceItemClicked(final DeviceGuiElement device);
    }
}


/*
 * Copyright 2015 Stefano Cappa
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package it.polimi.spf.wfd;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import it.polimi.spf.wfd.events.EternalConnectEvent;
import it.polimi.spf.wfd.events.NineBus;
import it.polimi.spf.wfd.util.WfdLog;
import lombok.Getter;
import lombok.Setter;

/**
 * Created by Stefano Cappa on 13/10/15.
 */
public class EternalConnect {
    private static final String TAG = EternalConnect.class.getSimpleName();
    private static final int MAX_ETERNAL_COUNT = 4;

    @Getter
    @Setter
    private boolean eternalConnectStatus = false;
    @Getter
    @Setter
    private int eternalCounter = 0;
    private ScheduledExecutorService scheduler = null;

    private static final EternalConnect instance = new EternalConnect();

    /**
     * Method to get the instance of this class.
     *
     * @return instance of this class.
     */
    public static EternalConnect get() {
        return instance;
    }

    /**
     * Private constructor, because is a singleton class.
     */
    private EternalConnect() {
    }

    public void eternalConnect(final boolean proximityKilledByUser) {
        //to be sure that there aren't any eternal connect's cycles running
        killScheduler();

        scheduler = Executors.newScheduledThreadPool(1);

        final Runnable runnable = new Runnable() {
            //to execute into another Thread
            public void run() {
                WfdLog.d(TAG, "Eternal Connect is creating a new connection, please wait...");

                //if user disabled the proximity service manually fromt he gui
                if (proximityKilledByUser) {
                    WfdLog.d(TAG, "Killing Eternal connect because proximity killed by user...");
                    killScheduler();
                    eternalCounter = 0;
                    return;
                }

                if (eternalCounter > MAX_ETERNAL_COUNT) {
                    WfdLog.d(TAG, "Eternal Connect count has reached the max value. " +
                            "Terminating Eternal Connect...");
                    killScheduler();
                    eternalCounter = 0;
                    //TODO find a way to disable the proximity switch in the gui, because the eternal connect
                    //TODO is failed after MAX_ETERNAL_COUNT tries.
                } else {
                    WfdLog.d(TAG, "Eternal Counter = " + eternalCounter);

                    NineBus.get().post(new EternalConnectEvent(EternalConnectEvent.NEW_EC_CYCLE));

                    eternalCounter++;
                }
            }
        };

        final ScheduledFuture<?> eternalHandle = scheduler.scheduleAtFixedRate(runnable, 10, 15, TimeUnit.SECONDS);
        scheduler.schedule(new Runnable() {
            public void run() {
                eternalHandle.cancel(true);
            }
        }, 15, TimeUnit.SECONDS);
    }

    public boolean onNetworkDisconnected(boolean proximityKilledByUser, boolean isAutonomous) {
        if (isEternalConnectStatus() && !proximityKilledByUser) {
            if (!isAutonomous) {
                WfdLog.d(TAG, "onNetworkDisconnected - Eternal connect is active...Reconnecting...");
                this.eternalConnect(proximityKilledByUser);
                return true;
            } else {
                WfdLog.d(TAG, "onNetworkDisconnected - Simple Eternal connect. Reconnecting...");
                //TODO FIXME here i should check if there aren't client connected to proceed with the reconnection
                //FIXME because if i don't want to destroy an existing group, only because a client decided to leave.
                NineBus.get().post(EternalConnectEvent.SIMPLE_EC_RECONNECTION);
            }
        }
        return false;
    }

    public boolean onConnectFailed(boolean proximityKilledByUser) {
        if (!proximityKilledByUser) {
            WfdLog.d(TAG, "onConnectFailed - Eternal connect is active...Reconnecting...");
            this.eternalConnect(proximityKilledByUser);
            return true;
        }
        return false;
    }

    public boolean onGroupCreationFailed(boolean proximityKilledByUser) {
        if (!proximityKilledByUser) {
            WfdLog.d(TAG, "onGroupCreationFailed - Simple Eternal connect. Reconnecting...");
            //attention: it' useless for an autonomous go to continuously restart the connection,
            //because it should be discoverable without making direct connections to clients
            //Indeed, Clients should connect (join) to the autonomous GO.

            NineBus.get().post(new EternalConnectEvent(EternalConnectEvent.SIMPLE_EC_RECONNECTION));
            return true;
        }
        return false;
    }

    /**
     * Call this method if you want to stop the Eternal Connect procedure.
     * Call this only if you created a connection.
     */
    public void eternalCompletedSuccessfully() {
        this.killScheduler();
        this.eternalCounter = 0;
        this.eternalConnectStatus = true;
    }

    /**
     * Kill the scheduler to stop the Eternal Connect in any case.
     */
    private void killScheduler() {
        if (scheduler != null) {
            WfdLog.d(TAG, "scheduler killed");
            scheduler.shutdown();
            scheduler = null;
        }
    }
}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.framework;

import android.app.Notification;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import it.polimi.spf.framework.local.SPFService;
import it.polimi.spf.framework.proximity.ProximityMiddleware;
import it.polimi.spf.framework.security.AppRegistrationHandler;

/**
 * Main entry point for the SPF framework.
 *
 * @author darioarchetti
 */
public class SPFContext {

    private static final String NOT_INITIALIZED_MESSAGE = "SPFContext not initialized. Don't forget to call SPFContext." +
            "initialize(context) before any call to SPF components";

    // Singleton
    private static SPFContext sInstance;

    // Events
    public static final int EVENT_NOTIFICATION_MESSAGE_RECEIVED = 0;
    public static final int EVENT_CONTACT_REQUEST_RECEIVED = 1;
    public static final int EVENT_ADVERTISING_STATE_CHANGED = 2;

    // Payload extra keys
    public static final String EXTRA_NOTIFICATION_MESSAGE = "notification_message";
    public static final String EXTRA_ACTIVE = "active";

    // Private constants
    private static final String TAG = "EventBroadcaster";

    private Set<OnEventListener> mEventListeners;
    private Handler mHandler;
    private AppRegistrationHandler mRegistrationHandler;
    private Notification mNotification;

    /**
     * Initializes SPFContext. After this method has been called, you can get
     * references to SPFContext and SPF.
     *
     * @param context
     */
    public static synchronized void initialize(Context context, int goIntent, boolean isAutonomous,
                                               ProximityMiddleware.Factory factory) {
        if (context == null || factory == null) {
            throw new NullPointerException("Arguments cannot be null");
        }

        SPF.initialize(context, goIntent, isAutonomous, factory);
        sInstance = new SPFContext();
    }

    /**
     * Initializes SPFContext. After this method has been called, you can get
     * references to SPFContext and SPF.
     *
     * @param context
     */
    public static synchronized void initializeForcedNoSingleton(Context context, int goIntent,
                                                                boolean isAutonomous, ProximityMiddleware.Factory factory) {
        if (context == null || factory == null) {
            throw new NullPointerException("Arguments cannot be null");
        }

        SPF.initializeForcedNoSingleton(context, goIntent, isAutonomous, factory);
        sInstance = new SPFContext();
    }

    /**
     * Obtains a reference to the {@link SPFContext} singleton. You need to
     * initialize SPFContext before calling this method
     *
     * @return the instance
     * @see #initialize(Context, int, boolean, ProximityMiddleware.Factory)
     */
    public static synchronized SPFContext get() {
        assertInitialization();
        return sInstance;
    }

    /* package */
    static void assertInitialization() {
        if (sInstance == null) {
            throw new IllegalStateException(NOT_INITIALIZED_MESSAGE);
        }
    }

    private SPFContext() {
        mEventListeners = Collections.newSetFromMap(new ConcurrentHashMap<OnEventListener, Boolean>());
        mRegistrationHandler = new AppRegistrationHandler.Default();
        mHandler = new Handler(Looper.getMainLooper());
    }

    /**
     * Interface for components to listen to events broadcasted by
     * {@link SPFContext}. It is possible to register a broadcast listener using
     * {@link SPFContext#registerEventListener(OnEventListener)}.
     *
     * @author darioarchetti
     */
    public static interface OnEventListener {

        /**
         * Called when an event is broadcasted through SPFApp. This call
         * happens on the main thread.
         *
         * @param eventCode - the code of the event.
         * @param payload   - a Bundle containing event-specific information.
         */
        void onEvent(int eventCode, Bundle payload);
    }

    /**
     * Registers a listener to be notified when an event is broadcasted.
     *
     * @param listener - the listener to register.
     */
    public void registerEventListener(OnEventListener listener) {
        if (listener == null) {
            throw new NullPointerException("listener == null");
        }

        mEventListeners.add(listener);
    }

    /**
     * Unregisters a listener.
     *
     * @param listener - the listener to unregister
     */
    public void unregisterEventListener(OnEventListener listener) {
        if (listener == null) {
            throw new NullPointerException("listener == null");
        }

        mEventListeners.remove(listener);
    }

    /**
     * Broadcasts an event to all registered listeners.
     *
     * @param code - the event code
     */
    public void broadcastEvent(int code) {
        broadcastEvent(code, null);
    }

    //TODO synchronization: event should not be broadcast after unregistration

    /**
     * Broadcasts an event to all registered listeners. Each listener will
     * receive a reference to the given bundle.
     *
     * @param code    - the event code
     * @param payload - the event payload
     */
    public void broadcastEvent(final int code, final Bundle payload) {
        if (SPFConfig.DEBUG) {
            Log.d(TAG, "Broadcasting event " + code + " with payload " + payload);
        }

        for (final OnEventListener listener : mEventListeners) {//TODO is it thread safe?
            mHandler.post(new Runnable() {

                @Override
                public void run() {
                    listener.onEvent(code, payload);
                }
            });
        }
    }

    /**
     * Obtains a reference to the current {@link AppRegistrationHandler}. If no
     * handler has been set via
     * {@link #setAppRegistrationHandler(AppRegistrationHandler)}, an instance
     * of {@link AppRegistrationHandler.Default} is returned.
     *
     * @return - the app registration handler
     */
    public AppRegistrationHandler getAppRegistrationHandler() {
        return mRegistrationHandler;
    }

    /**
     * Sets the {@link AppRegistrationHandler} to handle incoming app
     * registration request. The instance will be returned by
     * {@link #getAppRegistrationHandler()}
     *
     * @param registrationHandler
     */
    public void setAppRegistrationHandler(AppRegistrationHandler registrationHandler) {
        if (registrationHandler == null) {
            throw new NullPointerException();
        }

        this.mRegistrationHandler = registrationHandler;
    }

    /**
     * Sets the notification that will be shown when {@link SPFService} is run
     * in foreground.
     *
     * @param notification - the notification to show
     */
    public void setServiceNotification(Notification notification) {
        this.mNotification = notification;
    }

    /**
     * Gets the notification to show when {@link SPFService} is run in
     * foreground. It is set with {@link #setServiceNotification(Notification)},
     * and is null if never set.
     *
     * @return - the notification.
     */
    public Notification getServiceNotification() {
        return mNotification;
    }
}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 * Copyright 2015 Stefano Cappa
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.app.fragments.profile;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.Toast;

import com.soundcloud.android.crop.Crop;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;

import butterknife.Bind;
import butterknife.ButterKnife;
import de.hdodenhof.circleimageview.CircleImageView;
import it.polimi.spf.app.LoadersConfig;
import it.polimi.spf.app.MainActivity;
import it.polimi.spf.app.R;
import it.polimi.spf.app.fragments.contacts.ContactConfirmDialogView;
import it.polimi.spf.framework.SPF;
import it.polimi.spf.framework.profile.SPFPersona;
import it.polimi.spf.framework.proximity.SPFRemoteInstance;
import it.polimi.spf.shared.model.ProfileField;
import it.polimi.spf.shared.model.ProfileFieldContainer;
import lombok.Getter;

public class ProfileFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<ProfileFieldContainer>,
        OnItemSelectedListener,
        OnClickListener {

    public interface ProfilePhotoListener {
        void onPhotoReady(Bitmap bitmap);
    }

    @Bind(R.id.profileedit_tabs)
    TabLayout tabLayout;
    @Bind(R.id.profileedit_pager)
    ViewPager mViewPager;

    @Nullable
    @Bind(R.id.profile_picture)
    CircleImageView resultView;

    /**
     * Possible visualization modes of fields values.
     *
     * @author darioarchetti
     */
    public enum Mode {
        /**
         * Shows the profile of the local user
         */
        SELF,

        /**
         * Shows the profile of a remote user
         */
        REMOTE,

        /**
         * Shows the profile of the local user and allows modifications
         */
        EDIT;
    }

    /**
     * Creates a new instance of {@link ProfileFragment} to show the profile of
     * a remote user. The fragment will allow only to view the fields and not to
     * modify them.
     *
     * @param personIdentifer - the identifier of the person whose profile to show
     * @return an instance of ProfileFragment
     */
    public static ProfileFragment createRemoteProfileFragment(String personIdentifer) {
        Bundle b = new Bundle();
        b.putInt(EXTRA_VIEW_MODE, Mode.REMOTE.ordinal());
        b.putString(EXTRA_PERSON_IDENTIFIER, personIdentifer);
        ProfileFragment fragment = new ProfileFragment();
        fragment.setArguments(b);
        return fragment;
    }

    /**
     * Creates a new instance of ProfileFragment to show the local profile.
     *
     * @return an instance of ProfileFragment
     */
    public static ProfileFragment createViewSelfProfileFragment() {
        Bundle b = new Bundle();
        b.putInt(EXTRA_VIEW_MODE, Mode.SELF.ordinal());
        ProfileFragment fragment = new ProfileFragment();
        fragment.setArguments(b);
        return fragment;
    }

    public static ProfileFragment createEditSelfProfileFragment(SPFPersona persona) {
        Bundle b = new Bundle();
        b.putInt(EXTRA_VIEW_MODE, Mode.EDIT.ordinal());
        b.putParcelable(EXTRA_CURRENT_PERSONA, persona);
        ProfileFragment fragment = new ProfileFragment();
        fragment.setArguments(b);
        return fragment;
    }

    private static final String EXTRA_PERSON_IDENTIFIER = "personIdentifier";
    private static final String EXTRA_PROFILE_CONTAINER = "profileContainer";
    private static final String EXTRA_CURRENT_PERSONA = "persona";
    private static final String EXTRA_VIEW_MODE = "viewMode";

    private static final int ACTIVITY_EDIT_PROFILE_CODE = 0;
    private static final String TAG = "ProfileFragment";

    private String mPersonIdentifier;
    @Getter
    private SPFPersona mCurrentPersona;
    private Mode mMode;
    private ProfileFieldContainer mContainer;

    private ProfileFieldViewFactory mFactory;
    private boolean mModifiedAtLeastOnce = false;
    private int deviceWidth;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View root;
        mMode = Mode.values()[getArguments().getInt(EXTRA_VIEW_MODE)];
        switch (mMode) {
            case EDIT:
            case REMOTE:
                root = inflater.inflate(R.layout.content_fragment_profile, container, false);
                break;
            case SELF:
            default:
                root = inflater.inflate(R.layout.content_fragment_profile_no_photo, container, false);
                break;
        }

        ButterKnife.bind(this, root);

        deviceWidth = root.getMeasuredWidth();

        return root;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        if (savedInstanceState == null) {
            switch (mMode) {
                case SELF:
                    String callerApp = getActivity().getCallingPackage();
                    if (callerApp == null) {
                        mCurrentPersona = SPFPersona.DEFAULT;
                    } else {
                        mCurrentPersona = SPF.get().getSecurityMonitor().getPersonaOf(callerApp);
                    }
                    break;
                case REMOTE:
                    mPersonIdentifier = getArguments().getString(EXTRA_PERSON_IDENTIFIER);
                    break;
                case EDIT:
                    mCurrentPersona = getArguments().getParcelable(EXTRA_CURRENT_PERSONA);
            }

            // Initialize the loader of profile information
            startLoader(LoadersConfig.LOAD_PROFILE_LOADER_ID);
        } else {
            mPersonIdentifier = savedInstanceState.getString(EXTRA_PERSON_IDENTIFIER);
            mCurrentPersona = savedInstanceState.getParcelable(EXTRA_CURRENT_PERSONA);
            mMode = Mode.values()[savedInstanceState.getInt(EXTRA_VIEW_MODE)];
            mContainer = savedInstanceState.getParcelable(EXTRA_PROFILE_CONTAINER);

            onProfileDataAvailable();
        }

        //workaround used to center tabs or scroll tabs based on screen width
        //http://stackoverflow.com/questions/30616474/
        // android-support-design-tablayout-gravity-center-and-mode-scrollable/31861336#31861336
        tabLayout.post(new Runnable() {
            @Override
            public void run() {

                final int tabLayoutWidth = tabLayout.getWidth();

                Log.d(TAG, "tabLayoutWidth: " + tabLayoutWidth + " , deviceWidth: " + deviceWidth);

                //removed to prevent a crash during screen rotarion
//                DisplayMetrics metrics = new DisplayMetrics();
//                getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);

                if (tabLayoutWidth < deviceWidth) {
                    Log.d(TAG, (tabLayoutWidth < deviceWidth) + "");
                    tabLayout.setTabMode(TabLayout.MODE_FIXED);
                    tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
                } else {
                    Log.d(TAG, (tabLayoutWidth < deviceWidth) + "");
                    tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
                }
            }
        });

    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString(EXTRA_PERSON_IDENTIFIER, mPersonIdentifier);
        outState.putParcelable(EXTRA_CURRENT_PERSONA, mCurrentPersona);
        outState.putInt(EXTRA_VIEW_MODE, mMode.ordinal());
        outState.putParcelable(EXTRA_PROFILE_CONTAINER, mContainer);
    }

    /*
     * LOADERS - Used to load and save profile data.
     */
    @Override
    public Loader<ProfileFieldContainer> onCreateLoader(int id, Bundle args) {
        switch (id) {
            case LoadersConfig.LOAD_PROFILE_LOADER_ID:
                return new AsyncTaskLoader<ProfileFieldContainer>(getActivity()) {

                    @Override
                    public ProfileFieldContainer loadInBackground() {
                        if (mMode == Mode.SELF || mMode == Mode.EDIT) {
                            return SPF.get().getProfileManager().getProfileFieldBulk(mCurrentPersona, ProfilePagerAdapter.DEFAULT_FIELDS);
                        } else {
                            SPFRemoteInstance instance = SPF.get().getPeopleManager().getPerson(mPersonIdentifier);
                            if (instance == null) {
                                throw new IllegalStateException("Person " + mPersonIdentifier + " not found in proximity");
                            } else {
                                String app = getActivity().getCallingPackage();
                                app = app == null ? "it.polimi.spf.app" : app;
                                return instance.getProfileBulk(ProfileField.toIdentifierList(ProfilePagerAdapter.DEFAULT_FIELDS), app);
                            }
                        }
                    }
                };

            case LoadersConfig.SAVE_PROFILE_LOADER_ID:
                if (mMode != Mode.EDIT) {
                    Log.e(TAG, "SAVE_PROFILE_LOADER initialized in mode " + mMode);
                }

                return new AsyncTaskLoader<ProfileFieldContainer>(getActivity()) {

                    @Override
                    public ProfileFieldContainer loadInBackground() {
                        SPF.get().getProfileManager().setProfileFieldBulk(mContainer, mCurrentPersona);
                        return null;
                    }
                };

            default:
                throw new IllegalArgumentException("No loader for id " + id);
        }
    }

    @Override
    public void onLoadFinished(Loader<ProfileFieldContainer> loader, ProfileFieldContainer data) {
        switch (loader.getId()) {
            case LoadersConfig.LOAD_PROFILE_LOADER_ID:
                mContainer = data;
                onProfileDataAvailable();
                break;
            case LoadersConfig.SAVE_PROFILE_LOADER_ID:
                mContainer.clearModified();
                mModifiedAtLeastOnce = true;
                getActivity().finish();
                break;
        }
    }

    @Override
    public void onLoaderReset(Loader<ProfileFieldContainer> loader) {
        // Do nothing
    }

    // Called when the profile data is available, thus we can set up the view
    private void onProfileDataAvailable() {
        Log.d(TAG, "onProfileDataAvailable");
        mFactory = new ProfileFieldViewFactory(getActivity(), mMode, mCurrentPersona, mContainer);

        String[] mPageTitles = this.getResources().getStringArray(R.array.profileedit_fragments_titles);

        tabLayout.removeAllTabs();
        tabLayout.addTab(tabLayout.newTab().setText(mPageTitles[0].toUpperCase(Locale.getDefault())));
        tabLayout.addTab(tabLayout.newTab().setText(mPageTitles[1].toUpperCase(Locale.getDefault())));
        tabLayout.addTab(tabLayout.newTab().setText(mPageTitles[2].toUpperCase(Locale.getDefault())));
        tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);

        ProfilePagerAdapter mPagerAdapter = new ProfilePagerAdapter(getChildFragmentManager(), mMode, tabLayout.getTabCount());

        mViewPager.setAdapter(mPagerAdapter);
        mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
        tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                mViewPager.setCurrentItem(tab.getPosition());
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {
            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {
            }
        });

        if (mMode != Mode.SELF) {
            showPicture(mContainer.getFieldValue(ProfileField.PHOTO));
        } else {
            if (getActivity() instanceof MainActivity) {
                ((MainActivity) getActivity()).onPhotoReady(mContainer.getFieldValue(ProfileField.PHOTO));
            } else if (getActivity() instanceof ProfileViewActivity) {
                //TODO implement this
//                ((ProfileViewActivity) getActivity()).onPhotoReady(mContainer.getFieldValue(ProfileField.PHOTO));
            }
        }

        // Refresh field fragments
        mPagerAdapter.onRefresh();
    }

    private void showPicture(Bitmap photo) {
        if (this.resultView == null) {
            return;
        }

        // Show picture
        if (mMode == Mode.EDIT) {
            this.resultView.setOnClickListener(this);
        }

        if (photo != null) {
            this.resultView.setImageBitmap(photo);
            this.resultView.invalidate();
        }
    }

    // Methods to be called from child ProfileFieldsFragment to obtain views and
    // to notify of
    // changes in values of circles

    /**
     * Creates a view for the given profile field. Depending on the {@link Mode}
     * of visualization, the view may allow the modification of the value. This
     * method will not attach the view to the provided {@link ViewGroup}
     *
     * @param field     - the field for which to create the view.
     * @param container - the container to which the view will be attached, needed by
     *                  {@link LayoutInflater#inflate(int, ViewGroup, boolean)} to
     *                  evaluate layout params.
     * @return
     */
    public <E> View createViewFor(ProfileField<E> field, ViewGroup container) {
        switch (mMode) {
            case SELF:
            case REMOTE:
                return mFactory.createViewForField(field, container, null);
            case EDIT:
                return mFactory.createViewForField(field, container, new ProfileFieldViewFactory.FieldValueListener<E>() {

                    @Override
                    public void onFieldValueChanged(ProfileField<E> field, E value) {
                        mContainer.setFieldValue(field, value);
                    }

                    @Override
                    public void onInvalidFieldValue(ProfileField<E> field, String fieldFriendlyName) {
                        Toast.makeText(getActivity(), "Invalid value for field " + fieldFriendlyName, Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onCircleAdded(ProfileField<E> field, String circle) {
                        Log.d(TAG, "Circle " + circle + " added to field " + field + " of persona " + mCurrentPersona);
                        SPF.get().getProfileManager().addGroupToField(field, circle, mCurrentPersona);
                    }

                    @Override
                    public void onCircleRemoved(ProfileField<E> field, String circle) {
                        Log.d(TAG, "Circle " + circle + " removed from field " + field + " of persona " + mCurrentPersona);
                        SPF.get().getProfileManager().removeGroupFromField(field, circle, mCurrentPersona);
                    }
                });
            default:
                return null;
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        switch (requestCode) {
            case ACTIVITY_EDIT_PROFILE_CODE:
                // Profile may have changed, reload it
                if (resultCode == Activity.RESULT_CANCELED) {
                    Log.d(TAG, "Edit finished but no data was modified");
                    onProfileDataNotSaved();
                } else {
                    onProfileDataSaved();
                }
                startLoader(LoadersConfig.LOAD_PROFILE_LOADER_ID);
                break;
        }
    }

    /**
     * Method to start the activity to crop an image.
     *
     * @param source
     */
    public void beginCrop(Uri source) {
        Uri destination = Uri.fromFile(new File(this.getActivity().getCacheDir(), "cropped"));
        Crop.of(source, destination).asSquare().start(this.getActivity());
    }

    /**
     * Method to set an show a cropped imaged.
     *
     * @param resultCode
     * @param result
     */
    public void handleCrop(int resultCode, Intent result) {
        if (resultCode == Activity.RESULT_OK) {
            Uri uri = Crop.getOutput(result);
            resultView.setImageURI(uri);

            InputStream inputStream = null;
            try {
                inputStream = new FileInputStream(uri.getPath());
                Bitmap myBitmap = BitmapFactory.decodeStream(inputStream);
                myBitmap = Bitmap.createScaledBitmap(myBitmap, 130, 130, false);

                mContainer.setFieldValue(ProfileField.PHOTO, myBitmap);
                showPicture(myBitmap);

            } catch (FileNotFoundException e) {
                Log.e(TAG, "handleCrop FileInputStream-file not found from uri.getpath", e);
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        Log.e(TAG, "handleCrop closing input stream error", e);
                    }
                }
            }
        } else if (resultCode == Crop.RESULT_ERROR) {
            Toast.makeText(this.getActivity(), Crop.getError(result).getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

    public void clickedOptionItemEdit() {
        Intent i = new Intent(getActivity(), ProfileEditActivity.class);
        i.putExtra(ProfileEditActivity.EXTRA_PERSONA, mCurrentPersona);
        startActivityForResult(i, ACTIVITY_EDIT_PROFILE_CODE);
    }

    public void clickedOptionItemSendContactRequest() {
        final String displayName = mContainer.getFieldValue(ProfileField.DISPLAY_NAME);
        final Bitmap picture = mContainer.getFieldValue(ProfileField.PHOTO);

        final ContactConfirmDialogView view = new ContactConfirmDialogView(getActivity(), displayName, picture);
        new AlertDialog.Builder(getActivity()).setTitle("Send contact request?").setView(view).setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                SPF.get().getSecurityMonitor().getPersonRegistry().sendContactRequestTo(mPersonIdentifier, view.getPassphrase(), displayName, picture, view.getSelectedCircles());
            }
        }).setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        }).show();
    }

    public void clickedOptionItemSave() {
        if (mContainer.isModified()) {
            startLoader(LoadersConfig.SAVE_PROFILE_LOADER_ID);
        } else {
            onSaveNotNecessary();
        }
    }

    /*
        * Click on the profile picture in edit mode starts an activity to change
        * the profile picture
        */
    @Override
    public void onClick(View v) {
        Crop.pickImage(this.getActivity());
    }

    /*
     * ItemSelectListener for Persona spinner in actionbar
     */
    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
        SPFPersona persona = (SPFPersona) parent.getItemAtPosition(position);
        if (mCurrentPersona.equals(persona)) {
            return;
        }

        mCurrentPersona = persona;
        startLoader(LoadersConfig.LOAD_PROFILE_LOADER_ID);
    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
        // Do nothing
    }

    private void startLoader(int id) {
        getLoaderManager().destroyLoader(id);
        getLoaderManager().initLoader(id, null, this).forceLoad();
    }

    private void onProfileDataSaved() {
        Toast.makeText(getActivity(), "Profile data saved", Toast.LENGTH_SHORT).show();
    }

    private void onProfileDataNotSaved() {
        Toast.makeText(getActivity(), "Profile data NOT saved", Toast.LENGTH_SHORT).show();
    }

    private void onSaveNotNecessary() {
        Toast.makeText(getActivity(), "No field modified", Toast.LENGTH_SHORT).show();
    }

    public boolean isContainerModified() {
        return mContainer.isModified();
    }

    public boolean isContainerModifiedAtLeastOnce() {
        return mModifiedAtLeastOnce;
    }
}


/**
 * The MIT License
 *
 * Copyright (C) 2015 Asterios Raptis
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package de.alpharogroup.xml.json;

import java.io.IOException;
import java.util.Collection;
import java.util.List;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.experimental.UtilityClass;

/**
 * The class {@link JsonToObjectExtensions} converts json strings to java object and java
 * collections.
 */
@UtilityClass
public final class JsonToObjectExtensions
{

	/**
	 * Transforms the given json string into a java object.
	 *
	 * @param <T>
	 *            the generic type of the return type
	 * @param jsonString
	 *            the json string
	 * @param clazz
	 *            the clazz of the generic type
	 * @return the object
	 * @throws JsonParseException
	 *             If an error occurs when parsing the string into Object
	 * @throws JsonMappingException
	 *             the If an error occurs when mapping the string into Object
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public static <T> T toObject(final String jsonString, final Class<T> clazz)
		throws JsonParseException, JsonMappingException, IOException
	{
		return toObject(jsonString, clazz, false);
	}

	/**
	 * Transforms the given json string into a java object.
	 *
	 * @param <T>
	 *            the generic type of the return type
	 * @param jsonString
	 *            the json string
	 * @param clazz
	 *            the clazz of the generic type
	 * @param newMapper
	 *            flag that indicates if a new ObjectMapper should be created. if true a new
	 *            ObjectMapper will be created otherwise the ObjectMapper from this class will be
	 *            returned.
	 * @return the object
	 * @throws JsonParseException
	 *             If an error occurs when parsing the string into Object
	 * @throws JsonMappingException
	 *             the If an error occurs when mapping the string into Object
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public static <T> T toObject(final String jsonString, final Class<T> clazz,
		final boolean newMapper) throws JsonParseException, JsonMappingException, IOException
	{
		final ObjectMapper mapper = ObjectMapperFactory.getObjectMapper(newMapper);
		final T object = mapper.readValue(jsonString, clazz);
		return object;
	}

	/**
	 * Transforms the given json string into a java object.
	 *
	 * @param <T>
	 *            the generic type of the return type
	 * @param jsonString
	 *            the json string
	 * @param clazz
	 *            the clazz of the generic type
	 * @param modules
	 *            The modules to register for the mapper
	 * @return the object
	 * @throws JsonParseException
	 *             If an error occurs when parsing the string into Object
	 * @throws JsonMappingException
	 *             the If an error occurs when mapping the string into Object
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public static <T> T toObject(final String jsonString, final Class<T> clazz,
		final Module... modules) throws JsonParseException, JsonMappingException, IOException
	{
		ObjectMapper mapper = ObjectMapperFactory.getObjectMapper(true);
		mapper = mapper.registerModules(modules);
		final T object = mapper.readValue(jsonString, clazz);
		return object;
	}

	/**
	 * Transforms the given json string into a java object {@link List}
	 *
	 * @param <T>
	 *            the generic type of the return type
	 * @param jsonString
	 *            the json string
	 * @param elementClass
	 *            the element class of the generic type
	 * @return the list with the java objects.
	 * @throws JsonParseException
	 *             If an error occurs when parsing the string into Object
	 * @throws JsonMappingException
	 *             the If an error occurs when mapping the string into Object
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public static <T> List<T> toObjectList(final String jsonString, final Class<T> elementClass)
		throws JsonParseException, JsonMappingException, IOException
	{
		return (List<T>)toObjectCollection(jsonString, List.class, elementClass);
	}

	/**
	 * Transforms the given json string into a java object {@link Collection}
	 *
	 * @param <T>
	 *            the generic type of the return type
	 * @param jsonString
	 *            the json string
	 * @param collectionClass
	 *            the collection class
	 * @param elementClass
	 *            the element class
	 * @return the list with the java objects.
	 * @throws JsonParseException
	 *             If an error occurs when parsing the string into Object
	 * @throws JsonMappingException
	 *             the If an error occurs when mapping the string into Object
	 * @throws IOException
	 *             Signals that an I/O exception has occurred
	 */
	public static <T> Collection<T> toObjectCollection(final String jsonString,
		@SuppressWarnings("rawtypes") Class<? extends Collection> collectionClass,
		Class<T> elementClass) throws JsonParseException, JsonMappingException, IOException
	{
		final ObjectMapper mapper = ObjectMapperFactory.getObjectMapper(true);
		return mapper.readValue(jsonString,
			mapper.getTypeFactory().constructCollectionType(collectionClass, elementClass));
	}

}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 * Copyright 2015 Stefano Cappa
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.app;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.provider.Settings;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import java.util.List;

import it.polimi.spf.app.fragments.appmanager.PermissionArrayAdapter;
import it.polimi.spf.framework.SPF;
import it.polimi.spf.framework.profile.SPFPersona;
import it.polimi.spf.framework.security.AppRegistrationHandler;
import it.polimi.spf.framework.security.PermissionHelper;
import it.polimi.spf.shared.model.AppDescriptor;
import it.polimi.spf.shared.model.Permission;

/**
 * {@link AppRegistrationHandler} implementation that displays a popup to let
 * the user choose whether or not to accept the registration request.
 *
 * @author darioarchetti
 */
public class PopupAppRegistrationHandler implements AppRegistrationHandler {

    /*
     * (non-Javadoc)
     *
     * @see it.polimi.spf.framework.security.AppRegistrationHandler#
     * handleRegistrationRequest(android.content.Context,
     * it.polimi.spf.shared.model.AppDescriptor,
     * it.polimi.spf.framework.security.AppRegistrationHandler.Callback)
     */
    @Override
    public void handleRegistrationRequest(Context context, AppDescriptor descriptor, final Callback callback) {
        final DialogView dialogView = new DialogView(context, descriptor);

        Dialog dialog = new AlertDialog.Builder(context).setPositiveButton(R.string.app_permission_dialog_allow, new AlertDialog.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                callback.onRequestAccepted(dialogView.getSelectedPersona());
            }
        }).setNegativeButton(R.string.app_permission_dialog_deny, new AlertDialog.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                callback.onRequestRefused();
            }
        }).setView(dialogView.getView()).create();

        /*
         * Workaround to fix the crash:
		 * android.view.WindowManager$BadTokenException: Unable to add window
		 * android.view.ViewRootImpl$W@3d67307 -- permission denied for this window type
		 * that appears only on Android 6.0 Marshmallow or greater.
		 * Start a dialog fragment to explain the procedure to the user.
		 * When the user accepts, onClickOnUnderstoodButton() will be called.
		 */
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //if android MarshMallow or greater
            if (Settings.canDrawOverlays(context)) {
                dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
                dialog.show();
            } else {
                //to prevent a crash
                Toast.makeText(context, "You must activate SPF!", Toast.LENGTH_SHORT).show();
            }
        } else {
            //other older Android's versions
            dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
            dialog.show();
        }
    }

    private static class DialogView implements OnItemSelectedListener {
        private AppDescriptor mDescriptor;
        private Context mContext;
        private SPFPersona mCurrentPersona;

        public DialogView(Context context, AppDescriptor mDescriptor) {
            this.mDescriptor = mDescriptor;
            this.mContext = context;
        }

        // See http://www.doubleencore.com/2013/05/layout-inflation-as-intended/
        public View getView() {
            View v = LayoutInflater.from(mContext).inflate(R.layout.app_auth_dialog, null);
            ((TextView) v.findViewById(R.id.app_name_view)).setText(mDescriptor.getAppName());
            ((TextView) v.findViewById(R.id.app_identifier_view)).setText(mDescriptor.getAppIdentifier());

            try {
                Drawable icon = mContext.getPackageManager().getApplicationIcon(mDescriptor.getAppIdentifier());
                ((ImageView) v.findViewById(R.id.app_icon_view)).setBackground(icon);
            } catch (NameNotFoundException e) {
                // TODO identifier is not valid
            }

            // Show permission list
            Permission[] permissionList = PermissionHelper.getPermissions(mDescriptor.getPermissionCode());
            PermissionArrayAdapter adapter = new PermissionArrayAdapter(mContext, permissionList);
            ((ListView) v.findViewById(R.id.app_permission_list)).setAdapter(adapter);

            // Show persona list
            List<SPFPersona> personas = SPF.get().getProfileManager().getAvailablePersonas();
            ArrayAdapter<SPFPersona> personaAdapter = new ArrayAdapter<>(mContext, android.R.layout.simple_list_item_1, personas);
            Spinner personaSpinner = (Spinner) v.findViewById(R.id.app_persona);
            personaSpinner.setOnItemSelectedListener(this);
            personaSpinner.setAdapter(personaAdapter);

            return v;
        }

        public SPFPersona getSelectedPersona() {
            return mCurrentPersona;
        }

        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            mCurrentPersona = (SPFPersona) parent.getItemAtPosition(position);
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
            mCurrentPersona = null;
        }

    }
}


/**
 * The MIT License
 *
 * Copyright (C) 2015 Asterios Raptis
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package de.alpharogroup.xml.json;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;

import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.experimental.UtilityClass;

/**
 * The class {@link ObjectToJsonExtensions} converts java objects to json string objects.
 */
@UtilityClass
public class ObjectToJsonExtensions
{

	/**
	 * Creates from the given {@link List} to a json string.
	 *
	 * @param <T>
	 *            the generic type
	 * @param list
	 *            the list
	 * @return the json string.
	 * @throws JsonGenerationException
	 *             If an error occurs by writing json string
	 * @throws JsonMappingException
	 *             the If an error occurs when mapping the string into Object
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public static <T> String toJson(final List<T> list)
		throws JsonGenerationException, JsonMappingException, IOException
	{
		final ByteArrayOutputStream out = new ByteArrayOutputStream();
		final ObjectMapper mapper = new ObjectMapper();

		mapper.writeValue(out, list);

		final byte[] bytes = out.toByteArray();
		out.close();
		return new String(bytes);
	}

	/**
	 * Creates from the given Object a json string.
	 *
	 * @param <T>
	 *            the generic type of the given argument
	 * @param object
	 *            the object.
	 * @return the json string.
	 * @throws JsonProcessingException
	 *             If an error occurs when converting object to String
	 */
	public static <T> String toJson(final T object) throws JsonProcessingException
	{
		return toJson(object, false);
	}

	/**
	 * To json.
	 *
	 * @param <T>
	 *            the generic type
	 * @param object
	 *            the object
	 * @param newMapper
	 *            flag that indicates if a new ObjectMapper should be created. if true a new
	 *            ObjectMapper will be created otherwise the ObjectMapper from this class will be
	 *            returned.
	 * @return the string
	 * @throws JsonProcessingException
	 *             If an error occurs when converting object to String
	 */
	public static <T> String toJson(final T object, final boolean newMapper)
		throws JsonProcessingException
	{
		final ObjectMapper mapper = ObjectMapperFactory.getObjectMapper(newMapper);
		final String json = mapper.writeValueAsString(object);
		return json;
	}

}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.lib.services;

import it.polimi.spf.lib.SPF;
import it.polimi.spf.lib.SPFPerson;
import it.polimi.spf.shared.model.InvocationRequest;
import it.polimi.spf.shared.model.InvocationResponse;
import it.polimi.spf.shared.model.SPFActivity;
import it.polimi.spf.shared.model.SPFServiceDescriptor;

import android.content.Context;

/**
 * A {@link SPFComponent} that allows the invocation of the services of a remote
 * person.
 *
 * @author darioarchetti
 *
 */
public class SPFServiceExecutor {

	private SPF mExecutionInterface;

	// TODO @hide
	public SPFServiceExecutor(Context context, SPF executionInterface) {

		this.mExecutionInterface = executionInterface;
	}

	/**
	 * Creates an invocation stub to send service invocation requests to a
	 * target {@link SPFPerson} performing method calls. The stub is created
	 * from a provided service interface, which must be annotated with
	 * {@link ServiceInterface} describing the service. The method returns an
	 * object implementing the aforementioned interface that can be used to
	 * perform method invocation.
	 *
	 * @param target
	 *            - the person who the service invocation requests will be
	 *            dispatched to.
	 * @param serviceInterface
	 *            - the interface of the service.
	 * @param classLoader
	 *            - the ClassLoader to load classes.
	 * @return an invocation stub to perform method calls.
	 */
	public <E> E createStub(final SPFPerson target, Class<E> serviceInterface, ClassLoader classLoader) {
		return InvocationStub.from(serviceInterface, classLoader, new InvocationStub.Target() {

			@Override
			public void prepareArguments(Object[] arguments) throws ServiceInvocationException {
				mExecutionInterface.injectActivities(target.getIdentifier(), arguments);
			}

			@Override
			public InvocationResponse executeService(InvocationRequest request) throws ServiceInvocationException {
				return mExecutionInterface.executeService(target.getIdentifier(), request);
			}

		});
	}

	/**
	 * Creates an invocation stub to send service invocation requests to a
	 * target {@link SPFPerson} providing the name and the parameter list. The
	 * object is created from a {@link ServiceDescriptor} containing the
	 * required details.
	 *
	 * @param target
	 *            - the person who the service invocation requests will be
	 *            dispatched to.
	 * @param descriptor
	 *            - the {@link ServiceDescriptor} of the service whose methods
	 *            to invoke.
	 * @return a {@link InvocationStub} to perform invocations of remote
	 *         services.
	 */
	public InvocationStub createStub(final SPFPerson target, SPFServiceDescriptor descriptor) {
		return InvocationStub.from(descriptor, new InvocationStub.Target() {

			@Override
			public void prepareArguments(Object[] arguments) throws ServiceInvocationException {
				mExecutionInterface.injectActivities(target.getIdentifier(), arguments);
			}

			@Override
			public InvocationResponse executeService(InvocationRequest request) {
				return mExecutionInterface.executeService(target.getIdentifier(), request);
			}
		});
	}

	/**
	 * Dispatches an activity to the SPF instances matching the given
	 * identifier. The framework will perform information injection into the
	 * activity: such information will be available to the caller once the
	 * method ends.
	 *
	 * @param target
	 *            - the target of the activity.
	 * @param activity
	 *            - the activity to dispatch.
	 * @return - true if the activity has been correctly consumed by the
	 *         recipient.
	 */
	public boolean sendActivity(String target, SPFActivity activity) {
		InvocationResponse resp = mExecutionInterface.sendActivity(target, activity);
		if(resp.isResult()){
			return GsonHelper.gson.fromJson(resp.getPayload(), Boolean.class);
		} else {
			return false;
		}
	}

		protected void recycle() {
		// TODO Auto-generated method stub
	}
}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 * Copyright 2015 Stefano Cappa
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.app.view;

import it.polimi.spf.app.R;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;

/**
 * @author Jacopo
 *
 */
public class TagsViewer extends FlowLayout {
	boolean editable;
	private OnRemovedListener removedTaglistener;

	/**
	 * Used by xml inflater
	 *
	 * @param context
	 * @param attrs
	 */
	public TagsViewer(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context);
	}

	/**
	 * Used manually by code
	 *
	 * @param context
	 * @param editable
	 */
	public TagsViewer(Context context, boolean editable) {
		super(context);
		init(context, editable);
	}

	private void init(Context context, boolean editable) {
		inflate(context, R.layout.view_tags_viewer, null);
		this.editable = editable;
	}

	private void init(Context context) {
		init(context, true);
	}

	private void notifyRemovedTagListener(final OnRemovedListener rl, final String tag) {
		post(new Runnable() {
			public void run() {
				rl.onRemovedTag(tag);
			}
		});

	}

	private OnClickListener bubbleClickListener = new OnClickListener() {

		@Override
		public void onClick(View v) {
			if (v instanceof TagBubble) {
				String tag = ((TagBubble) v).getText().toString();
				tags.remove(tag);
				removeView(v);
				final OnRemovedListener rl = removedTaglistener;
				if (rl != null) {
					notifyRemovedTagListener(rl, tag);
				}
			}
		}
	};

	/**
	 * Adds and shows a new tag.
	 *
	 * @param tag
	 */
	public void addTag(String tag) {
		TagBubble tb = new TagBubble(getContext());
		tb.setText(tag);
		tb.setEditable(editable);
		tb.setOnRemoveTagListener(bubbleClickListener);
		tags.add(tag.toString());
		addView(tb);
	}

	public void setEditable(boolean editable) {
		if(this.editable == editable){
			return;
		}

		this.editable = editable;
		removeAllViews();
		for(String tag : tags){
			addTag(tag);
		}
	}

	private List<String> tags = new LinkedList<>();

	/**
	 * returns the list of displayed tags.
	 *
	 * @return
	 */
	public List<String> getTags() {
		return new ArrayList<>(tags);
	}

	/**
	 * Set the list of tags to be displayed.
	 *
	 * @param tags
	 */
	public void setTags(List<String> tags) {
		this.tags.clear();
		removeAllViews();
		for (String tag : tags) {
			addTag(tag);
		}
	}

	/**
	 *
	 * {@link OnRemovedListener} contains the callback signature to be notified
	 * about tag removal events.
	 *
	 */
	public interface OnRemovedListener {
		/**
		 *
		 * @param tag
		 *            - the removed tag
		 */
		void onRemovedTag(String tag);
	}

	/**
	 * Set the {@link OnRemovedListener}.
	 *
	 * @param listener
	 */
	public void setOnRemovedTagListener(OnRemovedListener listener) {
		this.removedTaglistener = listener;
	}

}


/**
 * The MIT License
 *
 * Copyright (C) 2015 Asterios Raptis
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package de.alpharogroup.xml.json;

import java.io.IOException;

import org.json.JSONException;
import org.json.JSONObject;
import org.json.XML;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;

import de.alpharogroup.xml.ObjectToXmlExtensions;
import lombok.experimental.UtilityClass;

/**
 * The class {@link JsonToXmlExtensions} helps to transform a given json string to an xml string.
 */
@UtilityClass
public final class JsonToXmlExtensions
{

	/**
	 * Transform the given json as {@link String} object to an xml as {@link String} object.
	 *
	 * @param jsonString
	 *            the json as {@link String} object
	 * @return the transformed xml as {@link String} object
	 * @throws JSONException
	 *             if there is a syntax error in the source string or a duplicated key.
	 */
	public static String toXml(final String jsonString) throws JSONException
	{
		final JSONObject json = new JSONObject(jsonString);
		final String xmlString = XML.toString(json);
		return xmlString;
	}

	/**
	 * Transform the given json as {@link String} object to an xml as {@link String} object.
	 *
	 * @param <T>
	 *            the generic type
	 * @param jsonString
	 *            the json as {@link String} object
	 * @param clazz
	 *            the clazz of the generic type
	 * @return the transformed xml as {@link String} object
	 * @throws JsonParseException
	 *             If an error occurs when parsing the string into Object
	 * @throws JsonMappingException
	 *             the If an error occurs when mapping the string into Object
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public static <T> String toXml(final String jsonString, final Class<T> clazz)
		throws JsonParseException, JsonMappingException, IOException
	{
		final T object = JsonToObjectExtensions.toObject(jsonString, clazz);
		final String xmlString = ObjectToXmlExtensions.toXmlWithXStream(object);
		return xmlString;
	}

}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 * Copyright 2015 Stefano Cappa
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.framework.notification;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import java.util.List;

import it.polimi.spf.framework.SPFContext;
import it.polimi.spf.shared.model.ProfileField;
import it.polimi.spf.shared.model.SPFTrigger;

/**
 * @author Jacopo Aliprandi
 *         <p/>
 *         {@link SPFNotificationManager} is the component that manages the
 *         SPFNotification services.
 */
public class SPFNotificationManager implements SPFActionPerformer {

    private final Context mContext;
    private SPFTriggerTable mTriggerTable;
    private SPFActionCache mTriggerCache;
    private SPFTriggerEngine mTriggerEngine;

    private boolean isRunning = false;

    // thread and handler resources
    private SPFNotificationHandler mHandler;
    private HandlerThread mHandlerThread;

    // Notification messages
    private NotificationMessageTable mMessageTable;

    /**
     *
     */
    public SPFNotificationManager(Context c) {
        mContext = c;
        mTriggerEngine = new SPFTriggerEngine(this);
        mTriggerTable = new SPFTriggerTable(mContext);
        mMessageTable = new NotificationMessageTable(mContext);
    }

    /**
     * @param actionPerformer
     */
    public SPFNotificationManager(Context c, SPFActionPerformer actionPerformer) {
        mContext = c;
        mTriggerEngine = new SPFTriggerEngine(actionPerformer);
        mTriggerTable = new SPFTriggerTable(mContext);
        mMessageTable = new NotificationMessageTable(mContext);
    }

    /**
     * Finds a trigger by id.
     *
     * @param triggerId
     * @param appPackageName
     * @return
     */
    public SPFTrigger getTrigger(long triggerId, String appPackageName) {

        return mTriggerTable.getTrigger(triggerId, appPackageName);
    }

    /**
     * Saves the trigger on the database.
     *
     * @param t
     */
    public long saveTrigger(SPFTrigger trigger, String appPackageName) {
        trigger = mTriggerTable.saveTrigger(trigger, appPackageName);
        if (trigger != null) {
            if (mHandler != null)
                mHandler.postAddTrigger(trigger);
            return trigger.getId();
        } else {
            return -1;
        }
    }

    /**
     * Deletes the trigger specified by its id.
     *
     * @param id
     * @return true if the trigger has been deleted
     */
    public boolean deleteTrigger(long id, String appPackageName) {

        boolean success = mTriggerTable.deleteTrigger(id, appPackageName);
        if (success) {
            if (mHandler != null)
                mHandler.postRemoveTrigger(id);
        }
        return success;
    }

    /**
     * Returns a list of triggers saved by the application specified with its
     * package name.
     *
     * @param appIdentifier
     * @return a list of triggers
     */
    public List<SPFTrigger> listTriggers(String appPackageName) {

        return mTriggerTable.getAllTriggers(appPackageName);
    }

    /**
     * Delete all the trigger of the application specified by mean of its
     * package name.
     *
     * @return
     */
    public boolean deleteAllTrigger(String appPackageName) {

        return mTriggerTable.deleteAllTriggerOf(appPackageName);

    }

    private class SPFNotificationHandler extends Handler {

        public static final int ACTION_SETUP = 1;
        public static final int ACTION_STOP = 2;
        public static final int ACTION_ANALYZE = 3;
        public static final int ACTION_ADD_TRIGGER = 4;
        public static final int ACTION_REMOVE_TRIGGER = 5;

        public SPFNotificationHandler(Looper looper) {
            super(looper);

        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case ACTION_ANALYZE:
                    mTriggerEngine.lookForMatchingTrigger((SPFAdvProfile) msg.obj);
                    break;
                case ACTION_ADD_TRIGGER:
                    mTriggerEngine.put((SPFTrigger) msg.obj);
                    break;
                case ACTION_REMOVE_TRIGGER:
                    mTriggerEngine.remove((Long) msg.obj);
                    break;
                case ACTION_SETUP:
                    SPFNotificationManager.this.setup();
                    break;
                case ACTION_STOP:
                    this.getLooper().quit();// change to quitSafely() if api level >
                    // 18
                default:
                    super.handleMessage(msg);
            }
        }

        void postStop() {
            sendEmptyMessage(ACTION_STOP);
        }

        void postSetup(SPFNotificationManager setup) {
            Message msg = obtainMessage(ACTION_SETUP, setup);
            sendMessage(msg);
        }

        void postAnalyzeAdvertisement(SPFAdvProfile advProfile) {
            Message msg = obtainMessage(ACTION_ANALYZE);
            msg.obj = advProfile;
            sendMessage(msg);
        }

        void postAddTrigger(SPFTrigger trigger) {
            Message msg = mHandler.obtainMessage(SPFNotificationHandler.ACTION_ADD_TRIGGER, trigger);
            sendMessage(msg);
        }

        void postRemoveTrigger(long id) {
            Message msg = obtainMessage(ACTION_REMOVE_TRIGGER, Long.valueOf(id));
            sendMessage(msg);
        }
    }

    /**
     * Release all the resources used by SPFNotification service.
     */
    public void stop() {
        isRunning = false;
        mHandler.postStop();
    }

    /**
     * Return true if the logic that handles triggers processing is running.
     * This value becomes true after a call to
     * {@link SPFNotificationManager#start()} and returns false after
     * {@link SPFNotificationManager#stop()} has been called.
     *
     * @return
     */
    public boolean isRunning() {
        return isRunning;
    }

    /**
     * Initialize and start all the threads and resources used by
     * SPFNotification service. Call this method to initialize the logic that
     * handles triggers processing.
     */
    public void start() {
        this.mHandlerThread = new HandlerThread("notification-handler-thread");
        this.mHandlerThread.start();
        this.mHandler = new SPFNotificationHandler(mHandlerThread.getLooper());
        mHandler.postSetup(this);
        isRunning = true;
    }

    // to be called after on start: initializes the triggers' logic
    private void setup() {
        List<SPFTrigger> triggers = mTriggerTable.getAllTriggers();
        mTriggerCache = new SPFActionCache(mContext);
        mTriggerEngine.refreshTriggers(triggers);
        mTriggerCache.refresh(triggers);
    }

    /**
     * Look for a trigger match on the given profile.
     *
     * @param advProfile
     */
    public void onAdvertisementReceived(SPFAdvProfile advProfile) {
        if (mHandler != null) {
            mHandler.postAnalyzeAdvertisement(advProfile);
        }
    }

    @Override
    public void perform(SPFAdvProfile target, SPFTrigger trigger) {
        final String targetId = target.getField(ProfileField.IDENTIFIER.getIdentifier());
        final long triggerId = trigger.getId();
        if (!mTriggerCache.triggerIsSleepingOnTarget(targetId, triggerId)) {
            new SPFActionPerformerDelegate().perform(target, trigger);
            mTriggerCache.add(targetId, trigger);
        }

    }

    // Notification messages
    public List<NotificationMessage> getAvailableNotifications() {
        Log.d("TAG", "notification available: " + mMessageTable.getAvailableNotifications().size());
        return mMessageTable.getAvailableNotifications();
    }

    public boolean deleteNotification(long id) {
        return mMessageTable.deleteNotification(id);
    }

    public boolean deleteAllNotifications() {
        return mMessageTable.deleteAllNotifications();
    }

    public void onNotificationMessageReceived(NotificationMessage message) {
        mMessageTable.saveNotification(message);
        Bundle b = new Bundle();
        b.putParcelable(SPFContext.EXTRA_NOTIFICATION_MESSAGE, message);
        SPFContext.get().broadcastEvent(SPFContext.EVENT_NOTIFICATION_MESSAGE_RECEIVED, b);
    }

    public int getAvailableNotificationCount() {
        return mMessageTable.getAvailableNotificationCount();
    }
}