de.lmu.ifi.medien.probui.system.ProbUIManager.java Source code

Java tutorial

Introduction

Here is the source code for de.lmu.ifi.medien.probui.system.ProbUIManager.java

Source

/*
ProbUI - a probabilistic reinterpretation of bounding boxes
designed to facilitate creating dynamic and adaptive mobile touch GUIs.
Copyright (C) 2017 Daniel Buschek
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
This program 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 General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package de.lmu.ifi.medien.probui.system;

import android.support.v4.view.MotionEventCompat;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;

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

import de.lmu.ifi.medien.probui.R;
import de.lmu.ifi.medien.probui.behaviours.ProbBehaviourLinker;
import de.lmu.ifi.medien.probui.exceptions.WrongObservationDelegationException;
import de.lmu.ifi.medien.probui.gui.ProbInteractor;
import de.lmu.ifi.medien.probui.gui.ProbUIContainer;
import de.lmu.ifi.medien.probui.observations.ProbObservationFactory;
import de.lmu.ifi.medien.probui.observations.ProbObservationTouch;

/**
 * Created by Daniel on 05.07.2015.
 */
public class ProbUIManager {

    /**
     * Instance for the singleton pattern.
     */
    protected static ProbUIManager instance;

    /**
     * The view to be managed by this manager. This can be thought of as the root view
     * of the (part of the) interface that should be managed probabilistically.
     */
    protected View view;

    /**
     * The container layout within the view that is managed by this manager.
     * This is mainly needed to intercept touch events across all normal Android UI elements,
     * for example dragging across multiple buttons.
     */
    ProbUIContainer container;

    protected int screenWidth;
    protected int screenHeight;

    /**
     * The mediator used for probabilistic reasoning over the probInteractors managed by this manager.
     */
    protected ProbUIMediator mediator;

    /**
     * Array to hold last touch observations.
     * Defined here instead of local variable for GC reasons.
     */
    protected List<ProbObservationTouch> currentTouchObservations = new ArrayList<ProbObservationTouch>();
    protected List<ProbObservationTouch> previousTouchObservations = new ArrayList<ProbObservationTouch>();

    /**
     * List of the probInteractors managed by this manager.
     */
    protected List<ProbInteractor> probInteractors;
    private List<View> nonProbInteractors;

    protected boolean setupFinalised = false;
    private MotionEvent lastTouchEvent;

    public ProbUIManager(View view, int containerID) {

        this.view = view;
        container = (ProbUIContainer) view.findViewById(containerID);
        container.registerProbUIManager(this);
        // Call finaliseSetup() after Android has finished layouting the container:
        ViewTreeObserver vto = container.getViewGroup().getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                container.getViewGroup().getViewTreeObserver().removeGlobalOnLayoutListener(this);
                screenWidth = container.getViewGroup().getMeasuredWidth();
                screenHeight = container.getViewGroup().getMeasuredHeight();
                Log.d("ProbUIManager", "onGlobalLayout: " + screenWidth + ", " + screenHeight);
                finaliseSetup();
            }
        });

        // init probInteractors list:
        this.probInteractors = new ArrayList<ProbInteractor>();

        this.nonProbInteractors = new ArrayList<View>();

        // init mediator:
        this.mediator = new ProbUIMediatorImpl();
    }

    /**
     * Adds the given interactor to this manager.
     * The manager can only manage probInteractors added to it in this way.
     *
     * @param interactor
     */
    public void addProbInteractor(ProbInteractor interactor) {

        this.probInteractors.add(interactor);
        this.mediator.addInteractor(interactor);
        interactor.getCore().setMediationRequestListener(this.mediator);
    }

    private void addNonProbInteractor(View interactor) {
        this.nonProbInteractors.add(interactor);
    }

    /**
     * Called once implicitly, when the ProbUIContainer is ready (i.e. has been layouted by Android).
     * Triggers behaviour setup of the probInteractors.
     */
    public void finaliseSetup() {

        for (ProbInteractor interactor : this.probInteractors) {
            interactor.getCore().updateSurfaceSize(this.screenWidth, this.screenHeight);
        }

        // one time switch (once container has actual size, i.e. Android has finally finished layouting...):
        if (this.setupFinalised || this.container.getWidth() == 0)
            return;
        this.setupFinalised = true;

        // call on setup of the probInteractors:
        for (ProbInteractor interactor : this.probInteractors) {
            interactor.onProbSetup();
            interactor.getCore().setReady();
        }

        // add a default touch pattern:
        for (ProbInteractor interactor : this.probInteractors) {
            String[] bbs = interactor.getDefaultBehaviours();
            if (bbs != null && bbs.length > 0)
                for (int i = 0; i < bbs.length; i++) {
                    ProbBehaviourLinker.linkPresetProbBehaviourTouch(this, interactor, bbs[i], i > 0,
                            this.view.getContext().getResources().getDisplayMetrics().density);
                }
        }

    }

    public int getSurfaceWidth() {
        return this.screenWidth;
    }

    public int getSurfaceHeight() {
        return this.screenHeight;
    }

    public List<ProbInteractor> getProbInteractors() {
        return this.probInteractors;
    }

    /**
     * Called by the container ViewGroup when a touch event is received.
     *
     * @param ev
     */
    public void manageTouchEvent(MotionEvent ev) throws WrongObservationDelegationException {

        this.lastTouchEvent = ev;
        this.manageHelper(ev);

    }

    public void manageHelper(MotionEvent ev) throws WrongObservationDelegationException {

        this.currentTouchObservations.clear();

        int action = MotionEventCompat.getActionMasked(ev);
        int index = MotionEventCompat.getActionIndex(ev);
        int pointerID = ev.getPointerId(index);

        int type = -1;
        switch (action) {
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_POINTER_DOWN:
            type = ProbObservationTouch.TYPE_TOUCH_DOWN;
            break;
        case MotionEvent.ACTION_MOVE:
            type = ProbObservationTouch.TYPE_TOUCH_MOVE;
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_POINTER_UP:
            type = ProbObservationTouch.TYPE_TOUCH_UP;
            break;
        default:
            type = -1;
            break;
        }

        long timestamp = ev.getEventTime();

        ProbObservationTouch observation = ProbObservationFactory.createTouchObservation(ev.getX(index),
                ev.getY(index), ev.getX(index) * 1.0 / container.getWidth(),
                ev.getY(index) * 1.0 / container.getHeight(), ev.getOrientation(index),
                ev.getTouchMinor(index) * 1.0 / container.getWidth(),
                ev.getTouchMajor(index) * 1.0 / container.getHeight(), ev.getPressure(index), type, pointerID,
                timestamp);
        this.currentTouchObservations.add(observation);

        // Since move is always associated with the first pointer,
        // we need to manually duplicate it for the second one
        // (TODO: and for further pointers, if we change it to more than 2):
        if (ev.getPointerCount() == 2 && type == ProbObservationTouch.TYPE_TOUCH_MOVE) {
            ProbObservationTouch observation2 = ProbObservationFactory.createTouchObservation(ev.getX(index),
                    ev.getY(index), ev.getX(1) * 1.0 / container.getWidth(),
                    ev.getY(1) * 1.0 / container.getHeight(), ev.getOrientation(1),
                    ev.getToolMinor(1) * 1.0 / container.getWidth(),
                    ev.getToolMajor(1) * 1.0 / container.getHeight(), ev.getPressure(1), type, ev.getPointerId(1),
                    timestamp);
            this.currentTouchObservations.add(observation2);
        }

        //Log.d("MULTITOUCH", "type: " + type + ", index: " + pointerID + ", size: " + ev.getTouchMajor(index) * 1.0 / container.getHeight());

        // Distribute touch observation to the cores of all probInteractors
        // (for reasoning by these interactor cores!, not for visual feedback etc. - that comes below: interactor.onTouchDown etc.)
        boolean passedOn = false;
        for (ProbInteractor interactor : this.probInteractors) {

            for (int i = 0; i < this.currentTouchObservations.size(); i++) {
                ProbObservationTouch obs = this.currentTouchObservations.get(i);
                if (obs == null)
                    continue;
                if (obs.getNominalFeatures()[0] != ProbObservationTouch.TYPE_TOUCH_MOVE
                        || this.currentTouchObservations.size() != this.previousTouchObservations.size()) {
                    interactor.getCore().onTouchObservation(obs);
                    passedOn = true;
                } else { // This code filters out move events that moved very little (potentially improves performance):
                    double[] obsXY = this.currentTouchObservations.get(i).getRealFeatures();
                    double[] obsPrevXY = this.previousTouchObservations.get(i).getRealFeatures();
                    double dx = obsXY[0] - obsPrevXY[0];
                    double dy = obsXY[1] - obsPrevXY[1];
                    double dist = Math.sqrt(dx * dx + dy * dy);
                    if (dist > 0.0125) { // TODO: movement threshold currently hardcoded: 0.0125
                        interactor.getCore().onTouchObservation(obs);
                        passedOn = true;
                    } else {
                    }
                }
            }

        }

        if (passedOn) {
            this.previousTouchObservations.clear();
            this.previousTouchObservations.addAll(this.currentTouchObservations);
        }

        // Forward the touch observation for probInteractors
        // to react (e.g. visual feedback, triggering actions, nothing to do with the mediation):
        for (ProbInteractor interactor : this.probInteractors) {
            for (ProbObservationTouch obs : this.currentTouchObservations) {
                if (obs != null) {
                    switch (obs.getNominalFeatures()[0]) {

                    case ProbObservationTouch.TYPE_TOUCH_DOWN:
                        interactor.onTouchDown(obs);
                        break;

                    case ProbObservationTouch.TYPE_TOUCH_MOVE:
                        interactor.onTouchMove(obs);
                        break;

                    case ProbObservationTouch.TYPE_TOUCH_UP:
                        interactor.onTouchUp(obs, ev.getPointerCount() - 1);
                        break;
                    default:
                        break;
                    }
                }
            }
        }

        // If no element is determined yet (i.e. no decision yet), update the reasoning process.
        if (!isOneDetermined() && passedOn) {
            this.mediator.mediate(false);
        }

        // Post mediation: Forward the touch observation again
        // to the post-mediation versions of the onTouch... methods
        for (ProbInteractor interactor : this.probInteractors) {
            for (ProbObservationTouch obs : this.currentTouchObservations) {
                if (obs != null) {
                    switch (obs.getNominalFeatures()[0]) {

                    case ProbObservationTouch.TYPE_TOUCH_DOWN:
                        interactor.onTouchDownPost(obs);
                        break;

                    case ProbObservationTouch.TYPE_TOUCH_MOVE:
                        interactor.onTouchMovePost(obs);
                        break;

                    case ProbObservationTouch.TYPE_TOUCH_UP:
                        interactor.onTouchUpPost(obs, ev.getPointerCount() - 1);
                        break;
                    default:
                        break;
                    }
                }
            }
        }

        // Pass on to other GUI elements:
        if (!isOneDetermined()) {
            for (View view : this.nonProbInteractors) {
                if (view.isFocusable() && view.isEnabled())
                    view.onTouchEvent(ev);
            }
        }
    }

    /**
     * Checks whether there is a determined interactor among those managed by this manager.
     *
     * @return
     */
    public boolean isOneDetermined() {
        for (ProbInteractor interactor : this.probInteractors) {
            if (interactor.getCore().isDetermined()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Automatically adds all ProbInteractor views within the probUIContainer ViewGroup to this manager.
     */
    public void autoAssignInteractors() {

        List<ProbInteractor> foundProbInteractors = Tools.getAllProbInteractors(this.container.getViewGroup());

        for (ProbInteractor interactor : foundProbInteractors) {
            this.addProbInteractor(interactor);
        }

        List<View> foundNonProbInteractors = Tools.getAllNonProbViews(this.container.getViewGroup());

        for (View view : foundNonProbInteractors) {
            this.addNonProbInteractor(view);
        }

        Log.d("ProbUIManager", "Auto-assign added " + foundProbInteractors.size() + " probInteractors.");
    }

}