Merge "Initial implementation of IntelligenceService.onContentCaptureEvents()"
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 8d54e91..86ed267 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -120,6 +120,7 @@
import android.view.autofill.AutofillManager.AutofillClient;
import android.view.autofill.AutofillPopupWindow;
import android.view.autofill.IAutofillWindowPresenter;
+import android.view.intelligence.ContentCaptureEvent;
import android.view.intelligence.IntelligenceManager;
import android.widget.AdapterView;
import android.widget.Toast;
@@ -1023,6 +1024,31 @@
return mIntelligenceManager;
}
+ private void notifyIntelligenceManagerIfNeeded(@ContentCaptureEvent.EventType int event) {
+ final IntelligenceManager im = getIntelligenceManager();
+ if (im == null || !im.isContentCaptureEnabled()) {
+ return;
+ }
+ switch (event) {
+ case ContentCaptureEvent.TYPE_ACTIVITY_CREATED:
+ //TODO(b/111276913): decide whether the InteractionSessionId should be
+ // saved / restored in the activity bundle.
+ im.onActivityCreated(mToken, getComponentName());
+ break;
+ case ContentCaptureEvent.TYPE_ACTIVITY_DESTROYED:
+ im.onActivityDestroyed();
+ break;
+ case ContentCaptureEvent.TYPE_ACTIVITY_STARTED:
+ case ContentCaptureEvent.TYPE_ACTIVITY_RESUMED:
+ case ContentCaptureEvent.TYPE_ACTIVITY_PAUSED:
+ case ContentCaptureEvent.TYPE_ACTIVITY_STOPPED:
+ im.onActivityLifecycleEvent(event);
+ break;
+ default:
+ Log.w(TAG, "notifyIntelligenceManagerIfNeeded(): invalid type " + event);
+ }
+ }
+
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
@@ -1099,11 +1125,7 @@
mRestoredFromBundle = savedInstanceState != null;
mCalled = true;
- if (getIntelligenceManager() != null) {
- //TODO(b/111276913): decide whether the screen_obs session id should be saved / restored
- // in the activity bundle.
- mIntelligenceManager.onActivityCreated(mToken, getComponentName());
- }
+ notifyIntelligenceManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_CREATED);
}
/**
@@ -1337,6 +1359,7 @@
if (mAutoFillResetNeeded) {
getAutofillManager().onVisibleForAutofill();
}
+ notifyIntelligenceManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_STARTED);
}
/**
@@ -1419,6 +1442,7 @@
}
}
}
+ notifyIntelligenceManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_RESUMED);
mCalled = true;
}
@@ -1812,6 +1836,7 @@
mAutoFillIgnoreFirstResumePause = false;
}
}
+ notifyIntelligenceManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_PAUSED);
mCalled = true;
}
@@ -2000,6 +2025,7 @@
getAutofillManager().onPendingSaveUi(AutofillManager.PENDING_UI_OPERATION_CANCEL,
mIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN));
}
+ notifyIntelligenceManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_STOPPED);
}
}
@@ -2071,9 +2097,8 @@
getApplication().dispatchActivityDestroyed(this);
- if (getIntelligenceManager() != null) {
- mIntelligenceManager.onActivityDestroyed();
- }
+ notifyIntelligenceManagerIfNeeded(ContentCaptureEvent.TYPE_ACTIVITY_DESTROYED);
+
}
/**
diff --git a/core/java/android/service/intelligence/IIntelligenceService.aidl b/core/java/android/service/intelligence/IIntelligenceService.aidl
index ee93326..bacad8b 100644
--- a/core/java/android/service/intelligence/IIntelligenceService.aidl
+++ b/core/java/android/service/intelligence/IIntelligenceService.aidl
@@ -19,6 +19,11 @@
import android.service.intelligence.InteractionSessionId;
import android.service.intelligence.InteractionContext;
+import android.view.intelligence.ContentCaptureEvent;
+
+import java.util.List;
+
+
/**
* Interface from the system to an intelligence service.
*
@@ -28,4 +33,7 @@
// Called when session is created (context not null) or destroyed (context null)
void onSessionLifecycle(in InteractionContext context, in InteractionSessionId sessionId);
+
+ void onContentCaptureEvents(in InteractionSessionId sessionId,
+ in List<ContentCaptureEvent> events);
}
diff --git a/core/java/android/service/intelligence/IntelligenceService.java b/core/java/android/service/intelligence/IntelligenceService.java
index ce0a88a..a2b60f0 100644
--- a/core/java/android/service/intelligence/IntelligenceService.java
+++ b/core/java/android/service/intelligence/IntelligenceService.java
@@ -70,6 +70,14 @@
IntelligenceService.this, sessionId));
}
}
+ @Override
+ public void onContentCaptureEvents(InteractionSessionId sessionId,
+ List<ContentCaptureEvent> events) {
+ mHandler.sendMessage(
+ obtainMessage(IntelligenceService::onContentCaptureEvent,
+ IntelligenceService.this, sessionId, events));
+
+ }
};
@CallSuper
@@ -105,6 +113,7 @@
* @param sessionId the session's Id
* @param events the events
*/
+ // TODO(b/111276913): rename to onContentCaptureEvents
public abstract void onContentCaptureEvent(@NonNull InteractionSessionId sessionId,
@NonNull List<ContentCaptureEvent> events);
diff --git a/core/java/android/view/intelligence/ContentCaptureEvent.aidl b/core/java/android/view/intelligence/ContentCaptureEvent.aidl
new file mode 100644
index 0000000..c66a6cb
--- /dev/null
+++ b/core/java/android/view/intelligence/ContentCaptureEvent.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.intelligence;
+
+parcelable ContentCaptureEvent;
diff --git a/core/java/android/view/intelligence/ContentCaptureEvent.java b/core/java/android/view/intelligence/ContentCaptureEvent.java
index d6aec34..2530ae3 100644
--- a/core/java/android/view/intelligence/ContentCaptureEvent.java
+++ b/core/java/android/view/intelligence/ContentCaptureEvent.java
@@ -30,6 +30,11 @@
@SystemApi
public final class ContentCaptureEvent implements Parcelable {
+ /** @hide */
+ public static final int TYPE_ACTIVITY_DESTROYED = -2;
+ /** @hide */
+ public static final int TYPE_ACTIVITY_CREATED = -1;
+
/**
* Called when the activity is started.
*/
@@ -85,10 +90,18 @@
TYPE_VIEW_TEXT_CHANGED
})
@Retention(RetentionPolicy.SOURCE)
- @interface EventType{}
+ public @interface EventType{}
+
+ private final int mType;
+ private final long mEventTime;
+ private final int mFlags;
+
/** @hide */
- ContentCaptureEvent() {
+ public ContentCaptureEvent(int type, long eventTime, int flags) {
+ mType = type;
+ mEventTime = eventTime;
+ mFlags = flags;
}
/**
@@ -99,14 +112,14 @@
* {@link #TYPE_VIEW_ADDED}, {@link #TYPE_VIEW_REMOVED}, or {@link #TYPE_VIEW_TEXT_CHANGED}.
*/
public @EventType int getType() {
- return 42;
+ return mType;
}
/**
* Gets when the event was generated, in ms.
*/
public long getEventTime() {
- return 48151623;
+ return mEventTime;
}
/**
@@ -116,7 +129,7 @@
* {@link android.view.intelligence.IntelligenceManager#FLAG_USER_INPUT}.
*/
public int getFlags() {
- return 0;
+ return mFlags;
}
/**
@@ -150,13 +163,25 @@
}
@Override
+ public String toString() {
+ final StringBuilder string = new StringBuilder("ContentCaptureEvent[type=")
+ .append(getTypeAsString(mType)).append(", time=").append(mEventTime);
+ if (mFlags > 0) {
+ string.append(", flags=").append(mFlags);
+ }
+ return string.append(']').toString();
+ }
+
+ @Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int flags) {
- // TODO(b/111276913): implement
+ parcel.writeInt(mType);
+ parcel.writeLong(mEventTime);
+ parcel.writeInt(mFlags);
}
public static final Parcelable.Creator<ContentCaptureEvent> CREATOR =
@@ -164,8 +189,10 @@
@Override
public ContentCaptureEvent createFromParcel(Parcel parcel) {
- // TODO(b/111276913): implement
- return null;
+ final int type = parcel.readInt();
+ final long eventTime = parcel.readLong();
+ final int flags = parcel.readInt();
+ return new ContentCaptureEvent(type, eventTime, flags);
}
@Override
@@ -173,4 +200,27 @@
return new ContentCaptureEvent[size];
}
};
+
+
+ /** @hide */
+ public static String getTypeAsString(@EventType int type) {
+ switch (type) {
+ case TYPE_ACTIVITY_STARTED:
+ return "ACTIVITY_STARTED";
+ case TYPE_ACTIVITY_RESUMED:
+ return "ACTIVITY_RESUMED";
+ case TYPE_ACTIVITY_PAUSED:
+ return "ACTIVITY_PAUSED";
+ case TYPE_ACTIVITY_STOPPED:
+ return "ACTIVITY_STOPPED";
+ case TYPE_VIEW_ADDED:
+ return "VIEW_ADDED";
+ case TYPE_VIEW_REMOVED:
+ return "VIEW_REMOVED";
+ case TYPE_VIEW_TEXT_CHANGED:
+ return "VIEW_TEXT_CHANGED";
+ default:
+ return "UKNOWN_TYPE: " + type;
+ }
+ }
}
diff --git a/core/java/android/view/intelligence/IIntelligenceManager.aidl b/core/java/android/view/intelligence/IIntelligenceManager.aidl
index f4901c3..7bbe99a 100644
--- a/core/java/android/view/intelligence/IIntelligenceManager.aidl
+++ b/core/java/android/view/intelligence/IIntelligenceManager.aidl
@@ -17,9 +17,15 @@
package android.view.intelligence;
import android.content.ComponentName;
+
import android.os.IBinder;
+
+import android.view.intelligence.ContentCaptureEvent;
+
import com.android.internal.os.IResultReceiver;
+import java.util.List;
+
/**
* {@hide}
*/
@@ -33,6 +39,14 @@
/**
* Finishes a session.
*/
+ // TODO(b/111276913): pass just (global) session id
void finishSession(int userId, IBinder activityToken, in ComponentName componentName,
int localSessionId, int globalSessionId);
+
+ /**
+ * Sends a batch of events
+ */
+ // TODO(b/111276913): pass just (global) session id
+ void sendEvents(int userId, IBinder activityToken, in ComponentName componentName,
+ int localSessionId, int globalSessionId, in List<ContentCaptureEvent> events);
}
diff --git a/core/java/android/view/intelligence/IntelligenceManager.java b/core/java/android/view/intelligence/IntelligenceManager.java
index b1d06f7..a30d77e 100644
--- a/core/java/android/view/intelligence/IntelligenceManager.java
+++ b/core/java/android/view/intelligence/IntelligenceManager.java
@@ -24,13 +24,17 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.Log;
+import android.view.intelligence.ContentCaptureEvent.EventType;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.Preconditions;
import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.List;
import java.util.Set;
/**
@@ -161,6 +165,38 @@
}
}
+ /**
+ * Used for intermediate events (i.e, other than created and destroyed).
+ *
+ * @hide
+ */
+ public void onActivityLifecycleEvent(@EventType int type) {
+ if (!isContentCaptureEnabled()) return;
+
+ //TODO(b/111276913): should buffer event (and call service on handler thread), instead of
+ // calling right away
+ final ContentCaptureEvent event = new ContentCaptureEvent(type, SystemClock.uptimeMillis(),
+ 0);
+ final List<ContentCaptureEvent> events = Arrays.asList(event);
+
+ synchronized (mLock) {
+ //TODO(b/111276913): check session state; for example, how to handle if it's waiting for
+ // remote id
+
+ if (VERBOSE) {
+ Log.v(TAG, "onActivityLifecycleEvent() for " + mComponentName.flattenToShortString()
+ + ": " + ContentCaptureEvent.getTypeAsString(type));
+ }
+
+ try {
+ mService.sendEvents(mContext.getUserId(), mApplicationToken, mComponentName,
+ mLocalSessionId, mRemoteSessionId, events);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
/** @hide */
public void onActivityDestroyed() {
if (!isContentCaptureEnabled()) return;
diff --git a/services/autofill/java/com/android/server/intelligence/ContentCaptureSession.java b/services/autofill/java/com/android/server/intelligence/ContentCaptureSession.java
index a437a39..14b28d3 100644
--- a/services/autofill/java/com/android/server/intelligence/ContentCaptureSession.java
+++ b/services/autofill/java/com/android/server/intelligence/ContentCaptureSession.java
@@ -23,12 +23,14 @@
import android.service.intelligence.InteractionContext;
import android.service.intelligence.InteractionSessionId;
import android.util.Slog;
+import android.view.intelligence.ContentCaptureEvent;
import com.android.internal.annotations.GuardedBy;
import com.android.server.AbstractRemoteService;
import com.android.server.intelligence.RemoteIntelligenceService.RemoteIntelligenceServiceCallbacks;
import java.io.PrintWriter;
+import java.util.List;
final class ContentCaptureSession implements RemoteIntelligenceServiceCallbacks {
@@ -66,6 +68,13 @@
}
/**
+ * Notifies the {@link IntelligenceService} of a batch of events.
+ */
+ public void sendEventsLocked(List<ContentCaptureEvent> events) {
+ mRemoteService.onContentCaptureEventsRequest(mId, events);
+ }
+
+ /**
* Cleans up the session and remove itself from the service.
*
* @param notifyRemoteService whether it should trigger a {@link
@@ -95,10 +104,10 @@
}
@Override // from RemoteScreenObservationServiceCallbacks
- public void onSessionLifecycleRequestFailureOrTimeout(boolean timedOut) {
+ public void onFailureOrTimeout(boolean timedOut) {
// TODO(b/111276913): log metrics on whether timed out or not
if (mService.isDebug()) {
- Slog.d(TAG, "onSessionLifecycleRequestFailure(" + mId + "): timed out=" + timedOut);
+ Slog.d(TAG, "onFailureOrTimeout(" + mId + "): timed out=" + timedOut);
}
synchronized (mLock) {
removeSelfLocked(/* notifyRemoteService= */ false);
diff --git a/services/autofill/java/com/android/server/intelligence/IntelligenceManagerService.java b/services/autofill/java/com/android/server/intelligence/IntelligenceManagerService.java
index 4ea9036..d67c15b 100644
--- a/services/autofill/java/com/android/server/intelligence/IntelligenceManagerService.java
+++ b/services/autofill/java/com/android/server/intelligence/IntelligenceManagerService.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.os.IBinder;
import android.os.UserManager;
+import android.view.intelligence.ContentCaptureEvent;
import android.view.intelligence.IIntelligenceManager;
import com.android.internal.annotations.GuardedBy;
@@ -35,6 +36,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.List;
/**
* A service used to observe the contents of the screen.
@@ -104,6 +106,18 @@
}
@Override
+ public void sendEvents(int userId, @NonNull IBinder activityToken,
+ @NonNull ComponentName componentName, int localSessionId, int globalSessionId,
+ List<ContentCaptureEvent> events) {
+ Preconditions.checkNotNull(events);
+
+ synchronized (mLock) {
+ final IntelligencePerUserService service = getServiceForUserLocked(userId);
+ service.sendEventsLocked(componentName, events);
+ }
+ }
+
+ @Override
public void finishSession(int userId, @NonNull IBinder activityToken,
@NonNull ComponentName componentName, int localSessionId, int globalSessionId) {
Preconditions.checkNotNull(activityToken);
diff --git a/services/autofill/java/com/android/server/intelligence/IntelligencePerUserService.java b/services/autofill/java/com/android/server/intelligence/IntelligencePerUserService.java
index b62b239..1d98819 100644
--- a/services/autofill/java/com/android/server/intelligence/IntelligencePerUserService.java
+++ b/services/autofill/java/com/android/server/intelligence/IntelligencePerUserService.java
@@ -27,12 +27,14 @@
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Slog;
+import android.view.intelligence.ContentCaptureEvent;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.IResultReceiver;
import com.android.server.AbstractPerUserSystemService;
import java.io.PrintWriter;
+import java.util.List;
/**
* Per-user instance of {@link IntelligenceManagerService}.
@@ -142,6 +144,20 @@
}
@GuardedBy("mLock")
+ public void sendEventsLocked(@NonNull ComponentName componentName,
+ @NonNull List<ContentCaptureEvent> events) {
+ final ContentCaptureSession session = mSessions.get(componentName);
+ if (session == null) {
+ Slog.w(TAG, "sendEventsLocked(): no session for " + componentName);
+ return;
+ }
+ if (mMaster.verbose) {
+ Slog.v(TAG, "sendEventsLocked(): comp=" + componentName + "; events =" + events.size());
+ }
+ session.sendEventsLocked(events);
+ }
+
+ @GuardedBy("mLock")
public void removeSessionLocked(@NonNull ComponentName key) {
mSessions.remove(key);
}
@@ -171,4 +187,5 @@
Slog.w(TAG, "Error async reporting result to client: " + e);
}
}
+
}
diff --git a/services/autofill/java/com/android/server/intelligence/RemoteIntelligenceService.java b/services/autofill/java/com/android/server/intelligence/RemoteIntelligenceService.java
index ee66b4e..9d241fb 100644
--- a/services/autofill/java/com/android/server/intelligence/RemoteIntelligenceService.java
+++ b/services/autofill/java/com/android/server/intelligence/RemoteIntelligenceService.java
@@ -27,9 +27,12 @@
import android.service.intelligence.InteractionSessionId;
import android.text.format.DateUtils;
import android.util.Slog;
+import android.view.intelligence.ContentCaptureEvent;
import com.android.server.AbstractRemoteService;
+import java.util.List;
+
final class RemoteIntelligenceService extends AbstractRemoteService {
private static final String TAG = "RemoteIntelligenceService";
@@ -79,44 +82,89 @@
scheduleRequest(new PendingSessionLifecycleRequest(this, context, sessionId));
}
- private static final class PendingSessionLifecycleRequest
+ /**
+ * Called by {@link ContentCaptureSession} to send a batch of events to the service.
+ */
+ public void onContentCaptureEventsRequest(@NonNull InteractionSessionId sessionId,
+ @NonNull List<ContentCaptureEvent> events) {
+ cancelScheduledUnbind();
+ scheduleRequest(new PendingOnContentCaptureEventsRequest(this, sessionId, events));
+ }
+
+
+ private abstract static class MyPendingRequest
extends PendingRequest<RemoteIntelligenceService> {
+ protected final InteractionSessionId mSessionId;
- private final InteractionContext mContext;
- private final InteractionSessionId mSessionId;
-
- protected PendingSessionLifecycleRequest(@NonNull RemoteIntelligenceService service,
- @Nullable InteractionContext context, @NonNull InteractionSessionId sessionId) {
+ private MyPendingRequest(@NonNull RemoteIntelligenceService service,
+ @NonNull InteractionSessionId sessionId) {
super(service);
- mContext = context;
mSessionId = sessionId;
}
@Override // from PendingRequest
- public void run() {
+ protected final void onTimeout(RemoteIntelligenceService remoteService) {
+ Slog.w(TAG, "timed out handling " + getClass().getSimpleName() + " for "
+ + mSessionId);
+ remoteService.mCallbacks.onFailureOrTimeout(/* timedOut= */ true);
+ }
+
+ @Override // from PendingRequest
+ public final void run() {
final RemoteIntelligenceService remoteService = getService();
if (remoteService != null) {
try {
- remoteService.mService.onSessionLifecycle(mContext, mSessionId);
+ myRun(remoteService);
+ // We don't expect the service to call us back, so we finish right away.
+ finish();
} catch (RemoteException e) {
- Slog.w(TAG, "exception handling PendingSessionLifecycleRequest for "
+ Slog.w(TAG, "exception handling " + getClass().getSimpleName() + " for "
+ mSessionId + ": " + e);
- remoteService.mCallbacks
- .onSessionLifecycleRequestFailureOrTimeout(/* timedOut= */ false);
+ remoteService.mCallbacks.onFailureOrTimeout(/* timedOut= */ false);
}
}
}
- @Override // from PendingRequest
- protected void onTimeout(RemoteIntelligenceService remoteService) {
- Slog.w(TAG, "timed out handling PendingSessionLifecycleRequest for "
- + mSessionId);
- remoteService.mCallbacks
- .onSessionLifecycleRequestFailureOrTimeout(/* timedOut= */ true);
+ protected abstract void myRun(@NonNull RemoteIntelligenceService service)
+ throws RemoteException;
+
+ }
+
+ private static final class PendingSessionLifecycleRequest extends MyPendingRequest {
+
+ private final InteractionContext mContext;
+
+ protected PendingSessionLifecycleRequest(@NonNull RemoteIntelligenceService service,
+ @Nullable InteractionContext context, @NonNull InteractionSessionId sessionId) {
+ super(service, sessionId);
+ mContext = context;
+ }
+
+ @Override // from MyPendingRequest
+ public void myRun(@NonNull RemoteIntelligenceService remoteService) throws RemoteException {
+ remoteService.mService.onSessionLifecycle(mContext, mSessionId);
+ }
+ }
+
+ private static final class PendingOnContentCaptureEventsRequest extends MyPendingRequest {
+
+ private final List<ContentCaptureEvent> mEvents;
+
+ protected PendingOnContentCaptureEventsRequest(@NonNull RemoteIntelligenceService service,
+ @NonNull InteractionSessionId sessionId,
+ @NonNull List<ContentCaptureEvent> events) {
+ super(service, sessionId);
+ mEvents = events;
+ }
+
+ @Override // from MyPendingRequest
+ public void myRun(@NonNull RemoteIntelligenceService remoteService) throws RemoteException {
+ remoteService.mService.onContentCaptureEvents(mSessionId, mEvents);
}
}
public interface RemoteIntelligenceServiceCallbacks extends VultureCallback {
- void onSessionLifecycleRequestFailureOrTimeout(boolean timedOut);
+ // To keep it simple, we use the same callback for all failures / timeouts.
+ void onFailureOrTimeout(boolean timedOut);
}
}
diff --git a/services/core/java/com/android/server/AbstractRemoteService.java b/services/core/java/com/android/server/AbstractRemoteService.java
index 1d3a34c..181d7fd 100644
--- a/services/core/java/com/android/server/AbstractRemoteService.java
+++ b/services/core/java/com/android/server/AbstractRemoteService.java
@@ -366,11 +366,12 @@
mCompleted = true;
}
- Slog.w(mTag, "timed out");
final S remoteService = mWeakService.get();
if (remoteService != null) {
- Slog.w(mTag, " timed out after " + service.getRemoteRequestMillis() + " ms");
+ Slog.w(mTag, "timed out after " + service.getRemoteRequestMillis() + " ms");
onTimeout(remoteService);
+ } else {
+ Slog.w(mTag, "timed out (no service)");
}
};
mServiceHandler.postAtTime(mTimeoutTrigger,