Merge "TextClassifierService.onSelectionEvent"

This commit is contained in:
TreeHugger Robot
2018-03-07 17:44:23 +00:00
committed by Android (Google) Code Review
13 changed files with 313 additions and 40 deletions

View File

@@ -51062,7 +51062,8 @@ package android.view.textclassifier {
method public java.lang.String getWidgetVersion();
}
public final class SelectionEvent {
public final class SelectionEvent implements android.os.Parcelable {
method public int describeContents();
method public long getDurationSincePreviousEvent();
method public long getDurationSinceSessionStart();
method public int getEnd();
@@ -51079,6 +51080,7 @@ package android.view.textclassifier {
method public int getStart();
method public java.lang.String getWidgetType();
method public java.lang.String getWidgetVersion();
method public void writeToParcel(android.os.Parcel, int);
field public static final int ACTION_ABANDON = 107; // 0x6b
field public static final int ACTION_COPY = 101; // 0x65
field public static final int ACTION_CUT = 103; // 0x67
@@ -51090,6 +51092,7 @@ package android.view.textclassifier {
field public static final int ACTION_SELECT_ALL = 200; // 0xc8
field public static final int ACTION_SHARE = 104; // 0x68
field public static final int ACTION_SMART_SHARE = 105; // 0x69
field public static final android.os.Parcelable.Creator<android.view.textclassifier.SelectionEvent> CREATOR;
field public static final int EVENT_AUTO_SELECTION = 5; // 0x5
field public static final int EVENT_SELECTION_MODIFIED = 2; // 0x2
field public static final int EVENT_SELECTION_STARTED = 1; // 0x1

View File

@@ -4693,6 +4693,7 @@ package android.service.textclassifier {
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract void onClassifyText(java.lang.CharSequence, int, int, android.view.textclassifier.TextClassification.Options, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextClassification>);
method public abstract void onGenerateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextLinks>);
method public void onSelectionEvent(android.view.textclassifier.SelectionEvent);
method public abstract void onSuggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback<android.view.textclassifier.TextSelection>);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.textclassifier.TextClassifierService";
}

View File

@@ -19,13 +19,14 @@ package android.service.textclassifier;
import android.service.textclassifier.ITextClassificationCallback;
import android.service.textclassifier.ITextLinksCallback;
import android.service.textclassifier.ITextSelectionCallback;
import android.view.textclassifier.SelectionEvent;
import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextLinks;
import android.view.textclassifier.TextSelection;
/**
* TextClassifierService binder interface.
* See TextClassifier for interface documentation.
* See TextClassifier (and TextClassifier.Logger) for interface documentation.
* {@hide}
*/
oneway interface ITextClassifierService {
@@ -44,4 +45,6 @@ oneway interface ITextClassifierService {
in CharSequence text,
in TextLinks.Options options,
in ITextLinksCallback c);
void onSelectionEvent(in SelectionEvent event);
}

View File

@@ -33,6 +33,7 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Slog;
import android.view.textclassifier.SelectionEvent;
import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextLinks;
@@ -170,6 +171,12 @@ public abstract class TextClassifierService extends Service {
}
});
}
/** {@inheritDoc} */
@Override
public void onSelectionEvent(SelectionEvent event) throws RemoteException {
TextClassifierService.this.onSelectionEvent(event);
}
};
@Nullable
@@ -236,6 +243,15 @@ public abstract class TextClassifierService extends Service {
@NonNull CancellationSignal cancellationSignal,
@NonNull Callback<TextLinks> callback);
/**
* Writes the selection event.
* This is called when a selection event occurs. e.g. user changed selection; or smart selection
* happened.
*
* <p>The default implementation ignores the event.
*/
public void onSelectionEvent(@NonNull SelectionEvent event) {}
/**
* Callbacks for TextClassifierService results.
*

View File

@@ -94,10 +94,7 @@ public abstract class Logger {
}
/**
* Writes the selection event.
*
* <p><strong>NOTE: </strong>This method is designed for subclasses.
* Apps should not call it directly.
* Writes the selection event to a log.
*/
public abstract void writeEvent(@NonNull SelectionEvent event);

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2018 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 android.view.textclassifier;
parcelable SelectionEvent;

View File

@@ -18,6 +18,8 @@ package android.view.textclassifier;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.textclassifier.TextClassifier.EntityType;
import com.android.internal.util.Preconditions;
@@ -25,12 +27,13 @@ import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Locale;
import java.util.Objects;
/**
* A selection event.
* Specify index parameters as word token indices.
*/
public final class SelectionEvent {
public final class SelectionEvent implements Parcelable {
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -121,9 +124,9 @@ public final class SelectionEvent {
private String mSignature;
private long mEventTime;
private long mDurationSinceSessionStart;
private long mDurationSinceLastEvent;
private long mDurationSincePreviousEvent;
private int mEventIndex;
private String mSessionId;
@Nullable private String mSessionId;
private int mStart;
private int mEnd;
private int mSmartStart;
@@ -146,6 +149,60 @@ public final class SelectionEvent {
mInvocationMethod = invocationMethod;
}
private SelectionEvent(Parcel in) {
mAbsoluteStart = in.readInt();
mAbsoluteEnd = in.readInt();
mEventType = in.readInt();
mEntityType = in.readString();
mWidgetVersion = in.readInt() > 0 ? in.readString() : null;
mPackageName = in.readString();
mWidgetType = in.readString();
mInvocationMethod = in.readInt();
mSignature = in.readString();
mEventTime = in.readLong();
mDurationSinceSessionStart = in.readLong();
mDurationSincePreviousEvent = in.readLong();
mEventIndex = in.readInt();
mSessionId = in.readInt() > 0 ? in.readString() : null;
mStart = in.readInt();
mEnd = in.readInt();
mSmartStart = in.readInt();
mSmartEnd = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mAbsoluteStart);
dest.writeInt(mAbsoluteEnd);
dest.writeInt(mEventType);
dest.writeString(mEntityType);
dest.writeInt(mWidgetVersion != null ? 1 : 0);
if (mWidgetVersion != null) {
dest.writeString(mWidgetVersion);
}
dest.writeString(mPackageName);
dest.writeString(mWidgetType);
dest.writeInt(mInvocationMethod);
dest.writeString(mSignature);
dest.writeLong(mEventTime);
dest.writeLong(mDurationSinceSessionStart);
dest.writeLong(mDurationSincePreviousEvent);
dest.writeInt(mEventIndex);
dest.writeInt(mSessionId != null ? 1 : 0);
if (mSessionId != null) {
dest.writeString(mSessionId);
}
dest.writeInt(mStart);
dest.writeInt(mEnd);
dest.writeInt(mSmartStart);
dest.writeInt(mSmartEnd);
}
@Override
public int describeContents() {
return 0;
}
int getAbsoluteStart() {
return mAbsoluteStart;
}
@@ -240,11 +297,11 @@ public final class SelectionEvent {
* in the selection session was triggered.
*/
public long getDurationSincePreviousEvent() {
return mDurationSinceLastEvent;
return mDurationSincePreviousEvent;
}
SelectionEvent setDurationSincePreviousEvent(long durationMs) {
this.mDurationSinceLastEvent = durationMs;
this.mDurationSincePreviousEvent = durationMs;
return this;
}
@@ -341,16 +398,67 @@ public final class SelectionEvent {
}
}
@Override
public int hashCode() {
return Objects.hash(mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType,
mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, mSignature,
mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent,
mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof SelectionEvent)) {
return false;
}
final SelectionEvent other = (SelectionEvent) obj;
return mAbsoluteStart == other.mAbsoluteStart
&& mAbsoluteEnd == other.mAbsoluteEnd
&& mEventType == other.mEventType
&& Objects.equals(mEntityType, other.mEntityType)
&& Objects.equals(mWidgetVersion, other.mWidgetVersion)
&& Objects.equals(mPackageName, other.mPackageName)
&& Objects.equals(mWidgetType, other.mWidgetType)
&& mInvocationMethod == other.mInvocationMethod
&& Objects.equals(mSignature, other.mSignature)
&& mEventTime == other.mEventTime
&& mDurationSinceSessionStart == other.mDurationSinceSessionStart
&& mDurationSincePreviousEvent == other.mDurationSincePreviousEvent
&& mEventIndex == other.mEventIndex
&& Objects.equals(mSessionId, other.mSessionId)
&& mStart == other.mStart
&& mEnd == other.mEnd
&& mSmartStart == other.mSmartStart
&& mSmartEnd == other.mSmartEnd;
}
@Override
public String toString() {
return String.format(Locale.US,
"SelectionEvent {absoluteStart=%d, absoluteEnd=%d, eventType=%d, entityType=%s, "
+ "widgetVersion=%s, packageName=%s, widgetType=%s, signature=%s, "
+ "eventTime=%d, durationSinceSessionStart=%d, durationSinceLastEvent=%d, "
+ "eventIndex=%d, sessionId=%s, start=%d, end=%d, smartStart=%d, smartEnd=%d}",
+ "widgetVersion=%s, packageName=%s, widgetType=%s, invocationMethod=%s, "
+ "signature=%s, eventTime=%d, durationSinceSessionStart=%d, "
+ "durationSincePreviousEvent=%d, eventIndex=%d, sessionId=%s, start=%d, end=%d, "
+ "smartStart=%d, smartEnd=%d}",
mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType,
mWidgetVersion, mPackageName, mWidgetType, mSignature,
mEventTime, mDurationSinceSessionStart, mDurationSinceLastEvent,
mWidgetVersion, mPackageName, mWidgetType, mInvocationMethod, mSignature,
mEventTime, mDurationSinceSessionStart, mDurationSincePreviousEvent,
mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd);
}
public static final Creator<SelectionEvent> CREATOR = new Creator<SelectionEvent>() {
@Override
public SelectionEvent createFromParcel(Parcel in) {
return new SelectionEvent(in);
}
@Override
public SelectionEvent[] newArray(int size) {
return new SelectionEvent[size];
}
};
}

View File

@@ -29,6 +29,7 @@ import android.service.textclassifier.ITextClassifierService;
import android.service.textclassifier.ITextLinksCallback;
import android.service.textclassifier.ITextSelectionCallback;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import java.util.concurrent.CountDownLatch;
@@ -46,6 +47,12 @@ final class SystemTextClassifier implements TextClassifier {
private final TextClassifier mFallback;
private final String mPackageName;
private final Object mLoggerLock = new Object();
@GuardedBy("mLoggerLock")
private Logger.Config mLoggerConfig;
@GuardedBy("mLoggerLock")
private Logger mLogger;
SystemTextClassifier(Context context, TextClassificationConstants settings)
throws ServiceManager.ServiceNotFoundException {
mManagerService = ITextClassifierService.Stub.asInterface(
@@ -58,6 +65,7 @@ final class SystemTextClassifier implements TextClassifier {
/**
* @inheritDoc
*/
@Override
@WorkerThread
public TextSelection suggestSelection(
@NonNull CharSequence text,
@@ -84,6 +92,7 @@ final class SystemTextClassifier implements TextClassifier {
/**
* @inheritDoc
*/
@Override
@WorkerThread
public TextClassification classifyText(
@NonNull CharSequence text,
@@ -109,6 +118,7 @@ final class SystemTextClassifier implements TextClassifier {
/**
* @inheritDoc
*/
@Override
@WorkerThread
public TextLinks generateLinks(
@NonNull CharSequence text, @Nullable TextLinks.Options options) {
@@ -142,11 +152,33 @@ final class SystemTextClassifier implements TextClassifier {
* @inheritDoc
*/
@Override
@WorkerThread
public int getMaxGenerateLinksTextLength() {
// TODO: retrieve this from the bound service.
return mFallback.getMaxGenerateLinksTextLength();
}
@Override
public Logger getLogger(@NonNull Logger.Config config) {
Preconditions.checkNotNull(config);
synchronized (mLoggerLock) {
if (mLogger == null || !config.equals(mLoggerConfig)) {
mLoggerConfig = config;
mLogger = new Logger(config) {
@Override
public void writeEvent(SelectionEvent event) {
try {
mManagerService.onSelectionEvent(event);
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
}
};
}
}
return mLogger;
}
private static final class TextSelectionCallback extends ITextSelectionCallback.Stub {
final ResponseReceiver<TextSelection> mReceiver = new ResponseReceiver<>();

View File

@@ -323,6 +323,7 @@ public interface TextClassifier {
* @see #generateLinks(CharSequence)
* @see #generateLinks(CharSequence, TextLinks.Options)
*/
@WorkerThread
default int getMaxGenerateLinksTextLength() {
return Integer.MAX_VALUE;
}

View File

@@ -18,6 +18,7 @@ package android.view.textclassifier;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.app.SearchManager;
import android.content.ComponentName;
import android.content.ContentUris;
@@ -42,7 +43,6 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
@@ -78,7 +78,6 @@ public final class TextClassifierImpl implements TextClassifier {
private final Context mContext;
private final TextClassifier mFallback;
private final GenerateLinksLogger mGenerateLinksLogger;
private final Object mLock = new Object();
@@ -91,9 +90,9 @@ public final class TextClassifierImpl implements TextClassifier {
private final Object mLoggerLock = new Object();
@GuardedBy("mLoggerLock") // Do not access outside this lock.
private WeakReference<Logger.Config> mLoggerConfig = new WeakReference<>(null);
private Logger.Config mLoggerConfig;
@GuardedBy("mLoggerLock") // Do not access outside this lock.
private Logger mLogger; // Should never be null if mLoggerConfig.get() is not null.
private Logger mLogger;
private final TextClassificationConstants mSettings;
@@ -106,6 +105,7 @@ public final class TextClassifierImpl implements TextClassifier {
/** @inheritDoc */
@Override
@WorkerThread
public TextSelection suggestSelection(
@NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
@Nullable TextSelection.Options options) {
@@ -169,6 +169,7 @@ public final class TextClassifierImpl implements TextClassifier {
/** @inheritDoc */
@Override
@WorkerThread
public TextClassification classifyText(
@NonNull CharSequence text, int startIndex, int endIndex,
@Nullable TextClassification.Options options) {
@@ -204,6 +205,7 @@ public final class TextClassifierImpl implements TextClassifier {
/** @inheritDoc */
@Override
@WorkerThread
public TextLinks generateLinks(
@NonNull CharSequence text, @Nullable TextLinks.Options options) {
Utils.validate(text, getMaxGenerateLinksTextLength(), false /* allowInMainThread */);
@@ -282,16 +284,17 @@ public final class TextClassifierImpl implements TextClassifier {
}
}
/** @inheritDoc */
@Override
public Logger getLogger(@NonNull Logger.Config config) {
Preconditions.checkNotNull(config);
synchronized (mLoggerLock) {
if (mLoggerConfig.get() == null || !mLoggerConfig.get().equals(config)) {
mLoggerConfig = new WeakReference<>(config);
if (mLogger == null || !config.equals(mLoggerConfig)) {
mLoggerConfig = config;
mLogger = new DefaultLogger(config);
}
return mLogger;
}
return mLogger;
}
private TextClassifierImplNative getNative(LocaleList localeList)

View File

@@ -648,6 +648,9 @@ public final class SelectionActionModeHelper {
* Part selection of a word e.g. "or" is counted as selecting the
* entire word i.e. equivalent to "York", and each special character is counted as a word, e.g.
* "," is at [2, 3). Whitespaces are ignored.
*
* NOTE that the definition of a word is defined by the TextClassifier's Logger's token
* iterator.
*/
private static final class SelectionMetricsLogger {

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2018 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 android.view.textclassifier;
import static org.junit.Assert.assertEquals;
import android.os.Parcel;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class SelectionEventTest {
@Test
public void testParcel() {
final SelectionEvent[] captured = new SelectionEvent[1];
final Logger logger = new Logger(new Logger.Config(
InstrumentationRegistry.getTargetContext(), Logger.WIDGET_TEXTVIEW, null)) {
@Override
public void writeEvent(SelectionEvent event) {
captured[0] = event;
}
};
logger.logSelectionStartedEvent(SelectionEvent.INVOCATION_MANUAL, 0);
final SelectionEvent event = captured[0];
final Parcel parcel = Parcel.obtain();
event.writeToParcel(parcel, event.describeContents());
parcel.setDataPosition(0);
assertEquals(event, SelectionEvent.CREATOR.createFromParcel(parcel));
}
}

View File

@@ -16,11 +16,12 @@
package com.android.server.textclassifier;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -30,6 +31,7 @@ import android.service.textclassifier.ITextClassificationCallback;
import android.service.textclassifier.ITextLinksCallback;
import android.service.textclassifier.ITextSelectionCallback;
import android.service.textclassifier.TextClassifierService;
import android.view.textclassifier.SelectionEvent;
import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextLinks;
@@ -216,6 +218,23 @@ public final class TextClassificationManagerService extends ITextClassifierServi
}
}
@Override
public void onSelectionEvent(SelectionEvent event) throws RemoteException {
validateInput(event, mContext);
synchronized (mLock) {
if (isBoundLocked()) {
mService.onSelectionEvent(event);
} else {
final Callable<Void> request = () -> {
onSelectionEvent(event);
return null;
};
enqueueRequestLocked(request, null /* onServiceFailure */, null /* binder */);
}
}
}
/**
* @return true if the service is bound or in the process of being bound.
* Returns false otherwise.
@@ -281,8 +300,8 @@ public final class TextClassificationManagerService extends ITextClassifierServi
private final class PendingRequest implements IBinder.DeathRecipient {
private final Callable<Void> mRequest;
private final Callable<Void> mOnServiceFailure;
private final IBinder mBinder;
@Nullable private final Callable<Void> mOnServiceFailure;
@Nullable private final IBinder mBinder;
/**
* Initializes a new pending request.
@@ -292,15 +311,17 @@ public final class TextClassificationManagerService extends ITextClassifierServi
* @param binder binder to the process that made this pending request
*/
PendingRequest(
@NonNull Callable<Void> request, @NonNull Callable<Void> onServiceFailure,
@NonNull IBinder binder) {
Callable<Void> request, @Nullable Callable<Void> onServiceFailure,
@Nullable IBinder binder) {
mRequest = Preconditions.checkNotNull(request);
mOnServiceFailure = Preconditions.checkNotNull(onServiceFailure);
mBinder = Preconditions.checkNotNull(binder);
try {
mBinder.linkToDeath(this, 0);
} catch (RemoteException e) {
e.printStackTrace();
mOnServiceFailure = onServiceFailure;
mBinder = binder;
if (mBinder != null) {
try {
mBinder.linkToDeath(this, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@@ -317,11 +338,13 @@ public final class TextClassificationManagerService extends ITextClassifierServi
@GuardedBy("mLock")
void notifyServiceFailureLocked() {
removeLocked();
try {
mOnServiceFailure.call();
} catch (Exception e) {
Slog.d(LOG_TAG, "Error notifying callback of service failure: "
+ e.getMessage());
if (mOnServiceFailure != null) {
try {
mOnServiceFailure.call();
} catch (Exception e) {
Slog.d(LOG_TAG, "Error notifying callback of service failure: "
+ e.getMessage());
}
}
}
@@ -336,7 +359,9 @@ public final class TextClassificationManagerService extends ITextClassifierServi
@GuardedBy("mLock")
private void removeLocked() {
mPendingRequests.remove(this);
mBinder.unlinkToDeath(this, 0);
if (mBinder != null) {
mBinder.unlinkToDeath(this, 0);
}
}
}
@@ -359,4 +384,16 @@ public final class TextClassificationManagerService extends ITextClassifierServi
throw new RemoteException(e.getMessage());
}
}
private static void validateInput(SelectionEvent event, Context context)
throws RemoteException {
try {
final int uid = context.getPackageManager()
.getPackageUid(event.getPackageName(), 0);
Preconditions.checkArgument(Binder.getCallingUid() == uid);
} catch (IllegalArgumentException | NullPointerException |
PackageManager.NameNotFoundException e) {
throw new RemoteException(e.getMessage());
}
}
}