blob: 806712100e4639c27f17f1390f85e52f0720a679 [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.globalsearch;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.util.Log;
import android.view.KeyEvent;
import java.util.List;
/**
* Logs clicks to an external content provider.
* The click log provider is protected by a permission to avoid log stuffing,
* and the click log provider must have a special permission to avoid log stealing.
*/
public class ClickLogger {
private static final boolean DBG = false;
private static final String TAG = "GlobalSearch.ClickLogger";
private final Context mContext;
private final ProviderInfo mLogReceiverInfo;
private ClickLogger(Context context, ProviderInfo logReceiverInfo) {
mContext = context;
mLogReceiverInfo = logReceiverInfo;
}
/**
* Logs a click.
*
* @param query Query as typed by the user.
* @param clickPos The index of the clicked suggestion.
* @param displayedSuggestions The suggestions that were displayed.
* @param actionKey action key used to click the suggestion, or KeyEvent.KEYCODE_UNKNOWN.
* @param actionMsg action message for the action key used, or {@code null}.
*/
public void logClick(String query, int clickPos, List<SuggestionData> displayedSuggestions,
int actionKey, String actionMsg) {
int suggestionCount = displayedSuggestions.size();
if (clickPos < 0 || clickPos >= suggestionCount) {
Log.w(TAG, "Click out of range: " + clickPos + ", count: " + suggestionCount);
return;
}
SuggestionData clicked = displayedSuggestions.get(clickPos);
// Only log clicks on suggestions that come from the same package as the log receiver.
if (!isSuggestionFromLogReceiver(clicked)) {
return;
}
ContentValues row = new ContentValues();
row.put(ClickLoggerContract.COL_QUERY, query);
row.put(ClickLoggerContract.COL_POS, clickPos);
String extraData = clicked.getIntentExtraData();
if (extraData != null) {
row.put(ClickLoggerContract.COL_EXTRA_DATA, extraData);
}
if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
row.put(ClickLoggerContract.COL_ACTION_KEY, actionKey);
}
if (actionMsg != null) {
row.put(ClickLoggerContract.COL_ACTION_MSG, actionMsg);
}
StringBuilder slots = new StringBuilder();
for (int i = 0; i < suggestionCount; i++) {
SuggestionData suggestion = displayedSuggestions.get(i);
slots.append(getSlotInfo(suggestion));
if (i < suggestionCount - 1) {
slots.append(",");
}
}
row.put(ClickLoggerContract.COL_SLOTS, slots.toString());
try {
if (DBG) Log.d(TAG, "insert(" + ClickLoggerContract.CLICK_LOG_URI + "," + row + ")");
mContext.getContentResolver().insert(ClickLoggerContract.CLICK_LOG_URI, row);
} catch (RuntimeException ex) {
// Guard against buggy logger implementations
Log.e(TAG, "Failed to log click: " + ex);
}
}
/**
* Checks whether the given suggestion comes from the same application as the one
* hosting the click log receiver.
*/
private boolean isSuggestionFromLogReceiver(SuggestionData clicked) {
String packageName = clicked.getSource().getPackageName();
String receiverPackage = mLogReceiverInfo.applicationInfo.packageName;
return packageName != null && packageName.equals(receiverPackage);
}
/**
* Gets the information that we will log for each displayed suggestion.
*/
private String getSlotInfo(SuggestionData suggestion) {
if (SuggestionFactoryImpl.BUILTIN_SOURCE_COMPONENT.equals(suggestion.getSource())) {
return ClickLoggerContract.TYPE_BUILTIN;
} else if (isSuggestionFromLogReceiver(suggestion)) {
// This is a bit of a hack, we assume that the app that handles log requests is
// the web suggestion source.
return ClickLoggerContract.TYPE_WEB;
} else {
return ClickLoggerContract.TYPE_OTHER;
}
}
/**
* Gets a click logger, if clicks should be logged.
*
* @param context
* @return A click logger, or {@code null} if clicks should not be logged.
*/
public static ClickLogger getClickLogger(Context context) {
PackageManager pm = context.getPackageManager();
ProviderInfo providerInfo =
pm.resolveContentProvider(ClickLoggerContract.LOG_AUHTORITY, 0);
if (providerInfo == null) {
// This is not an error, since the platform may not include a click logger
if (DBG) Log.d(TAG, "No provider found for " + ClickLoggerContract.LOG_AUHTORITY);
return null;
}
String providerPackage = providerInfo.applicationInfo.packageName;
// Check if the content provider has permission to receive click log data
if (pm.checkPermission(ClickLoggerContract.PERMISSION_RECEIVE_GLOBALSEARCH_LOG,
providerPackage) != PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "Package " + providerPackage + " does not have permission "
+ ClickLoggerContract.PERMISSION_RECEIVE_GLOBALSEARCH_LOG);
return null;
}
return new ClickLogger(context, providerInfo);
}
}