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

Last change on this file since 1493 was 1493, checked in by pharms, 10 years ago
  • state of the HCSE 2014 Paper. An appropriate tag will follow.
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            UsabilityDefectSeverity severity = UsabilityDefectSeverity.getSeverity
81                (entry.getValue(), 2000, 1000, 500, 50, entry.getKey(), taskModel);
82
83            if (severity != 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                System.out.println("\n\n#################################");
92               
93                Event exampleEvent = null;
94                for (ITaskInstance instance : entry.getKey().getInstances()) {
95                    List<List<Event>> clicksOnIdenticalButton =
96                        getSubsequentClicksOnIdenticalButton((IIterationInstance) instance);
97                   
98                    if (clicksOnIdenticalButton != null) {
99                        multipleClickCount += clicksOnIdenticalButton.size();
100                       
101                        for (List<Event> subsequence : clicksOnIdenticalButton) {
102                            exampleEvent = subsequence.get(0);
103                            System.out.println(exampleEvent.getTimestamp());
104                           
105                            Event endEvent = subsequence.get(subsequence.size() - 1);
106                            long timeDiff = endEvent.getTimestamp() - exampleEvent.getTimestamp();
107                           
108                            cummulatedWaitingTime += timeDiff;
109                            numberOfAdditionalClicks += subsequence.size() - 1;
110                        }
111                    }
112                   
113                    allClickCount += ((IIterationInstance) instance).size();
114                }
115               
116                parameters.put("multipleClickCount", multipleClickCount);
117                parameters.put("allClickCount", allClickCount);
118                parameters.put("averageWaitingTime",
119                               (cummulatedWaitingTime / (numberOfAdditionalClicks * 1000)));
120               
121                parameters.put("button", exampleEvent.getTarget());
122               
123                parameters.put("task", entry.getKey());
124               
125                results.addDefect
126                    (severity, UsabilityDefectDescription.MISSING_FEEDBACK, parameters);
127            }
128        }
129    }
130
131    /**
132     *
133     */
134    private Map<ITask, Integer> getTasksShowingMissingFeedback(Collection<ITask> tasks) {
135        Map<ITask, Integer> impatienceRatios = new HashMap<ITask, Integer>();
136       
137        for (ITask task : tasks) {
138            if (isSubsequentClickOnButton(task))  {
139                int ratio = getAverageRatioOfUserImpatience((IIteration) task);
140               
141                if (ratio > 0) {
142                    impatienceRatios.put(task, ratio);
143                }
144            }
145        }
146       
147        return impatienceRatios;
148    }
149
150    /**
151     *
152     */
153    private boolean isSubsequentClickOnButton(ITask task) {
154        if (!(task instanceof IIteration)) {
155            return false;
156        }
157       
158        if (!(((IIteration) task).getMarkedTask() instanceof IEventTask)) {
159            return false;
160        }
161       
162        IEventTask childTask = (IEventTask) ((IIteration) task).getMarkedTask();
163       
164        if ((childTask.getInstances() != null) && (childTask.getInstances().size() > 0)) {
165            Event event =
166                ((IEventTaskInstance) childTask.getInstances().iterator().next()).getEvent();
167           
168            return
169                ((event.getType() instanceof MouseClick) && (event.getTarget() instanceof IButton));
170        }
171        else {
172            return false;
173        }
174    }
175
176    /**
177     *
178     */
179    private int getAverageRatioOfUserImpatience(IIteration task) {
180        if (task.getInstances().size() > 0) {
181            int cummulativeImpatienceRatio = 0;
182            for (ITaskInstance instance : task.getInstances()) {
183                cummulativeImpatienceRatio += getImpatienceRatio((IIterationInstance) instance);
184            }
185
186            return cummulativeImpatienceRatio / task.getInstances().size();
187        }
188        else {
189            return 0;
190        }
191    }
192
193    /**
194     *
195     */
196    private long getImpatienceRatio(IIterationInstance instance) {
197        List<List<Event>> clicksOnIdenticalButton = getSubsequentClicksOnIdenticalButton(instance);
198       
199        if (clicksOnIdenticalButton != null) {
200            long cummulativeImpatience = 0;
201           
202            for (List<Event> subsequence : clicksOnIdenticalButton) {
203                int startIndex = 0;
204                int endIndex = 1;
205               
206                while (endIndex < subsequence.size()) {
207                    Event startEvent = subsequence.get(startIndex);
208
209                    boolean includeNext = false;
210                   
211                    if ((endIndex + 1) < subsequence.size()) {
212                        Event nextEvent = subsequence.get(endIndex + 1);
213                        long extendedTimeDiff =
214                            nextEvent.getTimestamp() - startEvent.getTimestamp();
215                        includeNext = extendedTimeDiff < 15000;
216                    }
217                   
218                    if (!includeNext && ((endIndex - startIndex) >= 1)) {
219                        Event endEvent = subsequence.get(endIndex);
220                        long timeDiff = endEvent.getTimestamp() - startEvent.getTimestamp();
221                       
222                        if ((((endIndex - startIndex) > 1) || (timeDiff > 1000)) &&
223                            (timeDiff < 15000))
224                        {
225                            // the user clicked on the same link several times. In case of only two
226                            // clicks, this was not occasionally, as the time differences between
227                            // the clicks is above one second. And it is also due to impatience, as
228                            // it is below 15 seconds (everything above 15 seconds is considered a
229                            // new action e.g. clicking again on a download link to download a
230                            // file a second time
231                            cummulativeImpatience += timeDiff * (endIndex - startIndex);
232                        }
233                       
234                        startIndex = endIndex;
235                    }
236                    endIndex++;
237                }
238            }
239           
240            return cummulativeImpatience;
241        }
242
243        return 0;
244    }
245
246
247    /**
248     *
249     */
250    private List<List<Event>> getSubsequentClicksOnIdenticalButton(IIterationInstance instance) {
251        if (instance.size() >= 2) {
252            List<List<Event>> result = new LinkedList<List<Event>>();
253            List<Event> currentList = new LinkedList<Event>();
254            for (int i = 0; i < instance.size(); i++) {
255                Event event = ((IEventTaskInstance) instance.get(i)).getEvent();
256               
257                if (currentList.size() == 0) {
258                    // initially fill the current list with first event
259                    currentList.add(event);
260                }
261                else if (currentList.get(currentList.size() - 1).getTarget() == event.getTarget()) {
262                    // check if the targets are really identical. A check for equal targets would
263                    // also reveal a re-click on a semantically equal target in a distinct view.
264                   
265                    // a further event with an identical target has been detected. Add it to the
266                    // current list, as well.
267                    currentList.add(event);
268                }
269                else {
270                    // the current target is not identical to the previous one
271                    if (currentList.size() > 1) {
272                        // there were several preceding events with identical targets. Memorize
273                        // this.
274                        result.add(currentList);
275                        currentList = new LinkedList<Event>();
276                    }
277                    else {
278                        currentList.clear();
279                    }
280                   
281                    // a new list of events with identical targets may start. Add the current
282                    // event as the first one to the list.
283                    currentList.add(event);
284                }
285            }
286
287            if (currentList.size() > 1) {
288                result.add(currentList);
289            }
290           
291            if (result.size() > 0) {
292                return result;
293            }
294        }
295
296        return null;
297    }
298}
Note: See TracBrowser for help on using the repository browser.