1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.dialer.performancereport;
18 
19 import android.os.SystemClock;
20 import android.support.annotation.Nullable;
21 import android.support.v7.widget.RecyclerView;
22 import android.widget.AbsListView;
23 import com.android.dialer.common.LogUtil;
24 import com.android.dialer.logging.UiAction;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.concurrent.TimeUnit;
28 
29 /** Tracks UI performance for a call. */
30 public final class PerformanceReport {
31 
32   private static final long INVALID_TIME = -1;
33   private static final long ACTIVE_DURATION = TimeUnit.MINUTES.toMillis(5);
34 
35   private static final List<UiAction.Type> actions = new ArrayList<>();
36   private static final List<Long> actionTimestamps = new ArrayList<>();
37 
38   private static final RecyclerView.OnScrollListener recordOnScrollListener =
39       new RecyclerView.OnScrollListener() {
40         @Override
41         public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
42           if (newState == RecyclerView.SCROLL_STATE_SETTLING) {
43             PerformanceReport.recordClick(UiAction.Type.SCROLL);
44           }
45           super.onScrollStateChanged(recyclerView, newState);
46         }
47       };
48 
49   private static boolean recording = false;
50   private static long appLaunchTimeMillis = INVALID_TIME;
51   private static long firstClickTimeMillis = INVALID_TIME;
52   private static long lastActionTimeMillis = INVALID_TIME;
53 
54   @Nullable private static UiAction.Type ignoreActionOnce = null;
55 
56   private static int startingTabIndex = -1; // UNKNOWN
57 
PerformanceReport()58   private PerformanceReport() {}
59 
startRecording()60   public static void startRecording() {
61     LogUtil.enterBlock("PerformanceReport.startRecording");
62 
63     appLaunchTimeMillis = SystemClock.elapsedRealtime();
64     lastActionTimeMillis = appLaunchTimeMillis;
65     if (!actions.isEmpty()) {
66       actions.clear();
67       actionTimestamps.clear();
68     }
69     recording = true;
70   }
71 
stopRecording()72   public static void stopRecording() {
73     LogUtil.enterBlock("PerformanceReport.stopRecording");
74     recording = false;
75   }
76 
recordClick(UiAction.Type action)77   public static void recordClick(UiAction.Type action) {
78     if (!recording) {
79       return;
80     }
81 
82     if (action == ignoreActionOnce) {
83       LogUtil.i("PerformanceReport.recordClick", "%s is ignored", action.toString());
84       ignoreActionOnce = null;
85       return;
86     }
87     ignoreActionOnce = null;
88 
89     LogUtil.v("PerformanceReport.recordClick", action.toString());
90 
91     // Timeout
92     long currentTime = SystemClock.elapsedRealtime();
93     if (currentTime - lastActionTimeMillis > ACTIVE_DURATION) {
94       startRecording();
95       recordClick(action);
96       return;
97     }
98 
99     lastActionTimeMillis = currentTime;
100     if (actions.isEmpty()) {
101       firstClickTimeMillis = currentTime;
102     }
103     actions.add(action);
104     actionTimestamps.add(currentTime - appLaunchTimeMillis);
105   }
106 
recordScrollStateChange(int scrollState)107   public static void recordScrollStateChange(int scrollState) {
108     if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
109       recordClick(UiAction.Type.SCROLL);
110     }
111   }
112 
logOnScrollStateChange(RecyclerView recyclerView)113   public static void logOnScrollStateChange(RecyclerView recyclerView) {
114     // Remove the listener in case it was added before
115     recyclerView.removeOnScrollListener(recordOnScrollListener);
116     recyclerView.addOnScrollListener(recordOnScrollListener);
117   }
118 
isRecording()119   public static boolean isRecording() {
120     return recording;
121   }
122 
getTimeSinceAppLaunch()123   public static long getTimeSinceAppLaunch() {
124     if (appLaunchTimeMillis == INVALID_TIME) {
125       return INVALID_TIME;
126     }
127     return SystemClock.elapsedRealtime() - appLaunchTimeMillis;
128   }
129 
getTimeSinceFirstClick()130   public static long getTimeSinceFirstClick() {
131     if (firstClickTimeMillis == INVALID_TIME) {
132       return INVALID_TIME;
133     }
134     return SystemClock.elapsedRealtime() - firstClickTimeMillis;
135   }
136 
getActions()137   public static List<UiAction.Type> getActions() {
138     return actions;
139   }
140 
getActionTimestamps()141   public static List<Long> getActionTimestamps() {
142     return actionTimestamps;
143   }
144 
getStartingTabIndex()145   public static int getStartingTabIndex() {
146     return startingTabIndex;
147   }
148 
setStartingTabIndex(int startingTabIndex)149   public static void setStartingTabIndex(int startingTabIndex) {
150     PerformanceReport.startingTabIndex = startingTabIndex;
151   }
152 
setIgnoreActionOnce(@ullable UiAction.Type ignoreActionOnce)153   public static void setIgnoreActionOnce(@Nullable UiAction.Type ignoreActionOnce) {
154     PerformanceReport.ignoreActionOnce = ignoreActionOnce;
155     LogUtil.i(
156         "PerformanceReport.setIgnoreActionOnce",
157         "next action will be ignored once if it is %s",
158         ignoreActionOnce.toString());
159   }
160 }
161