summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jeff Sharkey <jsharkey@google.com> 2024-02-21 09:46:20 -0700
committer Jeff Sharkey <jsharkey@google.com> 2024-02-27 10:43:39 -0700
commit16f7cdf2e01e158efe8d8334a15e9e20e59de929 (patch)
tree9748a0e79a8c2c01d545defedecec1e3bef87298
parent2f6d34303f04be05b600d3d9a59e4b3441be7784 (diff)
First pass of "fake" services on Ravenwood.
One of our eventual goals with Ravenwood is to support usage of system services from test code. A recent change added support for "real" services on Ravenwood, and this change expands that to add support for "fake" services. Some services are so tangled with dependencies it would take a long time until we'd be able to run their "real" code. Also, some services with deep hardware dependencies that it can be difficult to add new abstraction layers to support Ravenwood. Finally, we want to support service owners that only have resources to meet the "Pareto principle", where enabling 20% of their functionality on Ravenwood is enough to unblock 80% of test use-cases. Thus, we're supporting teams bringing either "real" or "fake" implementations of their services. Since all test interactions still go through published FooManager and FooManagerInternal-style interfaces, teams can start with a "fake" and slowly transition to using more of their "real" code over time, without having to update test clients. And as a final reminder, because Ravenwood requires test suites like CTS, we're already verifying that "fake" services behave similarly to the "real" services on physical devices. Bug: 325506297 Test: atest CtsContentTestCasesRavenwood Change-Id: I8ed5bd030e03143c15cb9fa945bbdcb0b412611e
-rw-r--r--Ravenwood.bp11
-rw-r--r--core/java/android/content/ClipData.java34
-rw-r--r--core/java/android/content/ClipboardManager.java3
-rw-r--r--core/java/android/os/HandlerThread.java17
-rw-r--r--ravenwood/framework-minus-apex-ravenwood-policies.txt2
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java83
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java7
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java6
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java11
-rw-r--r--ravenwood/ravenwood-annotation-allowed-classes.txt1
-rw-r--r--services/fakes/Android.bp20
-rw-r--r--services/fakes/java/com/android/server/FakeClipboardService.java165
12 files changed, 343 insertions, 17 deletions
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 4e360759c137..c73e04896173 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -145,6 +145,16 @@ java_library {
}
java_library {
+ name: "services.fakes.ravenwood-jarjar",
+ installable: false,
+ srcs: [":services.fakes-sources"],
+ libs: [
+ "services.core.ravenwood",
+ ],
+ jarjar_rules: ":ravenwood-services-jarjar-rules",
+}
+
+java_library {
name: "mockito-ravenwood-prebuilt",
installable: false,
static_libs: [
@@ -189,6 +199,7 @@ android_ravenwood_libgroup {
"ravenwood-helper-runtime",
"hoststubgen-helper-runtime.ravenwood",
"services.core.ravenwood-jarjar",
+ "services.fakes.ravenwood-jarjar",
// Provide runtime versions of utils linked in below
"junit",
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 728c350bfb51..b42133939f28 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -169,6 +169,8 @@ import java.util.List;
*/
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class ClipData implements Parcelable {
+ private static final String TAG = "ClipData";
+
static final String[] MIMETYPES_TEXT_PLAIN = new String[] {
ClipDescription.MIMETYPE_TEXT_PLAIN };
static final String[] MIMETYPES_TEXT_HTML = new String[] {
@@ -476,7 +478,6 @@ public class ClipData implements Parcelable {
* @return Returns the item's textual representation.
*/
//BEGIN_INCLUDE(coerceToText)
- @android.ravenwood.annotation.RavenwoodThrow
public CharSequence coerceToText(Context context) {
// If this Item has an explicit textual value, simply return that.
CharSequence text = getText();
@@ -484,13 +485,20 @@ public class ClipData implements Parcelable {
return text;
}
+ // Gracefully handle cases where resolver isn't available
+ ContentResolver resolver = null;
+ try {
+ resolver = context.getContentResolver();
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to obtain ContentResolver: " + e);
+ }
+
// If this Item has a URI value, try using that.
Uri uri = getUri();
- if (uri != null) {
+ if (uri != null && resolver != null) {
// First see if the URI can be opened as a plain text stream
// (of any sub-type). If so, this is the best textual
// representation for it.
- final ContentResolver resolver = context.getContentResolver();
AssetFileDescriptor descr = null;
FileInputStream stream = null;
InputStreamReader reader = null;
@@ -499,7 +507,7 @@ public class ClipData implements Parcelable {
// Ask for a stream of the desired type.
descr = resolver.openTypedAssetFileDescriptor(uri, "text/*", null);
} catch (SecurityException e) {
- Log.w("ClipData", "Failure opening stream", e);
+ Log.w(TAG, "Failure opening stream", e);
} catch (FileNotFoundException|RuntimeException e) {
// Unable to open content URI as text... not really an
// error, just something to ignore.
@@ -519,7 +527,7 @@ public class ClipData implements Parcelable {
return builder.toString();
} catch (IOException e) {
// Something bad has happened.
- Log.w("ClipData", "Failure loading text", e);
+ Log.w(TAG, "Failure loading text", e);
return e.toString();
}
}
@@ -528,7 +536,8 @@ public class ClipData implements Parcelable {
IoUtils.closeQuietly(stream);
IoUtils.closeQuietly(reader);
}
-
+ }
+ if (uri != null) {
// If we couldn't open the URI as a stream, use the URI itself as a textual
// representation (but not for "content", "android.resource" or "file" schemes).
final String scheme = uri.getScheme();
@@ -704,7 +713,7 @@ public class ClipData implements Parcelable {
}
} catch (SecurityException e) {
- Log.w("ClipData", "Failure opening stream", e);
+ Log.w(TAG, "Failure opening stream", e);
} catch (FileNotFoundException e) {
// Unable to open content URI as text... not really an
@@ -712,7 +721,7 @@ public class ClipData implements Parcelable {
} catch (IOException e) {
// Something bad has happened.
- Log.w("ClipData", "Failure loading text", e);
+ Log.w(TAG, "Failure loading text", e);
return Html.escapeHtml(e.toString());
} finally {
@@ -1123,7 +1132,7 @@ public class ClipData implements Parcelable {
*
* @hide
*/
- @android.ravenwood.annotation.RavenwoodThrow
+ @android.ravenwood.annotation.RavenwoodKeep
public void prepareToLeaveProcess(boolean leavingPackage) {
// Assume that callers are going to be granting permissions
prepareToLeaveProcess(leavingPackage, Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -1134,7 +1143,7 @@ public class ClipData implements Parcelable {
*
* @hide
*/
- @android.ravenwood.annotation.RavenwoodThrow
+ @android.ravenwood.annotation.RavenwoodReplace
public void prepareToLeaveProcess(boolean leavingPackage, int intentFlags) {
final int size = mItems.size();
for (int i = 0; i < size; i++) {
@@ -1154,6 +1163,11 @@ public class ClipData implements Parcelable {
}
}
+ /** @hide */
+ public void prepareToLeaveProcess$ravenwood(boolean leavingPackage, int intentFlags) {
+ // No process boundaries on Ravenwood; ignored
+ }
+
/** {@hide} */
@android.ravenwood.annotation.RavenwoodThrow
public void prepareToEnterProcess(AttributionSource source) {
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index 107f1078b11e..2fabcbae9bbb 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -50,6 +50,7 @@ import java.util.Objects;
* </div>
*/
@SystemService(Context.CLIPBOARD_SERVICE)
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class ClipboardManager extends android.text.ClipboardManager {
/**
@@ -143,6 +144,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION)
+ @android.ravenwood.annotation.RavenwoodThrow
public boolean areClipboardAccessNotificationsEnabled() {
try {
return mService.areClipboardAccessNotificationsEnabledForUser(mContext.getUserId());
@@ -159,6 +161,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION)
+ @android.ravenwood.annotation.RavenwoodThrow
public void setClipboardAccessNotificationsEnabled(boolean enable) {
try {
mService.setClipboardAccessNotificationsEnabledForUser(enable, mContext.getUserId());
diff --git a/core/java/android/os/HandlerThread.java b/core/java/android/os/HandlerThread.java
index 36730cb07344..f852d3cd69b2 100644
--- a/core/java/android/os/HandlerThread.java
+++ b/core/java/android/os/HandlerThread.java
@@ -19,6 +19,8 @@ package android.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import java.util.concurrent.Executor;
+
/**
* A {@link Thread} that has a {@link Looper}.
* The {@link Looper} can then be used to create {@link Handler}s.
@@ -30,7 +32,8 @@ public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
- private @Nullable Handler mHandler;
+ private volatile @Nullable Handler mHandler;
+ private volatile @Nullable Executor mExecutor;
public HandlerThread(String name) {
super(name);
@@ -131,6 +134,18 @@ public class HandlerThread extends Thread {
}
/**
+ * @return a shared {@link Executor} associated with this thread
+ * @hide
+ */
+ @NonNull
+ public Executor getThreadExecutor() {
+ if (mExecutor == null) {
+ mExecutor = new HandlerExecutor(getThreadHandler());
+ }
+ return mExecutor;
+ }
+
+ /**
* Quits the handler thread's looper.
* <p>
* Causes the handler thread's looper to terminate without processing any
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index 6b6736476210..371c3acab144 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -55,3 +55,5 @@ class android.content.Context stub
method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; stub
class android.content.pm.PackageManager stub
method <init> ()V stub
+class android.text.ClipboardManager stub
+ method <init> ()V stub
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
index 3668b03e58d3..c17d0903f856 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java
@@ -16,18 +16,28 @@
package android.platform.test.ravenwood;
+import android.content.ClipboardManager;
import android.content.Context;
import android.hardware.ISerialManager;
import android.hardware.SerialManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
import android.os.PermissionEnforcer;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.test.mock.MockContext;
import android.util.ArrayMap;
import android.util.Singleton;
+import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.function.Supplier;
public class RavenwoodContext extends MockContext {
+ private final String mPackageName;
+ private final HandlerThread mMainThread;
+
private final RavenwoodPermissionEnforcer mEnforcer = new RavenwoodPermissionEnforcer();
private final ArrayMap<Class<?>, String> mClassToName = new ArrayMap<>();
@@ -39,7 +49,13 @@ public class RavenwoodContext extends MockContext {
mNameToFactory.put(serviceName, serviceSupplier);
}
- public RavenwoodContext() {
+ public RavenwoodContext(String packageName, HandlerThread mainThread) {
+ mPackageName = packageName;
+ mMainThread = mainThread;
+
+ registerService(ClipboardManager.class,
+ Context.CLIPBOARD_SERVICE, asSingleton(() ->
+ new ClipboardManager(this, getMainThreadHandler())));
registerService(PermissionEnforcer.class,
Context.PERMISSION_ENFORCER_SERVICE, () -> mEnforcer);
registerService(SerialManager.class,
@@ -73,18 +89,79 @@ public class RavenwoodContext extends MockContext {
}
}
+ @Override
+ public Looper getMainLooper() {
+ Objects.requireNonNull(mMainThread,
+ "Test must request setProvideMainThread() via RavenwoodRule");
+ return mMainThread.getLooper();
+ }
+
+ @Override
+ public Handler getMainThreadHandler() {
+ Objects.requireNonNull(mMainThread,
+ "Test must request setProvideMainThread() via RavenwoodRule");
+ return mMainThread.getThreadHandler();
+ }
+
+ @Override
+ public Executor getMainExecutor() {
+ Objects.requireNonNull(mMainThread,
+ "Test must request setProvideMainThread() via RavenwoodRule");
+ return mMainThread.getThreadExecutor();
+ }
+
+ @Override
+ public String getPackageName() {
+ return Objects.requireNonNull(mPackageName,
+ "Test must request setPackageName() via RavenwoodRule");
+ }
+
+ @Override
+ public String getOpPackageName() {
+ return Objects.requireNonNull(mPackageName,
+ "Test must request setPackageName() via RavenwoodRule");
+ }
+
+ @Override
+ public String getAttributionTag() {
+ return null;
+ }
+
+ @Override
+ public UserHandle getUser() {
+ return android.os.UserHandle.of(android.os.UserHandle.myUserId());
+ }
+
+ @Override
+ public int getUserId() {
+ return android.os.UserHandle.myUserId();
+ }
+
+ @Override
+ public int getDeviceId() {
+ return Context.DEVICE_ID_DEFAULT;
+ }
+
/**
* Wrap the given {@link Supplier} to become a memoized singleton.
*/
- private static <T> Supplier<T> asSingleton(Supplier<T> supplier) {
+ private static <T> Supplier<T> asSingleton(ThrowingSupplier<T> supplier) {
final Singleton<T> singleton = new Singleton<>() {
@Override
protected T create() {
- return supplier.get();
+ try {
+ return supplier.get();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
}
};
return () -> {
return singleton.get();
};
}
+
+ public interface ThrowingSupplier<T> {
+ T get() throws Exception;
+ }
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index 231cce95f353..56a3c64a5750 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -110,13 +110,16 @@ public class RavenwoodRuleImpl {
ActivityManager.init$ravenwood(rule.mCurrentUser);
+ final HandlerThread main;
if (rule.mProvideMainThread) {
- final HandlerThread main = new HandlerThread(MAIN_THREAD_NAME);
+ main = new HandlerThread(MAIN_THREAD_NAME);
main.start();
Looper.setMainLooperForTest(main.getLooper());
+ } else {
+ main = null;
}
- rule.mContext = new RavenwoodContext();
+ rule.mContext = new RavenwoodContext(rule.mPackageName, main);
rule.mInstrumentation = new Instrumentation();
rule.mInstrumentation.basicInit(rule.mContext);
InstrumentationRegistry.registerInstance(rule.mInstrumentation, Bundle.EMPTY);
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
index bb280f47ccd9..3de96c0990ea 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
@@ -16,6 +16,7 @@
package android.platform.test.ravenwood;
+import android.content.ClipboardManager;
import android.hardware.SerialManager;
import android.os.SystemClock;
import android.util.ArrayMap;
@@ -40,7 +41,10 @@ public class RavenwoodSystemServer {
// authors to exhaustively declare all transitive services
static {
- sKnownServices.put(SerialManager.class, "com.android.server.SerialService$Lifecycle");
+ sKnownServices.put(ClipboardManager.class,
+ "com.android.server.FakeClipboardService$Lifecycle");
+ sKnownServices.put(SerialManager.class,
+ "com.android.server.SerialService$Lifecycle");
}
private static TimingsTraceAndSlog sTimings;
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index a8c24fcbd7e0..a520d4ccafa1 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -121,6 +121,8 @@ public class RavenwoodRule implements TestRule {
int mUid = NOBODY_UID;
int mPid = sNextPid.getAndIncrement();
+ String mPackageName;
+
boolean mProvideMainThread = false;
final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
@@ -158,6 +160,15 @@ public class RavenwoodRule implements TestRule {
}
/**
+ * Configure the identity of this process to be the given package name for the duration
+ * of the test. Has no effect on non-Ravenwood environments.
+ */
+ public Builder setPackageName(/* @NonNull */ String packageName) {
+ mRule.mPackageName = Objects.requireNonNull(packageName);
+ return this;
+ }
+
+ /**
* Configure a "main" thread to be available for the duration of the test, as defined
* by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments.
*/
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index eb3c55cb4ff6..9b4d378cc7b7 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -186,6 +186,7 @@ android.os.WorkSource
android.content.ClipData
android.content.ClipData$Item
android.content.ClipDescription
+android.content.ClipboardManager
android.content.ComponentName
android.content.ContentUris
android.content.ContentValues
diff --git a/services/fakes/Android.bp b/services/fakes/Android.bp
new file mode 100644
index 000000000000..148054b31e89
--- /dev/null
+++ b/services/fakes/Android.bp
@@ -0,0 +1,20 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+// NOTE: These "fake" services are intended for use under the Ravenwood
+// deviceless test environment, and should *not* be included in the build
+// artifacts for physical devices, as they already supply "real" services
+filegroup {
+ name: "services.fakes-sources",
+ srcs: [
+ "java/**/*.java",
+ ],
+ path: "java",
+ visibility: ["//frameworks/base"],
+}
diff --git a/services/fakes/java/com/android/server/FakeClipboardService.java b/services/fakes/java/com/android/server/FakeClipboardService.java
new file mode 100644
index 000000000000..01016219e73d
--- /dev/null
+++ b/services/fakes/java/com/android/server/FakeClipboardService.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2024 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.server;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.IClipboard;
+import android.content.IOnPrimaryClipChangedListener;
+import android.os.PermissionEnforcer;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Fake implementation of {@code ClipboardManager} since the real implementation is tightly
+ * coupled with many other internal services.
+ */
+public class FakeClipboardService extends IClipboard.Stub {
+ private final RemoteCallbackList<IOnPrimaryClipChangedListener> mListeners =
+ new RemoteCallbackList<>();
+
+ private ClipData mPrimaryClip;
+ private String mPrimaryClipSource;
+
+ public FakeClipboardService(Context context) {
+ super(PermissionEnforcer.fromContext(context));
+ }
+
+ public static class Lifecycle extends SystemService {
+ private FakeClipboardService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mService = new FakeClipboardService(getContext());
+ publishBinderService(Context.CLIPBOARD_SERVICE, mService);
+ }
+ }
+
+ private static void checkArguments(int userId, int deviceId) {
+ Preconditions.checkArgument(userId == UserHandle.USER_SYSTEM,
+ "Fake only supports USER_SYSTEM user");
+ Preconditions.checkArgument(deviceId == Context.DEVICE_ID_DEFAULT,
+ "Fake only supports DEVICE_ID_DEFAULT device");
+ }
+
+ private void dispatchPrimaryClipChanged() {
+ mListeners.broadcast((listener) -> {
+ try {
+ listener.dispatchPrimaryClipChanged();
+ } catch (RemoteException ignored) {
+ }
+ });
+ }
+
+ @Override
+ public void setPrimaryClip(ClipData clip, String callingPackage, String attributionTag,
+ int userId, int deviceId) {
+ checkArguments(userId, deviceId);
+ mPrimaryClip = clip;
+ mPrimaryClipSource = callingPackage;
+ dispatchPrimaryClipChanged();
+ }
+
+ @Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.SET_CLIP_SOURCE)
+ public void setPrimaryClipAsPackage(ClipData clip, String callingPackage, String attributionTag,
+ int userId, int deviceId, String sourcePackage) {
+ setPrimaryClipAsPackage_enforcePermission();
+ checkArguments(userId, deviceId);
+ mPrimaryClip = clip;
+ mPrimaryClipSource = sourcePackage;
+ dispatchPrimaryClipChanged();
+ }
+
+ @Override
+ public void clearPrimaryClip(String callingPackage, String attributionTag, int userId,
+ int deviceId) {
+ checkArguments(userId, deviceId);
+ mPrimaryClip = null;
+ mPrimaryClipSource = null;
+ dispatchPrimaryClipChanged();
+ }
+
+ @Override
+ public ClipData getPrimaryClip(String pkg, String attributionTag, int userId, int deviceId) {
+ checkArguments(userId, deviceId);
+ return mPrimaryClip;
+ }
+
+ @Override
+ public ClipDescription getPrimaryClipDescription(String callingPackage, String attributionTag,
+ int userId, int deviceId) {
+ checkArguments(userId, deviceId);
+ return (mPrimaryClip != null) ? mPrimaryClip.getDescription() : null;
+ }
+
+ @Override
+ public boolean hasPrimaryClip(String callingPackage, String attributionTag, int userId,
+ int deviceId) {
+ checkArguments(userId, deviceId);
+ return mPrimaryClip != null;
+ }
+
+ @Override
+ public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
+ String callingPackage, String attributionTag, int userId, int deviceId) {
+ checkArguments(userId, deviceId);
+ mListeners.register(listener);
+ }
+
+ @Override
+ public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
+ String callingPackage, String attributionTag, int userId, int deviceId) {
+ checkArguments(userId, deviceId);
+ mListeners.unregister(listener);
+ }
+
+ @Override
+ public boolean hasClipboardText(String callingPackage, String attributionTag, int userId,
+ int deviceId) {
+ checkArguments(userId, deviceId);
+ return (mPrimaryClip != null) && (mPrimaryClip.getItemCount() > 0)
+ && (mPrimaryClip.getItemAt(0).getText() != null);
+ }
+
+ @Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.SET_CLIP_SOURCE)
+ public String getPrimaryClipSource(String callingPackage, String attributionTag, int userId,
+ int deviceId) {
+ getPrimaryClipSource_enforcePermission();
+ checkArguments(userId, deviceId);
+ return mPrimaryClipSource;
+ }
+
+ @Override
+ public boolean areClipboardAccessNotificationsEnabledForUser(int userId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setClipboardAccessNotificationsEnabledForUser(boolean enable, int userId) {
+ throw new UnsupportedOperationException();
+ }
+}