Amith Yamasani c566b43d02 Fix crosstalk between users for widgets hosted in lockscreen
This was initially about the Clock widget crashing repeatedly on some
devices with multiple users. Turned out that there were race conditions
when switching users that could result in remote views of one user calling
back to the RemoteViewsAdapter in keyguard that in turn sent an incorrect widget id
to a different user's widget, resulting in a crash.

Since KeyguardHostView is instantiated in the same process for different users,
it needs to carry a user identity to pass along to AppWidgetService so that
remote views services were bound to the correct user and callbacks were attached and
detached properly.

Added some aidl calls that take the userId to do the binding properly. A more
complete fix might be needed in the future so that all calls from Keyguard carry
the user id.

Also, there was a problem in comparing host uid for secondary users, since Settings
for a secondary user has a different uid than keyguard. Not an issue on single-user
systems. Changed the host.uid comparison to accomodate for the secondary user.

Bug: 7450247
Change-Id: Idbc36e3c60023cac74174f6cb7f2b2130dd3052c
2012-11-30 17:28:08 -08:00

407 lines
16 KiB
Java

/*
* Copyright (C) 2007 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.appwidget.AppWidgetProviderInfo;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Slog;
import android.util.SparseArray;
import android.widget.RemoteViews;
import com.android.internal.appwidget.IAppWidgetHost;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.util.IndentingPrintWriter;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
import java.util.Locale;
/**
* Redirects calls to this service to the instance of the service for the appropriate user.
*/
class AppWidgetService extends IAppWidgetService.Stub
{
private static final String TAG = "AppWidgetService";
Context mContext;
Locale mLocale;
PackageManager mPackageManager;
boolean mSafeMode;
private final Handler mSaveStateHandler;
private final SparseArray<AppWidgetServiceImpl> mAppWidgetServices;
AppWidgetService(Context context) {
mContext = context;
HandlerThread handlerThread = new HandlerThread("AppWidgetService -- Save state");
handlerThread.start();
mSaveStateHandler = new Handler(handlerThread.getLooper());
mAppWidgetServices = new SparseArray<AppWidgetServiceImpl>(5);
AppWidgetServiceImpl primary = new AppWidgetServiceImpl(context, 0, mSaveStateHandler);
mAppWidgetServices.append(0, primary);
}
public void systemReady(boolean safeMode) {
mSafeMode = safeMode;
mAppWidgetServices.get(0).systemReady(safeMode);
// Register for the boot completed broadcast, so we can send the
// ENABLE broacasts. If we try to send them now, they time out,
// because the system isn't ready to handle them yet.
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
// Register for configuration changes so we can update the names
// of the widgets when the locale changes.
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED), null, null);
// Register for broadcasts about package install, etc., so we can
// update the provider list.
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
filter, null, null);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
sdFilter, null, null);
IntentFilter userFilter = new IntentFilter();
userFilter.addAction(Intent.ACTION_USER_REMOVED);
userFilter.addAction(Intent.ACTION_USER_STOPPING);
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
onUserRemoved(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
UserHandle.USER_NULL));
} else if (Intent.ACTION_USER_STOPPING.equals(intent.getAction())) {
onUserStopping(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
UserHandle.USER_NULL));
}
}
}, userFilter);
}
/**
* This returns the user id of the caller, if the caller is not the system process,
* otherwise it assumes that the calls are from the lockscreen and hence are meant for the
* current user. TODO: Instead, have lockscreen make explicit calls with userId
*/
private int getCallingOrCurrentUserId() {
int callingUid = Binder.getCallingUid();
// Also check the PID because Settings (power control widget) also runs as System UID
if (callingUid == android.os.Process.myUid()
&& Binder.getCallingPid() == android.os.Process.myPid()) {
try {
return ActivityManagerNative.getDefault().getCurrentUser().id;
} catch (RemoteException re) {
return UserHandle.getUserId(callingUid);
}
} else {
return UserHandle.getUserId(callingUid);
}
}
@Override
public int allocateAppWidgetId(String packageName, int hostId) throws RemoteException {
return getImplForUser(getCallingOrCurrentUserId()).allocateAppWidgetId(
packageName, hostId);
}
@Override
public int[] getAppWidgetIdsForHost(int hostId) throws RemoteException {
return getImplForUser(getCallingOrCurrentUserId()).getAppWidgetIdsForHost(hostId);
}
@Override
public void deleteAppWidgetId(int appWidgetId) throws RemoteException {
getImplForUser(getCallingOrCurrentUserId()).deleteAppWidgetId(appWidgetId);
}
@Override
public void deleteHost(int hostId) throws RemoteException {
getImplForUser(getCallingOrCurrentUserId()).deleteHost(hostId);
}
@Override
public void deleteAllHosts() throws RemoteException {
getImplForUser(getCallingOrCurrentUserId()).deleteAllHosts();
}
@Override
public void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options)
throws RemoteException {
getImplForUser(getCallingOrCurrentUserId()).bindAppWidgetId(appWidgetId, provider,
options);
}
@Override
public boolean bindAppWidgetIdIfAllowed(
String packageName, int appWidgetId, ComponentName provider, Bundle options)
throws RemoteException {
return getImplForUser(getCallingOrCurrentUserId()).bindAppWidgetIdIfAllowed(
packageName, appWidgetId, provider, options);
}
@Override
public boolean hasBindAppWidgetPermission(String packageName) throws RemoteException {
return getImplForUser(getCallingOrCurrentUserId()).hasBindAppWidgetPermission(
packageName);
}
@Override
public void setBindAppWidgetPermission(String packageName, boolean permission)
throws RemoteException {
getImplForUser(getCallingOrCurrentUserId()).setBindAppWidgetPermission(
packageName, permission);
}
@Override
public void bindRemoteViewsService(int appWidgetId, Intent intent, IBinder connection,
int userId) throws RemoteException {
if (Binder.getCallingPid() != android.os.Process.myPid()
&& userId != UserHandle.getCallingUserId()) {
throw new SecurityException("Call from non-system process. Calling uid = "
+ Binder.getCallingUid());
}
getImplForUser(userId).bindRemoteViewsService(
appWidgetId, intent, connection);
}
@Override
public int[] startListening(IAppWidgetHost host, String packageName, int hostId,
List<RemoteViews> updatedViews) throws RemoteException {
return getImplForUser(getCallingOrCurrentUserId()).startListening(host,
packageName, hostId, updatedViews);
}
@Override
public int[] startListeningAsUser(IAppWidgetHost host, String packageName, int hostId,
List<RemoteViews> updatedViews, int userId) throws RemoteException {
if (Binder.getCallingPid() != android.os.Process.myPid()
&& userId != UserHandle.getCallingUserId()) {
throw new SecurityException("Call from non-system process. Calling uid = "
+ Binder.getCallingUid());
}
return getImplForUser(userId).startListening(host, packageName, hostId, updatedViews);
}
public void onUserRemoved(int userId) {
if (userId < 1) return;
synchronized (mAppWidgetServices) {
AppWidgetServiceImpl impl = mAppWidgetServices.get(userId);
mAppWidgetServices.remove(userId);
if (impl == null) {
AppWidgetServiceImpl.getSettingsFile(userId).delete();
} else {
impl.onUserRemoved();
}
}
}
public void onUserStopping(int userId) {
if (userId < 1) return;
synchronized (mAppWidgetServices) {
AppWidgetServiceImpl impl = mAppWidgetServices.get(userId);
if (impl != null) {
mAppWidgetServices.remove(userId);
impl.onUserStopping();
}
}
}
private AppWidgetServiceImpl getImplForUser(int userId) {
boolean sendInitial = false;
AppWidgetServiceImpl service;
synchronized (mAppWidgetServices) {
service = mAppWidgetServices.get(userId);
if (service == null) {
Slog.i(TAG, "Unable to find AppWidgetServiceImpl for user " + userId + ", adding");
// TODO: Verify that it's a valid user
service = new AppWidgetServiceImpl(mContext, userId, mSaveStateHandler);
service.systemReady(mSafeMode);
// Assume that BOOT_COMPLETED was received, as this is a non-primary user.
mAppWidgetServices.append(userId, service);
sendInitial = true;
}
}
if (sendInitial) {
service.sendInitialBroadcasts();
}
return service;
}
@Override
public int[] getAppWidgetIds(ComponentName provider) throws RemoteException {
return getImplForUser(getCallingOrCurrentUserId()).getAppWidgetIds(provider);
}
@Override
public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) throws RemoteException {
return getImplForUser(getCallingOrCurrentUserId()).getAppWidgetInfo(appWidgetId);
}
@Override
public RemoteViews getAppWidgetViews(int appWidgetId) throws RemoteException {
return getImplForUser(getCallingOrCurrentUserId()).getAppWidgetViews(appWidgetId);
}
@Override
public void updateAppWidgetOptions(int appWidgetId, Bundle options) {
getImplForUser(getCallingOrCurrentUserId()).updateAppWidgetOptions(appWidgetId, options);
}
@Override
public Bundle getAppWidgetOptions(int appWidgetId) {
return getImplForUser(getCallingOrCurrentUserId()).getAppWidgetOptions(appWidgetId);
}
@Override
public List<AppWidgetProviderInfo> getInstalledProviders(int categoryFilter)
throws RemoteException {
return getImplForUser(getCallingOrCurrentUserId()).getInstalledProviders(categoryFilter);
}
@Override
public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId)
throws RemoteException {
getImplForUser(getCallingOrCurrentUserId()).notifyAppWidgetViewDataChanged(
appWidgetIds, viewId);
}
@Override
public void partiallyUpdateAppWidgetIds(int[] appWidgetIds, RemoteViews views)
throws RemoteException {
getImplForUser(getCallingOrCurrentUserId()).partiallyUpdateAppWidgetIds(
appWidgetIds, views);
}
@Override
public void stopListening(int hostId) throws RemoteException {
getImplForUser(getCallingOrCurrentUserId()).stopListening(hostId);
}
@Override
public void stopListeningAsUser(int hostId, int userId) throws RemoteException {
if (Binder.getCallingPid() != android.os.Process.myPid()
&& userId != UserHandle.getCallingUserId()) {
throw new SecurityException("Call from non-system process. Calling uid = "
+ Binder.getCallingUid());
}
getImplForUser(userId).stopListening(hostId);
}
@Override
public void unbindRemoteViewsService(int appWidgetId, Intent intent, int userId)
throws RemoteException {
if (Binder.getCallingPid() != android.os.Process.myPid()
&& userId != UserHandle.getCallingUserId()) {
throw new SecurityException("Call from non-system process. Calling uid = "
+ Binder.getCallingUid());
}
getImplForUser(userId).unbindRemoteViewsService(
appWidgetId, intent);
}
@Override
public void updateAppWidgetIds(int[] appWidgetIds, RemoteViews views) throws RemoteException {
getImplForUser(getCallingOrCurrentUserId()).updateAppWidgetIds(appWidgetIds, views);
}
@Override
public void updateAppWidgetProvider(ComponentName provider, RemoteViews views)
throws RemoteException {
getImplForUser(getCallingOrCurrentUserId()).updateAppWidgetProvider(provider, views);
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
// Dump the state of all the app widget providers
synchronized (mAppWidgetServices) {
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
for (int i = 0; i < mAppWidgetServices.size(); i++) {
pw.println("User: " + mAppWidgetServices.keyAt(i));
ipw.increaseIndent();
AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i);
service.dump(fd, ipw, args);
ipw.decreaseIndent();
}
}
}
BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// Slog.d(TAG, "received " + action);
if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
if (userId >= 0) {
getImplForUser(userId).sendInitialBroadcasts();
} else {
Slog.w(TAG, "Incorrect user handle supplied in " + intent);
}
} else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
for (int i = 0; i < mAppWidgetServices.size(); i++) {
AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i);
service.onConfigurationChanged();
}
} else {
int sendingUser = getSendingUserId();
if (sendingUser == UserHandle.USER_ALL) {
for (int i = 0; i < mAppWidgetServices.size(); i++) {
AppWidgetServiceImpl service = mAppWidgetServices.valueAt(i);
service.onBroadcastReceived(intent);
}
} else {
AppWidgetServiceImpl service = mAppWidgetServices.get(sendingUser);
if (service != null) {
service.onBroadcastReceived(intent);
}
}
}
}
};
}