diff options
Diffstat (limited to 'services/java')
25 files changed, 1610 insertions, 268 deletions
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 5c7a3ed..9676eb9 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -32,6 +32,7 @@ import static android.net.ConnectivityManager.isNetworkTypeValid; import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; +import android.app.Activity; import android.bluetooth.BluetoothTetheringDataTracker; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -548,6 +549,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY); mSettingsObserver.observe(mContext); + mCaptivePortalTracker = CaptivePortalTracker.makeCaptivePortalTracker(mContext, this); loadGlobalProxy(); } @@ -1694,7 +1696,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); - intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info)); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType()); if (info.isFailover()) { intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); @@ -1825,7 +1827,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } Intent intent = new Intent(bcastType); - intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info)); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType()); if (info.isFailover()) { intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); @@ -1882,7 +1884,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { loge("Attempt to connect to " + info.getTypeName() + " failed" + reasonText); Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); - intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); + intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info)); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType()); if (getActiveNetworkInfo() == null) { intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); @@ -2075,8 +2077,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) { if (isNewNetTypePreferredOverCurrentNetType(type)) { if (DBG) log("Captive check on " + info.getTypeName()); - mCaptivePortalTracker = CaptivePortalTracker.detect(mContext, info, - ConnectivityService.this); + mCaptivePortalTracker.detectCaptivePortal(new NetworkInfo(info)); return; } else { if (DBG) log("Tear down low priority net " + info.getTypeName()); @@ -2092,7 +2093,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { /** @hide */ public void captivePortalCheckComplete(NetworkInfo info) { mNetTrackers[info.getType()].captivePortalCheckComplete(); - mCaptivePortalTracker = null; } /** diff --git a/services/java/com/android/server/DreamController.java b/services/java/com/android/server/DreamController.java new file mode 100644 index 0000000..498e581 --- /dev/null +++ b/services/java/com/android/server/DreamController.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2012 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.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.IBinder.DeathRecipient; +import android.service.dreams.IDreamService; +import android.util.Slog; +import android.view.IWindowManager; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; + +import com.android.internal.util.DumpUtils; + +import java.io.PrintWriter; +import java.util.NoSuchElementException; + +/** + * Internal controller for starting and stopping the current dream and managing related state. + * + * Assumes all operations (except {@link #dump}) are called from a single thread. + */ +final class DreamController { + private static final boolean DEBUG = true; + private static final String TAG = DreamController.class.getSimpleName(); + + public interface Listener { + void onDreamStopped(boolean wasTest); + } + + private final Context mContext; + private final IWindowManager mIWindowManager; + private final DeathRecipient mDeathRecipient; + private final ServiceConnection mServiceConnection; + private final Listener mListener; + + private Handler mHandler; + + private ComponentName mCurrentDreamComponent; + private IDreamService mCurrentDream; + private Binder mCurrentDreamToken; + private boolean mCurrentDreamIsTest; + + public DreamController(Context context, DeathRecipient deathRecipient, + ServiceConnection serviceConnection, Listener listener) { + mContext = context; + mDeathRecipient = deathRecipient; + mServiceConnection = serviceConnection; + mListener = listener; + mIWindowManager = WindowManagerGlobal.getWindowManagerService(); + } + + public void setHandler(Handler handler) { + mHandler = handler; + } + + public void dump(PrintWriter pw) { + if (mHandler== null || pw == null) { + return; + } + DumpUtils.dumpAsync(mHandler, new DumpUtils.Dump() { + @Override + public void dump(PrintWriter pw) { + pw.print(" component="); pw.println(mCurrentDreamComponent); + pw.print(" token="); pw.println(mCurrentDreamToken); + pw.print(" dream="); pw.println(mCurrentDream); + } + }, pw, 200); + } + + public void start(ComponentName dream, boolean isTest) { + if (DEBUG) Slog.v(TAG, String.format("start(%s,%s)", dream, isTest)); + + if (mCurrentDreamComponent != null ) { + if (dream.equals(mCurrentDreamComponent) && isTest == mCurrentDreamIsTest) { + if (DEBUG) Slog.v(TAG, "Dream is already started: " + dream); + return; + } + // stop the current dream before starting a new one + stop(); + } + + mCurrentDreamComponent = dream; + mCurrentDreamIsTest = isTest; + mCurrentDreamToken = new Binder(); + + try { + if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurrentDreamToken + + " for window type: " + WindowManager.LayoutParams.TYPE_DREAM); + mIWindowManager.addWindowToken(mCurrentDreamToken, + WindowManager.LayoutParams.TYPE_DREAM); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to add window token."); + stop(); + return; + } + + Intent intent = new Intent(Intent.ACTION_MAIN) + .setComponent(mCurrentDreamComponent) + .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + .putExtra("android.dreams.TEST", mCurrentDreamIsTest); + + if (!mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)) { + Slog.w(TAG, "Unable to bind service"); + stop(); + return; + } + if (DEBUG) Slog.v(TAG, "Bound service"); + } + + public void attach(ComponentName name, IBinder dream) { + if (DEBUG) Slog.v(TAG, String.format("attach(%s,%s)", name, dream)); + mCurrentDream = IDreamService.Stub.asInterface(dream); + + boolean linked = linkDeathRecipient(dream); + if (!linked) { + stop(); + return; + } + + try { + if (DEBUG) Slog.v(TAG, "Attaching with token:" + mCurrentDreamToken); + mCurrentDream.attach(mCurrentDreamToken); + } catch (Throwable ex) { + Slog.w(TAG, "Unable to send window token to dream:" + ex); + stop(); + } + } + + public void stop() { + if (DEBUG) Slog.v(TAG, "stop()"); + + if (mCurrentDream != null) { + unlinkDeathRecipient(mCurrentDream.asBinder()); + + if (DEBUG) Slog.v(TAG, "Unbinding: " + mCurrentDreamComponent + " service: " + mCurrentDream); + mContext.unbindService(mServiceConnection); + } + if (mCurrentDreamToken != null) { + removeWindowToken(mCurrentDreamToken); + } + + final boolean wasTest = mCurrentDreamIsTest; + mCurrentDream = null; + mCurrentDreamToken = null; + mCurrentDreamComponent = null; + mCurrentDreamIsTest = false; + + if (mListener != null && mHandler != null) { + mHandler.post(new Runnable(){ + @Override + public void run() { + mListener.onDreamStopped(wasTest); + }}); + } + } + + public void stopSelf(IBinder token) { + if (DEBUG) Slog.v(TAG, String.format("stopSelf(%s)", token)); + if (token == null || token != mCurrentDreamToken) { + Slog.w(TAG, "Stop requested for non-current dream token: " + token); + } else { + stop(); + } + } + + private void removeWindowToken(IBinder token) { + if (DEBUG) Slog.v(TAG, "Removing window token: " + token); + try { + mIWindowManager.removeWindowToken(token); + } catch (Throwable e) { + Slog.w(TAG, "Error removing window token", e); + } + } + + private boolean linkDeathRecipient(IBinder dream) { + if (DEBUG) Slog.v(TAG, "Linking death recipient"); + try { + dream.linkToDeath(mDeathRecipient, 0); + return true; + } catch (RemoteException e) { + Slog.w(TAG, "Unable to link death recipient", e); + return false; + } + } + + private void unlinkDeathRecipient(IBinder dream) { + if (DEBUG) Slog.v(TAG, "Unlinking death recipient"); + try { + dream.unlinkToDeath(mDeathRecipient, 0); + } catch (NoSuchElementException e) { + // we tried + } + } + +}
\ No newline at end of file diff --git a/services/java/com/android/server/DreamManagerService.java b/services/java/com/android/server/DreamManagerService.java new file mode 100644 index 0000000..b02ea7f --- /dev/null +++ b/services/java/com/android/server/DreamManagerService.java @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2012 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 static android.provider.Settings.Secure.SCREENSAVER_COMPONENTS; +import static android.provider.Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT; + +import android.app.ActivityManagerNative; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; +import android.service.dreams.Dream; +import android.service.dreams.IDreamManager; +import android.util.Slog; +import android.util.SparseArray; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * Service api for managing dreams. + * + * @hide + */ +public final class DreamManagerService + extends IDreamManager.Stub + implements ServiceConnection { + private static final boolean DEBUG = true; + private static final String TAG = DreamManagerService.class.getSimpleName(); + + private static final Intent mDreamingStartedIntent = new Intent(Dream.ACTION_DREAMING_STARTED) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + private static final Intent mDreamingStoppedIntent = new Intent(Dream.ACTION_DREAMING_STOPPED) + .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + + private final Object mLock = new Object(); + private final DreamController mController; + private final DreamControllerHandler mHandler; + private final Context mContext; + + private final CurrentUserManager mCurrentUserManager = new CurrentUserManager(); + + private final DeathRecipient mAwakenOnBinderDeath = new DeathRecipient() { + @Override + public void binderDied() { + if (DEBUG) Slog.v(TAG, "binderDied()"); + awaken(); + } + }; + + private final DreamController.Listener mControllerListener = new DreamController.Listener() { + @Override + public void onDreamStopped(boolean wasTest) { + synchronized(mLock) { + setDreamingLocked(false, wasTest); + } + }}; + + private boolean mIsDreaming; + + public DreamManagerService(Context context) { + if (DEBUG) Slog.v(TAG, "DreamManagerService startup"); + mContext = context; + mController = new DreamController(context, mAwakenOnBinderDeath, this, mControllerListener); + mHandler = new DreamControllerHandler(mController); + mController.setHandler(mHandler); + } + + public void systemReady() { + mCurrentUserManager.init(mContext); + + if (DEBUG) Slog.v(TAG, "Ready to dream!"); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + + pw.println("Dreamland:"); + mController.dump(pw); + mCurrentUserManager.dump(pw); + } + + // begin IDreamManager api + @Override + public ComponentName[] getDreamComponents() { + checkPermission(android.Manifest.permission.READ_DREAM_STATE); + int userId = UserHandle.getCallingUserId(); + + final long ident = Binder.clearCallingIdentity(); + try { + return getDreamComponentsForUser(userId); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public void setDreamComponents(ComponentName[] componentNames) { + checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); + int userId = UserHandle.getCallingUserId(); + + final long ident = Binder.clearCallingIdentity(); + try { + Settings.Secure.putStringForUser(mContext.getContentResolver(), + SCREENSAVER_COMPONENTS, + componentsToString(componentNames), + userId); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public ComponentName getDefaultDreamComponent() { + checkPermission(android.Manifest.permission.READ_DREAM_STATE); + int userId = UserHandle.getCallingUserId(); + + final long ident = Binder.clearCallingIdentity(); + try { + String name = Settings.Secure.getStringForUser(mContext.getContentResolver(), + SCREENSAVER_DEFAULT_COMPONENT, + userId); + return name == null ? null : ComponentName.unflattenFromString(name); + } finally { + Binder.restoreCallingIdentity(ident); + } + + } + + @Override + public boolean isDreaming() { + checkPermission(android.Manifest.permission.READ_DREAM_STATE); + + return mIsDreaming; + } + + @Override + public void dream() { + checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); + + final long ident = Binder.clearCallingIdentity(); + try { + if (DEBUG) Slog.v(TAG, "Dream now"); + ComponentName[] dreams = getDreamComponentsForUser(mCurrentUserManager.getCurrentUserId()); + ComponentName firstDream = dreams != null && dreams.length > 0 ? dreams[0] : null; + if (firstDream != null) { + mHandler.requestStart(firstDream, false /*isTest*/); + synchronized (mLock) { + setDreamingLocked(true, false /*isTest*/); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public void testDream(ComponentName dream) { + checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); + + final long ident = Binder.clearCallingIdentity(); + try { + if (DEBUG) Slog.v(TAG, "Test dream name=" + dream); + if (dream != null) { + mHandler.requestStart(dream, true /*isTest*/); + synchronized (mLock) { + setDreamingLocked(true, true /*isTest*/); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + + } + + @Override + public void awaken() { + checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); + + final long ident = Binder.clearCallingIdentity(); + try { + if (DEBUG) Slog.v(TAG, "Wake up"); + mHandler.requestStop(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public void awakenSelf(IBinder token) { + // requires no permission, called by Dream from an arbitrary process + + final long ident = Binder.clearCallingIdentity(); + try { + if (DEBUG) Slog.v(TAG, "Wake up from dream: " + token); + if (token != null) { + mHandler.requestStopSelf(token); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + // end IDreamManager api + + // begin ServiceConnection + @Override + public void onServiceConnected(ComponentName name, IBinder dream) { + if (DEBUG) Slog.v(TAG, "Service connected: " + name + " binder=" + + dream + " thread=" + Thread.currentThread().getId()); + mHandler.requestAttach(name, dream); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + if (DEBUG) Slog.v(TAG, "Service disconnected: " + name); + // Only happens in exceptional circumstances, awaken just to be safe + awaken(); + } + // end ServiceConnection + + private void checkPermission(String permission) { + if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(permission)) { + throw new SecurityException("Access denied to process: " + Binder.getCallingPid() + + ", must have permission " + permission); + } + } + + private void setDreamingLocked(boolean isDreaming, boolean isTest) { + boolean wasDreaming = mIsDreaming; + if (!isTest) { + if (!wasDreaming && isDreaming) { + if (DEBUG) Slog.v(TAG, "Firing ACTION_DREAMING_STARTED"); + mContext.sendBroadcast(mDreamingStartedIntent); + } else if (wasDreaming && !isDreaming) { + if (DEBUG) Slog.v(TAG, "Firing ACTION_DREAMING_STOPPED"); + mContext.sendBroadcast(mDreamingStoppedIntent); + } + } + mIsDreaming = isDreaming; + } + + private ComponentName[] getDreamComponentsForUser(int userId) { + String names = Settings.Secure.getStringForUser(mContext.getContentResolver(), + SCREENSAVER_COMPONENTS, + userId); + return names == null ? null : componentsFromString(names); + } + + private static String componentsToString(ComponentName[] componentNames) { + StringBuilder names = new StringBuilder(); + if (componentNames != null) { + for (ComponentName componentName : componentNames) { + if (names.length() > 0) { + names.append(','); + } + names.append(componentName.flattenToString()); + } + } + return names.toString(); + } + + private static ComponentName[] componentsFromString(String names) { + String[] namesArray = names.split(","); + ComponentName[] componentNames = new ComponentName[namesArray.length]; + for (int i = 0; i < namesArray.length; i++) { + componentNames[i] = ComponentName.unflattenFromString(namesArray[i]); + } + return componentNames; + } + + /** + * Keeps track of the current user, since dream() uses the current user's configuration. + */ + private static class CurrentUserManager { + private final Object mLock = new Object(); + private int mCurrentUserId; + + public void init(Context context) { + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_USER_SWITCHED); + context.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_USER_SWITCHED.equals(action)) { + synchronized(mLock) { + mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); + if (DEBUG) Slog.v(TAG, "userId " + mCurrentUserId + " is in the house"); + } + } + }}, filter); + try { + synchronized (mLock) { + mCurrentUserId = ActivityManagerNative.getDefault().getCurrentUser().id; + } + } catch (RemoteException e) { + Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e); + } + } + + public void dump(PrintWriter pw) { + pw.print(" user="); pw.println(getCurrentUserId()); + } + + public int getCurrentUserId() { + synchronized(mLock) { + return mCurrentUserId; + } + } + } + + /** + * Handler for asynchronous operations performed by the dream manager. + * + * Ensures operations to {@link DreamController} are single-threaded. + */ + private static final class DreamControllerHandler extends Handler { + private final DreamController mController; + private final Runnable mStopRunnable = new Runnable() { + @Override + public void run() { + mController.stop(); + }}; + + public DreamControllerHandler(DreamController controller) { + super(true /*async*/); + mController = controller; + } + + public void requestStart(final ComponentName name, final boolean isTest) { + post(new Runnable(){ + @Override + public void run() { + mController.start(name, isTest); + }}); + } + + public void requestAttach(final ComponentName name, final IBinder dream) { + post(new Runnable(){ + @Override + public void run() { + mController.attach(name, dream); + }}); + } + + public void requestStopSelf(final IBinder token) { + post(new Runnable(){ + @Override + public void run() { + mController.stopSelf(token); + }}); + } + + public void requestStop() { + post(mStopRunnable); + } + + } + +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 90783b7..6c5a4e2 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -38,7 +38,6 @@ import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.server.search.SearchManagerService; -import android.service.dreams.DreamManagerService; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; @@ -149,6 +148,7 @@ class ServerThread extends Thread { NetworkTimeUpdateService networkTimeUpdater = null; CommonTimeManagementService commonTimeMgmtService = null; InputManagerService inputManager = null; + TelephonyRegistry telephonyRegistry = null; // Create a shared handler thread for UI within the system server. // This thread is used by at least the following components: @@ -189,7 +189,7 @@ class ServerThread extends Thread { // For debug builds, log event loop stalls to dropbox for analysis. if (StrictMode.conditionallyEnableDebugLogging()) { - Slog.i(TAG, "Enabled StrictMode logging for UI Looper"); + Slog.i(TAG, "Enabled StrictMode logging for WM Looper"); } } }); @@ -219,7 +219,8 @@ class ServerThread extends Thread { ServiceManager.addService(Context.DISPLAY_SERVICE, display, true); Slog.i(TAG, "Telephony Registry"); - ServiceManager.addService("telephony.registry", new TelephonyRegistry(context)); + telephonyRegistry = new TelephonyRegistry(context); + ServiceManager.addService("telephony.registry", telephonyRegistry); Slog.i(TAG, "Scheduling Policy"); ServiceManager.addService(Context.SCHEDULING_POLICY_SERVICE, @@ -845,6 +846,7 @@ class ServerThread extends Thread { final StatusBarManagerService statusBarF = statusBar; final DreamManagerService dreamyF = dreamy; final InputManagerService inputManagerF = inputManager; + final TelephonyRegistry telephonyRegistryF = telephonyRegistry; // We now tell the activity manager it is okay to run third party // code. It will call back into us once it has gotten to the state @@ -972,6 +974,11 @@ class ServerThread extends Thread { } catch (Throwable e) { reportWtf("making InputManagerService ready", e); } + try { + if (telephonyRegistryF != null) telephonyRegistryF.systemReady(); + } catch (Throwable e) { + reportWtf("making TelephonyRegistry ready", e); + } } }); diff --git a/services/java/com/android/server/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java index 8361477..26684de 100644 --- a/services/java/com/android/server/TelephonyRegistry.java +++ b/services/java/com/android/server/TelephonyRegistry.java @@ -16,14 +16,19 @@ package com.android.server; +import android.app.ActivityManager; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.net.LinkCapabilities; import android.net.LinkProperties; import android.os.Binder; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; +import android.os.Message; import android.os.RemoteException; import android.os.UserHandle; import android.telephony.CellLocation; @@ -39,13 +44,11 @@ import java.util.ArrayList; import java.util.List; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.net.NetworkInterface; import com.android.internal.app.IBatteryStats; import com.android.internal.telephony.ITelephonyRegistry; import com.android.internal.telephony.IPhoneStateListener; import com.android.internal.telephony.DefaultPhoneNotifier; -import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.ServiceStateTracker; import com.android.internal.telephony.TelephonyIntents; @@ -58,6 +61,7 @@ import com.android.server.am.BatteryStatsService; class TelephonyRegistry extends ITelephonyRegistry.Stub { private static final String TAG = "TelephonyRegistry"; private static final boolean DBG = false; + private static final boolean DBG_LOC = false; private static class Record { String pkgForDebug; @@ -66,7 +70,15 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { IPhoneStateListener callback; + int callerUid; + int events; + + @Override + public String toString() { + return "{pkgForDebug=" + pkgForDebug + " callerUid=" + callerUid + + " events=" + Integer.toHexString(events) + "}"; + } } private final Context mContext; @@ -120,6 +132,32 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { PhoneStateListener.LISTEN_DATA_CONNECTION_STATE | PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR; + private static final int MSG_USER_SWITCHED = 1; + + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_USER_SWITCHED: { + Slog.d(TAG, "MSG_USER_SWITCHED userId=" + msg.arg1); + TelephonyRegistry.this.notifyCellLocation(mCellLocation); + break; + } + } + } + }; + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_USER_SWITCHED.equals(action)) { + mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHED, + intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0)); + } + } + }; + // we keep a copy of all of the state so we can send it out when folks // register for it // @@ -140,10 +178,24 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mConnectedApns = new ArrayList<String>(); } + public void systemReady() { + // Watch for interesting updates + final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_USER_SWITCHED); + filter.addAction(Intent.ACTION_USER_REMOVED); + mContext.registerReceiver(mBroadcastReceiver, filter); + } + + @Override public void listen(String pkgForDebug, IPhoneStateListener callback, int events, boolean notifyNow) { - // Slog.d(TAG, "listen pkg=" + pkgForDebug + " events=0x" + - // Integer.toHexString(events)); + int callerUid = UserHandle.getCallingUserId(); + int myUid = UserHandle.myUserId(); + if (DBG) { + Slog.d(TAG, "listen: E pkg=" + pkgForDebug + " events=0x" + Integer.toHexString(events) + + " myUid=" + myUid + + " callerUid=" + callerUid); + } if (events != 0) { /* Checks permission and throws Security exception */ checkListenerPermission(events); @@ -164,7 +216,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { r.binder = b; r.callback = callback; r.pkgForDebug = pkgForDebug; + r.callerUid = callerUid; mRecords.add(r); + if (DBG) Slog.i(TAG, "listen: add new record=" + r); } int send = events & (events ^ r.events); r.events = events; @@ -199,8 +253,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } - if ((events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) { + if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION)) { try { + if (DBG_LOC) Slog.d(TAG, "listen: mCellLocation=" + mCellLocation); r.callback.onCellLocationChanged(new Bundle(mCellLocation)); } catch (RemoteException ex) { remove(r.binder); @@ -242,8 +297,9 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { remove(r.binder); } } - if ((events & PhoneStateListener.LISTEN_CELL_INFO) != 0) { + if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO)) { try { + if (DBG_LOC) Slog.d(TAG, "listen: mCellInfo=" + mCellInfo); r.callback.onCellInfoChanged(mCellInfo); } catch (RemoteException ex) { remove(r.binder); @@ -346,8 +402,11 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { synchronized (mRecords) { mCellInfo = cellInfo; for (Record r : mRecords) { - if ((r.events & PhoneStateListener.LISTEN_CELL_INFO) != 0) { + if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO)) { try { + if (DBG_LOC) { + Slog.d(TAG, "notifyCellInfo: mCellInfo=" + mCellInfo + " r=" + r); + } r.callback.onCellInfoChanged(cellInfo); } catch (RemoteException ex) { mRemoveList.add(r.binder); @@ -424,7 +483,8 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { if (DBG) { Slog.i(TAG, "notifyDataConnection: state=" + state + " isDataConnectivityPossible=" + isDataConnectivityPossible + " reason='" + reason - + "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType); + + "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType + + " mRecords.size()=" + mRecords.size() + " mRecords=" + mRecords); } synchronized (mRecords) { boolean modified = false; @@ -506,8 +566,12 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { synchronized (mRecords) { mCellLocation = cellLocation; for (Record r : mRecords) { - if ((r.events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) { + if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION)) { try { + if (DBG_LOC) { + Slog.d(TAG, "notifyCellLocation: mCellLocation=" + mCellLocation + + " r=" + r); + } r.callback.onCellLocationChanged(new Bundle(cellLocation)); } catch (RemoteException ex) { mRemoveList.add(r.binder); @@ -712,4 +776,22 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mRemoveList.clear(); } } + + private boolean validateEventsAndUserLocked(Record r, int events) { + int foregroundUser; + long callingIdentity = Binder.clearCallingIdentity(); + boolean valid = false; + try { + foregroundUser = ActivityManager.getCurrentUser(); + valid = r.callerUid == foregroundUser && (r.events & events) != 0; + if (DBG | DBG_LOC) { + Slog.d(TAG, "validateEventsAndUserLocked: valid=" + valid + + " r.callerUid=" + r.callerUid + " foregroundUser=" + foregroundUser + + " r.events=" + r.events + " events=" + events); + } + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + return valid; + } } diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java index b027c1f..4225913 100644 --- a/services/java/com/android/server/WallpaperManagerService.java +++ b/services/java/com/android/server/WallpaperManagerService.java @@ -293,17 +293,18 @@ class WallpaperManagerService extends IWallpaperManager.Stub { @Override public void onPackageUpdateFinished(String packageName, int uid) { synchronized (mLock) { - for (int i = 0; i < mWallpaperMap.size(); i++) { - WallpaperData wallpaper = mWallpaperMap.valueAt(i); + if (mCurrentUserId != getChangingUserId()) { + return; + } + WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId); + if (wallpaper != null) { if (wallpaper.wallpaperComponent != null && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) { wallpaper.wallpaperUpdating = false; ComponentName comp = wallpaper.wallpaperComponent; clearWallpaperComponentLocked(wallpaper); - // Do this only for the current user's wallpaper - if (wallpaper.userId == mCurrentUserId - && !bindWallpaperComponentLocked(comp, false, false, - wallpaper, null)) { + if (!bindWallpaperComponentLocked(comp, false, false, + wallpaper, null)) { Slog.w(TAG, "Wallpaper no longer available; reverting to default"); clearWallpaperLocked(false, wallpaper.userId, null); } @@ -315,11 +316,14 @@ class WallpaperManagerService extends IWallpaperManager.Stub { @Override public void onPackageModified(String packageName) { synchronized (mLock) { - for (int i = 0; i < mWallpaperMap.size(); i++) { - WallpaperData wallpaper = mWallpaperMap.valueAt(i); + if (mCurrentUserId != getChangingUserId()) { + return; + } + WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId); + if (wallpaper != null) { if (wallpaper.wallpaperComponent == null || !wallpaper.wallpaperComponent.getPackageName().equals(packageName)) { - continue; + return; } doPackagesChangedLocked(true, wallpaper); } @@ -329,8 +333,11 @@ class WallpaperManagerService extends IWallpaperManager.Stub { @Override public void onPackageUpdateStarted(String packageName, int uid) { synchronized (mLock) { - for (int i = 0; i < mWallpaperMap.size(); i++) { - WallpaperData wallpaper = mWallpaperMap.valueAt(i); + if (mCurrentUserId != getChangingUserId()) { + return; + } + WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId); + if (wallpaper != null) { if (wallpaper.wallpaperComponent != null && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) { wallpaper.wallpaperUpdating = true; @@ -343,8 +350,11 @@ class WallpaperManagerService extends IWallpaperManager.Stub { public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { synchronized (mLock) { boolean changed = false; - for (int i = 0; i < mWallpaperMap.size(); i++) { - WallpaperData wallpaper = mWallpaperMap.valueAt(i); + if (mCurrentUserId != getChangingUserId()) { + return false; + } + WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId); + if (wallpaper != null) { boolean res = doPackagesChangedLocked(doit, wallpaper); changed |= res; } @@ -355,8 +365,11 @@ class WallpaperManagerService extends IWallpaperManager.Stub { @Override public void onSomePackagesChanged() { synchronized (mLock) { - for (int i = 0; i < mWallpaperMap.size(); i++) { - WallpaperData wallpaper = mWallpaperMap.valueAt(i); + if (mCurrentUserId != getChangingUserId()) { + return; + } + WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId); + if (wallpaper != null) { doPackagesChangedLocked(true, wallpaper); } } @@ -416,7 +429,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { ServiceManager.getService(Context.WINDOW_SERVICE)); mIPackageManager = AppGlobals.getPackageManager(); mMonitor = new MyPackageMonitor(); - mMonitor.register(context, null, true); + mMonitor.register(context, null, UserHandle.ALL, true); getWallpaperDir(UserHandle.USER_OWNER).mkdirs(); loadSettingsLocked(UserHandle.USER_OWNER); } diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 66b60ec..cd3aeff 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -147,6 +147,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -168,6 +169,7 @@ public final class ActivityManagerService extends ActivityManagerNative static final boolean localLOGV = DEBUG; static final boolean DEBUG_SWITCH = localLOGV || false; static final boolean DEBUG_TASKS = localLOGV || false; + static final boolean DEBUG_THUMBNAILS = localLOGV || false; static final boolean DEBUG_PAUSE = localLOGV || false; static final boolean DEBUG_OOM_ADJ = localLOGV || false; static final boolean DEBUG_TRANSITION = localLOGV || false; @@ -447,6 +449,11 @@ public final class ActivityManagerService extends ActivityManagerNative final ArrayList<Integer> mUserLru = new ArrayList<Integer>(); /** + * Constant array of the users that are currently started. + */ + int[] mStartedUserArray = new int[] { 0 }; + + /** * Registered observers of the user switching mechanics. */ final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers @@ -831,7 +838,8 @@ public final class ActivityManagerService extends ActivityManagerNative static ActivityManagerService mSelf; static ActivityThread mSystemThread; - private int mCurrentUserId; + private int mCurrentUserId = 0; + private int[] mCurrentUserArray = new int[] { 0 }; private UserManagerService mUserManager; private final class AppDeathRecipient implements IBinder.DeathRecipient { @@ -1567,6 +1575,7 @@ public final class ActivityManagerService extends ActivityManagerNative // User 0 is the first and only user that runs at boot. mStartedUsers.put(0, new UserStartedState(new UserHandle(0), true)); mUserLru.add(Integer.valueOf(0)); + updateStartedUserArrayLocked(); GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", ConfigurationInfo.GL_ES_VERSION_UNDEFINED); @@ -3749,6 +3758,7 @@ public final class ActivityManagerService extends ActivityManagerNative intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); } intent.putExtra(Intent.EXTRA_UID, uid); + intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(uid)); broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, false, false, @@ -5849,6 +5859,18 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } + public Bitmap getTaskTopThumbnail(int id) { + synchronized (this) { + enforceCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER, + "getTaskTopThumbnail()"); + TaskRecord tr = taskForIdLocked(id); + if (tr != null) { + return mMainStack.getTaskTopThumbnailLocked(tr); + } + } + return null; + } + public boolean removeSubTask(int taskId, int subTaskIndex) { synchronized (this) { enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS, @@ -9298,6 +9320,9 @@ public final class ActivityManagerService extends ActivityManagerNative pw.print(mUserLru.get(i)); } pw.println("]"); + if (dumpAll) { + pw.print(" mStartedUserArray: "); pw.println(Arrays.toString(mStartedUserArray)); + } pw.println(" mHomeProcess: " + mHomeProcess); pw.println(" mPreviousProcess: " + mPreviousProcess); if (dumpAll) { @@ -11485,7 +11510,7 @@ public final class ActivityManagerService extends ActivityManagerNative userId = handleIncomingUserLocked(callingPid, callingUid, userId, true, false, "broadcast", callerPackage); - // Make sure that the user who is receiving this broadcast is started + // Make sure that the user who is receiving this broadcast is started. // If not, we will just skip it. if (userId != UserHandle.USER_ALL && mStartedUsers.get(userId) == null) { if (callingUid != Process.SYSTEM_UID || (intent.getFlags() @@ -11680,13 +11705,10 @@ public final class ActivityManagerService extends ActivityManagerNative int[] users; if (userId == UserHandle.USER_ALL) { // Caller wants broadcast to go to all started users. - users = new int[mStartedUsers.size()]; - for (int i=0; i<mStartedUsers.size(); i++) { - users[i] = mStartedUsers.keyAt(i); - } + users = mStartedUserArray; } else { // Caller wants broadcast to go to one specific user. - users = new int[] {userId}; + users = mCurrentUserArray; } // Figure out who all will receive this broadcast. @@ -13962,13 +13984,17 @@ public final class ActivityManagerService extends ActivityManagerNative // we need to start it now. if (mStartedUsers.get(userId) == null) { mStartedUsers.put(userId, new UserStartedState(new UserHandle(userId), false)); + updateStartedUserArrayLocked(); } mCurrentUserId = userId; + mCurrentUserArray = new int[] { userId }; final Integer userIdInt = Integer.valueOf(userId); mUserLru.remove(userIdInt); mUserLru.add(userIdInt); + mWindowManager.setCurrentUser(userId); + final UserStartedState uss = mStartedUsers.get(userId); mHandler.removeMessages(REPORT_USER_SWITCH_MSG); @@ -14007,7 +14033,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (!haveActivities) { startHomeActivityLocked(userId); } - + sendUserSwitchBroadcastsLocked(oldUserId, userId); } } finally { @@ -14241,6 +14267,7 @@ public final class ActivityManagerService extends ActivityManagerNative // User can no longer run. mStartedUsers.remove(userId); mUserLru.remove(Integer.valueOf(userId)); + updateStartedUserArrayLocked(); // Clean up all state and processes associated with the user. // Kill all the processes for the user. @@ -14297,6 +14324,29 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override + public int[] getRunningUserIds() { + if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) + != PackageManager.PERMISSION_GRANTED) { + String msg = "Permission Denial: isUserRunning() from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid() + + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + synchronized (this) { + return mStartedUserArray; + } + } + + private void updateStartedUserArrayLocked() { + mStartedUserArray = new int[mStartedUsers.size()]; + for (int i=0; i<mStartedUsers.size(); i++) { + mStartedUserArray[i] = mStartedUsers.keyAt(i); + } + } + + @Override public void registerUserSwitchObserver(IUserSwitchObserver observer) { if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED) { diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index 009fb5d..7ff5748 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -219,7 +219,13 @@ final class ActivityRecord { pw.print(prefix); pw.print("frozenBeforeDestroy="); pw.print(frozenBeforeDestroy); pw.print(" thumbnailNeeded="); pw.print(thumbnailNeeded); pw.print(" forceNewConfig="); pw.println(forceNewConfig); - pw.print(prefix); pw.print("thumbHolder="); pw.println(thumbHolder); + pw.print(prefix); pw.print("thumbHolder: "); + pw.print(Integer.toHexString(System.identityHashCode(thumbHolder))); + if (thumbHolder != null) { + pw.print(" bm="); pw.print(thumbHolder.lastThumbnail); + pw.print(" desc="); pw.print(thumbHolder.lastDescription); + } + pw.println(); if (launchTime != 0 || startTime != 0) { pw.print(prefix); pw.print("launchTime="); if (launchTime == 0) pw.print("0"); @@ -674,19 +680,15 @@ final class ActivityRecord { } if (thumbHolder != null) { if (newThumbnail != null) { + if (ActivityManagerService.DEBUG_THUMBNAILS) Slog.i(ActivityManagerService.TAG, + "Setting thumbnail of " + this + " holder " + thumbHolder + + " to " + newThumbnail); thumbHolder.lastThumbnail = newThumbnail; } thumbHolder.lastDescription = description; } } - void clearThumbnail() { - if (thumbHolder != null) { - thumbHolder.lastThumbnail = null; - thumbHolder.lastDescription = null; - } - } - void startLaunchTickingLocked() { if (ActivityManagerService.IS_USER_BUILD) { return; diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 29ee0bc..df50d89 100755 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -1203,8 +1203,7 @@ final class ActivityStack { if (mMainStack) { mService.reportResumedActivityLocked(next); } - - next.clearThumbnail(); + if (mMainStack) { mService.setFocusedActivityLocked(next); } @@ -4328,18 +4327,33 @@ final class ActivityStack { finishTaskMoveLocked(task); return true; } - + public ActivityManager.TaskThumbnails getTaskThumbnailsLocked(TaskRecord tr) { TaskAccessInfo info = getTaskAccessInfoLocked(tr.taskId, true); ActivityRecord resumed = mResumedActivity; if (resumed != null && resumed.thumbHolder == tr) { info.mainThumbnail = resumed.stack.screenshotActivities(resumed); - } else { - info.mainThumbnail = tr.lastThumbnail; } return info; } + public Bitmap getTaskTopThumbnailLocked(TaskRecord tr) { + ActivityRecord resumed = mResumedActivity; + if (resumed != null && resumed.task == tr) { + // This task is the current resumed task, we just need to take + // a screenshot of it and return that. + return resumed.stack.screenshotActivities(resumed); + } + // Return the information about the task, to figure out the top + // thumbnail to return. + TaskAccessInfo info = getTaskAccessInfoLocked(tr.taskId, true); + if (info.numSubThumbbails <= 0) { + return info.mainThumbnail; + } else { + return info.subtasks.get(info.numSubThumbbails-1).holder.lastThumbnail; + } + } + public ActivityRecord removeTaskActivitiesLocked(int taskId, int subTaskIndex, boolean taskRequired) { TaskAccessInfo info = getTaskAccessInfoLocked(taskId, false); @@ -4370,7 +4384,6 @@ final class ActivityStack { } public TaskAccessInfo getTaskAccessInfoLocked(int taskId, boolean inclThumbs) { - ActivityRecord resumed = mResumedActivity; final TaskAccessInfo thumbs = new TaskAccessInfo(); // How many different sub-thumbnails? final int NA = mHistory.size(); @@ -4380,6 +4393,10 @@ final class ActivityStack { ActivityRecord ar = mHistory.get(j); if (!ar.finishing && ar.task.taskId == taskId) { holder = ar.thumbHolder; + if (holder != null) { + thumbs.mainThumbnail = holder.lastThumbnail; + } + j++; break; } j++; @@ -4394,7 +4411,6 @@ final class ActivityStack { ArrayList<TaskAccessInfo.SubTask> subtasks = new ArrayList<TaskAccessInfo.SubTask>(); thumbs.subtasks = subtasks; - ActivityRecord lastActivity = null; while (j < NA) { ActivityRecord ar = mHistory.get(j); j++; @@ -4404,30 +4420,28 @@ final class ActivityStack { if (ar.task.taskId != taskId) { break; } - lastActivity = ar; if (ar.thumbHolder != holder && holder != null) { thumbs.numSubThumbbails++; holder = ar.thumbHolder; TaskAccessInfo.SubTask sub = new TaskAccessInfo.SubTask(); - sub.thumbnail = holder.lastThumbnail; + sub.holder = holder; sub.activity = ar; sub.index = j-1; subtasks.add(sub); } } - if (lastActivity != null && subtasks.size() > 0) { - if (resumed == lastActivity) { - TaskAccessInfo.SubTask sub = subtasks.get(subtasks.size()-1); - sub.thumbnail = lastActivity.stack.screenshotActivities(lastActivity); - } - } if (thumbs.numSubThumbbails > 0) { thumbs.retriever = new IThumbnailRetriever.Stub() { public Bitmap getThumbnail(int index) { if (index < 0 || index >= thumbs.subtasks.size()) { return null; } - return thumbs.subtasks.get(index).thumbnail; + TaskAccessInfo.SubTask sub = thumbs.subtasks.get(index); + ActivityRecord resumed = mResumedActivity; + if (resumed != null && resumed.thumbHolder == sub.holder) { + return resumed.stack.screenshotActivities(resumed); + } + return sub.holder.lastThumbnail; } }; } diff --git a/services/java/com/android/server/am/TaskAccessInfo.java b/services/java/com/android/server/am/TaskAccessInfo.java index 5618c1a..50aeec1 100644 --- a/services/java/com/android/server/am/TaskAccessInfo.java +++ b/services/java/com/android/server/am/TaskAccessInfo.java @@ -19,11 +19,10 @@ package com.android.server.am; import java.util.ArrayList; import android.app.ActivityManager.TaskThumbnails; -import android.graphics.Bitmap; final class TaskAccessInfo extends TaskThumbnails { final static class SubTask { - Bitmap thumbnail; + ThumbnailHolder holder; ActivityRecord activity; int index; } diff --git a/services/java/com/android/server/am/UsageStatsService.java b/services/java/com/android/server/am/UsageStatsService.java index 7059674..6dae4aa 100644 --- a/services/java/com/android/server/am/UsageStatsService.java +++ b/services/java/com/android/server/am/UsageStatsService.java @@ -650,7 +650,7 @@ public final class UsageStatsService extends IUsageStats.Stub { public void monitorPackages() { mPackageMonitor = new PackageMonitor() { @Override - public void onPackageRemoved(String packageName, int uid) { + public void onPackageRemovedAllUsers(String packageName, int uid) { synchronized (mStatsLock) { mLastResumeTimes.remove(packageName); } diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java index 39f2418..02fc6b1 100644 --- a/services/java/com/android/server/display/DisplayManagerService.java +++ b/services/java/com/android/server/display/DisplayManagerService.java @@ -148,6 +148,9 @@ public final class DisplayManagerService extends IDisplayManager.Stub { private final DisplayViewport mDefaultViewport = new DisplayViewport(); private final DisplayViewport mExternalTouchViewport = new DisplayViewport(); + // Persistent data store for all internal settings maintained by the display manager service. + private final PersistentDataStore mPersistentDataStore = new PersistentDataStore(); + // Temporary callback list, used when sending display events to applications. // May be used outside of the lock but only on the handler thread. private final ArrayList<CallbackRecord> mTempCallbacks = new ArrayList<CallbackRecord>(); @@ -403,6 +406,50 @@ public final class DisplayManagerService extends IDisplayManager.Stub { } @Override // Binder call + public void renameWifiDisplay(String address, String alias) { + if (mContext.checkCallingPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission"); + } + if (address == null) { + throw new IllegalArgumentException("address must not be null"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + if (mWifiDisplayAdapter != null) { + mWifiDisplayAdapter.requestRenameLocked(address, alias); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call + public void forgetWifiDisplay(String address) { + if (mContext.checkCallingPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission"); + } + if (address == null) { + throw new IllegalArgumentException("address must not be null"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + if (mWifiDisplayAdapter != null) { + mWifiDisplayAdapter.requestForgetLocked(address); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override // Binder call public WifiDisplayStatus getWifiDisplayStatus() { if (mContext.checkCallingPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) != PackageManager.PERMISSION_GRANTED) { @@ -439,15 +486,27 @@ public final class DisplayManagerService extends IDisplayManager.Stub { private void registerAdditionalDisplayAdapters() { synchronized (mSyncRoot) { if (shouldRegisterNonEssentialDisplayAdaptersLocked()) { - registerDisplayAdapterLocked(new OverlayDisplayAdapter( - mSyncRoot, mContext, mHandler, mDisplayAdapterListener, mUiHandler)); - mWifiDisplayAdapter = new WifiDisplayAdapter( - mSyncRoot, mContext, mHandler, mDisplayAdapterListener); - registerDisplayAdapterLocked(mWifiDisplayAdapter); + registerOverlayDisplayAdapterLocked(); + registerWifiDisplayAdapterLocked(); } } } + private void registerOverlayDisplayAdapterLocked() { + registerDisplayAdapterLocked(new OverlayDisplayAdapter( + mSyncRoot, mContext, mHandler, mDisplayAdapterListener, mUiHandler)); + } + + private void registerWifiDisplayAdapterLocked() { + if (mContext.getResources().getBoolean( + com.android.internal.R.bool.config_enableWifiDisplay)) { + mWifiDisplayAdapter = new WifiDisplayAdapter( + mSyncRoot, mContext, mHandler, mDisplayAdapterListener, + mPersistentDataStore); + registerDisplayAdapterLocked(mWifiDisplayAdapter); + } + } + private boolean shouldRegisterNonEssentialDisplayAdaptersLocked() { // In safe mode, we disable non-essential display adapters to give the user // an opportunity to fix broken settings or other problems that might affect diff --git a/services/java/com/android/server/display/PersistentDataStore.java b/services/java/com/android/server/display/PersistentDataStore.java new file mode 100644 index 0000000..3a6e1a6 --- /dev/null +++ b/services/java/com/android/server/display/PersistentDataStore.java @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2012 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.display; + +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import android.hardware.display.WifiDisplay; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.Xml; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; + +import libcore.io.IoUtils; +import libcore.util.Objects; + +/** + * Manages persistent state recorded by the display manager service as an XML file. + * Caller must acquire lock on the data store before accessing it. + * + * File format: + * <code> + * <display-manager-state> + * <remembered-wifi-displays> + * <wifi-display deviceAddress="00:00:00:00:00:00" deviceName="XXXX" deviceAlias="YYYY" /> + * >remembered-wifi-displays> + * >/display-manager-state> + * </code> + * + * TODO: refactor this to extract common code shared with the input manager's data store + */ +final class PersistentDataStore { + static final String TAG = "DisplayManager"; + + // Remembered Wifi display devices. + private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>(); + + // The atomic file used to safely read or write the file. + private final AtomicFile mAtomicFile; + + // True if the data has been loaded. + private boolean mLoaded; + + // True if there are changes to be saved. + private boolean mDirty; + + public PersistentDataStore() { + mAtomicFile = new AtomicFile(new File("/data/system/display-manager-state.xml")); + } + + public void saveIfNeeded() { + if (mDirty) { + save(); + mDirty = false; + } + } + + public WifiDisplay[] getRememberedWifiDisplays() { + loadIfNeeded(); + return mRememberedWifiDisplays.toArray(new WifiDisplay[mRememberedWifiDisplays.size()]); + } + + public WifiDisplay applyWifiDisplayAlias(WifiDisplay display) { + if (display != null) { + loadIfNeeded(); + + String alias = null; + int index = findRememberedWifiDisplay(display.getDeviceAddress()); + if (index >= 0) { + alias = mRememberedWifiDisplays.get(index).getDeviceAlias(); + } + if (!Objects.equal(display.getDeviceAlias(), alias)) { + return new WifiDisplay(display.getDeviceAddress(), display.getDeviceName(), alias); + } + } + return display; + } + + public WifiDisplay[] applyWifiDisplayAliases(WifiDisplay[] displays) { + WifiDisplay[] results = displays; + if (results != null) { + int count = displays.length; + for (int i = 0; i < count; i++) { + WifiDisplay result = applyWifiDisplayAlias(displays[i]); + if (result != displays[i]) { + if (results == displays) { + results = new WifiDisplay[count]; + System.arraycopy(displays, 0, results, 0, count); + } + results[i] = result; + } + } + } + return results; + } + + public boolean rememberWifiDisplay(WifiDisplay display) { + loadIfNeeded(); + + int index = findRememberedWifiDisplay(display.getDeviceAddress()); + if (index >= 0) { + WifiDisplay other = mRememberedWifiDisplays.get(index); + if (other.equals(display)) { + return false; // already remembered without change + } + mRememberedWifiDisplays.set(index, display); + } else { + mRememberedWifiDisplays.add(display); + } + setDirty(); + return true; + } + + public boolean renameWifiDisplay(String deviceAddress, String alias) { + int index = findRememberedWifiDisplay(deviceAddress); + if (index >= 0) { + WifiDisplay display = mRememberedWifiDisplays.get(index); + if (Objects.equal(display.getDeviceAlias(), alias)) { + return false; // already has this alias + } + WifiDisplay renamedDisplay = new WifiDisplay(deviceAddress, + display.getDeviceName(), alias); + mRememberedWifiDisplays.set(index, renamedDisplay); + setDirty(); + return true; + } + return false; + } + + public boolean forgetWifiDisplay(String deviceAddress) { + int index = findRememberedWifiDisplay(deviceAddress); + if (index >= 0) { + mRememberedWifiDisplays.remove(index); + setDirty(); + return true; + } + return false; + } + + private int findRememberedWifiDisplay(String deviceAddress) { + int count = mRememberedWifiDisplays.size(); + for (int i = 0; i < count; i++) { + if (mRememberedWifiDisplays.get(i).getDeviceAddress().equals(deviceAddress)) { + return i; + } + } + return -1; + } + + private void loadIfNeeded() { + if (!mLoaded) { + load(); + mLoaded = true; + } + } + + private void setDirty() { + mDirty = true; + } + + private void clearState() { + mRememberedWifiDisplays.clear(); + } + + private void load() { + clearState(); + + final InputStream is; + try { + is = mAtomicFile.openRead(); + } catch (FileNotFoundException ex) { + return; + } + + XmlPullParser parser; + try { + parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream(is), null); + loadFromXml(parser); + } catch (IOException ex) { + Slog.w(TAG, "Failed to load display manager persistent store data.", ex); + clearState(); + } catch (XmlPullParserException ex) { + Slog.w(TAG, "Failed to load display manager persistent store data.", ex); + clearState(); + } finally { + IoUtils.closeQuietly(is); + } + } + + private void save() { + final FileOutputStream os; + try { + os = mAtomicFile.startWrite(); + boolean success = false; + try { + XmlSerializer serializer = new FastXmlSerializer(); + serializer.setOutput(new BufferedOutputStream(os), "utf-8"); + saveToXml(serializer); + serializer.flush(); + success = true; + } finally { + if (success) { + mAtomicFile.finishWrite(os); + } else { + mAtomicFile.failWrite(os); + } + } + } catch (IOException ex) { + Slog.w(TAG, "Failed to save display manager persistent store data.", ex); + } + } + + private void loadFromXml(XmlPullParser parser) + throws IOException, XmlPullParserException { + XmlUtils.beginDocument(parser, "display-manager-state"); + final int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + if (parser.getName().equals("remembered-wifi-displays")) { + loadRememberedWifiDisplaysFromXml(parser); + } + } + } + + private void loadRememberedWifiDisplaysFromXml(XmlPullParser parser) + throws IOException, XmlPullParserException { + final int outerDepth = parser.getDepth(); + while (XmlUtils.nextElementWithin(parser, outerDepth)) { + if (parser.getName().equals("wifi-display")) { + String deviceAddress = parser.getAttributeValue(null, "deviceAddress"); + String deviceName = parser.getAttributeValue(null, "deviceName"); + String deviceAlias = parser.getAttributeValue(null, "deviceAlias"); + if (deviceAddress == null || deviceName == null) { + throw new XmlPullParserException( + "Missing deviceAddress or deviceName attribute on wifi-display."); + } + if (findRememberedWifiDisplay(deviceAddress) >= 0) { + throw new XmlPullParserException( + "Found duplicate wifi display device address."); + } + + mRememberedWifiDisplays.add( + new WifiDisplay(deviceAddress, deviceName, deviceAlias)); + } + } + } + + private void saveToXml(XmlSerializer serializer) throws IOException { + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + serializer.startTag(null, "display-manager-state"); + serializer.startTag(null, "remembered-wifi-displays"); + for (WifiDisplay display : mRememberedWifiDisplays) { + serializer.startTag(null, "wifi-display"); + serializer.attribute(null, "deviceAddress", display.getDeviceAddress()); + serializer.attribute(null, "deviceName", display.getDeviceName()); + if (display.getDeviceAlias() != null) { + serializer.attribute(null, "deviceAlias", display.getDeviceAlias()); + } + serializer.endTag(null, "wifi-display"); + } + serializer.endTag(null, "remembered-wifi-displays"); + serializer.endTag(null, "display-manager-state"); + serializer.endDocument(); + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java index b57d3dc..1d50ded 100644 --- a/services/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/java/com/android/server/display/WifiDisplayAdapter.java @@ -49,21 +49,26 @@ import java.util.Arrays; final class WifiDisplayAdapter extends DisplayAdapter { private static final String TAG = "WifiDisplayAdapter"; + private PersistentDataStore mPersistentDataStore; + private WifiDisplayController mDisplayController; private WifiDisplayDevice mDisplayDevice; private WifiDisplayStatus mCurrentStatus; - private boolean mEnabled; + private int mFeatureState; private int mScanState; private int mActiveDisplayState; private WifiDisplay mActiveDisplay; - private WifiDisplay[] mKnownDisplays = WifiDisplay.EMPTY_ARRAY; + private WifiDisplay[] mAvailableDisplays = WifiDisplay.EMPTY_ARRAY; + private WifiDisplay[] mRememberedDisplays = WifiDisplay.EMPTY_ARRAY; private boolean mPendingStatusChangeBroadcast; public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, - Context context, Handler handler, Listener listener) { + Context context, Handler handler, Listener listener, + PersistentDataStore persistentDataStore) { super(syncRoot, context, handler, listener, TAG); + mPersistentDataStore = persistentDataStore; } @Override @@ -71,11 +76,12 @@ final class WifiDisplayAdapter extends DisplayAdapter { super.dumpLocked(pw); pw.println("mCurrentStatus=" + getWifiDisplayStatusLocked()); - pw.println("mEnabled=" + mEnabled); + pw.println("mFeatureState=" + mFeatureState); pw.println("mScanState=" + mScanState); pw.println("mActiveDisplayState=" + mActiveDisplayState); pw.println("mActiveDisplay=" + mActiveDisplay); - pw.println("mKnownDisplays=" + Arrays.toString(mKnownDisplays)); + pw.println("mAvailableDisplays=" + Arrays.toString(mAvailableDisplays)); + pw.println("mRememberedDisplays=" + Arrays.toString(mRememberedDisplays)); pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast); // Try to dump the controller state. @@ -93,6 +99,8 @@ final class WifiDisplayAdapter extends DisplayAdapter { public void registerLocked() { super.registerLocked(); + updateRememberedDisplaysLocked(); + getHandler().post(new Runnable() { @Override public void run() { @@ -135,18 +143,58 @@ final class WifiDisplayAdapter extends DisplayAdapter { }); } + public void requestRenameLocked(String address, String alias) { + if (alias != null) { + alias = alias.trim(); + if (alias.isEmpty()) { + alias = null; + } + } + + if (mPersistentDataStore.renameWifiDisplay(address, alias)) { + mPersistentDataStore.saveIfNeeded(); + updateRememberedDisplaysLocked(); + scheduleStatusChangedBroadcastLocked(); + } + } + + public void requestForgetLocked(String address) { + if (mPersistentDataStore.forgetWifiDisplay(address)) { + mPersistentDataStore.saveIfNeeded(); + updateRememberedDisplaysLocked(); + scheduleStatusChangedBroadcastLocked(); + } + + if (mActiveDisplay != null && mActiveDisplay.getDeviceAddress().equals(address)) { + requestDisconnectLocked(); + } + } + public WifiDisplayStatus getWifiDisplayStatusLocked() { if (mCurrentStatus == null) { - mCurrentStatus = new WifiDisplayStatus(mEnabled, mScanState, mActiveDisplayState, - mActiveDisplay, mKnownDisplays); + mCurrentStatus = new WifiDisplayStatus( + mFeatureState, mScanState, mActiveDisplayState, + mActiveDisplay, mAvailableDisplays, mRememberedDisplays); } return mCurrentStatus; } + private void updateRememberedDisplaysLocked() { + mRememberedDisplays = mPersistentDataStore.getRememberedWifiDisplays(); + mActiveDisplay = mPersistentDataStore.applyWifiDisplayAlias(mActiveDisplay); + mAvailableDisplays = mPersistentDataStore.applyWifiDisplayAliases(mAvailableDisplays); + } + private void handleConnectLocked(WifiDisplay display, Surface surface, int width, int height, int flags) { handleDisconnectLocked(); + if (mPersistentDataStore.rememberWifiDisplay(display)) { + mPersistentDataStore.saveIfNeeded(); + updateRememberedDisplaysLocked(); + scheduleStatusChangedBroadcastLocked(); + } + int deviceFlags = 0; if ((flags & RemoteDisplay.DISPLAY_FLAG_SECURE) != 0) { deviceFlags |= DisplayDeviceInfo.FLAG_SUPPORTS_SECURE_VIDEO_OUTPUT; @@ -154,7 +202,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { float refreshRate = 60.0f; // TODO: get this for real - String name = display.getDeviceName(); + String name = display.getFriendlyDisplayName(); IBinder displayToken = Surface.createDisplay(name); mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height, refreshRate, deviceFlags, surface); @@ -170,6 +218,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { } private void scheduleStatusChangedBroadcastLocked() { + mCurrentStatus = null; if (!mPendingStatusChangeBroadcast) { mPendingStatusChangeBroadcast = true; getHandler().post(mStatusChangeBroadcast); @@ -202,11 +251,10 @@ final class WifiDisplayAdapter extends DisplayAdapter { private final WifiDisplayController.Listener mWifiDisplayListener = new WifiDisplayController.Listener() { @Override - public void onEnablementChanged(boolean enabled) { + public void onFeatureStateChanged(int featureState) { synchronized (getSyncRoot()) { - if (mEnabled != enabled) { - mCurrentStatus = null; - mEnabled = enabled; + if (mFeatureState != featureState) { + mFeatureState = featureState; scheduleStatusChangedBroadcastLocked(); } } @@ -216,20 +264,21 @@ final class WifiDisplayAdapter extends DisplayAdapter { public void onScanStarted() { synchronized (getSyncRoot()) { if (mScanState != WifiDisplayStatus.SCAN_STATE_SCANNING) { - mCurrentStatus = null; mScanState = WifiDisplayStatus.SCAN_STATE_SCANNING; scheduleStatusChangedBroadcastLocked(); } } } - public void onScanFinished(WifiDisplay[] knownDisplays) { + public void onScanFinished(WifiDisplay[] availableDisplays) { synchronized (getSyncRoot()) { + availableDisplays = mPersistentDataStore.applyWifiDisplayAliases( + availableDisplays); + if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING - || !Arrays.equals(mKnownDisplays, knownDisplays)) { - mCurrentStatus = null; + || !Arrays.equals(mAvailableDisplays, availableDisplays)) { mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING; - mKnownDisplays = knownDisplays; + mAvailableDisplays = availableDisplays; scheduleStatusChangedBroadcastLocked(); } } @@ -238,10 +287,11 @@ final class WifiDisplayAdapter extends DisplayAdapter { @Override public void onDisplayConnecting(WifiDisplay display) { synchronized (getSyncRoot()) { + display = mPersistentDataStore.applyWifiDisplayAlias(display); + if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTING || mActiveDisplay == null || !mActiveDisplay.equals(display)) { - mCurrentStatus = null; mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTING; mActiveDisplay = display; scheduleStatusChangedBroadcastLocked(); @@ -254,7 +304,6 @@ final class WifiDisplayAdapter extends DisplayAdapter { synchronized (getSyncRoot()) { if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED || mActiveDisplay != null) { - mCurrentStatus = null; mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED; mActiveDisplay = null; scheduleStatusChangedBroadcastLocked(); @@ -266,12 +315,12 @@ final class WifiDisplayAdapter extends DisplayAdapter { public void onDisplayConnected(WifiDisplay display, Surface surface, int width, int height, int flags) { synchronized (getSyncRoot()) { + display = mPersistentDataStore.applyWifiDisplayAlias(display); handleConnectLocked(display, surface, width, height, flags); if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTED || mActiveDisplay == null || !mActiveDisplay.equals(display)) { - mCurrentStatus = null; mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTED; mActiveDisplay = display; scheduleStatusChangedBroadcastLocked(); @@ -287,7 +336,6 @@ final class WifiDisplayAdapter extends DisplayAdapter { if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED || mActiveDisplay != null) { - mCurrentStatus = null; mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED; mActiveDisplay = null; scheduleStatusChangedBroadcastLocked(); diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java index 328f687..84f4e83 100644 --- a/services/java/com/android/server/display/WifiDisplayController.java +++ b/services/java/com/android/server/display/WifiDisplayController.java @@ -19,13 +19,17 @@ package com.android.server.display; import com.android.internal.util.DumpUtils; import android.content.BroadcastReceiver; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.database.ContentObserver; import android.hardware.display.WifiDisplay; +import android.hardware.display.WifiDisplayStatus; import android.media.AudioManager; import android.media.RemoteDisplay; import android.net.NetworkInfo; +import android.net.Uri; import android.net.wifi.p2p.WifiP2pConfig; import android.net.wifi.p2p.WifiP2pDevice; import android.net.wifi.p2p.WifiP2pDeviceList; @@ -37,6 +41,7 @@ import android.net.wifi.p2p.WifiP2pManager.Channel; import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener; import android.net.wifi.p2p.WifiP2pManager.PeerListListener; import android.os.Handler; +import android.provider.Settings; import android.util.Slog; import android.view.Surface; @@ -48,6 +53,8 @@ import java.net.SocketException; import java.util.ArrayList; import java.util.Enumeration; +import libcore.util.Objects; + /** * Manages all of the various asynchronous interactions with the {@link WifiP2pManager} * on behalf of {@link WifiDisplayAdapter}. @@ -94,9 +101,12 @@ final class WifiDisplayController implements DumpUtils.Dump { private boolean mWfdEnabling; private NetworkInfo mNetworkInfo; - private final ArrayList<WifiP2pDevice> mKnownWifiDisplayPeers = + private final ArrayList<WifiP2pDevice> mAvailableWifiDisplayPeers = new ArrayList<WifiP2pDevice>(); + // True if Wifi display is enabled by the user. + private boolean mWifiDisplayOnSetting; + // True if there is a call to discoverPeers in progress. private boolean mDiscoverPeersInProgress; @@ -132,6 +142,13 @@ final class WifiDisplayController implements DumpUtils.Dump { // True if the remote submix is enabled. private boolean mRemoteSubmixOn; + // The information we have most recently told WifiDisplayAdapter about. + private WifiDisplay mAdvertisedDisplay; + private Surface mAdvertisedDisplaySurface; + private int mAdvertisedDisplayWidth; + private int mAdvertisedDisplayHeight; + private int mAdvertisedDisplayFlags; + public WifiDisplayController(Context context, Handler handler, Listener listener) { mContext = context; mHandler = handler; @@ -146,10 +163,31 @@ final class WifiDisplayController implements DumpUtils.Dump { intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); - context.registerReceiver(mWifiP2pReceiver, intentFilter); + context.registerReceiver(mWifiP2pReceiver, intentFilter, null, mHandler); + + ContentObserver settingsObserver = new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange, Uri uri) { + updateSettings(); + } + }; + + final ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.WIFI_DISPLAY_ON), false, settingsObserver); + updateSettings(); + } + + private void updateSettings() { + final ContentResolver resolver = mContext.getContentResolver(); + mWifiDisplayOnSetting = Settings.Global.getInt(resolver, + Settings.Global.WIFI_DISPLAY_ON, 0) != 0; + + updateWfdEnableState(); } public void dump(PrintWriter pw) { + pw.println("mWifiDisplayOnSetting=" + mWifiDisplayOnSetting); pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled); pw.println("mWfdEnabled=" + mWfdEnabled); pw.println("mWfdEnabling=" + mWfdEnabling); @@ -164,9 +202,14 @@ final class WifiDisplayController implements DumpUtils.Dump { pw.println("mRemoteDisplayInterface=" + mRemoteDisplayInterface); pw.println("mRemoteDisplayConnected=" + mRemoteDisplayConnected); pw.println("mRemoteSubmixOn=" + mRemoteSubmixOn); - - pw.println("mKnownWifiDisplayPeers: size=" + mKnownWifiDisplayPeers.size()); - for (WifiP2pDevice device : mKnownWifiDisplayPeers) { + pw.println("mAdvertisedDisplay=" + mAdvertisedDisplay); + pw.println("mAdvertisedDisplaySurface=" + mAdvertisedDisplaySurface); + pw.println("mAdvertisedDisplayWidth=" + mAdvertisedDisplayWidth); + pw.println("mAdvertisedDisplayHeight=" + mAdvertisedDisplayHeight); + pw.println("mAdvertisedDisplayFlags=" + mAdvertisedDisplayFlags); + + pw.println("mAvailableWifiDisplayPeers: size=" + mAvailableWifiDisplayPeers.size()); + for (WifiP2pDevice device : mAvailableWifiDisplayPeers) { pw.println(" " + describeWifiP2pDevice(device)); } } @@ -176,7 +219,7 @@ final class WifiDisplayController implements DumpUtils.Dump { } public void requestConnect(String address) { - for (WifiP2pDevice device : mKnownWifiDisplayPeers) { + for (WifiP2pDevice device : mAvailableWifiDisplayPeers) { if (device.deviceAddress.equals(address)) { connect(device); } @@ -187,49 +230,65 @@ final class WifiDisplayController implements DumpUtils.Dump { disconnect(); } - private void enableWfd() { - if (!mWfdEnabled && !mWfdEnabling) { - mWfdEnabling = true; - - WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo(); - wfdInfo.setWfdEnabled(true); - wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE); - wfdInfo.setSessionAvailable(true); - wfdInfo.setControlPort(DEFAULT_CONTROL_PORT); - wfdInfo.setMaxThroughput(MAX_THROUGHPUT); - mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() { - @Override - public void onSuccess() { - if (DEBUG) { - Slog.d(TAG, "Successfully set WFD info."); - } - if (mWfdEnabling) { - mWfdEnabling = false; - setWfdEnabled(true); + private void updateWfdEnableState() { + if (mWifiDisplayOnSetting && mWifiP2pEnabled) { + // WFD should be enabled. + if (!mWfdEnabled && !mWfdEnabling) { + mWfdEnabling = true; + + WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo(); + wfdInfo.setWfdEnabled(true); + wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE); + wfdInfo.setSessionAvailable(true); + wfdInfo.setControlPort(DEFAULT_CONTROL_PORT); + wfdInfo.setMaxThroughput(MAX_THROUGHPUT); + mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() { + @Override + public void onSuccess() { + if (DEBUG) { + Slog.d(TAG, "Successfully set WFD info."); + } + if (mWfdEnabling) { + mWfdEnabling = false; + mWfdEnabled = true; + reportFeatureState(); + } } - } - @Override - public void onFailure(int reason) { - if (DEBUG) { - Slog.d(TAG, "Failed to set WFD info with reason " + reason + "."); + @Override + public void onFailure(int reason) { + if (DEBUG) { + Slog.d(TAG, "Failed to set WFD info with reason " + reason + "."); + } + mWfdEnabling = false; } - mWfdEnabling = false; - } - }); + }); + } + } else { + // WFD should be disabled. + mWfdEnabling = false; + mWfdEnabled = false; + reportFeatureState(); + disconnect(); } } - private void setWfdEnabled(final boolean enabled) { - if (mWfdEnabled != enabled) { - mWfdEnabled = enabled; - mHandler.post(new Runnable() { - @Override - public void run() { - mListener.onEnablementChanged(enabled); - } - }); + private void reportFeatureState() { + final int featureState = computeFeatureState(); + mHandler.post(new Runnable() { + @Override + public void run() { + mListener.onFeatureStateChanged(featureState); + } + }); + } + + private int computeFeatureState() { + if (!mWifiP2pEnabled) { + return WifiDisplayStatus.FEATURE_STATE_DISABLED; } + return mWifiDisplayOnSetting ? WifiDisplayStatus.FEATURE_STATE_ON : + WifiDisplayStatus.FEATURE_STATE_OFF; } private void discoverPeers() { @@ -296,14 +355,14 @@ final class WifiDisplayController implements DumpUtils.Dump { Slog.d(TAG, "Received list of peers."); } - mKnownWifiDisplayPeers.clear(); + mAvailableWifiDisplayPeers.clear(); for (WifiP2pDevice device : peers.getDeviceList()) { if (DEBUG) { Slog.d(TAG, " " + describeWifiP2pDevice(device)); } if (isWifiDisplay(device)) { - mKnownWifiDisplayPeers.add(device); + mAvailableWifiDisplayPeers.add(device); } } @@ -322,10 +381,10 @@ final class WifiDisplayController implements DumpUtils.Dump { } private void handleScanFinished() { - final int count = mKnownWifiDisplayPeers.size(); + final int count = mAvailableWifiDisplayPeers.size(); final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count); for (int i = 0; i < count; i++) { - displays[i] = createWifiDisplay(mKnownWifiDisplayPeers.get(i)); + displays[i] = createWifiDisplay(mAvailableWifiDisplayPeers.get(i)); } mHandler.post(new Runnable() { @@ -368,18 +427,11 @@ final class WifiDisplayController implements DumpUtils.Dump { } private void retryConnection() { - if (mDesiredDevice != null && mConnectedDevice != mDesiredDevice - && mConnectionRetriesLeft > 0) { - mConnectionRetriesLeft -= 1; - Slog.i(TAG, "Retrying Wifi display connection. Retries left: " - + mConnectionRetriesLeft); - - // Cheap hack. Make a new instance of the device object so that we - // can distinguish it from the previous connection attempt. - // This will cause us to tear everything down before we try again. - mDesiredDevice = new WifiP2pDevice(mDesiredDevice); - updateConnection(); - } + // Cheap hack. Make a new instance of the device object so that we + // can distinguish it from the previous connection attempt. + // This will cause us to tear everything down before we try again. + mDesiredDevice = new WifiP2pDevice(mDesiredDevice); + updateConnection(); } /** @@ -401,13 +453,7 @@ final class WifiDisplayController implements DumpUtils.Dump { mHandler.removeCallbacks(mRtspTimeout); setRemoteSubmixOn(false); - - mHandler.post(new Runnable() { - @Override - public void run() { - mListener.onDisplayDisconnected(); - } - }); + unadvertiseDisplay(); // continue to next step } @@ -416,6 +462,8 @@ final class WifiDisplayController implements DumpUtils.Dump { if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) { Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName); + unadvertiseDisplay(); + final WifiP2pDevice oldDevice = mConnectedDevice; mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() { @Override @@ -446,6 +494,7 @@ final class WifiDisplayController implements DumpUtils.Dump { if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) { Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName); + unadvertiseDisplay(); mHandler.removeCallbacks(mConnectionTimeout); final WifiP2pDevice oldDevice = mConnectingDevice; @@ -475,6 +524,7 @@ final class WifiDisplayController implements DumpUtils.Dump { // Step 4. If we wanted to disconnect, then mission accomplished. if (mDesiredDevice == null) { + unadvertiseDisplay(); return; // done } @@ -488,13 +538,8 @@ final class WifiDisplayController implements DumpUtils.Dump { // Helps with STA & P2P concurrency config.groupOwnerIntent = WifiP2pConfig.MAX_GROUP_OWNER_INTENT; - final WifiDisplay display = createWifiDisplay(mConnectingDevice); - mHandler.post(new Runnable() { - @Override - public void run() { - mListener.onDisplayConnecting(display); - } - }); + WifiDisplay display = createWifiDisplay(mConnectingDevice); + advertiseDisplay(display, null, 0, 0, 0); final WifiP2pDevice newDevice = mDesiredDevice; mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() { @@ -543,8 +588,8 @@ final class WifiDisplayController implements DumpUtils.Dump { mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() { @Override - public void onDisplayConnected(final Surface surface, - final int width, final int height, final int flags) { + public void onDisplayConnected(Surface surface, + int width, int height, int flags) { if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) { Slog.i(TAG, "Opened RTSP connection with Wifi display: " + mConnectedDevice.deviceName); @@ -552,13 +597,7 @@ final class WifiDisplayController implements DumpUtils.Dump { mHandler.removeCallbacks(mRtspTimeout); final WifiDisplay display = createWifiDisplay(mConnectedDevice); - mHandler.post(new Runnable() { - @Override - public void run() { - mListener.onDisplayConnected(display, - surface, width, height, flags); - } - }); + advertiseDisplay(display, surface, width, height, flags); } } @@ -595,26 +634,13 @@ final class WifiDisplayController implements DumpUtils.Dump { } private void handleStateChanged(boolean enabled) { - if (mWifiP2pEnabled != enabled) { - mWifiP2pEnabled = enabled; - if (enabled) { - if (!mWfdEnabled) { - enableWfd(); - } - } else { - setWfdEnabled(false); - disconnect(); - } - } + mWifiP2pEnabled = enabled; + updateWfdEnableState(); } private void handlePeersChanged() { - if (mWifiP2pEnabled) { - if (mWfdEnabled) { - requestPeers(); - } else { - enableWfd(); - } + if (mWfdEnabled) { + requestPeers(); } } @@ -632,7 +658,8 @@ final class WifiDisplayController implements DumpUtils.Dump { if (mConnectingDevice != null && !info.contains(mConnectingDevice)) { Slog.i(TAG, "Aborting connection to Wifi display because " + "the current P2P group does not contain the device " - + "we expected to find: " + mConnectingDevice.deviceName); + + "we expected to find: " + mConnectingDevice.deviceName + + ", group info was: " + describeWifiP2pGroup(info)); handleConnectionFailure(false); return; } @@ -695,19 +722,18 @@ final class WifiDisplayController implements DumpUtils.Dump { private void handleConnectionFailure(boolean timeoutOccurred) { Slog.i(TAG, "Wifi display connection failed!"); - mHandler.post(new Runnable() { - @Override - public void run() { - mListener.onDisplayConnectionFailed(); - } - }); - if (mDesiredDevice != null) { if (mConnectionRetriesLeft > 0) { + final WifiP2pDevice oldDevice = mDesiredDevice; mHandler.postDelayed(new Runnable() { @Override public void run() { - retryConnection(); + if (mDesiredDevice == oldDevice && mConnectionRetriesLeft > 0) { + mConnectionRetriesLeft -= 1; + Slog.i(TAG, "Retrying Wifi display connection. Retries left: " + + mConnectionRetriesLeft); + retryConnection(); + } } }, timeoutOccurred ? 0 : CONNECT_RETRY_DELAY_MILLIS); } else { @@ -716,6 +742,48 @@ final class WifiDisplayController implements DumpUtils.Dump { } } + private void advertiseDisplay(final WifiDisplay display, + final Surface surface, final int width, final int height, final int flags) { + if (!Objects.equal(mAdvertisedDisplay, display) + || mAdvertisedDisplaySurface != surface + || mAdvertisedDisplayWidth != width + || mAdvertisedDisplayHeight != height + || mAdvertisedDisplayFlags != flags) { + final WifiDisplay oldDisplay = mAdvertisedDisplay; + final Surface oldSurface = mAdvertisedDisplaySurface; + + mAdvertisedDisplay = display; + mAdvertisedDisplaySurface = surface; + mAdvertisedDisplayWidth = width; + mAdvertisedDisplayHeight = height; + mAdvertisedDisplayFlags = flags; + + mHandler.post(new Runnable() { + @Override + public void run() { + if (oldSurface != null && surface != oldSurface) { + mListener.onDisplayDisconnected(); + } else if (oldDisplay != null && !Objects.equal(display, oldDisplay)) { + mListener.onDisplayConnectionFailed(); + } + + if (display != null) { + if (!Objects.equal(display, oldDisplay)) { + mListener.onDisplayConnecting(display); + } + if (surface != null && surface != oldSurface) { + mListener.onDisplayConnected(display, surface, width, height, flags); + } + } + } + }); + } + } + + private void unadvertiseDisplay() { + advertiseDisplay(null, null, 0, 0, 0); + } + private static Inet4Address getInterfaceAddress(WifiP2pGroup info) { NetworkInterface iface; try { @@ -768,7 +836,7 @@ final class WifiDisplayController implements DumpUtils.Dump { } private static WifiDisplay createWifiDisplay(WifiP2pDevice device) { - return new WifiDisplay(device.deviceAddress, device.deviceName); + return new WifiDisplay(device.deviceAddress, device.deviceName, null); } private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() { @@ -776,6 +844,8 @@ final class WifiDisplayController implements DumpUtils.Dump { public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) { + // This broadcast is sticky so we'll always get the initial Wifi P2P state + // on startup. boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, WifiP2pManager.WIFI_P2P_STATE_DISABLED)) == WifiP2pManager.WIFI_P2P_STATE_ENABLED; @@ -808,10 +878,10 @@ final class WifiDisplayController implements DumpUtils.Dump { * Called on the handler thread when displays are connected or disconnected. */ public interface Listener { - void onEnablementChanged(boolean enabled); + void onFeatureStateChanged(int featureState); void onScanStarted(); - void onScanFinished(WifiDisplay[] knownDisplays); + void onScanFinished(WifiDisplay[] availableDisplays); void onDisplayConnecting(WifiDisplay display); void onDisplayConnectionFailed(); diff --git a/services/java/com/android/server/net/LockdownVpnTracker.java b/services/java/com/android/server/net/LockdownVpnTracker.java index f2d6745..f32dd09 100644 --- a/services/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/java/com/android/server/net/LockdownVpnTracker.java @@ -268,9 +268,11 @@ public class LockdownVpnTracker { } public NetworkInfo augmentNetworkInfo(NetworkInfo info) { - final NetworkInfo vpnInfo = mVpn.getNetworkInfo(); - info = new NetworkInfo(info); - info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null); + if (info.isConnected()) { + final NetworkInfo vpnInfo = mVpn.getNetworkInfo(); + info = new NetworkInfo(info); + info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null); + } return info; } diff --git a/services/java/com/android/server/net/NetworkStatsCollection.java b/services/java/com/android/server/net/NetworkStatsCollection.java index 60666b4..3169035 100644 --- a/services/java/com/android/server/net/NetworkStatsCollection.java +++ b/services/java/com/android/server/net/NetworkStatsCollection.java @@ -31,6 +31,7 @@ import android.net.TrafficStats; import android.text.format.DateUtils; import android.util.AtomicFile; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FileRotator; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Objects; @@ -431,13 +432,13 @@ public class NetworkStatsCollection implements FileRotator.Reader { * moving any {@link NetworkStats#TAG_NONE} series to * {@link TrafficStats#UID_REMOVED}. */ - public void removeUid(int uid) { + public void removeUids(int[] uids) { final ArrayList<Key> knownKeys = Lists.newArrayList(); knownKeys.addAll(mStats.keySet()); // migrate all UID stats into special "removed" bucket for (Key key : knownKeys) { - if (key.uid == uid) { + if (ArrayUtils.contains(uids, key.uid)) { // only migrate combined TAG_NONE history if (key.tag == TAG_NONE) { final NetworkStatsHistory uidHistory = mStats.get(key); diff --git a/services/java/com/android/server/net/NetworkStatsRecorder.java b/services/java/com/android/server/net/NetworkStatsRecorder.java index c3ecf54..2b32b41 100644 --- a/services/java/com/android/server/net/NetworkStatsRecorder.java +++ b/services/java/com/android/server/net/NetworkStatsRecorder.java @@ -42,6 +42,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.ref.WeakReference; +import java.util.Arrays; import java.util.HashSet; import java.util.Map; @@ -233,23 +234,27 @@ public class NetworkStatsRecorder { * Remove the given UID from all {@link FileRotator} history, migrating it * to {@link TrafficStats#UID_REMOVED}. */ - public void removeUidLocked(int uid) { + public void removeUidsLocked(int[] uids) { try { - // process all existing data to migrate uid - mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uid)); + // Rewrite all persisted data to migrate UID stats + mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids)); } catch (IOException e) { - Log.wtf(TAG, "problem removing UID " + uid, e); + Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e); recoverFromWtf(); } - // clear UID from current stats snapshot + // Remove any pending stats + mPending.removeUids(uids); + mSinceBoot.removeUids(uids); + + // Clear UID from current stats snapshot if (mLastSnapshot != null) { - mLastSnapshot = mLastSnapshot.withoutUid(uid); + mLastSnapshot = mLastSnapshot.withoutUids(uids); } final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null; if (complete != null) { - complete.removeUid(uid); + complete.removeUids(uids); } } @@ -293,11 +298,11 @@ public class NetworkStatsRecorder { */ public static class RemoveUidRewriter implements FileRotator.Rewriter { private final NetworkStatsCollection mTemp; - private final int mUid; + private final int[] mUids; - public RemoveUidRewriter(long bucketDuration, int uid) { + public RemoveUidRewriter(long bucketDuration, int[] uids) { mTemp = new NetworkStatsCollection(bucketDuration); - mUid = uid; + mUids = uids; } @Override @@ -309,7 +314,7 @@ public class NetworkStatsRecorder { public void read(InputStream in) throws IOException { mTemp.read(in); mTemp.clearDirty(); - mTemp.removeUid(mUid); + mTemp.removeUids(mUids); } @Override diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java index 3a593e4..f2748a3 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -23,6 +23,7 @@ import static android.Manifest.permission.MODIFY_NETWORK_ACCOUNTING; import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY; import static android.content.Intent.ACTION_SHUTDOWN; import static android.content.Intent.ACTION_UID_REMOVED; +import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_UID; import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE; @@ -76,6 +77,8 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; import android.net.INetworkStatsService; @@ -112,6 +115,7 @@ import android.util.Slog; import android.util.SparseIntArray; import android.util.TrustedTime; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FileRotator; import com.android.internal.util.IndentingPrintWriter; import com.android.server.EventLogTags; @@ -122,8 +126,10 @@ import java.io.File; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.List; /** * Collect and persist detailed network statistics, and provide this data to @@ -322,6 +328,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final IntentFilter removedFilter = new IntentFilter(ACTION_UID_REMOVED); mContext.registerReceiver(mRemovedReceiver, removedFilter, null, mHandler); + // listen for user changes to clean stats + final IntentFilter userFilter = new IntentFilter(ACTION_USER_REMOVED); + mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler); + // persist stats during clean shutdown final IntentFilter shutdownFilter = new IntentFilter(ACTION_SHUTDOWN); mContext.registerReceiver(mShutdownReceiver, shutdownFilter); @@ -739,11 +749,34 @@ public class NetworkStatsService extends INetworkStatsService.Stub { public void onReceive(Context context, Intent intent) { // on background handler thread, and UID_REMOVED is protected // broadcast. - final int uid = intent.getIntExtra(EXTRA_UID, 0); + + final int uid = intent.getIntExtra(EXTRA_UID, -1); + if (uid == -1) return; + + synchronized (mStatsLock) { + mWakeLock.acquire(); + try { + removeUidsLocked(uid); + } finally { + mWakeLock.release(); + } + } + } + }; + + private BroadcastReceiver mUserReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // On background handler thread, and USER_REMOVED is protected + // broadcast. + + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userId == -1) return; + synchronized (mStatsLock) { mWakeLock.acquire(); try { - removeUidLocked(uid); + removeUserLocked(userId); } finally { mWakeLock.release(); } @@ -1034,15 +1067,37 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** * Clean up {@link #mUidRecorder} after UID is removed. */ - private void removeUidLocked(int uid) { - // perform one last poll before removing + private void removeUidsLocked(int... uids) { + if (LOGV) Slog.v(TAG, "removeUidsLocked() for UIDs " + Arrays.toString(uids)); + + // Perform one last poll before removing performPollLocked(FLAG_PERSIST_ALL); - mUidRecorder.removeUidLocked(uid); - mUidTagRecorder.removeUidLocked(uid); + mUidRecorder.removeUidsLocked(uids); + mUidTagRecorder.removeUidsLocked(uids); + + // Clear kernel stats associated with UID + for (int uid : uids) { + resetKernelUidStats(uid); + } + } + + /** + * Clean up {@link #mUidRecorder} after user is removed. + */ + private void removeUserLocked(int userId) { + if (LOGV) Slog.v(TAG, "removeUserLocked() for userId=" + userId); + + // Build list of UIDs that we should clean up + int[] uids = new int[0]; + final List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications( + PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_DISABLED_COMPONENTS); + for (ApplicationInfo app : apps) { + final int uid = UserHandle.getUid(userId, app.uid); + uids = ArrayUtils.appendInt(uids, uid); + } - // clear kernel stats associated with UID - resetKernelUidStats(uid); + removeUidsLocked(uids); } @Override diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index f0cc083..f3de8a4 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -719,7 +719,7 @@ public class PackageManagerService extends IPackageManager.Stub { PackageInstalledInfo res = data.res; if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { - res.removedInfo.sendBroadcast(false, true); + res.removedInfo.sendBroadcast(false, true, false); Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, res.uid); // Determine the set of users who are adding this @@ -5386,7 +5386,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (am != null) { try { if (userIds == null) { - userIds = sUserManager.getUserIds(); + userIds = am.getRunningUserIds(); } for (int id : userIds) { final Intent intent = new Intent(action, @@ -5403,6 +5403,7 @@ public class PackageManagerService extends IPackageManager.Stub { uid = UserHandle.getUid(id, UserHandle.getAppId(uid)); intent.putExtra(Intent.EXTRA_UID, uid); } + intent.putExtra(Intent.EXTRA_USER_HANDLE, id); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); if (DEBUG_BROADCASTS) { RuntimeException here = new RuntimeException("here"); @@ -8051,18 +8052,23 @@ public class PackageManagerService extends IPackageManager.Stub { } catch (RemoteException e) { } + boolean removedForAllUsers = false; + boolean systemUpdate = false; synchronized (mInstallLock) { res = deletePackageLI(packageName, (flags & PackageManager.DELETE_ALL_USERS) != 0 ? UserHandle.ALL : new UserHandle(UserHandle.getUserId(uid)), true, flags | REMOVE_CHATTY, info, true); + systemUpdate = info.isRemovedPackageSystemUpdate; + if (res && !systemUpdate && mPackages.get(packageName) == null) { + removedForAllUsers = true; + } } if (res) { - boolean systemUpdate = info.isRemovedPackageSystemUpdate; - info.sendBroadcast(true, systemUpdate); + info.sendBroadcast(true, systemUpdate, removedForAllUsers); - // If the removed package was a system update, the old system packaged + // If the removed package was a system update, the old system package // was re-enabled; we need to broadcast this information if (systemUpdate) { Bundle extras = new Bundle(1); @@ -8100,13 +8106,14 @@ public class PackageManagerService extends IPackageManager.Stub { // Clean up resources deleted packages. InstallArgs args = null; - void sendBroadcast(boolean fullRemove, boolean replacing) { + void sendBroadcast(boolean fullRemove, boolean replacing, boolean removedForAllUsers) { Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, removedAppId >= 0 ? removedAppId : uid); extras.putBoolean(Intent.EXTRA_DATA_REMOVED, fullRemove); if (replacing) { extras.putBoolean(Intent.EXTRA_REPLACING, true); } + extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, removedForAllUsers); if (removedPackage != null) { sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, extras, null, null, removedUsers); diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java index fda619c..7052ed5 100644 --- a/services/java/com/android/server/power/PowerManagerService.java +++ b/services/java/com/android/server/power/PowerManagerService.java @@ -50,6 +50,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.os.WorkSource; import android.provider.Settings; +import android.service.dreams.Dream; import android.service.dreams.IDreamManager; import android.util.EventLog; import android.util.Log; @@ -98,6 +99,8 @@ public final class PowerManagerService extends IPowerManager.Stub private static final int DIRTY_STAY_ON = 1 << 7; // Dirty bit: battery state changed private static final int DIRTY_BATTERY_STATE = 1 << 8; + // Dirty bit: dream ended + private static final int DIRTY_DREAM_ENDED = 1 << 9; // Wakefulness: The device is asleep and can only be awoken by a call to wakeUp(). // The screen should be off or in the process of being turned off by the display controller. @@ -364,6 +367,10 @@ public final class PowerManagerService extends IPowerManager.Stub filter.addAction(Intent.ACTION_DOCK_EVENT); mContext.registerReceiver(new DockReceiver(), filter); + filter = new IntentFilter(); + filter.addAction(Dream.ACTION_DREAMING_STOPPED); + mContext.registerReceiver(new DreamReceiver(), filter); + // Register for settings changes. final ContentResolver resolver = mContext.getContentResolver(); resolver.registerContentObserver(Settings.Secure.getUriFor( @@ -1146,8 +1153,12 @@ public final class PowerManagerService extends IPowerManager.Stub * Determines whether to post a message to the sandman to update the dream state. */ private void updateDreamLocked(int dirty) { - if ((dirty & (DIRTY_WAKEFULNESS | DIRTY_SETTINGS - | DIRTY_IS_POWERED | DIRTY_STAY_ON | DIRTY_BATTERY_STATE)) != 0) { + if ((dirty & (DIRTY_WAKEFULNESS + | DIRTY_SETTINGS + | DIRTY_IS_POWERED + | DIRTY_STAY_ON + | DIRTY_BATTERY_STATE + | DIRTY_DREAM_ENDED)) != 0) { scheduleSandmanLocked(); } } @@ -1230,15 +1241,15 @@ public final class PowerManagerService extends IPowerManager.Stub handleDreamFinishedLocked(); } - // Allow the sandman to detect when the dream has ended. - // FIXME: The DreamManagerService should tell us explicitly. + // In addition to listening for the intent, poll the sandman periodically to detect + // when the dream has ended (as a watchdog only, ensuring our state is always correct). if (mWakefulness == WAKEFULNESS_DREAMING || mWakefulness == WAKEFULNESS_NAPPING) { if (!mSandmanScheduled) { mSandmanScheduled = true; Message msg = mHandler.obtainMessage(MSG_SANDMAN); msg.setAsynchronous(true); - mHandler.sendMessageDelayed(msg, 1000); + mHandler.sendMessageDelayed(msg, 5000); } } } @@ -1472,6 +1483,11 @@ public final class PowerManagerService extends IPowerManager.Stub // TODO } + private void handleDreamEndedLocked() { + mDirty |= DIRTY_DREAM_ENDED; + updatePowerStateLocked(); + } + /** * Reboot the device immediately, passing 'reason' (may be null) * to the underlying __reboot system call. Should not return. @@ -1937,6 +1953,15 @@ public final class PowerManagerService extends IPowerManager.Stub } } + private final class DreamReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + synchronized (mLock) { + handleDreamEndedLocked(); + } + } + } + private final class SettingsObserver extends ContentObserver { public SettingsObserver(Handler handler) { super(handler); diff --git a/services/java/com/android/server/updates/ConfigUpdateInstallReceiver.java b/services/java/com/android/server/updates/ConfigUpdateInstallReceiver.java index a74a648..e07230d 100644 --- a/services/java/com/android/server/updates/ConfigUpdateInstallReceiver.java +++ b/services/java/com/android/server/updates/ConfigUpdateInstallReceiver.java @@ -89,8 +89,7 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver { // get the hash of the currently used value String currentHash = getCurrentHash(getCurrentContent()); if (!verifyVersion(currentVersion, altVersion)) { - EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED, - "New version is not greater than current version"); + Slog.i(TAG, "Not installing, new version is <= current version"); } else if (!verifyPreviousHash(currentHash, altRequiredHash)) { EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED, "Current hash did not match required value"); @@ -224,7 +223,7 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver { return signer.verify(Base64.decode(signature.getBytes(), Base64.DEFAULT)); } - private void writeUpdate(File dir, File file, String content) { + private void writeUpdate(File dir, File file, String content) throws IOException { FileOutputStream out = null; File tmp = null; try { @@ -248,8 +247,6 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver { if (!tmp.renameTo(file)) { throw new IOException("Failed to atomically rename " + file.getCanonicalPath()); } - } catch (IOException e) { - Slog.e(TAG, "Failed to write update", e); } finally { if (tmp != null) { tmp.delete(); @@ -258,7 +255,7 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver { } } - private void install(String content, int version) { + private void install(String content, int version) throws IOException { writeUpdate(updateDir, updateContent, content); writeUpdate(updateDir, updateVersion, Long.toString(version)); } diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 55a7c46..73cc7ed 100755 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -292,9 +292,6 @@ public class WindowManagerService extends IWindowManager.Stub if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) { mKeyguardDisableHandler.sendEmptyMessage( KeyguardDisableHandler.KEYGUARD_POLICY_CHANGED); - } else if (Intent.ACTION_USER_SWITCHED.equals(action)) { - final int newUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); - mCurrentUserId = newUserId; } } }; @@ -811,8 +808,6 @@ public class WindowManagerService extends IWindowManager.Stub // Track changes to DevicePolicyManager state so we can enable/disable keyguard. IntentFilter filter = new IntentFilter(); filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); - // Track user switching. - filter.addAction(Intent.ACTION_USER_SWITCHED); mContext.registerReceiver(mBroadcastReceiver, filter); mHoldingScreenWakeLock = pmc.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK @@ -1648,7 +1643,8 @@ public class WindowManagerService extends IWindowManager.Stub } } - if (mWallpaperTarget != foundW) { + if (mWallpaperTarget != foundW + && (mLowerWallpaperTarget == null || mLowerWallpaperTarget != foundW)) { if (DEBUG_WALLPAPER) { Slog.v(TAG, "New wallpaper target: " + foundW + " oldTarget: " + mWallpaperTarget); @@ -3391,7 +3387,7 @@ public class WindowManagerService extends IWindowManager.Stub // Exiting app if (scaleUp) { // noop animation - a = new AlphaAnimation(1, 1); + a = new AlphaAnimation(1, 0); a.setDuration(duration); } else { float scaleW = thumbWidth / displayInfo.appWidth; @@ -3440,7 +3436,7 @@ public class WindowManagerService extends IWindowManager.Stub "applyAnimation: atoken=" + atoken + " anim=" + a + " nextAppTransition=ANIM_CUSTOM" + " transit=" + transit + " isEntrance=" + enter - + " Callers " + Debug.getCallers(3)); + + " Callers=" + Debug.getCallers(3)); } else if (mNextAppTransitionType == ActivityOptions.ANIM_SCALE_UP) { a = createScaleUpAnimationLocked(transit, enter); initialized = true; @@ -3448,7 +3444,7 @@ public class WindowManagerService extends IWindowManager.Stub "applyAnimation: atoken=" + atoken + " anim=" + a + " nextAppTransition=ANIM_SCALE_UP" + " transit=" + transit + " isEntrance=" + enter - + " Callers " + Debug.getCallers(3)); + + " Callers=" + Debug.getCallers(3)); } else if (mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL_SCALE_UP || mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN) { boolean scaleUp = (mNextAppTransitionType == ActivityOptions.ANIM_THUMBNAIL_SCALE_UP); @@ -3459,7 +3455,7 @@ public class WindowManagerService extends IWindowManager.Stub Slog.v(TAG, "applyAnimation: atoken=" + atoken + " anim=" + a + " nextAppTransition=" + animName + " transit=" + transit + " isEntrance=" + enter - + " Callers " + Debug.getCallers(3)); + + " Callers=" + Debug.getCallers(3)); } } else { int animAttr = 0; @@ -3521,7 +3517,7 @@ public class WindowManagerService extends IWindowManager.Stub + " anim=" + a + " animAttr=0x" + Integer.toHexString(animAttr) + " transit=" + transit + " isEntrance=" + enter - + " Callers " + Debug.getCallers(3)); + + " Callers=" + Debug.getCallers(3)); } if (a != null) { if (DEBUG_ANIM) { @@ -4193,7 +4189,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { if (DEBUG_STARTING_WINDOW) Slog.v( - TAG, "setAppStartingIcon: token=" + token + " pkg=" + pkg + TAG, "setAppStartingWindow: token=" + token + " pkg=" + pkg + " transferFrom=" + transferFrom); AppWindowToken wtoken = findAppWindowToken(token); @@ -4225,7 +4221,7 @@ public class WindowManagerService extends IWindowManager.Stub mSkipAppTransitionAnimation = true; } if (DEBUG_STARTING_WINDOW) Slog.v(TAG, - "Moving existing starting from " + ttoken + "Moving existing starting " + startingWindow + " from " + ttoken + " to " + wtoken); final long origId = Binder.clearCallingIdentity(); @@ -4234,6 +4230,7 @@ public class WindowManagerService extends IWindowManager.Stub wtoken.startingData = ttoken.startingData; wtoken.startingView = ttoken.startingView; wtoken.startingDisplayed = ttoken.startingDisplayed; + ttoken.startingDisplayed = false; wtoken.startingWindow = startingWindow; wtoken.reportedVisible = ttoken.reportedVisible; ttoken.startingData = null; @@ -4243,6 +4240,8 @@ public class WindowManagerService extends IWindowManager.Stub startingWindow.mToken = wtoken; startingWindow.mRootToken = wtoken; startingWindow.mAppToken = wtoken; + startingWindow.mWinAnimator.mAppAnimator = wtoken.mAppAnimator; + if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) { Slog.v(TAG, "Removing starting window: " + startingWindow); } @@ -4550,9 +4549,9 @@ public class WindowManagerService extends IWindowManager.Stub } wtoken.hiddenRequested = !visible; - if (DEBUG_APP_TRANSITIONS) Slog.v( - TAG, "Setting dummy animation on: " + wtoken); if (!wtoken.startingDisplayed) { + if (DEBUG_APP_TRANSITIONS) Slog.v( + TAG, "Setting dummy animation on: " + wtoken); wtoken.mAppAnimator.setDummyAnimation(); } mOpeningApps.remove(wtoken); @@ -5396,6 +5395,13 @@ public class WindowManagerService extends IWindowManager.Stub mInputManager.setInputFilter(filter); } + public void setCurrentUser(final int newUserId) { + synchronized (mWindowMap) { + mCurrentUserId = newUserId; + mPolicy.setCurrentUserLw(newUserId); + } + } + public void enableScreenAfterBoot() { synchronized(mWindowMap) { if (DEBUG_BOOT) { @@ -8147,7 +8153,11 @@ public class WindowManagerService extends IWindowManager.Stub updateLayoutToAnimationLocked(); } if (DEBUG_LAYERS) Slog.v(TAG, "Assign layer " + w + ": " - + winAnimator.mAnimLayer); + + "mBase=" + w.mBaseLayer + + " mLayer=" + w.mLayer + + (w.mAppToken == null ? + "" : " mAppLayer=" + w.mAppToken.mAppAnimator.animLayerAdjustment) + + " =mAnimLayer=" + winAnimator.mAnimLayer); //System.out.println( // "Assigned layer " + curLayer + " to " + w.mClient.asBinder()); } @@ -8539,7 +8549,7 @@ public class WindowManagerService extends IWindowManager.Stub transit = WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE; if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit away from wallpaper: " + transit); - } else if (mWallpaperTarget != null) { + } else if (mWallpaperTarget != null && mWallpaperTarget.isVisibleLw()) { // We are transitioning from an activity without // a wallpaper to now showing the wallpaper transit = WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN; @@ -8596,7 +8606,7 @@ public class WindowManagerService extends IWindowManager.Stub for (i=0; i<NN; i++) { AppWindowToken wtoken = mClosingApps.get(i); if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "Now closing app" + wtoken); + "Now closing app " + wtoken); wtoken.mAppAnimator.clearThumbnail(); wtoken.inPendingTransaction = false; wtoken.mAppAnimator.animation = null; diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index b62028e..ac958b8 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -518,11 +518,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { MagnificationSpec spec = mDisplayContent.mMagnificationSpec; if (spec != null && !spec.isNop()) { if (mAttachedWindow != null) { - if (!mPolicy.canMagnifyWindow(mAttachedWindow.mAttrs)) { + if (!mPolicy.canMagnifyWindowLw(mAttachedWindow.mAttrs)) { return null; } } - if (!mPolicy.canMagnifyWindow(mAttrs)) { + if (!mPolicy.canMagnifyWindowLw(mAttrs)) { return null; } } diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java index 000a191..5f40709 100644 --- a/services/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/java/com/android/server/wm/WindowStateAnimator.java @@ -62,7 +62,7 @@ class WindowStateAnimator { final WindowState mWin; final WindowStateAnimator mAttachedWinAnimator; final WindowAnimator mAnimator; - final AppWindowAnimator mAppAnimator; + AppWindowAnimator mAppAnimator; final Session mSession; final WindowManagerPolicy mPolicy; final Context mContext; @@ -1520,7 +1520,7 @@ class WindowStateAnimator { "applyAnimation: win=" + this + " anim=" + anim + " attr=0x" + Integer.toHexString(attr) + " a=" + a - + " mAnimation=" + mAnimation + + " transit=" + transit + " isEntrance=" + isEntrance + " Callers " + Debug.getCallers(3)); if (a != null) { if (WindowManagerService.DEBUG_ANIM) { |
