1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server;
18 
19 import android.content.ClipData;
20 import android.content.ClipDescription;
21 import android.content.Context;
22 import android.content.IClipboard;
23 import android.content.IOnPrimaryClipChangedListener;
24 import android.os.PermissionEnforcer;
25 import android.os.RemoteCallbackList;
26 import android.os.RemoteException;
27 import android.os.UserHandle;
28 
29 import com.android.internal.util.Preconditions;
30 
31 /**
32  * Fake implementation of {@code ClipboardManager} since the real implementation is tightly
33  * coupled with many other internal services.
34  */
35 public class FakeClipboardService extends IClipboard.Stub {
36     private final RemoteCallbackList<IOnPrimaryClipChangedListener> mListeners =
37             new RemoteCallbackList<>();
38 
39     private ClipData mPrimaryClip;
40     private String mPrimaryClipSource;
41 
FakeClipboardService(Context context)42     public FakeClipboardService(Context context) {
43         super(PermissionEnforcer.fromContext(context));
44     }
45 
46     public static class Lifecycle extends SystemService {
47         private FakeClipboardService mService;
48 
Lifecycle(Context context)49         public Lifecycle(Context context) {
50             super(context);
51         }
52 
53         @Override
onStart()54         public void onStart() {
55             mService = new FakeClipboardService(getContext());
56             publishBinderService(Context.CLIPBOARD_SERVICE, mService);
57         }
58     }
59 
checkArguments(int userId, int deviceId)60     private static void checkArguments(int userId, int deviceId) {
61         Preconditions.checkArgument(userId == UserHandle.USER_SYSTEM,
62                 "Fake only supports USER_SYSTEM user");
63         Preconditions.checkArgument(deviceId == Context.DEVICE_ID_DEFAULT,
64                 "Fake only supports DEVICE_ID_DEFAULT device");
65     }
66 
dispatchPrimaryClipChanged()67     private void dispatchPrimaryClipChanged() {
68         mListeners.broadcast((listener) -> {
69             try {
70                 listener.dispatchPrimaryClipChanged();
71             } catch (RemoteException ignored) {
72             }
73         });
74     }
75 
76     @Override
setPrimaryClip(ClipData clip, String callingPackage, String attributionTag, int userId, int deviceId)77     public void setPrimaryClip(ClipData clip, String callingPackage, String attributionTag,
78             int userId, int deviceId) {
79         checkArguments(userId, deviceId);
80         mPrimaryClip = clip;
81         mPrimaryClipSource = callingPackage;
82         dispatchPrimaryClipChanged();
83     }
84 
85     @Override
86     @android.annotation.EnforcePermission(android.Manifest.permission.SET_CLIP_SOURCE)
setPrimaryClipAsPackage(ClipData clip, String callingPackage, String attributionTag, int userId, int deviceId, String sourcePackage)87     public void setPrimaryClipAsPackage(ClipData clip, String callingPackage, String attributionTag,
88             int userId, int deviceId, String sourcePackage) {
89         setPrimaryClipAsPackage_enforcePermission();
90         checkArguments(userId, deviceId);
91         mPrimaryClip = clip;
92         mPrimaryClipSource = sourcePackage;
93         dispatchPrimaryClipChanged();
94     }
95 
96     @Override
clearPrimaryClip(String callingPackage, String attributionTag, int userId, int deviceId)97     public void clearPrimaryClip(String callingPackage, String attributionTag, int userId,
98             int deviceId) {
99         checkArguments(userId, deviceId);
100         mPrimaryClip = null;
101         mPrimaryClipSource = null;
102         dispatchPrimaryClipChanged();
103     }
104 
105     @Override
getPrimaryClip(String pkg, String attributionTag, int userId, int deviceId)106     public ClipData getPrimaryClip(String pkg, String attributionTag, int userId, int deviceId) {
107         checkArguments(userId, deviceId);
108         return mPrimaryClip;
109     }
110 
111     @Override
getPrimaryClipDescription(String callingPackage, String attributionTag, int userId, int deviceId)112     public ClipDescription getPrimaryClipDescription(String callingPackage, String attributionTag,
113             int userId, int deviceId) {
114         checkArguments(userId, deviceId);
115         return (mPrimaryClip != null) ? mPrimaryClip.getDescription() : null;
116     }
117 
118     @Override
hasPrimaryClip(String callingPackage, String attributionTag, int userId, int deviceId)119     public boolean hasPrimaryClip(String callingPackage, String attributionTag, int userId,
120             int deviceId) {
121         checkArguments(userId, deviceId);
122         return mPrimaryClip != null;
123     }
124 
125     @Override
addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener, String callingPackage, String attributionTag, int userId, int deviceId)126     public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
127             String callingPackage, String attributionTag, int userId, int deviceId) {
128         checkArguments(userId, deviceId);
129         mListeners.register(listener);
130     }
131 
132     @Override
removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener, String callingPackage, String attributionTag, int userId, int deviceId)133     public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
134             String callingPackage, String attributionTag, int userId, int deviceId) {
135         checkArguments(userId, deviceId);
136         mListeners.unregister(listener);
137     }
138 
139     @Override
hasClipboardText(String callingPackage, String attributionTag, int userId, int deviceId)140     public boolean hasClipboardText(String callingPackage, String attributionTag, int userId,
141             int deviceId) {
142         checkArguments(userId, deviceId);
143         return (mPrimaryClip != null) && (mPrimaryClip.getItemCount() > 0)
144                 && (mPrimaryClip.getItemAt(0).getText() != null);
145     }
146 
147     @Override
148     @android.annotation.EnforcePermission(android.Manifest.permission.SET_CLIP_SOURCE)
getPrimaryClipSource(String callingPackage, String attributionTag, int userId, int deviceId)149     public String getPrimaryClipSource(String callingPackage, String attributionTag, int userId,
150             int deviceId) {
151         getPrimaryClipSource_enforcePermission();
152         checkArguments(userId, deviceId);
153         return mPrimaryClipSource;
154     }
155 
156     @Override
areClipboardAccessNotificationsEnabledForUser(int userId)157     public boolean areClipboardAccessNotificationsEnabledForUser(int userId) {
158         throw new UnsupportedOperationException();
159     }
160 
161     @Override
setClipboardAccessNotificationsEnabledForUser(boolean enable, int userId)162     public void setClipboardAccessNotificationsEnabledForUser(boolean enable, int userId) {
163         throw new UnsupportedOperationException();
164     }
165 }
166