Skip to content
Snippets Groups Projects
Commit 4a24734d authored by Jeff Sharkey's avatar Jeff Sharkey Committed by Android (Google) Code Review
Browse files

Merge "First pass of "fake" services on Ravenwood." into main

parents bb4c7541 16f7cdf2
No related branches found
No related tags found
No related merge requests found
Showing
with 343 additions and 17 deletions
......@@ -144,6 +144,16 @@ java_library {
jarjar_rules: ":ravenwood-services-jarjar-rules",
}
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,
......@@ -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",
......
......@@ -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) {
......
......@@ -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());
......
......@@ -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);
......@@ -130,6 +133,18 @@ public class HandlerThread extends Thread {
return mHandler;
}
/**
* @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>
......
......@@ -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
......@@ -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;
}
}
......@@ -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);
......
......@@ -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;
......
......@@ -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();
......@@ -157,6 +159,15 @@ public class RavenwoodRule implements TestRule {
return this;
}
/**
* 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.
......
......@@ -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
......
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"],
}
/*
* 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();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment