TextClassifierService.onSelectionEvent
Bug: 74466564
Bug: 67609167
Test: bit FrameworksCoreTests:android.view.textclassifier.TextClassificationManagerTest
Test: bit FrameworksCoreTests:android.view.textclassifier.logging.SelectionEventTest
Merged-In: Ib5af1ec80a38432d1201fbc913acdc3597d6ba82
Change-Id: Ib5af1ec80a38432d1201fbc913acdc3597d6ba82
diff --git a/api/current.txt b/api/current.txt
index 3193d30..037dba9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -51067,7 +51067,8 @@
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();
@@ -51084,6 +51085,7 @@
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
@@ -51095,6 +51097,7 @@
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
diff --git a/api/system-current.txt b/api/system-current.txt
index c80f239b..e205331 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4707,6 +4707,7 @@
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";
}
diff --git a/core/java/android/service/textclassifier/ITextClassifierService.aidl b/core/java/android/service/textclassifier/ITextClassifierService.aidl
index d2ffe34..25e9d45 100644
--- a/core/java/android/service/textclassifier/ITextClassifierService.aidl
+++ b/core/java/android/service/textclassifier/ITextClassifierService.aidl
@@ -19,13 +19,14 @@
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 @@
in CharSequence text,
in TextLinks.Options options,
in ITextLinksCallback c);
+
+ void onSelectionEvent(in SelectionEvent event);
}
diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java
index 57c8def..88e29b0 100644
--- a/core/java/android/service/textclassifier/TextClassifierService.java
+++ b/core/java/android/service/textclassifier/TextClassifierService.java
@@ -33,6 +33,7 @@
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.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
@@ -171,6 +172,12 @@
}
});
}
+
+ /** {@inheritDoc} */
+ @Override
+ public void onSelectionEvent(SelectionEvent event) throws RemoteException {
+ TextClassifierService.this.onSelectionEvent(event);
+ }
};
@Nullable
@@ -238,6 +245,15 @@
@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) {}
+
+ /**
* Returns a TextClassifier that runs in this service's process.
* If the local TextClassifier is disabled, this returns {@link TextClassifier#NO_OP}.
*/
diff --git a/core/java/android/view/textclassifier/Logger.java b/core/java/android/view/textclassifier/Logger.java
index a4f5bf1..9c92fd4 100644
--- a/core/java/android/view/textclassifier/Logger.java
+++ b/core/java/android/view/textclassifier/Logger.java
@@ -94,10 +94,7 @@
}
/**
- * 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);
diff --git a/core/java/android/view/textclassifier/SelectionEvent.aidl b/core/java/android/view/textclassifier/SelectionEvent.aidl
new file mode 100644
index 0000000..10ed16e
--- /dev/null
+++ b/core/java/android/view/textclassifier/SelectionEvent.aidl
@@ -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;
diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java
index 90fd921..7ac094e 100644
--- a/core/java/android/view/textclassifier/SelectionEvent.java
+++ b/core/java/android/view/textclassifier/SelectionEvent.java
@@ -18,6 +18,8 @@
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 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 @@
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 @@
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 @@
* 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;
}
@@ -342,15 +399,66 @@
}
@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];
+ }
+ };
}
diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java
index 2b335fb..c783cae 100644
--- a/core/java/android/view/textclassifier/SystemTextClassifier.java
+++ b/core/java/android/view/textclassifier/SystemTextClassifier.java
@@ -29,6 +29,7 @@
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 @@
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 @@
/**
* @inheritDoc
*/
+ @Override
@WorkerThread
public TextSelection suggestSelection(
@NonNull CharSequence text,
@@ -84,6 +92,7 @@
/**
* @inheritDoc
*/
+ @Override
@WorkerThread
public TextClassification classifyText(
@NonNull CharSequence text,
@@ -109,6 +118,7 @@
/**
* @inheritDoc
*/
+ @Override
@WorkerThread
public TextLinks generateLinks(
@NonNull CharSequence text, @Nullable TextLinks.Options options) {
@@ -142,11 +152,33 @@
* @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<>();
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index ebd2ff9..887bebb 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -323,6 +323,7 @@
* @see #generateLinks(CharSequence)
* @see #generateLinks(CharSequence, TextLinks.Options)
*/
+ @WorkerThread
default int getMaxGenerateLinksTextLength() {
return Integer.MAX_VALUE;
}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 0a05269..a099820 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -18,6 +18,7 @@
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.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 @@
private final Context mContext;
private final TextClassifier mFallback;
-
private final GenerateLinksLogger mGenerateLinksLogger;
private final Object mLock = new Object();
@@ -91,9 +90,9 @@
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 @@
/** @inheritDoc */
@Override
+ @WorkerThread
public TextSelection suggestSelection(
@NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex,
@Nullable TextSelection.Options options) {
@@ -169,6 +169,7 @@
/** @inheritDoc */
@Override
+ @WorkerThread
public TextClassification classifyText(
@NonNull CharSequence text, int startIndex, int endIndex,
@Nullable TextClassification.Options options) {
@@ -204,6 +205,7 @@
/** @inheritDoc */
@Override
+ @WorkerThread
public TextLinks generateLinks(
@NonNull CharSequence text, @Nullable TextLinks.Options options) {
Utils.validate(text, getMaxGenerateLinksTextLength(), false /* allowInMainThread */);
@@ -282,16 +284,17 @@
}
}
+ /** @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)
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index 629f531..be8c34c 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -648,6 +648,9 @@
* 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 {
diff --git a/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java b/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java
new file mode 100644
index 0000000..b77982b
--- /dev/null
+++ b/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java
@@ -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));
+ }
+}
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 0ac853b..6053512 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -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.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 @@
}
}
+ @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 @@
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 @@
* @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 @@
@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 @@
@GuardedBy("mLock")
private void removeLocked() {
mPendingRequests.remove(this);
- mBinder.unlinkToDeath(this, 0);
+ if (mBinder != null) {
+ mBinder.unlinkToDeath(this, 0);
+ }
}
}
@@ -359,4 +384,16 @@
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());
+ }
+ }
}