source: trunk/autoquest-core-usability/src/main/java/de/ugoe/cs/autoquest/usability/MissingFeedbackRule.java

Last change on this file was 2042, checked in by pharms, 9 years ago
  • finalized smell detection for phd thesis
File size: 12.2 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15package de.ugoe.cs.autoquest.usability;
16
17import java.util.Collection;
18import java.util.HashMap;
19import java.util.LinkedList;
20import java.util.List;
21import java.util.Map;
22
23import de.ugoe.cs.autoquest.eventcore.Event;
24import de.ugoe.cs.autoquest.eventcore.gui.MouseClick;
25import de.ugoe.cs.autoquest.eventcore.guimodel.IButton;
26import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask;
27import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance;
28import de.ugoe.cs.autoquest.tasktrees.treeifc.IIteration;
29import de.ugoe.cs.autoquest.tasktrees.treeifc.IIterationInstance;
30import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
31import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;
32import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel;
33
34/**
35 * TODO comment
36 *
37 * @version $Revision: $ $Date: 16.07.2012$
38 * @author 2012, last modified by $Author: pharms$
39 */
40public class MissingFeedbackRule implements UsabilityEvaluationRule {
41
42    /*
43     * (non-Javadoc)
44     *
45     * @see de.ugoe.cs.usability.UsabilityEvaluationRule#evaluate(TaskTree)
46     */
47    @Override
48    public UsabilityEvaluationResult evaluate(ITaskModel taskModel) {
49        UsabilityEvaluationResult results = new UsabilityEvaluationResult(taskModel);
50
51        Map<ITask, Integer> smellingTasks = getTasksShowingMissingFeedback(taskModel.getTasks());
52        analyzeTasksShowingMissingFeedback(smellingTasks, results, taskModel);
53
54        return results;
55    }
56
57    /**
58     *
59     */
60    private void analyzeTasksShowingMissingFeedback(Map<ITask, Integer>       smellingTasks,
61                                                    UsabilityEvaluationResult results,
62                                                    ITaskModel                taskModel)
63    {
64
65        for (Map.Entry<ITask, Integer> entry : smellingTasks.entrySet()) {
66            // impatience ratio is the sum of the times between two clicks of a user on the
67            // identical (not semantically equal) button. If the user is highly impatient and
68            // clicks the identical button more than two times, the time difference between the
69            // first click and the last click is multiplied with the number of additional clicks
70            // to give such behavior a higher weight. The average impatience ratio is the
71            // cumulative impatience of all task instances divided by the number of task
72            // instances. I.e. if all users show impatience, i.e., all instances have an impatience
73            // ratio, the average impatience of a task is relatively high. Else, it is rather low.
74            // If, e.g., all users clicked the identical button again in between 3 seconds, the
75            // average impatience of the task would be 3000, which should be a usability smell.
76            // If the users click even more often on the identical button, the ratio is even higher.
77            // If only one of 50 users clicked twice, than the ratio is only 60, which should not
78            // be considered as usability smell.
79           
80            UsabilitySmellIntensity intensity =
81                UsabilitySmellIntensity.getIntensity(entry.getValue(), entry.getKey(), taskModel);
82
83            if (intensity != null) {
84                Map<String, Object> parameters = new HashMap<String, Object>();
85
86                int allClickCount = 0;
87                int multipleClickCount = 0;
88                long cummulatedWaitingTime = 0;
89                int numberOfAdditionalClicks = 0;
90               
91                Event exampleEvent = null;
92                for (ITaskInstance instance : entry.getKey().getInstances()) {
93                    List<List<Event>> clicksOnIdenticalButton =
94                        getSubsequentClicksOnIdenticalButton((IIterationInstance) instance);
95                   
96                    if (clicksOnIdenticalButton != null) {
97                        multipleClickCount += clicksOnIdenticalButton.size();
98                       
99                        for (List<Event> subsequence : clicksOnIdenticalButton) {
100                            exampleEvent = subsequence.get(0);
101                           
102                            Event endEvent = subsequence.get(subsequence.size() - 1);
103                            long timeDiff = endEvent.getTimestamp() - exampleEvent.getTimestamp();
104                           
105                            cummulatedWaitingTime += timeDiff;
106                            numberOfAdditionalClicks += subsequence.size() - 1;
107                        }
108                    }
109                   
110                    allClickCount += ((IIterationInstance) instance).size();
111                }
112               
113                parameters.put("multipleClickCount", multipleClickCount);
114                parameters.put("allClickCount", allClickCount);
115                parameters.put("averageWaitingTime",
116                               (cummulatedWaitingTime / (numberOfAdditionalClicks * 1000)));
117               
118                parameters.put("button", exampleEvent.getTarget());
119               
120                parameters.put("task", entry.getKey());
121               
122                results.addSmell(entry.getKey(), intensity,
123                                 UsabilitySmellDescription.MISSING_FEEDBACK, parameters);
124            }
125        }
126    }
127
128    /**
129     *
130     */
131    private Map<ITask, Integer> getTasksShowingMissingFeedback(Collection<ITask> tasks) {
132        Map<ITask, Integer> impatienceRatios = new HashMap<ITask, Integer>();
133       
134        for (ITask task : tasks) {
135            if (isSubsequentClickOnButton(task))  {
136                int ratio = getAverageRatioOfUserImpatience((IIteration) task);
137               
138                if (ratio > 0) {
139                    impatienceRatios.put(task, ratio);
140                }
141            }
142        }
143       
144        return impatienceRatios;
145    }
146
147    /**
148     *
149     */
150    private boolean isSubsequentClickOnButton(ITask task) {
151        if (!(task instanceof IIteration)) {
152            return false;
153        }
154       
155        if (!(((IIteration) task).getMarkedTask() instanceof IEventTask)) {
156            return false;
157        }
158       
159        IEventTask childTask = (IEventTask) ((IIteration) task).getMarkedTask();
160       
161        if ((childTask.getInstances() != null) && (childTask.getInstances().size() > 0)) {
162            Event event =
163                ((IEventTaskInstance) childTask.getInstances().iterator().next()).getEvent();
164           
165            return
166                ((event.getType() instanceof MouseClick) && (event.getTarget() instanceof IButton));
167        }
168        else {
169            return false;
170        }
171    }
172
173    /**
174     *
175     */
176    private int getAverageRatioOfUserImpatience(IIteration task) {
177        if (task.getInstances().size() > 0) {
178            int cummulativeImpatienceRatio = 0;
179            for (ITaskInstance instance : task.getInstances()) {
180                cummulativeImpatienceRatio += getImpatienceRatio((IIterationInstance) instance);
181            }
182
183            return cummulativeImpatienceRatio / task.getInstances().size();
184        }
185        else {
186            return 0;
187        }
188    }
189
190    /**
191     *
192     */
193    private long getImpatienceRatio(IIterationInstance instance) {
194        List<List<Event>> clicksOnIdenticalButton = getSubsequentClicksOnIdenticalButton(instance);
195       
196        if (clicksOnIdenticalButton != null) {
197            if (clicksOnIdenticalButton.size() > 1) {
198                //throw new IllegalStateException("not described in dissertation");
199            }
200           
201            long cummulativeImpatience = 0;
202           
203            for (List<Event> subsequence : clicksOnIdenticalButton) {
204                int startIndex = 0;
205                int endIndex = 1;
206               
207                while (endIndex < subsequence.size()) {
208                    Event startEvent = subsequence.get(startIndex);
209
210                    boolean includeNext = false;
211                   
212                    if ((endIndex + 1) < subsequence.size()) {
213                        Event nextEvent = subsequence.get(endIndex + 1);
214                        long extendedTimeDiff =
215                            nextEvent.getTimestamp() - startEvent.getTimestamp();
216                        includeNext = extendedTimeDiff < 15000;
217                    }
218                   
219                    if (!includeNext && ((endIndex - startIndex) >= 1)) {
220                        Event endEvent = subsequence.get(endIndex);
221                        long timeDiff = endEvent.getTimestamp() - startEvent.getTimestamp();
222                       
223                        if ((((endIndex - startIndex) > 1) || (timeDiff > 1000)) &&
224                            (timeDiff < 15000))
225                        {
226                            // the user clicked on the same link several times. In case of only two
227                            // clicks, this was not occasionally, as the time differences between
228                            // the clicks is above one second. And it is also due to impatience, as
229                            // it is below 15 seconds (everything above 15 seconds is considered a
230                            // new action e.g. clicking again on a download link to download a
231                            // file a second time
232                            cummulativeImpatience += timeDiff * (endIndex - startIndex);
233                        }
234                       
235                        startIndex = endIndex;
236                    }
237                    endIndex++;
238                }
239            }
240           
241            return cummulativeImpatience;
242        }
243
244        return 0;
245    }
246
247
248    /**
249     *
250     */
251    private List<List<Event>> getSubsequentClicksOnIdenticalButton(IIterationInstance instance) {
252        if (instance.size() >= 2) {
253            List<List<Event>> result = new LinkedList<List<Event>>();
254            List<Event> currentList = new LinkedList<Event>();
255            for (int i = 0; i < instance.size(); i++) {
256                Event event = ((IEventTaskInstance) instance.get(i)).getEvent();
257               
258                if (currentList.size() == 0) {
259                    // initially fill the current list with first event
260                    currentList.add(event);
261                }
262                else if (currentList.get(currentList.size() - 1).getTarget() == event.getTarget()) {
263                    // check if the targets are really identical. A check for equal targets would
264                    // also reveal a re-click on a semantically equal target in a distinct view.
265                   
266                    // a further event with an identical target has been detected. Add it to the
267                    // current list, as well.
268                    currentList.add(event);
269                }
270                else {
271                    // the current target is not identical to the previous one
272                    if (currentList.size() > 1) {
273                        // there were several preceding events with identical targets. Memorize
274                        // this.
275                        result.add(currentList);
276                        currentList = new LinkedList<Event>();
277                    }
278                    else {
279                        currentList.clear();
280                    }
281                   
282                    // a new list of events with identical targets may start. Add the current
283                    // event as the first one to the list.
284                    currentList.add(event);
285                }
286            }
287
288            if (currentList.size() > 1) {
289                result.add(currentList);
290            }
291           
292            if (result.size() > 0) {
293                return result;
294            }
295        }
296
297        return null;
298    }
299}
Note: See TracBrowser for help on using the repository browser.