• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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.globalsearch;
18 
19 import android.content.ContentValues;
20 import android.content.Context;
21 import android.content.pm.PackageManager;
22 import android.content.pm.ProviderInfo;
23 import android.util.Log;
24 import android.view.KeyEvent;
25 
26 import java.util.List;
27 
28 /**
29  * Logs clicks to an external content provider.
30  * The click log provider is protected by a permission to avoid log stuffing,
31  * and the click log provider must have a special permission to avoid log stealing.
32  */
33 public class ClickLogger {
34 
35     private static final boolean DBG = false;
36     private static final String TAG = "GlobalSearch.ClickLogger";
37 
38     private final Context mContext;
39 
40     private final ProviderInfo mLogReceiverInfo;
41 
ClickLogger(Context context, ProviderInfo logReceiverInfo)42     private ClickLogger(Context context, ProviderInfo logReceiverInfo) {
43         mContext = context;
44         mLogReceiverInfo = logReceiverInfo;
45     }
46 
47     /**
48      * Logs a click.
49      *
50      * @param query Query as typed by the user.
51      * @param clickPos The index of the clicked suggestion.
52      * @param displayedSuggestions The suggestions that were displayed.
53      * @param actionKey action key used to click the suggestion, or KeyEvent.KEYCODE_UNKNOWN.
54      * @param actionMsg action message for the action key used, or {@code null}.
55      */
logClick(String query, int clickPos, List<SuggestionData> displayedSuggestions, int actionKey, String actionMsg)56     public void logClick(String query, int clickPos, List<SuggestionData> displayedSuggestions,
57             int actionKey, String actionMsg) {
58         int suggestionCount = displayedSuggestions.size();
59         if (clickPos < 0 || clickPos >= suggestionCount) {
60             Log.w(TAG, "Click out of range: " + clickPos + ", count: " + suggestionCount);
61             return;
62         }
63         SuggestionData clicked = displayedSuggestions.get(clickPos);
64         // Only log clicks on suggestions that come from the same package as the log receiver.
65         if (!isSuggestionFromLogReceiver(clicked)) {
66             return;
67         }
68         ContentValues row = new ContentValues();
69         row.put(ClickLoggerContract.COL_QUERY, query);
70         row.put(ClickLoggerContract.COL_POS, clickPos);
71         String extraData = clicked.getIntentExtraData();
72         if (extraData != null) {
73             row.put(ClickLoggerContract.COL_EXTRA_DATA, extraData);
74         }
75         if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
76             row.put(ClickLoggerContract.COL_ACTION_KEY, actionKey);
77         }
78         if (actionMsg != null) {
79             row.put(ClickLoggerContract.COL_ACTION_MSG, actionMsg);
80         }
81         StringBuilder slots = new StringBuilder();
82         for (int i = 0; i < suggestionCount; i++) {
83             SuggestionData suggestion = displayedSuggestions.get(i);
84             slots.append(getSlotInfo(suggestion));
85             if (i < suggestionCount - 1) {
86                 slots.append(",");
87             }
88         }
89         row.put(ClickLoggerContract.COL_SLOTS, slots.toString());
90         try {
91             if (DBG) Log.d(TAG, "insert(" + ClickLoggerContract.CLICK_LOG_URI + "," + row + ")");
92             mContext.getContentResolver().insert(ClickLoggerContract.CLICK_LOG_URI, row);
93         } catch (RuntimeException ex) {
94             // Guard against buggy logger implementations
95             Log.e(TAG, "Failed to log click: " + ex);
96         }
97     }
98 
99     /**
100      * Checks whether the given suggestion comes from the same application as the one
101      * hosting the click log receiver.
102      */
isSuggestionFromLogReceiver(SuggestionData clicked)103     private boolean isSuggestionFromLogReceiver(SuggestionData clicked) {
104         String packageName = clicked.getSource().getPackageName();
105         String receiverPackage = mLogReceiverInfo.applicationInfo.packageName;
106         return packageName != null && packageName.equals(receiverPackage);
107     }
108 
109     /**
110      * Gets the information that we will log for each displayed suggestion.
111      */
getSlotInfo(SuggestionData suggestion)112     private String getSlotInfo(SuggestionData suggestion) {
113         if (SuggestionFactoryImpl.BUILTIN_SOURCE_COMPONENT.equals(suggestion.getSource())) {
114             return ClickLoggerContract.TYPE_BUILTIN;
115         } else if (isSuggestionFromLogReceiver(suggestion)) {
116             // This is a bit of a hack, we assume that the app that handles log requests is
117             // the web suggestion source.
118             return ClickLoggerContract.TYPE_WEB;
119         } else {
120             return ClickLoggerContract.TYPE_OTHER;
121         }
122     }
123 
124     /**
125      * Gets a click logger, if clicks should be logged.
126      *
127      * @param context
128      * @return A click logger, or {@code null} if clicks should not be logged.
129      */
getClickLogger(Context context)130     public static ClickLogger getClickLogger(Context context) {
131         PackageManager pm = context.getPackageManager();
132         ProviderInfo providerInfo =
133                 pm.resolveContentProvider(ClickLoggerContract.LOG_AUHTORITY, 0);
134         if (providerInfo == null) {
135             // This is not an error, since the platform may not include a click logger
136             if (DBG) Log.d(TAG, "No provider found for " + ClickLoggerContract.LOG_AUHTORITY);
137             return null;
138         }
139         String providerPackage = providerInfo.applicationInfo.packageName;
140         // Check if the content provider has permission to receive click log data
141         if (pm.checkPermission(ClickLoggerContract.PERMISSION_RECEIVE_GLOBALSEARCH_LOG,
142                 providerPackage) != PackageManager.PERMISSION_GRANTED) {
143             Log.w(TAG, "Package " + providerPackage + " does not have permission "
144                     + ClickLoggerContract.PERMISSION_RECEIVE_GLOBALSEARCH_LOG);
145             return null;
146         }
147         return new ClickLogger(context, providerInfo);
148     }
149 
150 }
151