The main problem here was a mistake when turning a single process structure to a multi-package-process structure with a common process. When we cloned the original process state, if there were any services already created for the process for that package, they would be left with their process pointer still referencing the original now common process instead of the package-specific process, allowing the active counts to get bad. Now we switch any of those processes over to the new package-specific process. There was also another smaller issue with how ServiceRecord is associated with a ServiceState -- we could be waiting for an old ServiceRecord to be destroyed while at the same time creating a new ServiceRecord for that same service class. These would share the same ServiceState, so when the old record finally finished destroying itself it would trample over whatever the new service is doing. This is fixed by changing the model to instead of using an "active" reference count, we have an object identifying the current owner of the ServiceState. Then when the old ServiceRecord is cleaning up, we know if it is still the owner at that point. Also some other small things along the way -- new Log.wtfStack() method that is convenient, new suite of Slog.wtf methods, fixed some services to use Slog.wtf when catching exceptions being returned to the caller so that we actually know about them. Change-Id: I75674ce38050b6423fd3c6f43d1be172b470741f
367 lines
13 KiB
Java
367 lines
13 KiB
Java
/*
|
|
* Copyright (C) 2008 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.app.ActivityManagerNative;
|
|
import android.app.AppGlobals;
|
|
import android.app.AppOpsManager;
|
|
import android.app.IActivityManager;
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.ClipData;
|
|
import android.content.ClipDescription;
|
|
import android.content.IClipboard;
|
|
import android.content.IOnPrimaryClipChangedListener;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.content.pm.IPackageManager;
|
|
import android.content.pm.PackageInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.pm.PackageManager.NameNotFoundException;
|
|
import android.net.Uri;
|
|
import android.os.Binder;
|
|
import android.os.IBinder;
|
|
import android.os.Parcel;
|
|
import android.os.Process;
|
|
import android.os.RemoteCallbackList;
|
|
import android.os.RemoteException;
|
|
import android.os.UserHandle;
|
|
import android.util.Pair;
|
|
import android.util.Slog;
|
|
import android.util.SparseArray;
|
|
|
|
import java.util.HashSet;
|
|
|
|
/**
|
|
* Implementation of the clipboard for copy and paste.
|
|
*/
|
|
public class ClipboardService extends IClipboard.Stub {
|
|
|
|
private static final String TAG = "ClipboardService";
|
|
|
|
private final Context mContext;
|
|
private final IActivityManager mAm;
|
|
private final PackageManager mPm;
|
|
private final AppOpsManager mAppOps;
|
|
private final IBinder mPermissionOwner;
|
|
|
|
private class ListenerInfo {
|
|
final int mUid;
|
|
final String mPackageName;
|
|
ListenerInfo(int uid, String packageName) {
|
|
mUid = uid;
|
|
mPackageName = packageName;
|
|
}
|
|
}
|
|
|
|
private class PerUserClipboard {
|
|
final int userId;
|
|
|
|
final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
|
|
= new RemoteCallbackList<IOnPrimaryClipChangedListener>();
|
|
|
|
ClipData primaryClip;
|
|
|
|
final HashSet<String> activePermissionOwners
|
|
= new HashSet<String>();
|
|
|
|
PerUserClipboard(int userId) {
|
|
this.userId = userId;
|
|
}
|
|
}
|
|
|
|
private SparseArray<PerUserClipboard> mClipboards = new SparseArray<PerUserClipboard>();
|
|
|
|
/**
|
|
* Instantiates the clipboard.
|
|
*/
|
|
public ClipboardService(Context context) {
|
|
mContext = context;
|
|
mAm = ActivityManagerNative.getDefault();
|
|
mPm = context.getPackageManager();
|
|
mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
|
|
IBinder permOwner = null;
|
|
try {
|
|
permOwner = mAm.newUriPermissionOwner("clipboard");
|
|
} catch (RemoteException e) {
|
|
Slog.w("clipboard", "AM dead", e);
|
|
}
|
|
mPermissionOwner = permOwner;
|
|
|
|
// Remove the clipboard if a user is removed
|
|
IntentFilter userFilter = new IntentFilter();
|
|
userFilter.addAction(Intent.ACTION_USER_REMOVED);
|
|
mContext.registerReceiver(new BroadcastReceiver() {
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
String action = intent.getAction();
|
|
if (Intent.ACTION_USER_REMOVED.equals(action)) {
|
|
removeClipboard(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
|
|
}
|
|
}
|
|
}, userFilter);
|
|
}
|
|
|
|
@Override
|
|
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
|
|
throws RemoteException {
|
|
try {
|
|
return super.onTransact(code, data, reply, flags);
|
|
} catch (RuntimeException e) {
|
|
if (!(e instanceof SecurityException)) {
|
|
Slog.wtf("clipboard", "Exception: ", e);
|
|
}
|
|
throw e;
|
|
}
|
|
|
|
}
|
|
|
|
private PerUserClipboard getClipboard() {
|
|
return getClipboard(UserHandle.getCallingUserId());
|
|
}
|
|
|
|
private PerUserClipboard getClipboard(int userId) {
|
|
synchronized (mClipboards) {
|
|
PerUserClipboard puc = mClipboards.get(userId);
|
|
if (puc == null) {
|
|
puc = new PerUserClipboard(userId);
|
|
mClipboards.put(userId, puc);
|
|
}
|
|
return puc;
|
|
}
|
|
}
|
|
|
|
private void removeClipboard(int userId) {
|
|
synchronized (mClipboards) {
|
|
mClipboards.remove(userId);
|
|
}
|
|
}
|
|
|
|
public void setPrimaryClip(ClipData clip, String callingPackage) {
|
|
synchronized (this) {
|
|
if (clip != null && clip.getItemCount() <= 0) {
|
|
throw new IllegalArgumentException("No items");
|
|
}
|
|
final int callingUid = Binder.getCallingUid();
|
|
if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, callingUid,
|
|
callingPackage) != AppOpsManager.MODE_ALLOWED) {
|
|
return;
|
|
}
|
|
checkDataOwnerLocked(clip, callingUid);
|
|
clearActiveOwnersLocked();
|
|
PerUserClipboard clipboard = getClipboard();
|
|
clipboard.primaryClip = clip;
|
|
final long ident = Binder.clearCallingIdentity();
|
|
final int n = clipboard.primaryClipListeners.beginBroadcast();
|
|
try {
|
|
for (int i = 0; i < n; i++) {
|
|
try {
|
|
ListenerInfo li = (ListenerInfo)
|
|
clipboard.primaryClipListeners.getBroadcastCookie(i);
|
|
if (mAppOps.checkOpNoThrow(AppOpsManager.OP_READ_CLIPBOARD, li.mUid,
|
|
li.mPackageName) == AppOpsManager.MODE_ALLOWED) {
|
|
clipboard.primaryClipListeners.getBroadcastItem(i)
|
|
.dispatchPrimaryClipChanged();
|
|
}
|
|
} catch (RemoteException e) {
|
|
// The RemoteCallbackList will take care of removing
|
|
// the dead object for us.
|
|
}
|
|
}
|
|
} finally {
|
|
clipboard.primaryClipListeners.finishBroadcast();
|
|
Binder.restoreCallingIdentity(ident);
|
|
}
|
|
}
|
|
}
|
|
|
|
public ClipData getPrimaryClip(String pkg) {
|
|
synchronized (this) {
|
|
if (mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
|
|
pkg) != AppOpsManager.MODE_ALLOWED) {
|
|
return null;
|
|
}
|
|
addActiveOwnerLocked(Binder.getCallingUid(), pkg);
|
|
return getClipboard().primaryClip;
|
|
}
|
|
}
|
|
|
|
public ClipDescription getPrimaryClipDescription(String callingPackage) {
|
|
synchronized (this) {
|
|
if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
|
|
callingPackage) != AppOpsManager.MODE_ALLOWED) {
|
|
return null;
|
|
}
|
|
PerUserClipboard clipboard = getClipboard();
|
|
return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null;
|
|
}
|
|
}
|
|
|
|
public boolean hasPrimaryClip(String callingPackage) {
|
|
synchronized (this) {
|
|
if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
|
|
callingPackage) != AppOpsManager.MODE_ALLOWED) {
|
|
return false;
|
|
}
|
|
return getClipboard().primaryClip != null;
|
|
}
|
|
}
|
|
|
|
public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
|
|
String callingPackage) {
|
|
synchronized (this) {
|
|
getClipboard().primaryClipListeners.register(listener,
|
|
new ListenerInfo(Binder.getCallingUid(), callingPackage));
|
|
}
|
|
}
|
|
|
|
public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
|
|
synchronized (this) {
|
|
getClipboard().primaryClipListeners.unregister(listener);
|
|
}
|
|
}
|
|
|
|
public boolean hasClipboardText(String callingPackage) {
|
|
synchronized (this) {
|
|
if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
|
|
callingPackage) != AppOpsManager.MODE_ALLOWED) {
|
|
return false;
|
|
}
|
|
PerUserClipboard clipboard = getClipboard();
|
|
if (clipboard.primaryClip != null) {
|
|
CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
|
|
return text != null && text.length() > 0;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private final void checkUriOwnerLocked(Uri uri, int uid) {
|
|
if (!"content".equals(uri.getScheme())) {
|
|
return;
|
|
}
|
|
long ident = Binder.clearCallingIdentity();
|
|
try {
|
|
// This will throw SecurityException for us.
|
|
mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
} catch (RemoteException e) {
|
|
} finally {
|
|
Binder.restoreCallingIdentity(ident);
|
|
}
|
|
}
|
|
|
|
private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
|
|
if (item.getUri() != null) {
|
|
checkUriOwnerLocked(item.getUri(), uid);
|
|
}
|
|
Intent intent = item.getIntent();
|
|
if (intent != null && intent.getData() != null) {
|
|
checkUriOwnerLocked(intent.getData(), uid);
|
|
}
|
|
}
|
|
|
|
private final void checkDataOwnerLocked(ClipData data, int uid) {
|
|
final int N = data.getItemCount();
|
|
for (int i=0; i<N; i++) {
|
|
checkItemOwnerLocked(data.getItemAt(i), uid);
|
|
}
|
|
}
|
|
|
|
private final void grantUriLocked(Uri uri, String pkg) {
|
|
long ident = Binder.clearCallingIdentity();
|
|
try {
|
|
mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri,
|
|
Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
} catch (RemoteException e) {
|
|
} finally {
|
|
Binder.restoreCallingIdentity(ident);
|
|
}
|
|
}
|
|
|
|
private final void grantItemLocked(ClipData.Item item, String pkg) {
|
|
if (item.getUri() != null) {
|
|
grantUriLocked(item.getUri(), pkg);
|
|
}
|
|
Intent intent = item.getIntent();
|
|
if (intent != null && intent.getData() != null) {
|
|
grantUriLocked(intent.getData(), pkg);
|
|
}
|
|
}
|
|
|
|
private final void addActiveOwnerLocked(int uid, String pkg) {
|
|
final IPackageManager pm = AppGlobals.getPackageManager();
|
|
final int targetUserHandle = UserHandle.getCallingUserId();
|
|
final long oldIdentity = Binder.clearCallingIdentity();
|
|
try {
|
|
PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle);
|
|
if (pi == null) {
|
|
throw new IllegalArgumentException("Unknown package " + pkg);
|
|
}
|
|
if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) {
|
|
throw new SecurityException("Calling uid " + uid
|
|
+ " does not own package " + pkg);
|
|
}
|
|
} catch (RemoteException e) {
|
|
// Can't happen; the package manager is in the same process
|
|
} finally {
|
|
Binder.restoreCallingIdentity(oldIdentity);
|
|
}
|
|
PerUserClipboard clipboard = getClipboard();
|
|
if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
|
|
final int N = clipboard.primaryClip.getItemCount();
|
|
for (int i=0; i<N; i++) {
|
|
grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg);
|
|
}
|
|
clipboard.activePermissionOwners.add(pkg);
|
|
}
|
|
}
|
|
|
|
private final void revokeUriLocked(Uri uri) {
|
|
long ident = Binder.clearCallingIdentity();
|
|
try {
|
|
mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
|
|
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
|
} catch (RemoteException e) {
|
|
} finally {
|
|
Binder.restoreCallingIdentity(ident);
|
|
}
|
|
}
|
|
|
|
private final void revokeItemLocked(ClipData.Item item) {
|
|
if (item.getUri() != null) {
|
|
revokeUriLocked(item.getUri());
|
|
}
|
|
Intent intent = item.getIntent();
|
|
if (intent != null && intent.getData() != null) {
|
|
revokeUriLocked(intent.getData());
|
|
}
|
|
}
|
|
|
|
private final void clearActiveOwnersLocked() {
|
|
PerUserClipboard clipboard = getClipboard();
|
|
clipboard.activePermissionOwners.clear();
|
|
if (clipboard.primaryClip == null) {
|
|
return;
|
|
}
|
|
final int N = clipboard.primaryClip.getItemCount();
|
|
for (int i=0; i<N; i++) {
|
|
revokeItemLocked(clipboard.primaryClip.getItemAt(i));
|
|
}
|
|
}
|
|
}
|