1 /*
2  * Copyright (C) 2016 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 package com.android.settingslib.core.instrumentation;
17 
18 import android.app.Activity;
19 import android.app.settings.SettingsEnums;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.text.TextUtils;
24 import android.util.Pair;
25 
26 import androidx.annotation.NonNull;
27 import androidx.preference.Preference;
28 
29 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
30 
31 import java.util.ArrayList;
32 import java.util.List;
33 
34 /**
35  * FeatureProvider for metrics.
36  */
37 public class MetricsFeatureProvider {
38     /**
39      * The metrics category constant for logging source when a setting fragment is opened.
40      */
41     public static final String EXTRA_SOURCE_METRICS_CATEGORY = ":settings:source_metrics";
42 
43     protected List<LogWriter> mLoggerWriters;
44 
MetricsFeatureProvider()45     public MetricsFeatureProvider() {
46         mLoggerWriters = new ArrayList<>();
47         installLogWriters();
48     }
49 
installLogWriters()50     protected void installLogWriters() {
51         mLoggerWriters.add(new EventLogWriter());
52     }
53 
54     /**
55      * Returns the attribution id for specified activity. If no attribution is set, returns {@link
56      * SettingsEnums#PAGE_UNKNOWN}.
57      *
58      * <p/> Attribution is a {@link SettingsEnums} page id that indicates where the specified
59      * activity is launched from.
60      */
getAttribution(Activity activity)61     public int getAttribution(Activity activity) {
62         if (activity == null) {
63             return SettingsEnums.PAGE_UNKNOWN;
64         }
65         final Intent intent = activity.getIntent();
66         if (intent == null) {
67             return SettingsEnums.PAGE_UNKNOWN;
68         }
69         return intent.getIntExtra(EXTRA_SOURCE_METRICS_CATEGORY,
70                 SettingsEnums.PAGE_UNKNOWN);
71     }
72 
73     /**
74      * Logs an event when target page is visible.
75      *
76      * @param source from this page id to target page
77      * @param category the target page id
78      * @param latency the latency of target page creation
79      */
visible(Context context, int source, int category, int latency)80     public void visible(Context context, int source, int category, int latency) {
81         for (LogWriter writer : mLoggerWriters) {
82             writer.visible(context, source, category, latency);
83         }
84     }
85 
86     /**
87      * Logs an event when target page is hidden.
88      *
89      * @param category the target page id
90      * @param visibleTime the time spending on target page since being visible
91      */
hidden(Context context, int category, int visibleTime)92     public void hidden(Context context, int category, int visibleTime) {
93         for (LogWriter writer : mLoggerWriters) {
94             writer.hidden(context, category, visibleTime);
95         }
96     }
97 
98     /**
99      * Logs a simple action without page id or attribution
100      *
101      * @param category the target page
102      * @param taggedData the data for {@link EventLogWriter}
103      */
action(Context context, int category, Pair<Integer, Object>... taggedData)104     public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
105         for (LogWriter writer : mLoggerWriters) {
106             writer.action(context, category, taggedData);
107         }
108     }
109 
110     /**
111      * Logs a generic Settings event.
112      */
action(Context context, int category, String pkg)113     public void action(Context context, int category, String pkg) {
114         for (LogWriter writer : mLoggerWriters) {
115             writer.action(context, category, pkg);
116         }
117     }
118 
119     /**
120      * Logs a generic Settings event.
121      */
action(int attribution, int action, int pageId, String key, int value)122     public void action(int attribution, int action, int pageId, String key, int value) {
123         for (LogWriter writer : mLoggerWriters) {
124             writer.action(attribution, action, pageId, key, value);
125         }
126     }
127 
action(Context context, int category, int value)128     public void action(Context context, int category, int value) {
129         for (LogWriter writer : mLoggerWriters) {
130             writer.action(context, category, value);
131         }
132     }
133 
action(Context context, int category, boolean value)134     public void action(Context context, int category, boolean value) {
135         for (LogWriter writer : mLoggerWriters) {
136             writer.action(context, category, value);
137         }
138     }
139 
getMetricsCategory(Object object)140     public int getMetricsCategory(Object object) {
141         if (object == null || !(object instanceof Instrumentable)) {
142             return MetricsEvent.VIEW_UNKNOWN;
143         }
144         return ((Instrumentable) object).getMetricsCategory();
145     }
146 
147     /**
148      * Logs an event when the preference is clicked.
149      *
150      * @return true if the preference is loggable, otherwise false
151      */
logClickedPreference(@onNull Preference preference, int sourceMetricsCategory)152     public boolean logClickedPreference(@NonNull Preference preference, int sourceMetricsCategory) {
153         if (preference == null) {
154             return false;
155         }
156         return logSettingsTileClick(preference.getKey(), sourceMetricsCategory)
157                 || logStartedIntent(preference.getIntent(), sourceMetricsCategory)
158                 || logSettingsTileClick(preference.getFragment(), sourceMetricsCategory);
159     }
160 
161     /**
162      * Logs an event when the intent is started.
163      *
164      * @return true if the intent is loggable, otherwise false
165      */
logStartedIntent(Intent intent, int sourceMetricsCategory)166     public boolean logStartedIntent(Intent intent, int sourceMetricsCategory) {
167         if (intent == null) {
168             return false;
169         }
170         final ComponentName cn = intent.getComponent();
171         return logSettingsTileClick(cn != null ? cn.flattenToString() : intent.getAction(),
172                 sourceMetricsCategory);
173     }
174 
175     /**
176      * Logs an event when the intent is started by Profile select dialog.
177      *
178      * @return true if the intent is loggable, otherwise false
179      */
logStartedIntentWithProfile(Intent intent, int sourceMetricsCategory, boolean isWorkProfile)180     public boolean logStartedIntentWithProfile(Intent intent, int sourceMetricsCategory,
181             boolean isWorkProfile) {
182         if (intent == null) {
183             return false;
184         }
185         final ComponentName cn = intent.getComponent();
186         final String key = cn != null ? cn.flattenToString() : intent.getAction();
187         return logSettingsTileClick(key + (isWorkProfile ? "/work" : "/personal"),
188                 sourceMetricsCategory);
189     }
190 
191     /**
192      * Logs an event when the setting key is clicked.
193      *
194      * @return true if the key is loggable, otherwise false
195      */
logSettingsTileClick(String logKey, int sourceMetricsCategory)196     public boolean logSettingsTileClick(String logKey, int sourceMetricsCategory) {
197         if (TextUtils.isEmpty(logKey)) {
198             // Not loggable
199             return false;
200         }
201         action(sourceMetricsCategory,
202                 MetricsEvent.ACTION_SETTINGS_TILE_CLICK,
203                 SettingsEnums.PAGE_UNKNOWN,
204                 logKey,
205                 0);
206         return true;
207     }
208 }
209