diff options
378 files changed, 9958 insertions, 3033 deletions
@@ -251,10 +251,14 @@ LOCAL_SRC_FILES += \ media/java/android/media/IAudioService.aidl \ media/java/android/media/IAudioFocusDispatcher.aidl \ media/java/android/media/IAudioRoutesObserver.aidl \ + media/java/android/media/IMediaRouterClient.aidl \ + media/java/android/media/IMediaRouterService.aidl \ media/java/android/media/IMediaScannerListener.aidl \ media/java/android/media/IMediaScannerService.aidl \ media/java/android/media/IRemoteControlClient.aidl \ media/java/android/media/IRemoteControlDisplay.aidl \ + media/java/android/media/IRemoteDisplayCallback.aidl \ + media/java/android/media/IRemoteDisplayProvider.aidl \ media/java/android/media/IRemoteVolumeObserver.aidl \ media/java/android/media/IRingtonePlayer.aidl \ telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \ diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index c18f542..0344d26 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -107,7 +107,7 @@ public class Am extends BaseCommand { " am switch-user <USER_ID>\n" + " am stop-user <USER_ID>\n" + " am stack create <TASK_ID> <RELATIVE_STACK_BOX_ID> <POSITION> <WEIGHT>\n" + - " am stack movetask <STACK_ID> <TASK_ID> [true|false]\n" + + " am stack movetask <TASK_ID> <STACK_ID> [true|false]\n" + " am stack resize <STACK_ID> <WEIGHT>\n" + " am stack boxes\n" + " am stack box <STACK_BOX_ID>\n" + diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 2666b41..db3d8bb 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -187,6 +187,12 @@ public final class Bmgr { } private void doWipe() { + String transport = nextArg(); + if (transport == null) { + showUsage(); + return; + } + String pkg = nextArg(); if (pkg == null) { showUsage(); @@ -194,8 +200,8 @@ public final class Bmgr { } try { - mBmgr.clearBackupData(pkg); - System.out.println("Wiped backup data for " + pkg); + mBmgr.clearBackupData(transport, pkg); + System.out.println("Wiped backup data for " + pkg + " on " + transport); } catch (RemoteException e) { System.err.println(e.toString()); System.err.println(BMGR_NOT_RUNNING_ERR); @@ -446,7 +452,7 @@ public final class Bmgr { System.err.println(" bmgr restore TOKEN PACKAGE..."); System.err.println(" bmgr restore PACKAGE"); System.err.println(" bmgr run"); - System.err.println(" bmgr wipe PACKAGE"); + System.err.println(" bmgr wipe TRANSPORT PACKAGE"); System.err.println(""); System.err.println("The 'backup' command schedules a backup pass for the named package."); System.err.println("Note that the backup pass will effectively be a no-op if the package"); @@ -462,8 +468,8 @@ public final class Bmgr { System.err.println(""); System.err.println("The 'list transports' command reports the names of the backup transports"); System.err.println("currently available on the device. These names can be passed as arguments"); - System.err.println("to the 'transport' command. The currently selected transport is indicated"); - System.err.println("with a '*' character."); + System.err.println("to the 'transport' and 'wipe' commands. The currently selected transport"); + System.err.println("is indicated with a '*' character."); System.err.println(""); System.err.println("The 'list sets' command reports the token and name of each restore set"); System.err.println("available to the device via the current transport."); @@ -491,7 +497,8 @@ public final class Bmgr { System.err.println("data changes."); System.err.println(""); System.err.println("The 'wipe' command causes all backed-up data for the given package to be"); - System.err.println("erased from the current transport's storage. The next backup operation"); + System.err.println("erased from the given transport's storage. The next backup operation"); System.err.println("that the given application performs will rewrite its entire data set."); + System.err.println("Transport names to use here are those reported by 'list transports'."); } } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 193724d..d6db8c2 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -4857,7 +4857,11 @@ public class Activity extends ContextThemeWrapper mFragments.dump(prefix, fd, writer, args); - getWindow().getDecorView().getViewRootImpl().dump(prefix, fd, writer, args); + if (getWindow() != null && + getWindow().peekDecorView() != null && + getWindow().peekDecorView().getViewRootImpl() != null) { + getWindow().peekDecorView().getViewRootImpl().dump(prefix, fd, writer, args); + } mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix); } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index bb04063..7ca3459 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -2272,9 +2272,12 @@ public class ActivityManager { public static void dumpPackageStateStatic(FileDescriptor fd, String packageName) { FileOutputStream fout = new FileOutputStream(fd); PrintWriter pw = new FastPrintWriter(fout); - dumpService(pw, fd, Context.ACTIVITY_SERVICE, new String[] { "package", packageName }); + dumpService(pw, fd, Context.ACTIVITY_SERVICE, new String[] { + "-a", "package", packageName }); pw.println(); - dumpService(pw, fd, ProcessStats.SERVICE_NAME, new String[] { packageName }); + dumpService(pw, fd, "meminfo", new String[] { "--local", packageName }); + pw.println(); + dumpService(pw, fd, ProcessStats.SERVICE_NAME, new String[] { "-a", packageName }); pw.println(); dumpService(pw, fd, "usagestats", new String[] { "--packages", packageName }); pw.println(); @@ -2296,7 +2299,7 @@ public class ActivityManager { pw.flush(); tp = new TransferPipe(); tp.setBufferPrefix(" "); - service.dump(tp.getWriteFd().getFileDescriptor(), args); + service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args); tp.go(fd); } catch (Throwable e) { if (tp != null) { diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 22a21cd..aab6ed8 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -205,7 +205,9 @@ public class KeyguardManager { try { mWM.exitKeyguardSecurely(new IOnKeyguardExitResult.Stub() { public void onKeyguardExitResult(boolean success) throws RemoteException { - callback.onKeyguardExitResult(success); + if (callback != null) { + callback.onKeyguardExitResult(success); + } } }); } catch (RemoteException e) { diff --git a/core/java/android/app/MediaRouteActionProvider.java b/core/java/android/app/MediaRouteActionProvider.java index 63b641c..dffa969 100644 --- a/core/java/android/app/MediaRouteActionProvider.java +++ b/core/java/android/app/MediaRouteActionProvider.java @@ -16,10 +16,7 @@ package android.app; -import com.android.internal.app.MediaRouteChooserDialogFragment; - import android.content.Context; -import android.content.ContextWrapper; import android.media.MediaRouter; import android.media.MediaRouter.RouteInfo; import android.util.Log; @@ -30,22 +27,38 @@ import android.view.ViewGroup; import java.lang.ref.WeakReference; +/** + * The media route action provider displays a {@link MediaRouteButton media route button} + * in the application's {@link ActionBar} to allow the user to select routes and + * to control the currently selected route. + * <p> + * The application must specify the kinds of routes that the user should be allowed + * to select by specifying the route types with the {@link #setRouteTypes} method. + * </p><p> + * Refer to {@link MediaRouteButton} for a description of the button that will + * appear in the action bar menu. Note that instead of disabling the button + * when no routes are available, the action provider will instead make the + * menu item invisible. In this way, the button will only be visible when it + * is possible for the user to discover and select a matching route. + * </p> + */ public class MediaRouteActionProvider extends ActionProvider { private static final String TAG = "MediaRouteActionProvider"; - private Context mContext; - private MediaRouter mRouter; - private MenuItem mMenuItem; - private MediaRouteButton mView; + private final Context mContext; + private final MediaRouter mRouter; + private final MediaRouterCallback mCallback; + private int mRouteTypes; + private MediaRouteButton mButton; private View.OnClickListener mExtendedSettingsListener; - private RouterCallback mCallback; public MediaRouteActionProvider(Context context) { super(context); + mContext = context; mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); - mCallback = new RouterCallback(this); + mCallback = new MediaRouterCallback(this); // Start with live audio by default. // TODO Update this when new route types are added; segment by API level @@ -53,80 +66,74 @@ public class MediaRouteActionProvider extends ActionProvider { setRouteTypes(MediaRouter.ROUTE_TYPE_LIVE_AUDIO); } + /** + * Sets the types of routes that will be shown in the media route chooser dialog + * launched by this button. + * + * @param types The route types to match. + */ public void setRouteTypes(int types) { - if (mRouteTypes == types) return; - if (mRouteTypes != 0) { - mRouter.removeCallback(mCallback); - } - mRouteTypes = types; - if (types != 0) { - mRouter.addCallback(types, mCallback); + if (mRouteTypes != types) { + // FIXME: We currently have no way of knowing whether the action provider + // is still needed by the UI. Unfortunately this means the action provider + // may leak callbacks until garbage collection occurs. This may result in + // media route providers doing more work than necessary in the short term + // while trying to discover routes that are no longer of interest to the + // application. To solve this problem, the action provider will need some + // indication from the framework that it is being destroyed. + if (mRouteTypes != 0) { + mRouter.removeCallback(mCallback); + } + mRouteTypes = types; + if (types != 0) { + mRouter.addCallback(types, mCallback, + MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); + } + refreshRoute(); + + if (mButton != null) { + mButton.setRouteTypes(mRouteTypes); + } } - if (mView != null) { - mView.setRouteTypes(mRouteTypes); + } + + public void setExtendedSettingsClickListener(View.OnClickListener listener) { + mExtendedSettingsListener = listener; + if (mButton != null) { + mButton.setExtendedSettingsClickListener(listener); } } @Override + @SuppressWarnings("deprecation") public View onCreateActionView() { throw new UnsupportedOperationException("Use onCreateActionView(MenuItem) instead."); } @Override public View onCreateActionView(MenuItem item) { - if (mMenuItem != null || mView != null) { + if (mButton != null) { Log.e(TAG, "onCreateActionView: this ActionProvider is already associated " + "with a menu item. Don't reuse MediaRouteActionProvider instances! " + "Abandoning the old one..."); } - mMenuItem = item; - mView = new MediaRouteButton(mContext); - mView.setCheatSheetEnabled(true); - mView.setRouteTypes(mRouteTypes); - mView.setExtendedSettingsClickListener(mExtendedSettingsListener); - mView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + + mButton = new MediaRouteButton(mContext); + mButton.setCheatSheetEnabled(true); + mButton.setRouteTypes(mRouteTypes); + mButton.setExtendedSettingsClickListener(mExtendedSettingsListener); + mButton.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); - return mView; + return mButton; } @Override public boolean onPerformDefaultAction() { - final FragmentManager fm = getActivity().getFragmentManager(); - // See if one is already attached to this activity. - MediaRouteChooserDialogFragment dialogFragment = - (MediaRouteChooserDialogFragment) fm.findFragmentByTag( - MediaRouteChooserDialogFragment.FRAGMENT_TAG); - if (dialogFragment != null) { - Log.w(TAG, "onPerformDefaultAction(): Chooser dialog already showing!"); - return false; - } - - dialogFragment = new MediaRouteChooserDialogFragment(); - dialogFragment.setExtendedSettingsClickListener(mExtendedSettingsListener); - dialogFragment.setRouteTypes(mRouteTypes); - dialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG); - return true; - } - - private Activity getActivity() { - // Gross way of unwrapping the Activity so we can get the FragmentManager - Context context = mContext; - while (context instanceof ContextWrapper && !(context instanceof Activity)) { - context = ((ContextWrapper) context).getBaseContext(); - } - if (!(context instanceof Activity)) { - throw new IllegalStateException("The MediaRouteActionProvider's Context " + - "is not an Activity."); - } - - return (Activity) context; - } - - public void setExtendedSettingsClickListener(View.OnClickListener listener) { - mExtendedSettingsListener = listener; - if (mView != null) { - mView.setExtendedSettingsClickListener(listener); + if (mButton != null) { + return mButton.showDialogInternal(); } + return false; } @Override @@ -136,36 +143,43 @@ public class MediaRouteActionProvider extends ActionProvider { @Override public boolean isVisible() { - return mRouter.getRouteCount() > 1; + return mRouter.isRouteAvailable(mRouteTypes, + MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE); + } + + private void refreshRoute() { + refreshVisibility(); } - private static class RouterCallback extends MediaRouter.SimpleCallback { - private WeakReference<MediaRouteActionProvider> mAp; + private static class MediaRouterCallback extends MediaRouter.SimpleCallback { + private final WeakReference<MediaRouteActionProvider> mProviderWeak; - RouterCallback(MediaRouteActionProvider ap) { - mAp = new WeakReference<MediaRouteActionProvider>(ap); + public MediaRouterCallback(MediaRouteActionProvider provider) { + mProviderWeak = new WeakReference<MediaRouteActionProvider>(provider); } @Override public void onRouteAdded(MediaRouter router, RouteInfo info) { - final MediaRouteActionProvider ap = mAp.get(); - if (ap == null) { - router.removeCallback(this); - return; - } - - ap.refreshVisibility(); + refreshRoute(router); } @Override public void onRouteRemoved(MediaRouter router, RouteInfo info) { - final MediaRouteActionProvider ap = mAp.get(); - if (ap == null) { + refreshRoute(router); + } + + @Override + public void onRouteChanged(MediaRouter router, RouteInfo info) { + refreshRoute(router); + } + + private void refreshRoute(MediaRouter router) { + MediaRouteActionProvider provider = mProviderWeak.get(); + if (provider != null) { + provider.refreshRoute(); + } else { router.removeCallback(this); - return; } - - ap.refreshVisibility(); } } } diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java index 7e0a27a..a7982f4 100644 --- a/core/java/android/app/MediaRouteButton.java +++ b/core/java/android/app/MediaRouteButton.java @@ -17,7 +17,7 @@ package android.app; import com.android.internal.R; -import com.android.internal.app.MediaRouteChooserDialogFragment; +import com.android.internal.app.MediaRouteDialogPresenter; import android.content.Context; import android.content.ContextWrapper; @@ -30,7 +30,6 @@ import android.media.MediaRouter.RouteGroup; import android.media.MediaRouter.RouteInfo; import android.text.TextUtils; import android.util.AttributeSet; -import android.util.Log; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.SoundEffectConstants; @@ -38,17 +37,15 @@ import android.view.View; import android.widget.Toast; public class MediaRouteButton extends View { - private static final String TAG = "MediaRouteButton"; + private final MediaRouter mRouter; + private final MediaRouterCallback mCallback; - private MediaRouter mRouter; - private final MediaRouteCallback mRouterCallback = new MediaRouteCallback(); private int mRouteTypes; private boolean mAttachedToWindow; private Drawable mRemoteIndicator; private boolean mRemoteActive; - private boolean mToggleMode; private boolean mCheatSheetEnabled; private boolean mIsConnecting; @@ -56,12 +53,13 @@ public class MediaRouteButton extends View { private int mMinHeight; private OnClickListener mExtendedSettingsClickListener; - private MediaRouteChooserDialogFragment mDialogFragment; + // The checked state is used when connected to a remote route. private static final int[] CHECKED_STATE_SET = { R.attr.state_checked }; + // The activated state is used while connecting to a remote route. private static final int[] ACTIVATED_STATE_SET = { R.attr.state_activated }; @@ -78,6 +76,7 @@ public class MediaRouteButton extends View { super(context, attrs, defStyleAttr); mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE); + mCallback = new MediaRouterCallback(); TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.MediaRouteButton, defStyleAttr, 0); @@ -98,54 +97,100 @@ public class MediaRouteButton extends View { setRouteTypes(routeTypes); } - private void setRemoteIndicatorDrawable(Drawable d) { - if (mRemoteIndicator != null) { - mRemoteIndicator.setCallback(null); - unscheduleDrawable(mRemoteIndicator); - } - mRemoteIndicator = d; - if (d != null) { - d.setCallback(this); - d.setState(getDrawableState()); - d.setVisible(getVisibility() == VISIBLE, false); + /** + * Gets the media route types for filtering the routes that the user can + * select using the media route chooser dialog. + * + * @return The route types. + */ + public int getRouteTypes() { + return mRouteTypes; + } + + /** + * Sets the types of routes that will be shown in the media route chooser dialog + * launched by this button. + * + * @param types The route types to match. + */ + public void setRouteTypes(int types) { + if (mRouteTypes != types) { + if (mAttachedToWindow && mRouteTypes != 0) { + mRouter.removeCallback(mCallback); + } + + mRouteTypes = types; + + if (mAttachedToWindow && types != 0) { + mRouter.addCallback(types, mCallback, + MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); + } + + refreshRoute(); } + } - refreshDrawableState(); + public void setExtendedSettingsClickListener(OnClickListener listener) { + mExtendedSettingsClickListener = listener; } - @Override - public boolean performClick() { - // Send the appropriate accessibility events and call listeners - boolean handled = super.performClick(); - if (!handled) { - playSoundEffect(SoundEffectConstants.CLICK); + /** + * Show the route chooser or controller dialog. + * <p> + * If the default route is selected or if the currently selected route does + * not match the {@link #getRouteTypes route types}, then shows the route chooser dialog. + * Otherwise, shows the route controller dialog to offer the user + * a choice to disconnect from the route or perform other control actions + * such as setting the route's volume. + * </p><p> + * This will attach a {@link DialogFragment} to the containing Activity. + * </p> + */ + public void showDialog() { + showDialogInternal(); + } + + boolean showDialogInternal() { + if (!mAttachedToWindow) { + return false; } - if (mToggleMode) { - if (mRemoteActive) { - mRouter.selectRouteInt(mRouteTypes, mRouter.getDefaultRoute()); - } else { - final int N = mRouter.getRouteCount(); - for (int i = 0; i < N; i++) { - final RouteInfo route = mRouter.getRouteAt(i); - if ((route.getSupportedTypes() & mRouteTypes) != 0 && - route != mRouter.getDefaultRoute()) { - mRouter.selectRouteInt(mRouteTypes, route); - } - } + DialogFragment f = MediaRouteDialogPresenter.showDialogFragment(getActivity(), + mRouteTypes, mExtendedSettingsClickListener); + return f != null; + } + + private Activity getActivity() { + // Gross way of unwrapping the Activity so we can get the FragmentManager + Context context = getContext(); + while (context instanceof ContextWrapper) { + if (context instanceof Activity) { + return (Activity)context; } - } else { - showDialog(); + context = ((ContextWrapper)context).getBaseContext(); } - - return handled; + throw new IllegalStateException("The MediaRouteButton's Context is not an Activity."); } + /** + * Sets whether to enable showing a toast with the content descriptor of the + * button when the button is long pressed. + */ void setCheatSheetEnabled(boolean enable) { mCheatSheetEnabled = enable; } @Override + public boolean performClick() { + // Send the appropriate accessibility events and call listeners + boolean handled = super.performClick(); + if (!handled) { + playSoundEffect(SoundEffectConstants.CLICK); + } + return showDialogInternal() || handled; + } + + @Override public boolean performLongClick() { if (super.performLongClick()) { return true; @@ -183,85 +228,9 @@ public class MediaRouteButton extends View { } cheatSheet.show(); performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - return true; } - public void setRouteTypes(int types) { - if (types == mRouteTypes) { - // Already registered; nothing to do. - return; - } - - if (mAttachedToWindow && mRouteTypes != 0) { - mRouter.removeCallback(mRouterCallback); - } - - mRouteTypes = types; - - if (mAttachedToWindow) { - updateRouteInfo(); - mRouter.addCallback(types, mRouterCallback); - } - } - - private void updateRouteInfo() { - updateRemoteIndicator(); - updateRouteCount(); - } - - public int getRouteTypes() { - return mRouteTypes; - } - - void updateRemoteIndicator() { - final RouteInfo selected = mRouter.getSelectedRoute(mRouteTypes); - final boolean isRemote = selected != mRouter.getDefaultRoute(); - final boolean isConnecting = selected != null && - selected.getStatusCode() == RouteInfo.STATUS_CONNECTING; - - boolean needsRefresh = false; - if (mRemoteActive != isRemote) { - mRemoteActive = isRemote; - needsRefresh = true; - } - if (mIsConnecting != isConnecting) { - mIsConnecting = isConnecting; - needsRefresh = true; - } - - if (needsRefresh) { - refreshDrawableState(); - } - } - - void updateRouteCount() { - final int N = mRouter.getRouteCount(); - int count = 0; - boolean hasVideoRoutes = false; - for (int i = 0; i < N; i++) { - final RouteInfo route = mRouter.getRouteAt(i); - final int routeTypes = route.getSupportedTypes(); - if ((routeTypes & mRouteTypes) != 0) { - if (route instanceof RouteGroup) { - count += ((RouteGroup) route).getRouteCount(); - } else { - count++; - } - if ((routeTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO) != 0) { - hasVideoRoutes = true; - } - } - } - - setEnabled(count != 0); - - // Only allow toggling if we have more than just user routes. - // Don't toggle if we support video routes, we may have to let the dialog scan. - mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0 && - !hasVideoRoutes; - } - @Override protected int[] onCreateDrawableState(int extraSpace) { final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); @@ -289,6 +258,21 @@ public class MediaRouteButton extends View { } } + private void setRemoteIndicatorDrawable(Drawable d) { + if (mRemoteIndicator != null) { + mRemoteIndicator.setCallback(null); + unscheduleDrawable(mRemoteIndicator); + } + mRemoteIndicator = d; + if (d != null) { + d.setCallback(this); + d.setState(getDrawableState()); + d.setVisible(getVisibility() == VISIBLE, false); + } + + refreshDrawableState(); + } + @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || who == mRemoteIndicator; @@ -297,12 +281,16 @@ public class MediaRouteButton extends View { @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); - if (mRemoteIndicator != null) mRemoteIndicator.jumpToCurrentState(); + + if (mRemoteIndicator != null) { + mRemoteIndicator.jumpToCurrentState(); + } } @Override public void setVisibility(int visibility) { super.setVisibility(visibility); + if (mRemoteIndicator != null) { mRemoteIndicator.setVisible(getVisibility() == VISIBLE, false); } @@ -311,19 +299,22 @@ public class MediaRouteButton extends View { @Override public void onAttachedToWindow() { super.onAttachedToWindow(); + mAttachedToWindow = true; if (mRouteTypes != 0) { - mRouter.addCallback(mRouteTypes, mRouterCallback); - updateRouteInfo(); + mRouter.addCallback(mRouteTypes, mCallback, + MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); } + refreshRoute(); } @Override public void onDetachedFromWindow() { + mAttachedToWindow = false; if (mRouteTypes != 0) { - mRouter.removeCallback(mRouterCallback); + mRouter.removeCallback(mCallback); } - mAttachedToWindow = false; + super.onDetachedFromWindow(); } @@ -386,93 +377,71 @@ public class MediaRouteButton extends View { final int drawLeft = left + (right - left - drawWidth) / 2; final int drawTop = top + (bottom - top - drawHeight) / 2; - mRemoteIndicator.setBounds(drawLeft, drawTop, drawLeft + drawWidth, drawTop + drawHeight); + mRemoteIndicator.setBounds(drawLeft, drawTop, + drawLeft + drawWidth, drawTop + drawHeight); mRemoteIndicator.draw(canvas); } - public void setExtendedSettingsClickListener(OnClickListener listener) { - mExtendedSettingsClickListener = listener; - if (mDialogFragment != null) { - mDialogFragment.setExtendedSettingsClickListener(listener); - } - } - - /** - * Asynchronously show the route chooser dialog. - * This will attach a {@link DialogFragment} to the containing Activity. - */ - public void showDialog() { - final FragmentManager fm = getActivity().getFragmentManager(); - if (mDialogFragment == null) { - // See if one is already attached to this activity. - mDialogFragment = (MediaRouteChooserDialogFragment) fm.findFragmentByTag( - MediaRouteChooserDialogFragment.FRAGMENT_TAG); - } - if (mDialogFragment != null) { - Log.w(TAG, "showDialog(): Already showing!"); - return; - } + private void refreshRoute() { + if (mAttachedToWindow) { + final MediaRouter.RouteInfo route = mRouter.getSelectedRoute(); + final boolean isRemote = !route.isDefault() && route.matchesTypes(mRouteTypes); + final boolean isConnecting = isRemote && route.isConnecting(); + + boolean needsRefresh = false; + if (mRemoteActive != isRemote) { + mRemoteActive = isRemote; + needsRefresh = true; + } + if (mIsConnecting != isConnecting) { + mIsConnecting = isConnecting; + needsRefresh = true; + } - mDialogFragment = new MediaRouteChooserDialogFragment(); - mDialogFragment.setExtendedSettingsClickListener(mExtendedSettingsClickListener); - mDialogFragment.setLauncherListener(new MediaRouteChooserDialogFragment.LauncherListener() { - @Override - public void onDetached(MediaRouteChooserDialogFragment detachedFragment) { - mDialogFragment = null; + if (needsRefresh) { + refreshDrawableState(); } - }); - mDialogFragment.setRouteTypes(mRouteTypes); - mDialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG); - } - private Activity getActivity() { - // Gross way of unwrapping the Activity so we can get the FragmentManager - Context context = getContext(); - while (context instanceof ContextWrapper && !(context instanceof Activity)) { - context = ((ContextWrapper) context).getBaseContext(); + setEnabled(mRouter.isRouteAvailable(mRouteTypes, + MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE)); } - if (!(context instanceof Activity)) { - throw new IllegalStateException("The MediaRouteButton's Context is not an Activity."); - } - - return (Activity) context; } - private class MediaRouteCallback extends MediaRouter.SimpleCallback { + private final class MediaRouterCallback extends MediaRouter.SimpleCallback { @Override - public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { - updateRemoteIndicator(); + public void onRouteAdded(MediaRouter router, RouteInfo info) { + refreshRoute(); } @Override - public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { - updateRemoteIndicator(); + public void onRouteRemoved(MediaRouter router, RouteInfo info) { + refreshRoute(); } @Override public void onRouteChanged(MediaRouter router, RouteInfo info) { - updateRemoteIndicator(); + refreshRoute(); } @Override - public void onRouteAdded(MediaRouter router, RouteInfo info) { - updateRouteCount(); + public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { + refreshRoute(); } @Override - public void onRouteRemoved(MediaRouter router, RouteInfo info) { - updateRouteCount(); + public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { + refreshRoute(); } @Override public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, int index) { - updateRouteCount(); + refreshRoute(); } @Override public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) { - updateRouteCount(); + refreshRoute(); } } } diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 7bcf43e..2045ed8 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -58,10 +58,7 @@ public class StatusBarManager { | DISABLE_SYSTEM_INFO | DISABLE_RECENT | DISABLE_HOME | DISABLE_BACK | DISABLE_CLOCK | DISABLE_SEARCH; - public static final int NAVIGATION_HINT_BACK_NOP = 1 << 0; - public static final int NAVIGATION_HINT_HOME_NOP = 1 << 1; - public static final int NAVIGATION_HINT_RECENT_NOP = 1 << 2; - public static final int NAVIGATION_HINT_BACK_ALT = 1 << 3; + public static final int NAVIGATION_HINT_BACK_ALT = 1 << 0; public static final int WINDOW_STATUS_BAR = 1; public static final int WINDOW_NAVIGATION_BAR = 2; diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 607930c..91b0d7c 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -146,7 +146,9 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { @Override public void shutdown() { synchronized (mLock) { - throwIfCalledByNotTrustedUidLocked(); + if (isConnectedLocked()) { + throwIfCalledByNotTrustedUidLocked(); + } throwIfShutdownLocked(); mIsShutdown = true; if (isConnectedLocked()) { diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index bb4f5f1..12ee3b6 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -43,14 +43,14 @@ interface IBackupManager { void dataChanged(String packageName); /** - * Erase all backed-up data for the given package from the storage + * Erase all backed-up data for the given package from the given storage * destination. * * Any application can invoke this method for its own package, but * only callers who hold the android.permission.BACKUP permission * may invoke it for arbitrary packages. */ - void clearBackupData(String packageName); + void clearBackupData(String transportName, String packageName); /** * Notifies the Backup Manager Service that an agent has become available. This diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index a9d0559..ddde3fb 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -398,135 +398,137 @@ public abstract class ContentProvider implements ComponentCallbacks2 { return AppOpsManager.MODE_ALLOWED; } - private void enforceReadPermissionInner(Uri uri) throws SecurityException { - final Context context = getContext(); - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - String missingPerm = null; - - if (UserHandle.isSameApp(uid, mMyUid)) { - return; + private int enforceWritePermission(String callingPkg, Uri uri) throws SecurityException { + enforceWritePermissionInner(uri); + if (mWriteOp != AppOpsManager.OP_NONE) { + return mAppOpsManager.noteOp(mWriteOp, Binder.getCallingUid(), callingPkg); } + return AppOpsManager.MODE_ALLOWED; + } + } - if (mExported) { - final String componentPerm = getReadPermission(); - if (componentPerm != null) { - if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) { - return; - } else { - missingPerm = componentPerm; - } + /** {@hide} */ + protected void enforceReadPermissionInner(Uri uri) throws SecurityException { + final Context context = getContext(); + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + String missingPerm = null; + + if (UserHandle.isSameApp(uid, mMyUid)) { + return; + } + + if (mExported) { + final String componentPerm = getReadPermission(); + if (componentPerm != null) { + if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) { + return; + } else { + missingPerm = componentPerm; } + } - // track if unprotected read is allowed; any denied - // <path-permission> below removes this ability - boolean allowDefaultRead = (componentPerm == null); - - final PathPermission[] pps = getPathPermissions(); - if (pps != null) { - final String path = uri.getPath(); - for (PathPermission pp : pps) { - final String pathPerm = pp.getReadPermission(); - if (pathPerm != null && pp.match(path)) { - if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) { - return; - } else { - // any denied <path-permission> means we lose - // default <provider> access. - allowDefaultRead = false; - missingPerm = pathPerm; - } + // track if unprotected read is allowed; any denied + // <path-permission> below removes this ability + boolean allowDefaultRead = (componentPerm == null); + + final PathPermission[] pps = getPathPermissions(); + if (pps != null) { + final String path = uri.getPath(); + for (PathPermission pp : pps) { + final String pathPerm = pp.getReadPermission(); + if (pathPerm != null && pp.match(path)) { + if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) { + return; + } else { + // any denied <path-permission> means we lose + // default <provider> access. + allowDefaultRead = false; + missingPerm = pathPerm; } } } - - // if we passed <path-permission> checks above, and no default - // <provider> permission, then allow access. - if (allowDefaultRead) return; - } - - // last chance, check against any uri grants - if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION) - == PERMISSION_GRANTED) { - return; } - final String failReason = mExported - ? " requires " + missingPerm + ", or grantUriPermission()" - : " requires the provider be exported, or grantUriPermission()"; - throw new SecurityException("Permission Denial: reading " - + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid - + ", uid=" + uid + failReason); + // if we passed <path-permission> checks above, and no default + // <provider> permission, then allow access. + if (allowDefaultRead) return; } - private int enforceWritePermission(String callingPkg, Uri uri) throws SecurityException { - enforceWritePermissionInner(uri); - if (mWriteOp != AppOpsManager.OP_NONE) { - return mAppOpsManager.noteOp(mWriteOp, Binder.getCallingUid(), callingPkg); - } - return AppOpsManager.MODE_ALLOWED; + // last chance, check against any uri grants + if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION) + == PERMISSION_GRANTED) { + return; } - private void enforceWritePermissionInner(Uri uri) throws SecurityException { - final Context context = getContext(); - final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - String missingPerm = null; + final String failReason = mExported + ? " requires " + missingPerm + ", or grantUriPermission()" + : " requires the provider be exported, or grantUriPermission()"; + throw new SecurityException("Permission Denial: reading " + + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid + + ", uid=" + uid + failReason); + } - if (UserHandle.isSameApp(uid, mMyUid)) { - return; - } + /** {@hide} */ + protected void enforceWritePermissionInner(Uri uri) throws SecurityException { + final Context context = getContext(); + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + String missingPerm = null; - if (mExported) { - final String componentPerm = getWritePermission(); - if (componentPerm != null) { - if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) { - return; - } else { - missingPerm = componentPerm; - } + if (UserHandle.isSameApp(uid, mMyUid)) { + return; + } + + if (mExported) { + final String componentPerm = getWritePermission(); + if (componentPerm != null) { + if (context.checkPermission(componentPerm, pid, uid) == PERMISSION_GRANTED) { + return; + } else { + missingPerm = componentPerm; } + } - // track if unprotected write is allowed; any denied - // <path-permission> below removes this ability - boolean allowDefaultWrite = (componentPerm == null); - - final PathPermission[] pps = getPathPermissions(); - if (pps != null) { - final String path = uri.getPath(); - for (PathPermission pp : pps) { - final String pathPerm = pp.getWritePermission(); - if (pathPerm != null && pp.match(path)) { - if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) { - return; - } else { - // any denied <path-permission> means we lose - // default <provider> access. - allowDefaultWrite = false; - missingPerm = pathPerm; - } + // track if unprotected write is allowed; any denied + // <path-permission> below removes this ability + boolean allowDefaultWrite = (componentPerm == null); + + final PathPermission[] pps = getPathPermissions(); + if (pps != null) { + final String path = uri.getPath(); + for (PathPermission pp : pps) { + final String pathPerm = pp.getWritePermission(); + if (pathPerm != null && pp.match(path)) { + if (context.checkPermission(pathPerm, pid, uid) == PERMISSION_GRANTED) { + return; + } else { + // any denied <path-permission> means we lose + // default <provider> access. + allowDefaultWrite = false; + missingPerm = pathPerm; } } } - - // if we passed <path-permission> checks above, and no default - // <provider> permission, then allow access. - if (allowDefaultWrite) return; } - // last chance, check against any uri grants - if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - == PERMISSION_GRANTED) { - return; - } + // if we passed <path-permission> checks above, and no default + // <provider> permission, then allow access. + if (allowDefaultWrite) return; + } - final String failReason = mExported - ? " requires " + missingPerm + ", or grantUriPermission()" - : " requires the provider be exported, or grantUriPermission()"; - throw new SecurityException("Permission Denial: writing " - + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid - + ", uid=" + uid + failReason); + // last chance, check against any uri grants + if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + == PERMISSION_GRANTED) { + return; } + + final String failReason = mExported + ? " requires " + missingPerm + ", or grantUriPermission()" + : " requires the provider be exported, or grantUriPermission()"; + throw new SecurityException("Permission Denial: writing " + + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + pid + + ", uid=" + uid + failReason); } /** diff --git a/core/java/android/content/SyncInfo.java b/core/java/android/content/SyncInfo.java index 0284882..cffc653 100644 --- a/core/java/android/content/SyncInfo.java +++ b/core/java/android/content/SyncInfo.java @@ -55,6 +55,14 @@ public class SyncInfo implements Parcelable { } /** @hide */ + public SyncInfo(SyncInfo other) { + this.authorityId = other.authorityId; + this.account = new Account(other.account.name, other.account.type); + this.authority = other.authority; + this.startTime = other.startTime; + } + + /** @hide */ public int describeContents() { return 0; } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 267fb2a..20002ad 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -53,6 +53,7 @@ import android.content.IntentSender; * {@hide} */ interface IPackageManager { + boolean isPackageAvailable(String packageName, int userId); PackageInfo getPackageInfo(String packageName, int flags, int userId); int getPackageUid(String packageName, int userId); int[] getPackageGids(String packageName); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 17d13e5..e6da288 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -282,6 +282,10 @@ public class PackageParser { || (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0; } + public static boolean isAvailable(PackageUserState state) { + return checkUseInstalledOrBlocked(0, state); + } + public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, HashSet<String> grantedPermissions, PackageUserState state, int userId) { diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 4fe2c4d..a38beec 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -334,6 +334,27 @@ public final class CameraCharacteristics extends CameraMetadata { /** * <p> + * If set to 1, the HAL will always split result + * metadata for a single capture into multiple buffers, + * returned using multiple process_capture_result calls. + * </p> + * <p> + * Does not need to be listed in static + * metadata. Support for partial results will be reworked in + * future versions of camera service. This quirk will stop + * working at that point; DO NOT USE without careful + * consideration of future support. + * </p> + * + * <b>Optional</b> - This value may be null on some devices. + * + * @hide + */ + public static final Key<Byte> QUIRKS_USE_PARTIAL_RESULT = + new Key<Byte>("android.quirks.usePartialResult", byte.class); + + /** + * <p> * How many output streams can be allocated at * the same time for each type of stream * </p> diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 7095e4d..9e8d7d1 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -631,6 +631,36 @@ public interface CameraDevice extends AutoCloseable { } /** + * This method is called when some results from an image capture are + * available. + * + * <p>The result provided here will contain some subset of the fields of + * a full result. Multiple onCapturePartial calls may happen per + * capture; a given result field will only be present in one partial + * capture at most. The final onCaptureCompleted call will always + * contain all the fields, whether onCapturePartial was called or + * not.</p> + * + * <p>The default implementation of this method does nothing.</p> + * + * @param camera The CameraDevice sending the callback. + * @param request The request that was given to the CameraDevice + * @param result The partial output metadata from the capture, which + * includes a subset of the CaptureResult fields. + * + * @see #capture + * @see #captureBurst + * @see #setRepeatingRequest + * @see #setRepeatingBurst + * + * @hide + */ + public void onCapturePartial(CameraDevice camera, + CaptureRequest request, CaptureResult result) { + // default empty implementation + } + + /** * This method is called when an image capture has completed and the * result metadata is available. * diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index dbd0457..535b963 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -591,6 +591,32 @@ public final class CaptureResult extends CameraMetadata { /** * <p> + * Whether a result given to the framework is the + * final one for the capture, or only a partial that contains a + * subset of the full set of dynamic metadata + * values. + * </p> + * <p> + * The entries in the result metadata buffers for a + * single capture may not overlap, except for this entry. The + * FINAL buffers must retain FIFO ordering relative to the + * requests that generate them, so the FINAL buffer for frame 3 must + * always be sent to the framework after the FINAL buffer for frame 2, and + * before the FINAL buffer for frame 4. PARTIAL buffers may be returned + * in any order relative to other frames, but all PARTIAL buffers for a given + * capture must arrive before the FINAL buffer for that capture. This entry may + * only be used by the HAL if quirks.usePartialResult is set to 1. + * </p> + * + * <b>Optional</b> - This value may be null on some devices. + * + * @hide + */ + public static final Key<Boolean> QUIRKS_PARTIAL_RESULT = + new Key<Boolean>("android.quirks.partialResult", boolean.class); + + /** + * <p> * A frame counter set by the framework. This value monotonically * increases with every new result (that is, each new result has a unique * frameCount value). diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java index c5d0999..40586f0 100644 --- a/core/java/android/hardware/camera2/impl/CameraDevice.java +++ b/core/java/android/hardware/camera2/impl/CameraDevice.java @@ -19,27 +19,24 @@ package android.hardware.camera2.impl; import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE; import android.hardware.camera2.CameraAccessException; -import android.hardware.camera2.CameraMetadata; -import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureResult; import android.hardware.camera2.ICameraDeviceCallbacks; import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.utils.CameraBinderDecorator; import android.hardware.camera2.utils.CameraRuntimeException; -import android.os.IBinder; -import android.os.RemoteException; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; +import android.os.RemoteException; import android.util.Log; import android.util.SparseArray; import android.view.Surface; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; -import java.util.Stack; /** * HAL2.1+ implementation of CameraDevice. Use CameraManager#open to instantiate @@ -49,6 +46,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private final String TAG; private final boolean DEBUG; + private static final int REQUEST_ID_NONE = -1; + // TODO: guard every function with if (!mRemoteDevice) check (if it was closed) private ICameraDeviceUser mRemoteDevice; @@ -63,7 +62,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private final SparseArray<CaptureListenerHolder> mCaptureListenerMap = new SparseArray<CaptureListenerHolder>(); - private final Stack<Integer> mRepeatingRequestIdStack = new Stack<Integer>(); + private int mRepeatingRequestId = REQUEST_ID_NONE; + private final ArrayList<Integer> mRepeatingRequestIdDeletedList = new ArrayList<Integer>(); // Map stream IDs to Surfaces private final SparseArray<Surface> mConfiguredOutputs = new SparseArray<Surface>(); @@ -186,7 +186,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { stopRepeating(); try { - mRemoteDevice.waitUntilIdle(); + waitUntilIdle(); // TODO: mRemoteDevice.beginConfigure // Delete all streams first (to free up HW resources) @@ -279,6 +279,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { checkIfCameraClosed(); int requestId; + if (repeating) { + stopRepeating(); + } + try { requestId = mRemoteDevice.submitRequest(request, repeating); } catch (CameraRuntimeException e) { @@ -293,7 +297,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } if (repeating) { - mRepeatingRequestIdStack.add(requestId); + mRepeatingRequestId = requestId; } if (mIdle) { @@ -327,8 +331,13 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { synchronized (mLock) { checkIfCameraClosed(); - while (!mRepeatingRequestIdStack.isEmpty()) { - int requestId = mRepeatingRequestIdStack.pop(); + if (mRepeatingRequestId != REQUEST_ID_NONE) { + + int requestId = mRepeatingRequestId; + mRepeatingRequestId = REQUEST_ID_NONE; + + // Queue for deletion after in-flight requests finish + mRepeatingRequestIdDeletedList.add(requestId); try { mRemoteDevice.cancelRequest(requestId); @@ -347,7 +356,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { synchronized (mLock) { checkIfCameraClosed(); - if (!mRepeatingRequestIdStack.isEmpty()) { + if (mRepeatingRequestId != REQUEST_ID_NONE) { throw new IllegalStateException("Active repeating request ongoing"); } @@ -359,6 +368,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { // impossible return; } + + mRepeatingRequestId = REQUEST_ID_NONE; + mRepeatingRequestIdDeletedList.clear(); + mCaptureListenerMap.clear(); } } @@ -564,6 +577,9 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } final CaptureListenerHolder holder; + Boolean quirkPartial = result.get(CaptureResult.QUIRKS_PARTIAL_RESULT); + boolean quirkIsPartialResult = (quirkPartial != null && quirkPartial); + synchronized (mLock) { // TODO: move this whole map into this class to make it more testable, // exposing the methods necessary like subscribeToRequest, unsubscribe.. @@ -572,13 +588,28 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { holder = CameraDevice.this.mCaptureListenerMap.get(requestId); // Clean up listener once we no longer expect to see it. - - // TODO: how to handle repeating listeners? - // we probably want cancelRequest to return # of times it already enqueued and - // keep a counter. - if (holder != null && !holder.isRepeating()) { + if (holder != null && !holder.isRepeating() && !quirkIsPartialResult) { CameraDevice.this.mCaptureListenerMap.remove(requestId); } + + // TODO: add 'capture sequence completed' callback to the + // service, and clean up repeating requests there instead. + + // If we received a result for a repeating request and have + // prior repeating requests queued for deletion, remove those + // requests from mCaptureListenerMap. + if (holder != null && holder.isRepeating() && !quirkIsPartialResult + && mRepeatingRequestIdDeletedList.size() > 0) { + Iterator<Integer> iter = mRepeatingRequestIdDeletedList.iterator(); + while (iter.hasNext()) { + int deletedRequestId = iter.next(); + if (deletedRequestId < requestId) { + CameraDevice.this.mCaptureListenerMap.remove(deletedRequestId); + iter.remove(); + } + } + } + } // Check if we have a listener for this @@ -591,8 +622,25 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { final CaptureRequest request = holder.getRequest(); final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId); - holder.getHandler().post( - new Runnable() { + Runnable resultDispatch = null; + + // Either send a partial result or the final capture completed result + if (quirkIsPartialResult) { + // Partial result + resultDispatch = new Runnable() { + @Override + public void run() { + if (!CameraDevice.this.isClosed()){ + holder.getListener().onCapturePartial( + CameraDevice.this, + request, + resultAsCapture); + } + } + }; + } else { + // Final capture result + resultDispatch = new Runnable() { @Override public void run() { if (!CameraDevice.this.isClosed()){ @@ -602,7 +650,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { resultAsCapture); } } - }); + }; + } + + holder.getHandler().post(resultDispatch); } } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index f12be5f..d5208d9 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -299,6 +299,10 @@ public final class DisplayManager { /** * Initiates a fresh scan of availble Wifi displays. * The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast. + * <p> + * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY}. + * </p> + * * @hide */ public void scanWifiDisplays() { @@ -312,8 +316,7 @@ public final class DisplayManager { * Automatically remembers the display after a successful connection, if not * already remembered. * </p><p> - * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY} to connect - * to unknown displays. No permissions are required to connect to already known displays. + * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY}. * </p> * * @param deviceAddress The MAC address of the device to which we should connect. diff --git a/core/java/android/net/PacProxySelector.java b/core/java/android/net/PacProxySelector.java index b674324..8a2c2b6 100644 --- a/core/java/android/net/PacProxySelector.java +++ b/core/java/android/net/PacProxySelector.java @@ -97,7 +97,7 @@ public class PacProxySelector extends ProxySelector { } catch (Exception e) { port = 8080; } - ret.add(new Proxy(Type.HTTP, new InetSocketAddress(host, port))); + ret.add(new Proxy(Type.HTTP, InetSocketAddress.createUnresolved(host, port))); } } if (ret.size() == 0) { diff --git a/core/java/android/net/ProxyProperties.java b/core/java/android/net/ProxyProperties.java index 78ac75f..010e527 100644 --- a/core/java/android/net/ProxyProperties.java +++ b/core/java/android/net/ProxyProperties.java @@ -139,6 +139,17 @@ public class ProxyProperties implements Parcelable { return false; } + public boolean isValid() { + if (!TextUtils.isEmpty(mPacFileUrl)) return true; + try { + Proxy.validate(mHost == null ? "" : mHost, mPort == 0 ? "" : Integer.toString(mPort), + mExclusionList == null ? "" : mExclusionList); + } catch (IllegalArgumentException e) { + return false; + } + return true; + } + public java.net.Proxy makeProxy() { java.net.Proxy proxy = java.net.Proxy.NO_PROXY; if (mHost != null) { diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index dbaa325..9ada6e6 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -106,6 +106,11 @@ public abstract class BatteryStats implements Parcelable { public static final int FOREGROUND_ACTIVITY = 10; /** + * A constant indicating a wifi batched scan is active + */ + public static final int WIFI_BATCHED_SCAN = 11; + + /** * Include all of the data in the stats, including previously saved data. */ public static final int STATS_SINCE_CHARGED = 0; @@ -270,6 +275,8 @@ public abstract class BatteryStats implements Parcelable { public abstract void noteFullWifiLockReleasedLocked(); public abstract void noteWifiScanStartedLocked(); public abstract void noteWifiScanStoppedLocked(); + public abstract void noteWifiBatchedScanStartedLocked(int csph); + public abstract void noteWifiBatchedScanStoppedLocked(); public abstract void noteWifiMulticastEnabledLocked(); public abstract void noteWifiMulticastDisabledLocked(); public abstract void noteAudioTurnedOnLocked(); @@ -281,6 +288,7 @@ public abstract class BatteryStats implements Parcelable { public abstract long getWifiRunningTime(long batteryRealtime, int which); public abstract long getFullWifiLockTime(long batteryRealtime, int which); public abstract long getWifiScanTime(long batteryRealtime, int which); + public abstract long getWifiBatchedScanTime(int csphBin, long batteryRealtime, int which); public abstract long getWifiMulticastTime(long batteryRealtime, int which); public abstract long getAudioTurnedOnTime(long batteryRealtime, int which); @@ -288,6 +296,8 @@ public abstract class BatteryStats implements Parcelable { public abstract Timer getForegroundActivityTimer(); public abstract Timer getVibratorOnTimer(); + public static final int NUM_WIFI_BATCHED_SCAN_BINS = 5; + /** * Note that these must match the constants in android.os.PowerManager. * Also, if the user activity types change, the BatteryStatsImpl.VERSION must @@ -844,12 +854,13 @@ public abstract class BatteryStats implements Parcelable { public static final int DATA_CONNECTION_EVDO_B = 12; public static final int DATA_CONNECTION_LTE = 13; public static final int DATA_CONNECTION_EHRPD = 14; - public static final int DATA_CONNECTION_OTHER = 15; + public static final int DATA_CONNECTION_HSPAP = 15; + public static final int DATA_CONNECTION_OTHER = 16; static final String[] DATA_CONNECTION_NAMES = { "none", "gprs", "edge", "umts", "cdma", "evdo_0", "evdo_A", "1xrtt", "hsdpa", "hsupa", "hspa", "iden", "evdo_b", "lte", - "ehrpd", "other" + "ehrpd", "hspap", "other" }; public static final int NUM_DATA_CONNECTION_TYPES = DATA_CONNECTION_OTHER+1; @@ -2080,9 +2091,11 @@ public abstract class BatteryStats implements Parcelable { TimeUtils.formatDuration(ew.usedTime, pw); pw.print(" over "); TimeUtils.formatDuration(ew.overTime, pw); - pw.print(" ("); - pw.print((ew.usedTime*100)/ew.overTime); - pw.println("%)"); + if (ew.overTime != 0) { + pw.print(" ("); + pw.print((ew.usedTime*100)/ew.overTime); + pw.println("%)"); + } } } uidActivity = true; diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java index 37a8102..f7d1eb7 100644 --- a/core/java/android/preference/Preference.java +++ b/core/java/android/preference/Preference.java @@ -1069,11 +1069,11 @@ public class Preference implements Comparable<Preference> { * @return 0 if the same; less than 0 if this Preference sorts ahead of <var>another</var>; * greater than 0 if this Preference sorts after <var>another</var>. */ + @Override public int compareTo(Preference another) { - if (mOrder != DEFAULT_ORDER - || (mOrder == DEFAULT_ORDER && another.mOrder != DEFAULT_ORDER)) { + if (mOrder != another.mOrder) { // Do order comparison - return mOrder - another.mOrder; + return mOrder - another.mOrder; } else if (mTitle == another.mTitle) { // If titles are null or share same object comparison return 0; diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java index 2ab5a91..7ddfa87 100644 --- a/core/java/android/preference/PreferenceActivity.java +++ b/core/java/android/preference/PreferenceActivity.java @@ -33,7 +33,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.AttributeSet; -import android.util.Log; import android.util.TypedValue; import android.util.Xml; import android.view.LayoutInflater; @@ -125,8 +124,6 @@ public abstract class PreferenceActivity extends ListActivity implements PreferenceManager.OnPreferenceTreeClickListener, PreferenceFragment.OnPreferenceStartFragmentCallback { - private static final String TAG = "PreferenceActivity"; - // Constants for state save/restore private static final String HEADERS_TAG = ":android:headers"; private static final String CUR_HEADER_TAG = ":android:cur_header"; @@ -524,7 +521,9 @@ public abstract class PreferenceActivity extends ListActivity implements int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0); int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0); - if (savedInstanceState != null) { + // Restore from headers only if they are supported which + // is in multi-pane mode. + if (savedInstanceState != null && !mSinglePane) { // We are restarting from a previous saved state; used that to // initialize, instead of starting fresh. ArrayList<Header> headers = savedInstanceState.getParcelableArrayList(HEADERS_TAG); diff --git a/core/java/android/print/IPrintDocumentAdapter.aidl b/core/java/android/print/IPrintDocumentAdapter.aidl index 9d384fb..2b95c12 100644 --- a/core/java/android/print/IPrintDocumentAdapter.aidl +++ b/core/java/android/print/IPrintDocumentAdapter.aidl @@ -37,4 +37,5 @@ oneway interface IPrintDocumentAdapter { void write(in PageRange[] pages, in ParcelFileDescriptor fd, IWriteResultCallback callback, int sequence); void finish(); + void cancel(); } diff --git a/core/java/android/print/PrintDocumentAdapter.java b/core/java/android/print/PrintDocumentAdapter.java index 9e811a6..1f59bef 100644 --- a/core/java/android/print/PrintDocumentAdapter.java +++ b/core/java/android/print/PrintDocumentAdapter.java @@ -141,15 +141,36 @@ public abstract class PrintDocumentAdapter { * or {@link LayoutResultCallback#onLayoutCancelled()} if layout was * cancelled in a response to a cancellation request via the passed in * {@link CancellationSignal}. Note that you <strong>must</strong> call one of - * the methods of the given callback for this method to be considered complete. + * the methods of the given callback for this method to be considered complete + * which is you will not receive any calls to this adapter until the current + * layout operation is complete by invoking a method on the callback instance. + * The callback methods can be invoked from an arbitrary thread. * </p> * <p> + * One of the arguments passed to this method is a {@link CancellationSignal} + * which is used to propagate requests from the system to your application for + * canceling the current layout operation. For example, a cancellation may be + * requested if the user changes a print option that may affect layout while + * you are performing a layout operation. In such a case the system will make + * an attempt to cancel the current layout as another one will have to be performed. + * Typically, you should register a cancellation callback in the cancellation + * signal. The cancellation callback <strong>will not</strong> be made on the + * main thread and can be registered as follows: + * </p> + * <pre> + * cancellationSignal.setOnCancelListener(new OnCancelListener() { + * @Override + * public void onCancel() { + * // Cancel layout + * } + * }); + * </pre> + * <p> * <strong>Note:</strong> If the content is large and a layout will be * performed, it is a good practice to schedule the work on a dedicated * thread and register an observer in the provided {@link * CancellationSignal} upon invocation of which you should stop the - * layout. The cancellation callback <strong>will not</strong> be made on - * the main thread. + * layout. * </p> * * @param oldAttributes The old print attributes. @@ -177,14 +198,36 @@ public abstract class PrintDocumentAdapter { * CharSequence)}, if an error occurred; or {@link WriteResultCallback#onWriteCancelled()}, * if writing was cancelled in a response to a cancellation request via the passed * in {@link CancellationSignal}. Note that you <strong>must</strong> call one of - * the methods of the given callback for this method to be considered complete. + * the methods of the given callback for this method to be considered complete which + * is you will not receive any calls to this adapter until the current write + * operation is complete by invoking a method on the callback instance. The callback + * methods can be invoked from an arbitrary thread. + * </p> + * <p> + * One of the arguments passed to this method is a {@link CancellationSignal} + * which is used to propagate requests from the system to your application for + * canceling the current write operation. For example, a cancellation may be + * requested if the user changes a print option that may affect layout while + * you are performing a write operation. In such a case the system will make + * an attempt to cancel the current write as a layout will have to be performed + * which then may be followed by a write. Typically, you should register a + * cancellation callback in the cancellation signal. The cancellation callback + * <strong>will not</strong> be made on the main thread and can be registered + * as follows: * </p> + * <pre> + * cancellationSignal.setOnCancelListener(new OnCancelListener() { + * @Override + * public void onCancel() { + * // Cancel write + * } + * }); + * </pre> * <p> * <strong>Note:</strong> If the printed content is large, it is a good * practice to schedule writing it on a dedicated thread and register an * observer in the provided {@link CancellationSignal} upon invocation of - * which you should stop writing. The cancellation callback will not be - * made on the main thread. + * which you should stop writing. * </p> * * @param pages The pages whose content to print - non-overlapping in ascending order. diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java index bbfc307..d1bb8fd 100644 --- a/core/java/android/print/PrintManager.java +++ b/core/java/android/print/PrintManager.java @@ -616,6 +616,18 @@ public final class PrintManager { } @Override + public void cancel() { + // Start not called or finish called or destroyed - nothing to do. + if (!mStartReqeusted || mFinishRequested || mDestroyed) { + return; + } + // Request cancellation of pending work if needed. + synchronized (mLock) { + cancelPreviousCancellableOperationLocked(); + } + } + + @Override public void onActivityPaused(Activity activity) { /* do nothing */ } @@ -848,6 +860,11 @@ public final class PrintManager { } final ILayoutResultCallback callback; synchronized (mLock) { + if (mDestroyed) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion?"); + return; + } callback = mCallback; clearLocked(); } @@ -864,6 +881,11 @@ public final class PrintManager { public void onLayoutFailed(CharSequence error) { final ILayoutResultCallback callback; synchronized (mLock) { + if (mDestroyed) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion?"); + return; + } callback = mCallback; clearLocked(); } @@ -879,6 +901,11 @@ public final class PrintManager { @Override public void onLayoutCancelled() { synchronized (mLock) { + if (mDestroyed) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion?"); + return; + } clearLocked(); } } @@ -906,6 +933,11 @@ public final class PrintManager { public void onWriteFinished(PageRange[] pages) { final IWriteResultCallback callback; synchronized (mLock) { + if (mDestroyed) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion?"); + return; + } callback = mCallback; clearLocked(); } @@ -928,6 +960,11 @@ public final class PrintManager { public void onWriteFailed(CharSequence error) { final IWriteResultCallback callback; synchronized (mLock) { + if (mDestroyed) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion?"); + return; + } callback = mCallback; clearLocked(); } @@ -943,6 +980,11 @@ public final class PrintManager { @Override public void onWriteCancelled() { synchronized (mLock) { + if (mDestroyed) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion?"); + return; + } clearLocked(); } } diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index e35b8eb..49816f8 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -62,7 +62,8 @@ import java.io.FileNotFoundException; * android:authorities="com.example.mycloudprovider" * android:exported="true" * android:grantUriPermissions="true" - * android:permission="android.permission.MANAGE_DOCUMENTS"> + * android:permission="android.permission.MANAGE_DOCUMENTS" + * android:enabled="@bool/isAtLeastKitKat"> * <intent-filter> * <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> * </intent-filter> @@ -216,6 +217,8 @@ public abstract class DocumentsProvider extends ContentProvider { * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and * limited to only return the 64 most recently modified documents. + * <p> + * Recent documents do not support change notifications. * * @param projection list of {@link Document} columns to put into the * cursor. If {@code null} all supported columns should be @@ -250,7 +253,8 @@ public abstract class DocumentsProvider extends ContentProvider { * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that * you are still fetching additional data. Then, when the network data is * available, you can send a change notification to trigger a requery and - * return the complete contents. + * return the complete contents. To return a Cursor with extras, you need to + * extend and override {@link Cursor#getExtras()}. * <p> * To support change notifications, you must * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant @@ -360,7 +364,7 @@ public abstract class DocumentsProvider extends ContentProvider { * @param documentId the document to return. * @param mode the mode to open with, such as 'r', 'w', or 'rw'. * @param signal used by the caller to signal if the request should be - * cancelled. + * cancelled. May be null. * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler, * OnCloseListener) * @see ParcelFileDescriptor#createReliablePipe() @@ -384,7 +388,7 @@ public abstract class DocumentsProvider extends ContentProvider { * @param documentId the document to return. * @param sizeHint hint of the optimal thumbnail dimensions. * @param signal used by the caller to signal if the request should be - * cancelled. + * cancelled. May be null. * @see Document#FLAG_SUPPORTS_THUMBNAIL */ @SuppressWarnings("unused") @@ -512,10 +516,7 @@ public abstract class DocumentsProvider extends ContentProvider { final boolean callerHasManage = context.checkCallingOrSelfPermission(android.Manifest.permission.MANAGE_DOCUMENTS) == PackageManager.PERMISSION_GRANTED; - if (!callerHasManage) { - getContext().enforceCallingOrSelfUriPermission( - documentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, method); - } + enforceWritePermissionInner(documentUri); final Bundle out = new Bundle(); try { diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index b808363..2752085 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -993,8 +993,16 @@ public class TextToSpeech { return runAction(new Action<Set<String>>() { @Override public Set<String> run(ITextToSpeechService service) throws RemoteException { - String[] features = service.getFeaturesForLanguage( + String[] features = null; + try { + features = service.getFeaturesForLanguage( locale.getISO3Language(), locale.getISO3Country(), locale.getVariant()); + } catch(MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve 3 letter ISO 639-2/T language and/or ISO 3166 " + + "country code for locale: " + locale, e); + return null; + } + if (features != null) { final Set<String> featureSet = new HashSet<String>(); Collections.addAll(featureSet, features); diff --git a/core/java/android/speech/tts/TtsEngines.java b/core/java/android/speech/tts/TtsEngines.java index 5fbd22e..4f996cd 100644 --- a/core/java/android/speech/tts/TtsEngines.java +++ b/core/java/android/speech/tts/TtsEngines.java @@ -44,6 +44,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Locale; +import java.util.MissingResourceException; /** * Support class for querying the list of available engines @@ -369,28 +370,34 @@ public class TtsEngines { public String getDefaultLocale() { final Locale locale = Locale.getDefault(); - // Note that the default locale might have an empty variant - // or language, and we take care that the construction is - // the same as {@link #getV1Locale} i.e no trailing delimiters - // or spaces. - String defaultLocale = locale.getISO3Language(); - if (TextUtils.isEmpty(defaultLocale)) { - Log.w(TAG, "Default locale is empty."); - return ""; - } + try { + // Note that the default locale might have an empty variant + // or language, and we take care that the construction is + // the same as {@link #getV1Locale} i.e no trailing delimiters + // or spaces. + String defaultLocale = locale.getISO3Language(); + if (TextUtils.isEmpty(defaultLocale)) { + Log.w(TAG, "Default locale is empty."); + return ""; + } + + if (!TextUtils.isEmpty(locale.getISO3Country())) { + defaultLocale += LOCALE_DELIMITER + locale.getISO3Country(); + } else { + // Do not allow locales of the form lang--variant with + // an empty country. + return defaultLocale; + } + if (!TextUtils.isEmpty(locale.getVariant())) { + defaultLocale += LOCALE_DELIMITER + locale.getVariant(); + } - if (!TextUtils.isEmpty(locale.getISO3Country())) { - defaultLocale += LOCALE_DELIMITER + locale.getISO3Country(); - } else { - // Do not allow locales of the form lang--variant with - // an empty country. return defaultLocale; + } catch (MissingResourceException e) { + // Default locale does not have a ISO 3166 and/or ISO 639-2/T codes. Return the + // default "eng-usa" (that would be the result of Locale.getDefault() == Locale.US). + return "eng-usa"; } - if (!TextUtils.isEmpty(locale.getVariant())) { - defaultLocale += LOCALE_DELIMITER + locale.getVariant(); - } - - return defaultLocale; } /** diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index 160c630..f839d52 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -391,6 +391,15 @@ public class Html { out.append(">"); } else if (c == '&') { out.append("&"); + } else if (c >= 0xD800 && c <= 0xDFFF) { + if (c < 0xDC00 && i + 1 < end) { + char d = text.charAt(i + 1); + if (d >= 0xDC00 && d <= 0xDFFF) { + i++; + int codepoint = 0x010000 | (int) c - 0xD800 << 10 | (int) d - 0xDC00; + out.append("&#").append(codepoint).append(";"); + } + } } else if (c > 0x7E || c < ' ') { out.append("&#").append((int) c).append(";"); } else if (c == ' ') { diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index f76e190..da9ba5a 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -1255,7 +1255,8 @@ public abstract class Transition implements Cloneable { Animator anim = runningAnimators.keyAt(i); if (anim != null) { AnimationInfo oldInfo = runningAnimators.get(anim); - if (oldInfo != null) { + if (oldInfo != null && oldInfo.view != null && + oldInfo.view.getContext() == sceneRoot.getContext()) { boolean cancel = false; TransitionValues oldValues = oldInfo.values; View oldView = oldInfo.view; diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java index 4af0f51..9f77d5e 100644 --- a/core/java/android/transition/TransitionInflater.java +++ b/core/java/android/transition/TransitionInflater.java @@ -20,9 +20,7 @@ import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; -import android.util.ArrayMap; import android.util.AttributeSet; -import android.util.SparseArray; import android.util.Xml; import android.view.InflateException; import android.view.ViewGroup; @@ -43,15 +41,7 @@ import java.util.ArrayList; */ public class TransitionInflater { - // We only need one inflater for any given context. Also, this allows us to associate - // ids with unique instances per-Context, used to avoid re-inflating - // already-inflated resources into new/different instances - private static final ArrayMap<Context, TransitionInflater> sInflaterMap = - new ArrayMap<Context, TransitionInflater>(); - private Context mContext; - // TODO: do we need id maps for transitions and transitionMgrs as well? - SparseArray<Scene> mScenes = new SparseArray<Scene>(); private TransitionInflater(Context context) { mContext = context; @@ -61,13 +51,7 @@ public class TransitionInflater { * Obtains the TransitionInflater from the given context. */ public static TransitionInflater from(Context context) { - TransitionInflater inflater = sInflaterMap.get(context); - if (inflater != null) { - return inflater; - } - inflater = new TransitionInflater(context); - sInflaterMap.put(context, inflater); - return inflater; + return new TransitionInflater(context); } /** diff --git a/core/java/android/util/MapCollections.java b/core/java/android/util/MapCollections.java index f4a9b0b..28b788b 100644 --- a/core/java/android/util/MapCollections.java +++ b/core/java/android/util/MapCollections.java @@ -97,10 +97,10 @@ abstract class MapCollections<K, V> { if (!mEntryValid) { throw new IllegalStateException(); } + colRemoveAt(mIndex); mIndex--; mEnd--; mEntryValid = false; - colRemoveAt(mIndex); } @Override diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 354ea66..7d310a2 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -643,6 +643,15 @@ public final class Display { || uid == 0; } + /** + * Returns true if the display is a public presentation display. + * @hide + */ + public boolean isPublicPresentation() { + return (mFlags & (Display.FLAG_PRIVATE | Display.FLAG_PRESENTATION)) == + Display.FLAG_PRESENTATION; + } + private void updateDisplayInfoLocked() { // Note: The display manager caches display info objects on our behalf. DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 1b76cb1..c92a104 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -78,7 +78,8 @@ interface IWindowManager void addWindowToken(IBinder token, int type); void removeWindowToken(IBinder token); void addAppToken(int addPos, IApplicationToken token, int groupId, int stackId, - int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId); + int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId, + int configChanges); void setAppGroupId(IBinder token, int groupId); void setAppOrientation(IApplicationToken token, int requestedOrientation); int getAppOrientation(IApplicationToken token); diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java index f36c78f..42a58a8 100644 --- a/core/java/android/view/ScaleGestureDetector.java +++ b/core/java/android/view/ScaleGestureDetector.java @@ -323,6 +323,10 @@ public class ScaleGestureDetector { mInProgress = false; mInitialSpan = 0; mDoubleTapMode = DOUBLE_TAP_MODE_NONE; + } else if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS && streamComplete) { + mInProgress = false; + mInitialSpan = 0; + mDoubleTapMode = DOUBLE_TAP_MODE_NONE; } if (streamComplete) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 6c04c0b..b0bae46 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -18,7 +18,6 @@ package android.view; import android.content.ClipData; import android.content.Context; -import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; @@ -3103,6 +3102,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static final int UNDEFINED_PADDING = Integer.MIN_VALUE; /** + * Cache if a left padding has been defined + */ + private boolean mLeftPaddingDefined = false; + + /** + * Cache if a right padding has been defined + */ + private boolean mRightPaddingDefined = false; + + /** * @hide */ int mOldWidthMeasureSpec = Integer.MIN_VALUE; @@ -3530,10 +3539,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int overScrollMode = mOverScrollMode; boolean initializeScrollbars = false; - boolean leftPaddingDefined = false; - boolean rightPaddingDefined = false; boolean startPaddingDefined = false; boolean endPaddingDefined = false; + boolean leftPaddingDefined = false; + boolean rightPaddingDefined = false; final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; @@ -3865,6 +3874,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setBackground(background); } + // setBackground above will record that padding is currently provided by the background. + // If we have padding specified via xml, record that here instead and use it. + mLeftPaddingDefined = leftPaddingDefined; + mRightPaddingDefined = rightPaddingDefined; + if (padding >= 0) { leftPadding = padding; topPadding = padding; @@ -3882,11 +3896,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Padding from the background drawable is stored at this point in mUserPaddingLeftInitial // and mUserPaddingRightInitial) so drawable padding will be used as ultimate default if // defined. - if (!leftPaddingDefined && startPaddingDefined) { + if (!mLeftPaddingDefined && startPaddingDefined) { leftPadding = startPadding; } mUserPaddingLeftInitial = (leftPadding >= 0) ? leftPadding : mUserPaddingLeftInitial; - if (!rightPaddingDefined && endPaddingDefined) { + if (!mRightPaddingDefined && endPaddingDefined) { rightPadding = endPadding; } mUserPaddingRightInitial = (rightPadding >= 0) ? rightPadding : mUserPaddingRightInitial; @@ -3898,10 +3912,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // defined. final boolean hasRelativePadding = startPaddingDefined || endPaddingDefined; - if (leftPaddingDefined && !hasRelativePadding) { + if (mLeftPaddingDefined && !hasRelativePadding) { mUserPaddingLeftInitial = leftPadding; } - if (rightPaddingDefined && !hasRelativePadding) { + if (mRightPaddingDefined && !hasRelativePadding) { mUserPaddingRightInitial = rightPadding; } } @@ -5900,6 +5914,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, sThreadLocal.set(localInsets); } boolean res = computeFitSystemWindows(insets, localInsets); + mUserPaddingLeftInitial = localInsets.left; + mUserPaddingRightInitial = localInsets.right; internalSetPadding(localInsets.left, localInsets.top, localInsets.right, localInsets.bottom); return res; @@ -12133,12 +12149,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (!isTextAlignmentResolved()) { resolveTextAlignment(); } - if (!isPaddingResolved()) { - resolvePadding(); - } + // Should resolve Drawables before Padding because we need the layout direction of the + // Drawable to correctly resolve Padding. if (!isDrawablesResolved()) { resolveDrawables(); } + if (!isPaddingResolved()) { + resolvePadding(); + } onRtlPropertiesChanged(getLayoutDirection()); return true; } @@ -12341,6 +12359,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // If start / end padding are defined, they will be resolved (hence overriding) to // left / right or right / left depending on the resolved layout direction. // If start / end padding are not defined, use the left / right ones. + if (mBackground != null && (!mLeftPaddingDefined || !mRightPaddingDefined)) { + Rect padding = sThreadLocal.get(); + if (padding == null) { + padding = new Rect(); + sThreadLocal.set(padding); + } + mBackground.getPadding(padding); + if (!mLeftPaddingDefined) { + mUserPaddingLeftInitial = padding.left; + } + if (!mRightPaddingDefined) { + mUserPaddingRightInitial = padding.right; + } + } switch (resolvedLayoutDirection) { case LAYOUT_DIRECTION_RTL: if (mUserPaddingStart != UNDEFINED_PADDING) { @@ -15336,6 +15368,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mUserPaddingRightInitial = padding.right; internalSetPadding(padding.left, padding.top, padding.right, padding.bottom); } + mLeftPaddingDefined = false; + mRightPaddingDefined = false; } // Compare the minimum sizes of the old Drawable and the new. If there isn't an old or @@ -15432,6 +15466,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mUserPaddingLeftInitial = left; mUserPaddingRightInitial = right; + mLeftPaddingDefined = true; + mRightPaddingDefined = true; + internalSetPadding(left, top, right, bottom); } @@ -15517,6 +15554,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mUserPaddingStart = start; mUserPaddingEnd = end; + mLeftPaddingDefined = true; + mRightPaddingDefined = true; switch(getLayoutDirection()) { case LAYOUT_DIRECTION_RTL: diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 637af6f..bc0d7e3 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3393,16 +3393,7 @@ public final class ViewRootImpl implements ViewParent, public final void deliver(QueuedInputEvent q) { if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) { forward(q); - } else if (mView == null || !mAdded) { - Slog.w(TAG, "Dropping event due to root view being removed: " + q.mEvent); - finish(q, false); - } else if (!mAttachInfo.mHasWindowFocus && - !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) && - !isTerminalInputEvent(q.mEvent)) { - // If this is a focused event and the window doesn't currently have input focus, - // then drop this event. This could be an event that came back from the previous - // stage but the window has lost focus in the meantime. - Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent); + } else if (shouldDropInputEvent(q)) { finish(q, false); } else { apply(q, onProcess(q)); @@ -3461,6 +3452,22 @@ public final class ViewRootImpl implements ViewParent, } } + protected boolean shouldDropInputEvent(QueuedInputEvent q) { + if (mView == null || !mAdded) { + Slog.w(TAG, "Dropping event due to root view being removed: " + q.mEvent); + return true; + } else if (!mAttachInfo.mHasWindowFocus && + !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) && + !isTerminalInputEvent(q.mEvent)) { + // If this is a focused event and the window doesn't currently have input focus, + // then drop this event. This could be an event that came back from the previous + // stage but the window has lost focus in the meantime. + Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent); + return true; + } + return false; + } + void dump(String prefix, PrintWriter writer) { if (mNext != null) { mNext.dump(prefix, writer); @@ -3846,6 +3853,10 @@ public final class ViewRootImpl implements ViewParent, return FINISH_HANDLED; } + if (shouldDropInputEvent(q)) { + return FINISH_NOT_HANDLED; + } + // If the Control modifier is held, try to interpret the key as a shortcut. if (event.getAction() == KeyEvent.ACTION_DOWN && event.isCtrlPressed() @@ -3854,12 +3865,18 @@ public final class ViewRootImpl implements ViewParent, if (mView.dispatchKeyShortcutEvent(event)) { return FINISH_HANDLED; } + if (shouldDropInputEvent(q)) { + return FINISH_NOT_HANDLED; + } } // Apply the fallback event policy. if (mFallbackEventHandler.dispatchKeyEvent(event)) { return FINISH_HANDLED; } + if (shouldDropInputEvent(q)) { + return FINISH_NOT_HANDLED; + } // Handle automatic focus changes. if (event.getAction() == KeyEvent.ACTION_DOWN) { diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java index 40f95ce..2ab3024 100644 --- a/core/java/android/view/inputmethod/InputMethodSubtype.java +++ b/core/java/android/view/inputmethod/InputMethodSubtype.java @@ -534,6 +534,13 @@ public final class InputMethodSubtype implements Parcelable { private static int hashCodeInternal(String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, boolean isAsciiCapable) { + // CAVEAT: Must revisit how to compute needsToCalculateCompatibleHashCode when a new + // attribute is added in order to avoid enabled subtypes being unexpectedly disabled. + final boolean needsToCalculateCompatibleHashCode = !isAsciiCapable; + if (needsToCalculateCompatibleHashCode) { + return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary, + overridesImplicitlyEnabledSubtype}); + } return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary, overridesImplicitlyEnabledSubtype, isAsciiCapable}); } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 3eb0052..092f474 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -6686,6 +6686,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te scrap.dispatchStartTemporaryDetach(); + // The the accessibility state of the view may change while temporary + // detached and we do not allow detached views to fire accessibility + // events. So we are announcing that the subtree changed giving a chance + // to clients holding on to a view in this subtree to refresh it. + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); + // Don't scrap views that have transient state. final boolean scrapHasTransientState = scrap.hasTransientState(); if (scrapHasTransientState) { diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index 9e35a23..7daf798 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -710,6 +710,7 @@ public class ImageView extends View { } d.setLevel(mLevel); d.setLayoutDirection(getLayoutDirection()); + d.setVisible(getVisibility() == VISIBLE, true); mDrawableWidth = d.getIntrinsicWidth(); mDrawableHeight = d.getIntrinsicHeight(); applyColorMod(); diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index 65a2d4d..5392a96 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -959,9 +959,11 @@ public class ProgressBar extends View { if (!mInDrawing) { if (verifyDrawable(dr)) { final Rect dirty = dr.getBounds(); + final int scrollX = mScrollX + mPaddingLeft; + final int scrollY = mScrollY + mPaddingTop; - invalidate(dirty.left + mScrollX, dirty.top + mScrollY, - dirty.right + mScrollX, dirty.bottom + mScrollY); + invalidate(dirty.left + scrollX, dirty.top + scrollY, + dirty.right + scrollX, dirty.bottom + scrollY); } else { super.invalidateDrawable(dr); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index cb930d6..7a9809f 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -56,6 +56,7 @@ import android.text.Selection; import android.text.SpanWatcher; import android.text.Spannable; import android.text.SpannableString; +import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.SpannedString; import android.text.StaticLayout; @@ -3494,19 +3495,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener ss.selEnd = end; if (mText instanceof Spanned) { - /* - * Calling setText() strips off any ChangeWatchers; - * strip them now to avoid leaking references. - * But do it to a copy so that if there are any - * further changes to the text of this view, it - * won't get into an inconsistent state. - */ - - Spannable sp = new SpannableString(mText); - - for (ChangeWatcher cw : sp.getSpans(0, sp.length(), ChangeWatcher.class)) { - sp.removeSpan(cw); - } + Spannable sp = new SpannableStringBuilder(mText); if (mEditor != null) { removeMisspelledSpans(sp); diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java index fbdf318..d57b739 100644 --- a/core/java/android/widget/VideoView.java +++ b/core/java/android/widget/VideoView.java @@ -56,7 +56,17 @@ import java.util.Vector; * can load images from various sources (such as resources or content * providers), takes care of computing its measurement from the video so that * it can be used in any layout manager, and provides various display options - * such as scaling and tinting. + * such as scaling and tinting.<p> + * + * <em>Note: VideoView does not retain its full state when going into the + * background.</em> In particular, it does not restore the current play state, + * play position, selected tracks, or any subtitle tracks added via + * {@link #addSubtitleSource addSubtitleSource()}. Applications should + * save and restore these on their own in + * {@link android.app.Activity#onSaveInstanceState} and + * {@link android.app.Activity#onRestoreInstanceState}.<p> + * Also note that the audio session id (from {@link #getAudioSessionId}) may + * change from its previously returned value when the VideoView is restored. */ public class VideoView extends SurfaceView implements MediaPlayerControl, SubtitleController.Anchor { diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 525517c..43c4b49 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -68,6 +68,8 @@ interface IBatteryStats { void noteFullWifiLockReleasedFromSource(in WorkSource ws); void noteWifiScanStartedFromSource(in WorkSource ws); void noteWifiScanStoppedFromSource(in WorkSource ws); + void noteWifiBatchedScanStartedFromSource(in WorkSource ws, int csph); + void noteWifiBatchedScanStoppedFromSource(in WorkSource ws); void noteWifiMulticastEnabledFromSource(in WorkSource ws); void noteWifiMulticastDisabledFromSource(in WorkSource ws); void noteNetworkInterfaceType(String iface, int type); diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialog.java b/core/java/com/android/internal/app/MediaRouteChooserDialog.java new file mode 100644 index 0000000..47d2a9c --- /dev/null +++ b/core/java/com/android/internal/app/MediaRouteChooserDialog.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2013 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.internal.app; + +import com.android.internal.R; + +import android.app.Dialog; +import android.content.Context; +import android.media.MediaRouter; +import android.media.MediaRouter.RouteInfo; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.Comparator; + +/** + * This class implements the route chooser dialog for {@link MediaRouter}. + * <p> + * This dialog allows the user to choose a route that matches a given selector. + * </p> + * + * @see MediaRouteButton + * @see MediaRouteActionProvider + * + * TODO: Move this back into the API, as in the support library media router. + */ +public class MediaRouteChooserDialog extends Dialog { + private final MediaRouter mRouter; + private final MediaRouterCallback mCallback; + + private int mRouteTypes; + private View.OnClickListener mExtendedSettingsClickListener; + private RouteAdapter mAdapter; + private ListView mListView; + private Button mExtendedSettingsButton; + private boolean mAttachedToWindow; + + public MediaRouteChooserDialog(Context context, int theme) { + super(context, theme); + + mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); + mCallback = new MediaRouterCallback(); + } + + /** + * Gets the media route types for filtering the routes that the user can + * select using the media route chooser dialog. + * + * @return The route types. + */ + public int getRouteTypes() { + return mRouteTypes; + } + + /** + * Sets the types of routes that will be shown in the media route chooser dialog + * launched by this button. + * + * @param types The route types to match. + */ + public void setRouteTypes(int types) { + if (mRouteTypes != types) { + mRouteTypes = types; + + if (mAttachedToWindow) { + mRouter.removeCallback(mCallback); + mRouter.addCallback(types, mCallback, + MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); + } + + refreshRoutes(); + } + } + + public void setExtendedSettingsClickListener(View.OnClickListener listener) { + if (listener != mExtendedSettingsClickListener) { + mExtendedSettingsClickListener = listener; + updateExtendedSettingsButton(); + } + } + + /** + * Returns true if the route should be included in the list. + * <p> + * The default implementation returns true for enabled non-default routes that + * match the route types. Subclasses can override this method to filter routes + * differently. + * </p> + * + * @param route The route to consider, never null. + * @return True if the route should be included in the chooser dialog. + */ + public boolean onFilterRoute(MediaRouter.RouteInfo route) { + return !route.isDefault() && route.isEnabled() && route.matchesTypes(mRouteTypes); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getWindow().requestFeature(Window.FEATURE_LEFT_ICON); + + setContentView(R.layout.media_route_chooser_dialog); + setTitle(mRouteTypes == MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY + ? R.string.media_route_chooser_title_for_remote_display + : R.string.media_route_chooser_title); + + // Must be called after setContentView. + getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, + R.drawable.ic_media_route_off_holo_dark); + + mAdapter = new RouteAdapter(getContext()); + mListView = (ListView)findViewById(R.id.media_route_list); + mListView.setAdapter(mAdapter); + mListView.setOnItemClickListener(mAdapter); + mListView.setEmptyView(findViewById(android.R.id.empty)); + + mExtendedSettingsButton = (Button)findViewById(R.id.media_route_extended_settings_button); + updateExtendedSettingsButton(); + } + + private void updateExtendedSettingsButton() { + if (mExtendedSettingsButton != null) { + mExtendedSettingsButton.setOnClickListener(mExtendedSettingsClickListener); + mExtendedSettingsButton.setVisibility( + mExtendedSettingsClickListener != null ? View.VISIBLE : View.GONE); + } + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + mAttachedToWindow = true; + mRouter.addCallback(mRouteTypes, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); + refreshRoutes(); + } + + @Override + public void onDetachedFromWindow() { + mAttachedToWindow = false; + mRouter.removeCallback(mCallback); + + super.onDetachedFromWindow(); + } + + /** + * Refreshes the list of routes that are shown in the chooser dialog. + */ + public void refreshRoutes() { + if (mAttachedToWindow) { + mAdapter.update(); + } + } + + private final class RouteAdapter extends ArrayAdapter<MediaRouter.RouteInfo> + implements ListView.OnItemClickListener { + private final LayoutInflater mInflater; + + public RouteAdapter(Context context) { + super(context, 0); + mInflater = LayoutInflater.from(context); + } + + public void update() { + clear(); + final int count = mRouter.getRouteCount(); + for (int i = 0; i < count; i++) { + MediaRouter.RouteInfo route = mRouter.getRouteAt(i); + if (onFilterRoute(route)) { + add(route); + } + } + sort(RouteComparator.sInstance); + notifyDataSetChanged(); + } + + @Override + public boolean areAllItemsEnabled() { + return false; + } + + @Override + public boolean isEnabled(int position) { + return getItem(position).isEnabled(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view = convertView; + if (view == null) { + view = mInflater.inflate(R.layout.media_route_list_item, parent, false); + } + MediaRouter.RouteInfo route = getItem(position); + TextView text1 = (TextView)view.findViewById(android.R.id.text1); + TextView text2 = (TextView)view.findViewById(android.R.id.text2); + text1.setText(route.getName()); + CharSequence description = route.getDescription(); + if (TextUtils.isEmpty(description)) { + text2.setVisibility(View.GONE); + text2.setText(""); + } else { + text2.setVisibility(View.VISIBLE); + text2.setText(description); + } + view.setEnabled(route.isEnabled()); + return view; + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + MediaRouter.RouteInfo route = getItem(position); + if (route.isEnabled()) { + route.select(); + dismiss(); + } + } + } + + private final class MediaRouterCallback extends MediaRouter.SimpleCallback { + @Override + public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) { + refreshRoutes(); + } + + @Override + public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) { + refreshRoutes(); + } + + @Override + public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) { + refreshRoutes(); + } + + @Override + public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { + dismiss(); + } + } + + private static final class RouteComparator implements Comparator<MediaRouter.RouteInfo> { + public static final RouteComparator sInstance = new RouteComparator(); + + @Override + public int compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs) { + return lhs.getName().toString().compareTo(rhs.getName().toString()); + } + } +} diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java index e300021..ae362af 100644 --- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java +++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Android Open Source Project + * Copyright (C) 2013 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. @@ -16,675 +16,86 @@ package com.android.internal.app; -import com.android.internal.R; - -import android.app.Activity; import android.app.Dialog; import android.app.DialogFragment; -import android.app.MediaRouteActionProvider; -import android.app.MediaRouteButton; import android.content.Context; -import android.graphics.drawable.Drawable; -import android.hardware.display.DisplayManager; -import android.media.MediaRouter; -import android.media.MediaRouter.RouteCategory; -import android.media.MediaRouter.RouteGroup; -import android.media.MediaRouter.RouteInfo; import android.os.Bundle; -import android.text.TextUtils; -import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.CheckBox; -import android.widget.Checkable; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.SeekBar; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import android.view.View.OnClickListener; /** - * This class implements the route chooser dialog for {@link MediaRouter}. + * Media route chooser dialog fragment. + * <p> + * Creates a {@link MediaRouteChooserDialog}. The application may subclass + * this dialog fragment to customize the media route chooser dialog. + * </p> * - * @see MediaRouteButton - * @see MediaRouteActionProvider + * TODO: Move this back into the API, as in the support library media router. */ public class MediaRouteChooserDialogFragment extends DialogFragment { - private static final String TAG = "MediaRouteChooserDialogFragment"; - public static final String FRAGMENT_TAG = "android:MediaRouteChooserDialogFragment"; - - private static final int[] ITEM_LAYOUTS = new int[] { - R.layout.media_route_list_item_top_header, - R.layout.media_route_list_item_section_header, - R.layout.media_route_list_item, - R.layout.media_route_list_item_checkable, - R.layout.media_route_list_item_collapse_group - }; + private final String ARGUMENT_ROUTE_TYPES = "routeTypes"; - MediaRouter mRouter; - private int mRouteTypes; - - private LayoutInflater mInflater; - private LauncherListener mLauncherListener; - private View.OnClickListener mExtendedSettingsListener; - private RouteAdapter mAdapter; - private ListView mListView; - private SeekBar mVolumeSlider; - private ImageView mVolumeIcon; - - final RouteComparator mComparator = new RouteComparator(); - final MediaRouterCallback mCallback = new MediaRouterCallback(); - private boolean mIgnoreSliderVolumeChanges; - private boolean mIgnoreCallbackVolumeChanges; + private View.OnClickListener mExtendedSettingsClickListener; + /** + * Creates a media route chooser dialog fragment. + * <p> + * All subclasses of this class must also possess a default constructor. + * </p> + */ public MediaRouteChooserDialogFragment() { - setStyle(STYLE_NO_TITLE, R.style.Theme_DeviceDefault_Dialog); + setCancelable(true); + setStyle(STYLE_NORMAL, android.R.style.Theme_DeviceDefault_Dialog); } - public void setLauncherListener(LauncherListener listener) { - mLauncherListener = listener; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - mRouter = (MediaRouter) activity.getSystemService(Context.MEDIA_ROUTER_SERVICE); - mRouter.addCallback(mRouteTypes, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN); - } - - @Override - public void onDetach() { - super.onDetach(); - if (mLauncherListener != null) { - mLauncherListener.onDetached(this); - } - if (mAdapter != null) { - mAdapter = null; - } - mInflater = null; - mRouter.removeCallback(mCallback); - mRouter = null; - } - - public void setExtendedSettingsClickListener(View.OnClickListener listener) { - mExtendedSettingsListener = listener; + public int getRouteTypes() { + Bundle args = getArguments(); + return args != null ? args.getInt(ARGUMENT_ROUTE_TYPES) : 0; } public void setRouteTypes(int types) { - mRouteTypes = types; - } - - void updateVolume() { - if (mRouter == null) return; - - final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); - mVolumeIcon.setImageResource(selectedRoute == null || - selectedRoute.getPlaybackType() == RouteInfo.PLAYBACK_TYPE_LOCAL ? - R.drawable.ic_audio_vol : R.drawable.ic_media_route_on_holo_dark); - - mIgnoreSliderVolumeChanges = true; - - if (selectedRoute == null || - selectedRoute.getVolumeHandling() == RouteInfo.PLAYBACK_VOLUME_FIXED) { - // Disable the slider and show it at max volume. - mVolumeSlider.setMax(1); - mVolumeSlider.setProgress(1); - mVolumeSlider.setEnabled(false); - } else { - mVolumeSlider.setEnabled(true); - mVolumeSlider.setMax(selectedRoute.getVolumeMax()); - mVolumeSlider.setProgress(selectedRoute.getVolume()); - } - - mIgnoreSliderVolumeChanges = false; - } - - void changeVolume(int newValue) { - if (mIgnoreSliderVolumeChanges) return; - - final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); - if (selectedRoute != null && - selectedRoute.getVolumeHandling() == RouteInfo.PLAYBACK_VOLUME_VARIABLE) { - final int maxVolume = selectedRoute.getVolumeMax(); - newValue = Math.max(0, Math.min(newValue, maxVolume)); - selectedRoute.requestSetVolume(newValue); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - mInflater = inflater; - final View layout = inflater.inflate(R.layout.media_route_chooser_layout, container, false); - - mVolumeIcon = (ImageView) layout.findViewById(R.id.volume_icon); - mVolumeSlider = (SeekBar) layout.findViewById(R.id.volume_slider); - updateVolume(); - mVolumeSlider.setOnSeekBarChangeListener(new VolumeSliderChangeListener()); - - if (mExtendedSettingsListener != null) { - final View extendedSettingsButton = layout.findViewById(R.id.extended_settings); - extendedSettingsButton.setVisibility(View.VISIBLE); - extendedSettingsButton.setOnClickListener(mExtendedSettingsListener); - } - - final ListView list = (ListView) layout.findViewById(R.id.list); - list.setItemsCanFocus(true); - list.setAdapter(mAdapter = new RouteAdapter()); - list.setOnItemClickListener(mAdapter); - - mListView = list; - - mAdapter.scrollToSelectedItem(); - - return layout; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - return new RouteChooserDialog(getActivity(), getTheme()); - } - - private static class ViewHolder { - public TextView text1; - public TextView text2; - public ImageView icon; - public ImageButton expandGroupButton; - public RouteAdapter.ExpandGroupListener expandGroupListener; - public int position; - public CheckBox check; - } - - private class RouteAdapter extends BaseAdapter implements ListView.OnItemClickListener { - private static final int VIEW_TOP_HEADER = 0; - private static final int VIEW_SECTION_HEADER = 1; - private static final int VIEW_ROUTE = 2; - private static final int VIEW_GROUPING_ROUTE = 3; - private static final int VIEW_GROUPING_DONE = 4; - - private int mSelectedItemPosition = -1; - private final ArrayList<Object> mItems = new ArrayList<Object>(); - - private RouteCategory mCategoryEditingGroups; - private RouteGroup mEditingGroup; - - // Temporary lists for manipulation - private final ArrayList<RouteInfo> mCatRouteList = new ArrayList<RouteInfo>(); - private final ArrayList<RouteInfo> mSortRouteList = new ArrayList<RouteInfo>(); - - private boolean mIgnoreUpdates; - - RouteAdapter() { - update(); - } - - void update() { - /* - * This is kind of wacky, but our data sets are going to be - * fairly small on average. Ideally we should be able to do some of this stuff - * in-place instead. - * - * Basic idea: each entry in mItems represents an item in the list for quick access. - * Entries can be a RouteCategory (section header), a RouteInfo with a category of - * mCategoryEditingGroups (a flattened RouteInfo pulled out of its group, allowing - * the user to change the group), - */ - if (mIgnoreUpdates) return; - - mItems.clear(); - - final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); - mSelectedItemPosition = -1; - - List<RouteInfo> routes; - final int catCount = mRouter.getCategoryCount(); - for (int i = 0; i < catCount; i++) { - final RouteCategory cat = mRouter.getCategoryAt(i); - routes = cat.getRoutes(mCatRouteList); - - if (!cat.isSystem()) { - mItems.add(cat); - } - - if (cat == mCategoryEditingGroups) { - addGroupEditingCategoryRoutes(routes); - } else { - addSelectableRoutes(selectedRoute, routes); - } - - routes.clear(); + if (types != getRouteTypes()) { + Bundle args = getArguments(); + if (args == null) { + args = new Bundle(); } + args.putInt(ARGUMENT_ROUTE_TYPES, types); + setArguments(args); - notifyDataSetChanged(); - if (mListView != null && mSelectedItemPosition >= 0) { - mListView.setItemChecked(mSelectedItemPosition, true); - } - } - - void scrollToEditingGroup() { - if (mCategoryEditingGroups == null || mListView == null) return; - - int pos = 0; - int bound = 0; - final int itemCount = mItems.size(); - for (int i = 0; i < itemCount; i++) { - final Object item = mItems.get(i); - if (item != null && item == mCategoryEditingGroups) { - bound = i; - } - if (item == null) { - pos = i; - break; // this is always below the category header; we can stop here. - } - } - - mListView.smoothScrollToPosition(pos, bound); - } - - void scrollToSelectedItem() { - if (mListView == null || mSelectedItemPosition < 0) return; - - mListView.smoothScrollToPosition(mSelectedItemPosition); - } - - void addSelectableRoutes(RouteInfo selectedRoute, List<RouteInfo> from) { - final int routeCount = from.size(); - for (int j = 0; j < routeCount; j++) { - final RouteInfo info = from.get(j); - if (info == selectedRoute) { - mSelectedItemPosition = mItems.size(); - } - mItems.add(info); - } - } - - void addGroupEditingCategoryRoutes(List<RouteInfo> from) { - // Unpack groups and flatten for presentation - // mSortRouteList will always be empty here. - final int topCount = from.size(); - for (int i = 0; i < topCount; i++) { - final RouteInfo route = from.get(i); - final RouteGroup group = route.getGroup(); - if (group == route) { - // This is a group, unpack it. - final int groupCount = group.getRouteCount(); - for (int j = 0; j < groupCount; j++) { - final RouteInfo innerRoute = group.getRouteAt(j); - mSortRouteList.add(innerRoute); - } - } else { - mSortRouteList.add(route); - } - } - // Sort by name. This will keep the route positions relatively stable even though they - // will be repeatedly added and removed. - Collections.sort(mSortRouteList, mComparator); - - mItems.addAll(mSortRouteList); - mSortRouteList.clear(); - - mItems.add(null); // Sentinel reserving space for the "done" button. - } - - @Override - public int getCount() { - return mItems.size(); - } - - @Override - public int getViewTypeCount() { - return 5; - } - - @Override - public int getItemViewType(int position) { - final Object item = getItem(position); - if (item instanceof RouteCategory) { - return position == 0 ? VIEW_TOP_HEADER : VIEW_SECTION_HEADER; - } else if (item == null) { - return VIEW_GROUPING_DONE; - } else { - final RouteInfo info = (RouteInfo) item; - if (info.getCategory() == mCategoryEditingGroups) { - return VIEW_GROUPING_ROUTE; - } - return VIEW_ROUTE; - } - } - - @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int position) { - switch (getItemViewType(position)) { - case VIEW_ROUTE: - return ((RouteInfo) mItems.get(position)).isEnabled(); - case VIEW_GROUPING_ROUTE: - case VIEW_GROUPING_DONE: - return true; - default: - return false; - } - } - - @Override - public Object getItem(int position) { - return mItems.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final int viewType = getItemViewType(position); - - ViewHolder holder; - if (convertView == null) { - convertView = mInflater.inflate(ITEM_LAYOUTS[viewType], parent, false); - holder = new ViewHolder(); - holder.position = position; - holder.text1 = (TextView) convertView.findViewById(R.id.text1); - holder.text2 = (TextView) convertView.findViewById(R.id.text2); - holder.icon = (ImageView) convertView.findViewById(R.id.icon); - holder.check = (CheckBox) convertView.findViewById(R.id.check); - holder.expandGroupButton = (ImageButton) convertView.findViewById( - R.id.expand_button); - if (holder.expandGroupButton != null) { - holder.expandGroupListener = new ExpandGroupListener(); - holder.expandGroupButton.setOnClickListener(holder.expandGroupListener); - } - - final View fview = convertView; - final ListView list = (ListView) parent; - final ViewHolder fholder = holder; - convertView.setOnClickListener(new View.OnClickListener() { - @Override public void onClick(View v) { - list.performItemClick(fview, fholder.position, 0); - } - }); - convertView.setTag(holder); - } else { - holder = (ViewHolder) convertView.getTag(); - holder.position = position; - } - - switch (viewType) { - case VIEW_ROUTE: - case VIEW_GROUPING_ROUTE: - bindItemView(position, holder); - break; - case VIEW_SECTION_HEADER: - case VIEW_TOP_HEADER: - bindHeaderView(position, holder); - break; - } - - convertView.setActivated(position == mSelectedItemPosition); - convertView.setEnabled(isEnabled(position)); - - return convertView; - } - - void bindItemView(int position, ViewHolder holder) { - RouteInfo info = (RouteInfo) mItems.get(position); - holder.text1.setText(info.getName(getActivity())); - final CharSequence status = info.getStatus(); - if (TextUtils.isEmpty(status)) { - holder.text2.setVisibility(View.GONE); - } else { - holder.text2.setVisibility(View.VISIBLE); - holder.text2.setText(status); - } - Drawable icon = info.getIconDrawable(); - if (icon != null) { - // Make sure we have a fresh drawable where it doesn't matter if we mutate it - icon = icon.getConstantState().newDrawable(getResources()); - } - holder.icon.setImageDrawable(icon); - holder.icon.setVisibility(icon != null ? View.VISIBLE : View.GONE); - - RouteCategory cat = info.getCategory(); - boolean canGroup = false; - if (cat == mCategoryEditingGroups) { - RouteGroup group = info.getGroup(); - holder.check.setEnabled(group.getRouteCount() > 1); - holder.check.setChecked(group == mEditingGroup); - } else { - if (cat.isGroupable()) { - final RouteGroup group = (RouteGroup) info; - canGroup = group.getRouteCount() > 1 || - getItemViewType(position - 1) == VIEW_ROUTE || - (position < getCount() - 1 && - getItemViewType(position + 1) == VIEW_ROUTE); - } - } - - if (holder.expandGroupButton != null) { - holder.expandGroupButton.setVisibility(canGroup ? View.VISIBLE : View.GONE); - holder.expandGroupListener.position = position; - } - } - - void bindHeaderView(int position, ViewHolder holder) { - RouteCategory cat = (RouteCategory) mItems.get(position); - holder.text1.setText(cat.getName(getActivity())); - } - - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - final int type = getItemViewType(position); - if (type == VIEW_SECTION_HEADER || type == VIEW_TOP_HEADER) { - return; - } else if (type == VIEW_GROUPING_DONE) { - finishGrouping(); - return; - } else { - final Object item = getItem(position); - if (!(item instanceof RouteInfo)) { - // Oops. Stale event running around? Skip it. - return; - } - - final RouteInfo route = (RouteInfo) item; - if (type == VIEW_ROUTE) { - mRouter.selectRouteInt(mRouteTypes, route); - dismiss(); - } else if (type == VIEW_GROUPING_ROUTE) { - final Checkable c = (Checkable) view; - final boolean wasChecked = c.isChecked(); - - mIgnoreUpdates = true; - RouteGroup oldGroup = route.getGroup(); - if (!wasChecked && oldGroup != mEditingGroup) { - // Assumption: in a groupable category oldGroup will never be null. - if (mRouter.getSelectedRoute(mRouteTypes) == oldGroup) { - // Old group was selected but is now empty. Select the group - // we're manipulating since that's where the last route went. - mRouter.selectRouteInt(mRouteTypes, mEditingGroup); - } - oldGroup.removeRoute(route); - mEditingGroup.addRoute(route); - c.setChecked(true); - } else if (wasChecked && mEditingGroup.getRouteCount() > 1) { - mEditingGroup.removeRoute(route); - - // In a groupable category this will add - // the route into its own new group. - mRouter.addRouteInt(route); - } - mIgnoreUpdates = false; - update(); - } - } - } - - boolean isGrouping() { - return mCategoryEditingGroups != null; - } - - void finishGrouping() { - mCategoryEditingGroups = null; - mEditingGroup = null; - getDialog().setCanceledOnTouchOutside(true); - update(); - scrollToSelectedItem(); - } - - class ExpandGroupListener implements View.OnClickListener { - int position; - - @Override - public void onClick(View v) { - // Assumption: this is only available for the user to click if we're presenting - // a groupable category, where every top-level route in the category is a group. - final RouteGroup group = (RouteGroup) getItem(position); - mEditingGroup = group; - mCategoryEditingGroups = group.getCategory(); - getDialog().setCanceledOnTouchOutside(false); - mRouter.selectRouteInt(mRouteTypes, mEditingGroup); - update(); - scrollToEditingGroup(); - } - } - } - - class MediaRouterCallback extends MediaRouter.Callback { - @Override - public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { - mAdapter.update(); - updateVolume(); - } - - @Override - public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { - mAdapter.update(); - } - - @Override - public void onRouteAdded(MediaRouter router, RouteInfo info) { - mAdapter.update(); - } - - @Override - public void onRouteRemoved(MediaRouter router, RouteInfo info) { - if (info == mAdapter.mEditingGroup) { - mAdapter.finishGrouping(); - } - mAdapter.update(); - } - - @Override - public void onRouteChanged(MediaRouter router, RouteInfo info) { - mAdapter.notifyDataSetChanged(); - } - - @Override - public void onRouteGrouped(MediaRouter router, RouteInfo info, - RouteGroup group, int index) { - mAdapter.update(); - } - - @Override - public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) { - mAdapter.update(); - } - - @Override - public void onRouteVolumeChanged(MediaRouter router, RouteInfo info) { - if (!mIgnoreCallbackVolumeChanges) { - updateVolume(); + MediaRouteChooserDialog dialog = (MediaRouteChooserDialog)getDialog(); + if (dialog != null) { + dialog.setRouteTypes(types); } } } - class RouteComparator implements Comparator<RouteInfo> { - @Override - public int compare(RouteInfo lhs, RouteInfo rhs) { - return lhs.getName(getActivity()).toString() - .compareTo(rhs.getName(getActivity()).toString()); - } - } - - class RouteChooserDialog extends Dialog { - public RouteChooserDialog(Context context, int theme) { - super(context, theme); - } - - @Override - public void onBackPressed() { - if (mAdapter != null && mAdapter.isGrouping()) { - mAdapter.finishGrouping(); - } else { - super.onBackPressed(); - } - } - - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && mVolumeSlider.isEnabled()) { - final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); - if (selectedRoute != null) { - selectedRoute.requestUpdateVolume(-1); - return true; - } - } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mVolumeSlider.isEnabled()) { - final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes); - if (selectedRoute != null) { - mRouter.getSelectedRoute(mRouteTypes).requestUpdateVolume(1); - return true; - } - } - return super.onKeyDown(keyCode, event); - } + public void setExtendedSettingsClickListener(View.OnClickListener listener) { + if (listener != mExtendedSettingsClickListener) { + mExtendedSettingsClickListener = listener; - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && mVolumeSlider.isEnabled()) { - return true; - } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mVolumeSlider.isEnabled()) { - return true; - } else { - return super.onKeyUp(keyCode, event); + MediaRouteChooserDialog dialog = (MediaRouteChooserDialog)getDialog(); + if (dialog != null) { + dialog.setExtendedSettingsClickListener(listener); } } } /** - * Implemented by the MediaRouteButton that launched this dialog + * Called when the chooser dialog is being created. + * <p> + * Subclasses may override this method to customize the dialog. + * </p> */ - public interface LauncherListener { - public void onDetached(MediaRouteChooserDialogFragment detachedFragment); + public MediaRouteChooserDialog onCreateChooserDialog( + Context context, Bundle savedInstanceState) { + return new MediaRouteChooserDialog(context, getTheme()); } - class VolumeSliderChangeListener implements SeekBar.OnSeekBarChangeListener { - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - changeVolume(progress); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - mIgnoreCallbackVolumeChanges = true; - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - mIgnoreCallbackVolumeChanges = false; - updateVolume(); - } - + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + MediaRouteChooserDialog dialog = onCreateChooserDialog(getActivity(), savedInstanceState); + dialog.setRouteTypes(getRouteTypes()); + dialog.setExtendedSettingsClickListener(mExtendedSettingsClickListener); + return dialog; } } diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialog.java b/core/java/com/android/internal/app/MediaRouteControllerDialog.java new file mode 100644 index 0000000..8fc99c7 --- /dev/null +++ b/core/java/com/android/internal/app/MediaRouteControllerDialog.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2013 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.internal.app; + +import com.android.internal.R; + +import android.app.Dialog; +import android.app.MediaRouteActionProvider; +import android.app.MediaRouteButton; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.media.MediaRouter; +import android.media.MediaRouter.RouteGroup; +import android.media.MediaRouter.RouteInfo; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.View; +import android.view.Window; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.SeekBar; + +/** + * This class implements the route controller dialog for {@link MediaRouter}. + * <p> + * This dialog allows the user to control or disconnect from the currently selected route. + * </p> + * + * @see MediaRouteButton + * @see MediaRouteActionProvider + * + * TODO: Move this back into the API, as in the support library media router. + */ +public class MediaRouteControllerDialog extends Dialog { + // Time to wait before updating the volume when the user lets go of the seek bar + // to allow the route provider time to propagate the change and publish a new + // route descriptor. + private static final int VOLUME_UPDATE_DELAY_MILLIS = 250; + + private final MediaRouter mRouter; + private final MediaRouterCallback mCallback; + private final MediaRouter.RouteInfo mRoute; + + private boolean mCreated; + private Drawable mMediaRouteConnectingDrawable; + private Drawable mMediaRouteOnDrawable; + private Drawable mCurrentIconDrawable; + + private boolean mVolumeControlEnabled = true; + private LinearLayout mVolumeLayout; + private SeekBar mVolumeSlider; + private boolean mVolumeSliderTouched; + + private View mControlView; + + private Button mDisconnectButton; + + public MediaRouteControllerDialog(Context context, int theme) { + super(context, theme); + + mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); + mCallback = new MediaRouterCallback(); + mRoute = mRouter.getSelectedRoute(); + } + + /** + * Gets the route that this dialog is controlling. + */ + public MediaRouter.RouteInfo getRoute() { + return mRoute; + } + + /** + * Provides the subclass an opportunity to create a view that will + * be included within the body of the dialog to offer additional media controls + * for the currently playing content. + * + * @param savedInstanceState The dialog's saved instance state. + * @return The media control view, or null if none. + */ + public View onCreateMediaControlView(Bundle savedInstanceState) { + return null; + } + + /** + * Gets the media control view that was created by {@link #onCreateMediaControlView(Bundle)}. + * + * @return The media control view, or null if none. + */ + public View getMediaControlView() { + return mControlView; + } + + /** + * Sets whether to enable the volume slider and volume control using the volume keys + * when the route supports it. + * <p> + * The default value is true. + * </p> + */ + public void setVolumeControlEnabled(boolean enable) { + if (mVolumeControlEnabled != enable) { + mVolumeControlEnabled = enable; + if (mCreated) { + updateVolume(); + } + } + } + + /** + * Returns whether to enable the volume slider and volume control using the volume keys + * when the route supports it. + */ + public boolean isVolumeControlEnabled() { + return mVolumeControlEnabled; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + getWindow().requestFeature(Window.FEATURE_LEFT_ICON); + + setContentView(R.layout.media_route_controller_dialog); + + mVolumeLayout = (LinearLayout)findViewById(R.id.media_route_volume_layout); + mVolumeSlider = (SeekBar)findViewById(R.id.media_route_volume_slider); + mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + private final Runnable mStopTrackingTouch = new Runnable() { + @Override + public void run() { + if (mVolumeSliderTouched) { + mVolumeSliderTouched = false; + updateVolume(); + } + } + }; + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + if (mVolumeSliderTouched) { + mVolumeSlider.removeCallbacks(mStopTrackingTouch); + } else { + mVolumeSliderTouched = true; + } + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Defer resetting mVolumeSliderTouched to allow the media route provider + // a little time to settle into its new state and publish the final + // volume update. + mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS); + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser) { + mRoute.requestSetVolume(progress); + } + } + }); + + mDisconnectButton = (Button)findViewById(R.id.media_route_disconnect_button); + mDisconnectButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mRoute.isSelected()) { + mRouter.getDefaultRoute().select(); + } + dismiss(); + } + }); + + mCreated = true; + if (update()) { + mControlView = onCreateMediaControlView(savedInstanceState); + FrameLayout controlFrame = + (FrameLayout)findViewById(R.id.media_route_control_frame); + if (mControlView != null) { + controlFrame.addView(mControlView); + controlFrame.setVisibility(View.VISIBLE); + } else { + controlFrame.setVisibility(View.GONE); + } + } + } + + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + mRouter.addCallback(0, mCallback, MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS); + update(); + } + + @Override + public void onDetachedFromWindow() { + mRouter.removeCallback(mCallback); + + super.onDetachedFromWindow(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN + || keyCode == KeyEvent.KEYCODE_VOLUME_UP) { + mRoute.requestUpdateVolume(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? -1 : 1); + return true; + } + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN + || keyCode == KeyEvent.KEYCODE_VOLUME_UP) { + return true; + } + return super.onKeyUp(keyCode, event); + } + + private boolean update() { + if (!mRoute.isSelected() || mRoute.isDefault()) { + dismiss(); + return false; + } + + setTitle(mRoute.getName()); + updateVolume(); + + Drawable icon = getIconDrawable(); + if (icon != mCurrentIconDrawable) { + mCurrentIconDrawable = icon; + getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, icon); + } + return true; + } + + private Drawable getIconDrawable() { + if (mRoute.isConnecting()) { + if (mMediaRouteConnectingDrawable == null) { + mMediaRouteConnectingDrawable = getContext().getResources().getDrawable( + R.drawable.ic_media_route_connecting_holo_dark); + } + return mMediaRouteConnectingDrawable; + } else { + if (mMediaRouteOnDrawable == null) { + mMediaRouteOnDrawable = getContext().getResources().getDrawable( + R.drawable.ic_media_route_on_holo_dark); + } + return mMediaRouteOnDrawable; + } + } + + private void updateVolume() { + if (!mVolumeSliderTouched) { + if (isVolumeControlAvailable()) { + mVolumeLayout.setVisibility(View.VISIBLE); + mVolumeSlider.setMax(mRoute.getVolumeMax()); + mVolumeSlider.setProgress(mRoute.getVolume()); + } else { + mVolumeLayout.setVisibility(View.GONE); + } + } + } + + private boolean isVolumeControlAvailable() { + return mVolumeControlEnabled && mRoute.getVolumeHandling() == + MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE; + } + + private final class MediaRouterCallback extends MediaRouter.SimpleCallback { + @Override + public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { + update(); + } + + @Override + public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) { + update(); + } + + @Override + public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) { + if (route == mRoute) { + updateVolume(); + } + } + + @Override + public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, + int index) { + update(); + } + + @Override + public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) { + update(); + } + } +} diff --git a/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java b/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java new file mode 100644 index 0000000..108e81f --- /dev/null +++ b/core/java/com/android/internal/app/MediaRouteControllerDialogFragment.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013 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.internal.app; + +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.os.Bundle; + +/** + * Media route controller dialog fragment. + * <p> + * Creates a {@link MediaRouteControllerDialog}. The application may subclass + * this dialog fragment to customize the media route controller dialog. + * </p> + * + * TODO: Move this back into the API, as in the support library media router. + */ +public class MediaRouteControllerDialogFragment extends DialogFragment { + /** + * Creates a media route controller dialog fragment. + * <p> + * All subclasses of this class must also possess a default constructor. + * </p> + */ + public MediaRouteControllerDialogFragment() { + setCancelable(true); + setStyle(STYLE_NORMAL, android.R.style.Theme_DeviceDefault_Dialog); + } + + /** + * Called when the controller dialog is being created. + * <p> + * Subclasses may override this method to customize the dialog. + * </p> + */ + public MediaRouteControllerDialog onCreateControllerDialog( + Context context, Bundle savedInstanceState) { + return new MediaRouteControllerDialog(context, getTheme()); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return onCreateControllerDialog(getActivity(), savedInstanceState); + } +} diff --git a/core/java/com/android/internal/app/MediaRouteDialogPresenter.java b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java new file mode 100644 index 0000000..fad7fd4 --- /dev/null +++ b/core/java/com/android/internal/app/MediaRouteDialogPresenter.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2013 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.internal.app; + + +import android.app.Activity; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.FragmentManager; +import android.content.Context; +import android.media.MediaRouter; +import android.util.Log; +import android.view.View; + +/** + * Shows media route dialog as appropriate. + * @hide + */ +public abstract class MediaRouteDialogPresenter { + private static final String TAG = "MediaRouter"; + + private static final String CHOOSER_FRAGMENT_TAG = + "android.app.MediaRouteButton:MediaRouteChooserDialogFragment"; + private static final String CONTROLLER_FRAGMENT_TAG = + "android.app.MediaRouteButton:MediaRouteControllerDialogFragment"; + + public static DialogFragment showDialogFragment(Activity activity, + int routeTypes, View.OnClickListener extendedSettingsClickListener) { + final MediaRouter router = (MediaRouter)activity.getSystemService( + Context.MEDIA_ROUTER_SERVICE); + final FragmentManager fm = activity.getFragmentManager(); + + MediaRouter.RouteInfo route = router.getSelectedRoute(); + if (route.isDefault() || !route.matchesTypes(routeTypes)) { + if (fm.findFragmentByTag(CHOOSER_FRAGMENT_TAG) != null) { + Log.w(TAG, "showDialog(): Route chooser dialog already showing!"); + return null; + } + MediaRouteChooserDialogFragment f = new MediaRouteChooserDialogFragment(); + f.setRouteTypes(routeTypes); + f.setExtendedSettingsClickListener(extendedSettingsClickListener); + f.show(fm, CHOOSER_FRAGMENT_TAG); + return f; + } else { + if (fm.findFragmentByTag(CONTROLLER_FRAGMENT_TAG) != null) { + Log.w(TAG, "showDialog(): Route controller dialog already showing!"); + return null; + } + MediaRouteControllerDialogFragment f = new MediaRouteControllerDialogFragment(); + f.show(fm, CONTROLLER_FRAGMENT_TAG); + return f; + } + } + + public static Dialog createDialog(Context context, + int routeTypes, View.OnClickListener extendedSettingsClickListener) { + final MediaRouter router = (MediaRouter)context.getSystemService( + Context.MEDIA_ROUTER_SERVICE); + + MediaRouter.RouteInfo route = router.getSelectedRoute(); + if (route.isDefault() || !route.matchesTypes(routeTypes)) { + final MediaRouteChooserDialog d = new MediaRouteChooserDialog( + context, android.R.style.Theme_DeviceDefault_Dialog); + d.setRouteTypes(routeTypes); + d.setExtendedSettingsClickListener(extendedSettingsClickListener); + return d; + } else { + MediaRouteControllerDialog d = new MediaRouteControllerDialog( + context, android.R.style.Theme_DeviceDefault_Dialog); + return d; + } + } +} diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java index 20b8c95..0cad33c 100644 --- a/core/java/com/android/internal/app/ProcessStats.java +++ b/core/java/com/android/internal/app/ProcessStats.java @@ -1758,21 +1758,34 @@ public final class ProcessStats implements Parcelable { mStartTime, now); ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap(); boolean printedHeader = false; + boolean sepNeeded = false; for (int ip=0; ip<pkgMap.size(); ip++) { - String pkgName = pkgMap.keyAt(ip); - if (reqPackage != null && !reqPackage.equals(pkgName)) { - continue; - } - SparseArray<PackageState> uids = pkgMap.valueAt(ip); + final String pkgName = pkgMap.keyAt(ip); + final SparseArray<PackageState> uids = pkgMap.valueAt(ip); for (int iu=0; iu<uids.size(); iu++) { - int uid = uids.keyAt(iu); - PackageState pkgState = uids.valueAt(iu); + final int uid = uids.keyAt(iu); + final PackageState pkgState = uids.valueAt(iu); final int NPROCS = pkgState.mProcesses.size(); final int NSRVS = pkgState.mServices.size(); + final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName); + if (!pkgMatch) { + boolean procMatch = false; + for (int iproc=0; iproc<NPROCS; iproc++) { + ProcessState proc = pkgState.mProcesses.valueAt(iproc); + if (reqPackage.equals(proc.mName)) { + procMatch = true; + break; + } + } + if (!procMatch) { + continue; + } + } if (NPROCS > 0 || NSRVS > 0) { if (!printedHeader) { pw.println("Per-Package Stats:"); printedHeader = true; + sepNeeded = true; } pw.print(" * "); pw.print(pkgName); pw.print(" / "); UserHandle.formatUid(pw, uid); pw.println(":"); @@ -1780,6 +1793,9 @@ public final class ProcessStats implements Parcelable { if (!dumpSummary || dumpAll) { for (int iproc=0; iproc<NPROCS; iproc++) { ProcessState proc = pkgState.mProcesses.valueAt(iproc); + if (!pkgMatch && !reqPackage.equals(proc.mName)) { + continue; + } if (activeOnly && !proc.isInUse()) { pw.print(" (Not active: "); pw.print(pkgState.mProcesses.keyAt(iproc)); pw.println(")"); @@ -1787,7 +1803,11 @@ public final class ProcessStats implements Parcelable { } pw.print(" Process "); pw.print(pkgState.mProcesses.keyAt(iproc)); - pw.print(" ("); + if (proc.mCommonProcess.mMultiPackage) { + pw.print(" (multi, "); + } else { + pw.print(" (unique, "); + } pw.print(proc.mDurationsTableSize); pw.print(" entries)"); pw.println(":"); @@ -1801,6 +1821,9 @@ public final class ProcessStats implements Parcelable { ArrayList<ProcessState> procs = new ArrayList<ProcessState>(); for (int iproc=0; iproc<NPROCS; iproc++) { ProcessState proc = pkgState.mProcesses.valueAt(iproc); + if (!pkgMatch && !reqPackage.equals(proc.mName)) { + continue; + } if (activeOnly && !proc.isInUse()) { continue; } @@ -1811,6 +1834,9 @@ public final class ProcessStats implements Parcelable { } for (int isvc=0; isvc<NSRVS; isvc++) { ServiceState svc = pkgState.mServices.valueAt(isvc); + if (!pkgMatch && !reqPackage.equals(svc.mProcessName)) { + continue; + } if (activeOnly && !svc.isInUse()) { pw.print(" (Not active: "); pw.print(pkgState.mServices.keyAt(isvc)); pw.println(")"); @@ -1840,64 +1866,73 @@ public final class ProcessStats implements Parcelable { if (svc.mOwner != null) { pw.print(" mOwner="); pw.println(svc.mOwner); } + if (svc.mStarted || svc.mRestarting) { + pw.print(" mStarted="); pw.print(svc.mStarted); + pw.print(" mRestarting="); pw.println(svc.mRestarting); + } } } } } - if (reqPackage == null) { - ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap(); - printedHeader = false; - int numShownProcs = 0, numTotalProcs = 0; - for (int ip=0; ip<procMap.size(); ip++) { - String procName = procMap.keyAt(ip); - SparseArray<ProcessState> uids = procMap.valueAt(ip); - for (int iu=0; iu<uids.size(); iu++) { - int uid = uids.keyAt(iu); - numTotalProcs++; - ProcessState proc = uids.valueAt(iu); - if (proc.mDurationsTableSize == 0 && proc.mCurState == STATE_NOTHING - && proc.mPssTableSize == 0) { - continue; - } - numShownProcs++; - if (!printedHeader) { - pw.println(); - pw.println("Per-Process Stats:"); - printedHeader = true; - } - if (activeOnly && !proc.isInUse()) { - pw.print(" (Not active: "); pw.print(procName); pw.println(")"); - continue; - } - pw.print(" * "); pw.print(procName); pw.print(" / "); - UserHandle.formatUid(pw, uid); - pw.print(" ("); pw.print(proc.mDurationsTableSize); - pw.print(" entries)"); pw.println(":"); - dumpProcessState(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ, - ALL_PROC_STATES, now); - dumpProcessPss(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ, - ALL_PROC_STATES); - if (dumpAll) { - dumpProcessInternalLocked(pw, " ", proc, dumpAll); - } + ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap(); + printedHeader = false; + int numShownProcs = 0, numTotalProcs = 0; + for (int ip=0; ip<procMap.size(); ip++) { + String procName = procMap.keyAt(ip); + SparseArray<ProcessState> uids = procMap.valueAt(ip); + for (int iu=0; iu<uids.size(); iu++) { + int uid = uids.keyAt(iu); + numTotalProcs++; + ProcessState proc = uids.valueAt(iu); + if (proc.mDurationsTableSize == 0 && proc.mCurState == STATE_NOTHING + && proc.mPssTableSize == 0) { + continue; } + if (!proc.mMultiPackage) { + continue; + } + if (reqPackage != null && !reqPackage.equals(procName) + && !reqPackage.equals(proc.mPackage)) { + continue; + } + numShownProcs++; + if (sepNeeded) { + pw.println(); + } + sepNeeded = true; + if (!printedHeader) { + pw.println("Multi-Package Common Processes:"); + printedHeader = true; + } + if (activeOnly && !proc.isInUse()) { + pw.print(" (Not active: "); pw.print(procName); pw.println(")"); + continue; + } + pw.print(" * "); pw.print(procName); pw.print(" / "); + UserHandle.formatUid(pw, uid); + pw.print(" ("); pw.print(proc.mDurationsTableSize); + pw.print(" entries)"); pw.println(":"); + dumpProcessState(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ, + ALL_PROC_STATES, now); + dumpProcessPss(pw, " ", proc, ALL_SCREEN_ADJ, ALL_MEM_ADJ, + ALL_PROC_STATES); + dumpProcessInternalLocked(pw, " ", proc, dumpAll); } - if (dumpAll) { - pw.println(); - pw.print(" Total procs: "); pw.print(numShownProcs); - pw.print(" shown of "); pw.print(numTotalProcs); pw.println(" total"); - } + } + if (dumpAll) { + pw.println(); + pw.print(" Total procs: "); pw.print(numShownProcs); + pw.print(" shown of "); pw.print(numTotalProcs); pw.println(" total"); + } + if (sepNeeded) { pw.println(); - if (dumpSummary) { - pw.println("Summary:"); - dumpSummaryLocked(pw, reqPackage, now, activeOnly); - } else { - dumpTotalsLocked(pw, now); - } + } + if (dumpSummary) { + pw.println("Summary:"); + dumpSummaryLocked(pw, reqPackage, now, activeOnly); } else { - pw.println(); dumpTotalsLocked(pw, now); } @@ -2031,17 +2066,20 @@ public final class ProcessStats implements Parcelable { public ArrayList<ProcessState> collectProcessesLocked(int[] screenStates, int[] memStates, int[] procStates, int sortProcStates[], long now, String reqPackage, boolean activeOnly) { - ArraySet<ProcessState> foundProcs = new ArraySet<ProcessState>(); - ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap(); + final ArraySet<ProcessState> foundProcs = new ArraySet<ProcessState>(); + final ArrayMap<String, SparseArray<PackageState>> pkgMap = mPackages.getMap(); for (int ip=0; ip<pkgMap.size(); ip++) { - if (reqPackage != null && !reqPackage.equals(pkgMap.keyAt(ip))) { - continue; - } - SparseArray<PackageState> procs = pkgMap.valueAt(ip); + final String pkgName = pkgMap.keyAt(ip); + final SparseArray<PackageState> procs = pkgMap.valueAt(ip); for (int iu=0; iu<procs.size(); iu++) { - PackageState state = procs.valueAt(iu); - for (int iproc=0; iproc<state.mProcesses.size(); iproc++) { - ProcessState proc = state.mProcesses.valueAt(iproc); + final PackageState state = procs.valueAt(iu); + final int NPROCS = state.mProcesses.size(); + final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName); + for (int iproc=0; iproc<NPROCS; iproc++) { + final ProcessState proc = state.mProcesses.valueAt(iproc); + if (!pkgMatch && !reqPackage.equals(proc.mName)) { + continue; + } if (activeOnly && !proc.isInUse()) { continue; } @@ -2601,23 +2639,35 @@ public final class ProcessStats implements Parcelable { } } - void incStartedServices(int memFactor, long now) { + void incStartedServices(int memFactor, long now, String serviceName) { + if (false) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Slog.d(TAG, "incStartedServices: " + this + " service=" + serviceName + + " to " + (mNumStartedServices+1), here); + } if (mCommonProcess != this) { - mCommonProcess.incStartedServices(memFactor, now); + mCommonProcess.incStartedServices(memFactor, now, serviceName); } mNumStartedServices++; if (mNumStartedServices == 1 && mCurState == STATE_NOTHING) { - setState(STATE_NOTHING, memFactor, now, null); + setState(STATE_SERVICE_RESTARTING + (memFactor*STATE_COUNT), now); } } - void decStartedServices(int memFactor, long now) { + void decStartedServices(int memFactor, long now, String serviceName) { + if (false) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Slog.d(TAG, "decActiveServices: " + this + " service=" + serviceName + + " to " + (mNumStartedServices-1), here); + } if (mCommonProcess != this) { - mCommonProcess.decStartedServices(memFactor, now); + mCommonProcess.decStartedServices(memFactor, now, serviceName); } mNumStartedServices--; - if (mNumStartedServices == 0 && mCurState == STATE_SERVICE_RESTARTING) { - setState(STATE_NOTHING, memFactor, now, null); + if (mNumStartedServices == 0 && (mCurState%STATE_COUNT) == STATE_SERVICE_RESTARTING) { + setState(STATE_NOTHING, now); } else if (mNumStartedServices < 0) { Slog.wtfStack(TAG, "Proc started services underrun: pkg=" + mPackage + " uid=" + mUid + " name=" + mName); @@ -2873,6 +2923,8 @@ public final class ProcessStats implements Parcelable { public int mRunState = STATE_NOTHING; long mRunStartTime; + boolean mStarted; + boolean mRestarting; int mStartedCount; public int mStartedState = STATE_NOTHING; long mStartedStartTime; @@ -2902,10 +2954,9 @@ public final class ProcessStats implements Parcelable { // There was already an old owner, reset this object for its // new owner. mOwner = newOwner; - if (mStartedState != STATE_NOTHING || mBoundState != STATE_NOTHING - || mExecState != STATE_NOTHING) { + if (mStarted || mBoundState != STATE_NOTHING || mExecState != STATE_NOTHING) { long now = SystemClock.uptimeMillis(); - if (mStartedState != STATE_NOTHING) { + if (mStarted) { if (DEBUG) Slog.d(TAG, "Service has new owner " + newOwner + " from " + mOwner + " while started: pkg=" + mPackage + " service=" + mName + " proc=" + mProc); @@ -2931,10 +2982,9 @@ public final class ProcessStats implements Parcelable { public void clearCurrentOwner(Object owner, boolean silently) { if (mOwner == owner) { mProc.decActiveServices(mName); - if (mStartedState != STATE_NOTHING || mBoundState != STATE_NOTHING - || mExecState != STATE_NOTHING) { + if (mStarted || mBoundState != STATE_NOTHING || mExecState != STATE_NOTHING) { long now = SystemClock.uptimeMillis(); - if (mStartedState != STATE_NOTHING) { + if (mStarted) { if (!silently) { Slog.wtfStack(TAG, "Service owner " + owner + " cleared while started: pkg=" + mPackage + " service=" @@ -3042,7 +3092,18 @@ public final class ProcessStats implements Parcelable { if (mOwner == null) { Slog.wtf(TAG, "Starting service " + this + " without owner"); } + mStarted = started; + updateStartedState(memFactor, now); + } + + public void setRestarting(boolean restarting, int memFactor, long now) { + mRestarting = restarting; + updateStartedState(memFactor, now); + } + + void updateStartedState(int memFactor, long now) { final boolean wasStarted = mStartedState != STATE_NOTHING; + final boolean started = mStarted || mRestarting; final int state = started ? memFactor : STATE_NOTHING; if (mStartedState != state) { if (mStartedState != STATE_NOTHING) { @@ -3056,9 +3117,9 @@ public final class ProcessStats implements Parcelable { mProc = mProc.pullFixedProc(mPackage); if (wasStarted != started) { if (started) { - mProc.incStartedServices(memFactor, now); + mProc.incStartedServices(memFactor, now, mName); } else { - mProc.decStartedServices(memFactor, now); + mProc.decStartedServices(memFactor, now, mName); } } updateRunning(memFactor, now); diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl index 5bfa1b2..1e37fd9 100644 --- a/core/java/com/android/internal/backup/IBackupTransport.aidl +++ b/core/java/com/android/internal/backup/IBackupTransport.aidl @@ -23,6 +23,12 @@ import android.os.ParcelFileDescriptor; /** {@hide} */ interface IBackupTransport { + /** + * Ask the transport for the name under which it should be registered. This will + * typically be its host service's component name, but need not be. + */ + String name(); + /** * Ask the transport for an Intent that can be used to launch any internal * configuration Activity that it wishes to present. For example, the transport diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java index eb2d1fe..494bc78 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/core/java/com/android/internal/backup/LocalTransport.java @@ -19,6 +19,7 @@ package com.android.internal.backup; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.app.backup.RestoreSet; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; @@ -71,6 +72,10 @@ public class LocalTransport extends IBackupTransport.Stub { } } + public String name() { + return new ComponentName(mContext, this.getClass()).flattenToShortString(); + } + public Intent configurationIntent() { // The local transport is not user-configurable return null; diff --git a/core/java/com/android/internal/backup/LocalTransportService.java b/core/java/com/android/internal/backup/LocalTransportService.java new file mode 100644 index 0000000..d05699a --- /dev/null +++ b/core/java/com/android/internal/backup/LocalTransportService.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013 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.internal.backup; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +public class LocalTransportService extends Service { + private static LocalTransport sTransport = null; + + @Override + public void onCreate() { + if (sTransport == null) { + sTransport = new LocalTransport(this); + } + } + + @Override + public IBinder onBind(Intent intent) { + return sTransport; + } +} diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index e0a154c..0a702ff 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -84,7 +84,7 @@ public final class BatteryStatsImpl extends BatteryStats { private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 66 + (USE_OLD_HISTORY ? 1000 : 0); + private static final int VERSION = 67 + (USE_OLD_HISTORY ? 1000 : 0); // Maximum number of items we will record in the history. private static final int MAX_HISTORY_ITEMS = 2000; @@ -154,6 +154,8 @@ public final class BatteryStatsImpl extends BatteryStats { final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<StopwatchTimer>(); final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<StopwatchTimer>(); final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<StopwatchTimer>(); + final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers = + new SparseArray<ArrayList<StopwatchTimer>>(); // Last partial timers we use for distributing CPU usage. final ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<StopwatchTimer>(); @@ -2172,6 +2174,9 @@ public final class BatteryStatsImpl extends BatteryStats { case TelephonyManager.NETWORK_TYPE_EHRPD: bin = DATA_CONNECTION_EHRPD; break; + case TelephonyManager.NETWORK_TYPE_HSPAP: + bin = DATA_CONNECTION_HSPAP; + break; default: bin = DATA_CONNECTION_OTHER; break; @@ -2401,6 +2406,14 @@ public final class BatteryStatsImpl extends BatteryStats { getUidStatsLocked(uid).noteWifiScanStoppedLocked(); } + public void noteWifiBatchedScanStartedLocked(int uid, int csph) { + getUidStatsLocked(uid).noteWifiBatchedScanStartedLocked(csph); + } + + public void noteWifiBatchedScanStoppedLocked(int uid) { + getUidStatsLocked(uid).noteWifiBatchedScanStoppedLocked(); + } + int mWifiMulticastNesting = 0; public void noteWifiMulticastEnabledLocked(int uid) { @@ -2453,6 +2466,20 @@ public final class BatteryStatsImpl extends BatteryStats { } } + public void noteWifiBatchedScanStartedFromSourceLocked(WorkSource ws, int csph) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteWifiBatchedScanStartedLocked(ws.get(i), csph); + } + } + + public void noteWifiBatchedScanStoppedFromSourceLocked(WorkSource ws) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteWifiBatchedScanStoppedLocked(ws.get(i)); + } + } + public void noteWifiMulticastEnabledFromSourceLocked(WorkSource ws) { int N = ws.size(); for (int i=0; i<N; i++) { @@ -2576,6 +2603,10 @@ public final class BatteryStatsImpl extends BatteryStats { boolean mWifiScanStarted; StopwatchTimer mWifiScanTimer; + private static final int NO_BATCHED_SCAN_STARTED = -1; + int mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED; + StopwatchTimer[] mWifiBatchedScanTimer; + boolean mWifiMulticastEnabled; StopwatchTimer mWifiMulticastTimer; @@ -2626,6 +2657,7 @@ public final class BatteryStatsImpl extends BatteryStats { mFullWifiLockTimers, mUnpluggables); mWifiScanTimer = new StopwatchTimer(Uid.this, WIFI_SCAN, mWifiScanTimers, mUnpluggables); + mWifiBatchedScanTimer = new StopwatchTimer[NUM_WIFI_BATCHED_SCAN_BINS]; mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED, mWifiMulticastTimers, mUnpluggables); } @@ -2716,6 +2748,36 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override + public void noteWifiBatchedScanStartedLocked(int csph) { + int bin = 0; + while (csph > 8 && bin < NUM_WIFI_BATCHED_SCAN_BINS) { + csph = csph >> 3; + bin++; + } + + if (mWifiBatchedScanBinStarted == bin) return; + + if (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED) { + mWifiBatchedScanTimer[mWifiBatchedScanBinStarted]. + stopRunningLocked(BatteryStatsImpl.this); + } + mWifiBatchedScanBinStarted = bin; + if (mWifiBatchedScanTimer[bin] == null) { + makeWifiBatchedScanBin(bin, null); + } + mWifiBatchedScanTimer[bin].startRunningLocked(BatteryStatsImpl.this); + } + + @Override + public void noteWifiBatchedScanStoppedLocked() { + if (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED) { + mWifiBatchedScanTimer[mWifiBatchedScanBinStarted]. + stopRunningLocked(BatteryStatsImpl.this); + mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED; + } + } + + @Override public void noteWifiMulticastEnabledLocked() { if (!mWifiMulticastEnabled) { mWifiMulticastEnabled = true; @@ -2851,6 +2913,15 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override + public long getWifiBatchedScanTime(int csphBin, long batteryRealtime, int which) { + if (csphBin < 0 || csphBin >= NUM_WIFI_BATCHED_SCAN_BINS) return 0; + if (mWifiBatchedScanTimer[csphBin] == null) { + return 0; + } + return mWifiBatchedScanTimer[csphBin].getTotalTimeLocked(batteryRealtime, which); + } + + @Override public long getWifiMulticastTime(long batteryRealtime, int which) { if (mWifiMulticastTimer == null) { return 0; @@ -2911,6 +2982,24 @@ public final class BatteryStatsImpl extends BatteryStats { return mUserActivityCounters[type].getCountLocked(which); } + void makeWifiBatchedScanBin(int i, Parcel in) { + if (i < 0 || i >= NUM_WIFI_BATCHED_SCAN_BINS) return; + + ArrayList<StopwatchTimer> collected = mWifiBatchedScanTimers.get(i); + if (collected == null) { + collected = new ArrayList<StopwatchTimer>(); + mWifiBatchedScanTimers.put(i, collected); + } + if (in == null) { + mWifiBatchedScanTimer[i] = new StopwatchTimer(this, WIFI_BATCHED_SCAN, collected, + mUnpluggables); + } else { + mWifiBatchedScanTimer[i] = new StopwatchTimer(this, WIFI_BATCHED_SCAN, collected, + mUnpluggables, in); + } + } + + void initUserActivityLocked() { mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES]; for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) { @@ -2971,6 +3060,14 @@ public final class BatteryStatsImpl extends BatteryStats { active |= !mWifiScanTimer.reset(BatteryStatsImpl.this, false); active |= mWifiScanStarted; } + if (mWifiBatchedScanTimer != null) { + for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) { + if (mWifiBatchedScanTimer[i] != null) { + active |= !mWifiBatchedScanTimer[i].reset(BatteryStatsImpl.this, false); + } + } + active |= (mWifiBatchedScanBinStarted != NO_BATCHED_SCAN_STARTED); + } if (mWifiMulticastTimer != null) { active |= !mWifiMulticastTimer.reset(BatteryStatsImpl.this, false); active |= mWifiMulticastEnabled; @@ -3077,6 +3174,11 @@ public final class BatteryStatsImpl extends BatteryStats { if (mWifiScanTimer != null) { mWifiScanTimer.detach(); } + for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) { + if (mWifiBatchedScanTimer[i] != null) { + mWifiBatchedScanTimer[i].detach(); + } + } if (mWifiMulticastTimer != null) { mWifiMulticastTimer.detach(); } @@ -3154,6 +3256,14 @@ public final class BatteryStatsImpl extends BatteryStats { } else { out.writeInt(0); } + for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) { + if (mWifiBatchedScanTimer[i] != null) { + out.writeInt(1); + mWifiBatchedScanTimer[i].writeToParcel(out, batteryRealtime); + } else { + out.writeInt(0); + } + } if (mWifiMulticastTimer != null) { out.writeInt(1); mWifiMulticastTimer.writeToParcel(out, batteryRealtime); @@ -3263,6 +3373,14 @@ public final class BatteryStatsImpl extends BatteryStats { } else { mWifiScanTimer = null; } + mWifiBatchedScanBinStarted = NO_BATCHED_SCAN_STARTED; + for (int i = 0; i < NUM_WIFI_BATCHED_SCAN_BINS; i++) { + if (in.readInt() != 0) { + makeWifiBatchedScanBin(i, in); + } else { + mWifiBatchedScanTimer[i] = null; + } + } mWifiMulticastEnabled = false; if (in.readInt() != 0) { mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED, @@ -5460,6 +5578,13 @@ public final class BatteryStatsImpl extends BatteryStats { if (in.readInt() != 0) { u.mWifiScanTimer.readSummaryFromParcelLocked(in); } + u.mWifiBatchedScanBinStarted = Uid.NO_BATCHED_SCAN_STARTED; + for (int i = 0; i < Uid.NUM_WIFI_BATCHED_SCAN_BINS; i++) { + if (in.readInt() != 0) { + u.makeWifiBatchedScanBin(i, null); + u.mWifiBatchedScanTimer[i].readSummaryFromParcelLocked(in); + } + } u.mWifiMulticastEnabled = false; if (in.readInt() != 0) { u.mWifiMulticastTimer.readSummaryFromParcelLocked(in); @@ -5671,6 +5796,14 @@ public final class BatteryStatsImpl extends BatteryStats { } else { out.writeInt(0); } + for (int i = 0; i < Uid.NUM_WIFI_BATCHED_SCAN_BINS; i++) { + if (u.mWifiBatchedScanTimer[i] != null) { + out.writeInt(1); + u.mWifiBatchedScanTimer[i].writeSummaryFromParcelLocked(out, NOWREAL); + } else { + out.writeInt(0); + } + } if (u.mWifiMulticastTimer != null) { out.writeInt(1); u.mWifiMulticastTimer.writeSummaryFromParcelLocked(out, NOWREAL); @@ -5906,6 +6039,7 @@ public final class BatteryStatsImpl extends BatteryStats { mWifiRunningTimers.clear(); mFullWifiLockTimers.clear(); mWifiScanTimers.clear(); + mWifiBatchedScanTimers.clear(); mWifiMulticastTimers.clear(); sNumSpeedSteps = in.readInt(); diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index 99a6843..94750d3 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -136,6 +136,13 @@ public class PowerProfile { public static final String POWER_CPU_SPEEDS = "cpu.speeds"; /** + * Power consumed by wif batched scaning. Broken down into bins by + * Channels Scanned per Hour. May do 1-720 scans per hour of 1-100 channels + * for a range of 1-72,000. Going logrithmic (1-8, 9-64, 65-512, 513-4096, 4097-)! + */ + public static final String POWER_WIFI_BATCHED_SCAN = "wifi.batchedscan"; + + /** * Battery capacity in milliAmpHour (mAh). */ public static final String POWER_BATTERY_CAPACITY = "battery.capacity"; @@ -171,7 +178,7 @@ public class PowerProfile { String element = parser.getName(); if (element == null) break; - + if (parsingArray && !element.equals(TAG_ARRAYITEM)) { // Finish array sPowerMap.put(arrayName, array.toArray(new Double[array.size()])); diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 73d34c3..c44afae 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -301,6 +301,8 @@ public class ZygoteInit { count++; } catch (ClassNotFoundException e) { Log.w(TAG, "Class not found for preloading: " + line); + } catch (UnsatisfiedLinkError e) { + Log.w(TAG, "Problem preloading " + line + ": " + e); } catch (Throwable t) { Log.e(TAG, "Error preloading " + line + ".", t); if (t instanceof Error) { diff --git a/core/java/com/android/internal/view/CheckableLinearLayout.java b/core/java/com/android/internal/view/CheckableLinearLayout.java deleted file mode 100644 index 3fb7cec..0000000 --- a/core/java/com/android/internal/view/CheckableLinearLayout.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.internal.view; - -import com.android.internal.R; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.Checkable; -import android.widget.CheckBox; -import android.widget.LinearLayout; - -public class CheckableLinearLayout extends LinearLayout implements Checkable { - private CheckBox mCheckBox; - - public CheckableLinearLayout(Context context) { - super(context); - // TODO Auto-generated constructor stub - } - - public CheckableLinearLayout(Context context, AttributeSet attrs) { - super(context, attrs); - // TODO Auto-generated constructor stub - } - - public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - // TODO Auto-generated constructor stub - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mCheckBox = (CheckBox) findViewById(R.id.check); - } - - @Override - public void setChecked(boolean checked) { - mCheckBox.setChecked(checked); - } - - @Override - public boolean isChecked() { - return mCheckBox.isChecked(); - } - - @Override - public void toggle() { - mCheckBox.toggle(); - } -} diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java index 44e7ec1..4654178 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java @@ -721,7 +721,8 @@ public class ActionMenuPresenter extends BaseMenuPresenter if (subMenu == null) return false; mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId(); - return false; + final MenuPresenter.Callback cb = getCallback(); + return cb != null ? cb.onOpenSubMenu(subMenu) : false; } @Override @@ -729,6 +730,10 @@ public class ActionMenuPresenter extends BaseMenuPresenter if (menu instanceof SubMenuBuilder) { ((SubMenuBuilder) menu).getRootMenu().close(false); } + final MenuPresenter.Callback cb = getCallback(); + if (cb != null) { + cb.onCloseMenu(menu, allMenusAreClosing); + } } } diff --git a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java index db0d6dd..92e9ea6 100644 --- a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java @@ -144,6 +144,10 @@ public abstract class BaseMenuPresenter implements MenuPresenter { mCallback = cb; } + public Callback getCallback() { + return mCallback; + } + /** * Create a new item view that can be re-bound to other item data later. * diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index b5d74e8..786f5cf 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -526,7 +526,7 @@ public class ActionBarView extends AbsActionBarView { if (mLogoNavItem != null) { mLogoNavItem.setTitle(title); } - mUpGoerFive.setContentDescription(buildHomeContentDescription()); + updateHomeAccessibility(mUpGoerFive.isEnabled()); } public CharSequence getSubtitle() { @@ -544,7 +544,7 @@ public class ActionBarView extends AbsActionBarView { (!TextUtils.isEmpty(mTitle) || !TextUtils.isEmpty(mSubtitle)); mTitleLayout.setVisibility(visible ? VISIBLE : GONE); } - mUpGoerFive.setContentDescription(buildHomeContentDescription()); + updateHomeAccessibility(mUpGoerFive.isEnabled()); } public void setHomeButtonEnabled(boolean enable) { @@ -566,7 +566,11 @@ public class ActionBarView extends AbsActionBarView { mUpGoerFive.setEnabled(enable); mUpGoerFive.setFocusable(enable); // Make sure the home button has an accurate content description for accessibility. - if (!enable) { + updateHomeAccessibility(enable); + } + + private void updateHomeAccessibility(boolean homeEnabled) { + if (!homeEnabled) { mUpGoerFive.setContentDescription(null); mUpGoerFive.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); } else { @@ -677,19 +681,7 @@ public class ActionBarView extends AbsActionBarView { } // Make sure the home button has an accurate content description for accessibility. - if (!mHomeLayout.isEnabled()) { - mHomeLayout.setContentDescription(null); - mHomeLayout.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - } else { - mHomeLayout.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_AUTO); - if ((options & ActionBar.DISPLAY_HOME_AS_UP) != 0) { - mHomeLayout.setContentDescription(mContext.getResources().getText( - R.string.action_bar_up_description)); - } else { - mHomeLayout.setContentDescription(mContext.getResources().getText( - R.string.action_bar_home_description)); - } - } + updateHomeAccessibility(mUpGoerFive.isEnabled()); } public void setIcon(Drawable icon) { @@ -1340,11 +1332,13 @@ public class ActionBarView extends AbsActionBarView { public void setHomeActionContentDescription(CharSequence description) { mHomeDescription = description; + updateHomeAccessibility(mUpGoerFive.isEnabled()); } public void setHomeActionContentDescription(int resId) { mHomeDescriptionRes = resId; mHomeDescription = resId != 0 ? getResources().getText(resId) : null; + updateHomeAccessibility(mUpGoerFive.isEnabled()); } static class SavedState extends BaseSavedState { diff --git a/core/jni/android/graphics/Typeface.cpp b/core/jni/android/graphics/Typeface.cpp index a7a0bb2..ccd75d5 100644 --- a/core/jni/android/graphics/Typeface.cpp +++ b/core/jni/android/graphics/Typeface.cpp @@ -34,6 +34,13 @@ static SkTypeface* Typeface_create(JNIEnv* env, jobject, jstring name, if (NULL != name) { AutoJavaStringToUTF8 str(env, name); face = SkTypeface::CreateFromName(str.c_str(), style); + // Try to find the closest matching font, using the standard heuristic + if (NULL == face) { + face = SkTypeface::CreateFromName(str.c_str(), (SkTypeface::Style)(style ^ SkTypeface::kItalic)); + } + for (int i = 0; NULL == face && i < 4; i++) { + face = SkTypeface::CreateFromName(str.c_str(), (SkTypeface::Style)i); + } } // return the default font at the best style if no exact match exists @@ -45,8 +52,13 @@ static SkTypeface* Typeface_create(JNIEnv* env, jobject, jstring name, static SkTypeface* Typeface_createFromTypeface(JNIEnv* env, jobject, SkTypeface* family, int style) { SkTypeface* face = SkTypeface::CreateFromTypeface(family, (SkTypeface::Style)style); - // return the default font at the best style if the requested style does not - // exist in the provided family + // Try to find the closest matching font, using the standard heuristic + if (NULL == face) { + face = SkTypeface::CreateFromTypeface(family, (SkTypeface::Style)(style ^ SkTypeface::kItalic)); + } + for (int i = 0; NULL == face && i < 4; i++) { + face = SkTypeface::CreateFromTypeface(family, (SkTypeface::Style)i); + } if (NULL == face) { face = SkTypeface::CreateFromName(NULL, (SkTypeface::Style)style); } diff --git a/core/jni/android/graphics/pdf/PdfDocument.cpp b/core/jni/android/graphics/pdf/PdfDocument.cpp index b57a0fe..6175a8f 100644 --- a/core/jni/android/graphics/pdf/PdfDocument.cpp +++ b/core/jni/android/graphics/pdf/PdfDocument.cpp @@ -17,62 +17,138 @@ #include "jni.h" #include "GraphicsJNI.h" #include <android_runtime/AndroidRuntime.h> +#include <vector> + +#include "CreateJavaOutputStreamAdaptor.h" #include "SkCanvas.h" -#include "SkPDFDevice.h" -#include "SkPDFDocument.h" +#include "SkDocument.h" +#include "SkPicture.h" +#include "SkStream.h" #include "SkRect.h" -#include "SkSize.h" -#include "CreateJavaOutputStreamAdaptor.h" -#include "JNIHelp.h" namespace android { -#define LOGD(x...) do { Log::Instance()->printf(Log::ELogD, x); } while(0) +struct PageRecord { -static jint nativeCreateDocument(JNIEnv* env, jobject clazz) { - return reinterpret_cast<jint>(new SkPDFDocument()); -} + PageRecord(int width, int height, const SkRect& contentRect) + : mPicture(new SkPicture()), mWidth(width), mHeight(height) { + mContentRect = contentRect; + } -static void nativeFinalize(JNIEnv* env, jobject thiz, jint documentPtr) { - delete reinterpret_cast<SkPDFDocument*>(documentPtr); -} + ~PageRecord() { + mPicture->unref(); + } -static jint nativeCreatePage(JNIEnv* env, jobject thiz, jint pageWidth, jint pageHeight, - jint contentLeft, jint contentTop, jint contentRight, jint contentBottom) { + SkPicture* const mPicture; + const int mWidth; + const int mHeight; + SkRect mContentRect; +}; + +class PdfDocument { +public: + PdfDocument() { + mCurrentPage = NULL; + } + + SkCanvas* startPage(int width, int height, + int contentLeft, int contentTop, int contentRight, int contentBottom) { + assert(mCurrentPage == NULL); + + SkRect contentRect = SkRect::MakeLTRB( + contentLeft, contentTop, contentRight, contentBottom); + PageRecord* page = new PageRecord(width, height, contentRect); + mPages.push_back(page); + mCurrentPage = page; + + SkCanvas* canvas = page->mPicture->beginRecording( + contentRect.width(), contentRect.height(), 0); + + // We pass this canvas to Java where it is used to construct + // a Java Canvas object which dereferences the pointer when it + // is destroyed, so we have to bump up the reference count. + canvas->ref(); + + return canvas; + } - SkMatrix transformation; - transformation.setTranslate(contentLeft, contentTop); + void finishPage() { + assert(mCurrentPage != NULL); + mCurrentPage->mPicture->endRecording(); + mCurrentPage = NULL; + } - SkISize skPageSize = SkISize::Make(pageWidth, pageHeight); - SkISize skContentSize = SkISize::Make(contentRight - contentLeft, contentBottom - contentTop); + void write(SkWStream* stream) { + SkDocument* document = SkDocument::CreatePDF(stream); + for (unsigned i = 0; i < mPages.size(); i++) { + PageRecord* page = mPages[i]; - SkPDFDevice* skPdfDevice = new SkPDFDevice(skPageSize, skContentSize, transformation); - return reinterpret_cast<jint>(new SkCanvas(skPdfDevice)); + SkCanvas* canvas = document->beginPage(page->mWidth, page->mHeight, + &(page->mContentRect)); + + canvas->clipRect(page->mContentRect); + canvas->translate(page->mContentRect.left(), page->mContentRect.top()); + canvas->drawPicture(*page->mPicture); + + document->endPage(); + } + document->close(); + } + + void close() { + for (unsigned i = 0; i < mPages.size(); i++) { + delete mPages[i]; + } + delete mCurrentPage; + mCurrentPage = NULL; + } + +private: + ~PdfDocument() { + close(); + } + + std::vector<PageRecord*> mPages; + PageRecord* mCurrentPage; +}; + +static jint nativeCreateDocument(JNIEnv* env, jobject thiz) { + return reinterpret_cast<jint>(new PdfDocument()); +} + +static jint nativeStartPage(JNIEnv* env, jobject thiz, jint documentPtr, + jint pageWidth, jint pageHeight, + jint contentLeft, jint contentTop, jint contentRight, jint contentBottom) { + PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr); + return reinterpret_cast<jint>(document->startPage(pageWidth, pageHeight, + contentLeft, contentTop, contentRight, contentBottom)); } -static void nativeAppendPage(JNIEnv* env, jobject thiz, jint documentPtr, jint pagePtr) { - SkCanvas* page = reinterpret_cast<SkCanvas*>(pagePtr); - SkPDFDocument* document = reinterpret_cast<SkPDFDocument*>(documentPtr); - SkPDFDevice* device = static_cast<SkPDFDevice*>(page->getDevice()); - document->appendPage(device); +static void nativeFinishPage(JNIEnv* env, jobject thiz, jint documentPtr) { + PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr); + document->finishPage(); } -static void nativeWriteTo(JNIEnv* env, jobject clazz, jint documentPtr, - jobject out, jbyteArray chunk) { +static void nativeWriteTo(JNIEnv* env, jobject thiz, jint documentPtr, jobject out, + jbyteArray chunk) { + PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr); SkWStream* skWStream = CreateJavaOutputStreamAdaptor(env, out, chunk); - SkPDFDocument* document = reinterpret_cast<SkPDFDocument*>(documentPtr); - document->emitPDF(skWStream); + document->write(skWStream); delete skWStream; } +static void nativeClose(JNIEnv* env, jobject thiz, jint documentPtr) { + PdfDocument* document = reinterpret_cast<PdfDocument*>(documentPtr); + document->close(); +} + static JNINativeMethod gPdfDocument_Methods[] = { {"nativeCreateDocument", "()I", (void*) nativeCreateDocument}, - {"nativeFinalize", "(I)V", (void*) nativeFinalize}, - {"nativeCreatePage", "(IIIIII)I", - (void*) nativeCreatePage}, - {"nativeAppendPage", "(II)V", (void*) nativeAppendPage}, - {"nativeWriteTo", "(ILjava/io/OutputStream;[B)V", (void*) nativeWriteTo} + {"nativeStartPage", "(IIIIIII)I", (void*) nativeStartPage}, + {"nativeFinishPage", "(I)V", (void*) nativeFinishPage}, + {"nativeWriteTo", "(ILjava/io/OutputStream;[B)V", (void*) nativeWriteTo}, + {"nativeClose", "(I)V", (void*) nativeClose} }; int register_android_graphics_pdf_PdfDocument(JNIEnv* env) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 6ddd3fe..b198937 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1992,6 +1992,14 @@ android:description="@string/permdesc_bindWallpaper" android:protectionLevel="signature|system" /> + <!-- Must be required by a {@link com.android.media.remotedisplay.RemoteDisplayProvider}, + to ensure that only the system can bind to it. + @hide --> + <permission android:name="android.permission.BIND_REMOTE_DISPLAY" + android:label="@string/permlab_bindRemoteDisplay" + android:description="@string/permdesc_bindRemoteDisplay" + android:protectionLevel="signature" /> + <!-- Must be required by device administration receiver, to ensure that only the system can interact with it. --> <permission android:name="android.permission.BIND_DEVICE_ADMIN" @@ -2681,6 +2689,15 @@ <service android:name="android.hardware.location.GeofenceHardwareService" android:permission="android.permission.LOCATION_HARDWARE" android:exported="false" /> + + <service android:name="com.android.internal.backup.LocalTransportService" + android:permission="android.permission.CONFIRM_FULL_BACKUP" + android:exported="false"> + <intent-filter> + <action android:name="android.backup.TRANSPORT_HOST" /> + </intent-filter> + </service> + </application> </manifest> diff --git a/core/res/res/drawable-hdpi/ic_media_group_collapse.png b/core/res/res/drawable-hdpi/ic_media_group_collapse.png Binary files differdeleted file mode 100644 index 89abf2c..0000000 --- a/core/res/res/drawable-hdpi/ic_media_group_collapse.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/ic_media_group_expand.png b/core/res/res/drawable-hdpi/ic_media_group_expand.png Binary files differdeleted file mode 100644 index d9470b2..0000000 --- a/core/res/res/drawable-hdpi/ic_media_group_expand.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_dark.png Binary files differindex b47d666..458a2a6 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_dark.png +++ b/core/res/res/drawable-hdpi/ic_media_route_disabled_holo_dark.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_off_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_off_holo_dark.png Binary files differindex 13d803c..c91faa9 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_off_holo_dark.png +++ b/core/res/res/drawable-hdpi/ic_media_route_off_holo_dark.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_off_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_off_holo_light.png Binary files differindex 3ae436b..14c9183 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_off_holo_light.png +++ b/core/res/res/drawable-hdpi/ic_media_route_off_holo_light.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_dark.png Binary files differindex 24824fc..b388d86 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_dark.png +++ b/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_dark.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_light.png Binary files differindex af3819b..76c1323 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_light.png +++ b/core/res/res/drawable-hdpi/ic_media_route_on_0_holo_light.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_dark.png Binary files differindex 83dc251..fd39f9d 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_dark.png +++ b/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_dark.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_light.png Binary files differindex 8d9d592..c74727a 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_light.png +++ b/core/res/res/drawable-hdpi/ic_media_route_on_1_holo_light.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_dark.png Binary files differindex 1310ec9..826c9ae 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_dark.png +++ b/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_dark.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_light.png Binary files differindex 1705074..d0baec3 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_light.png +++ b/core/res/res/drawable-hdpi/ic_media_route_on_2_holo_light.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_holo_dark.png b/core/res/res/drawable-hdpi/ic_media_route_on_holo_dark.png Binary files differindex 7027b88..c60ff59 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_on_holo_dark.png +++ b/core/res/res/drawable-hdpi/ic_media_route_on_holo_dark.png diff --git a/core/res/res/drawable-hdpi/ic_media_route_on_holo_light.png b/core/res/res/drawable-hdpi/ic_media_route_on_holo_light.png Binary files differindex 7027b88..75552cc 100644 --- a/core/res/res/drawable-hdpi/ic_media_route_on_holo_light.png +++ b/core/res/res/drawable-hdpi/ic_media_route_on_holo_light.png diff --git a/core/res/res/drawable-hdpi/ic_notification_cast_0.png b/core/res/res/drawable-hdpi/ic_notification_cast_0.png Binary files differnew file mode 100644 index 0000000..a35f281 --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_notification_cast_0.png diff --git a/core/res/res/drawable-hdpi/ic_notification_cast_1.png b/core/res/res/drawable-hdpi/ic_notification_cast_1.png Binary files differnew file mode 100644 index 0000000..9f6e2ad --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_notification_cast_1.png diff --git a/core/res/res/drawable-hdpi/ic_notification_cast_2.png b/core/res/res/drawable-hdpi/ic_notification_cast_2.png Binary files differnew file mode 100644 index 0000000..737137a --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_notification_cast_2.png diff --git a/core/res/res/drawable-hdpi/ic_notification_cast_on.png b/core/res/res/drawable-hdpi/ic_notification_cast_on.png Binary files differnew file mode 100644 index 0000000..ff2753a --- /dev/null +++ b/core/res/res/drawable-hdpi/ic_notification_cast_on.png diff --git a/core/res/res/drawable-hdpi/ic_notify_wifidisplay.png b/core/res/res/drawable-hdpi/ic_notify_wifidisplay.png Binary files differdeleted file mode 100644 index 35f27df..0000000 --- a/core/res/res/drawable-hdpi/ic_notify_wifidisplay.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/toast_frame.9.png b/core/res/res/drawable-hdpi/toast_frame.9.png Binary files differindex ca65994..a804a8a 100644 --- a/core/res/res/drawable-hdpi/toast_frame.9.png +++ b/core/res/res/drawable-hdpi/toast_frame.9.png diff --git a/core/res/res/drawable-hdpi/toast_frame_holo.9.png b/core/res/res/drawable-hdpi/toast_frame_holo.9.png Binary files differdeleted file mode 100644 index a804a8a..0000000 --- a/core/res/res/drawable-hdpi/toast_frame_holo.9.png +++ /dev/null diff --git a/core/res/res/drawable-ldpi/toast_frame.9.png b/core/res/res/drawable-ldpi/toast_frame.9.png Binary files differindex 3b344ff..e64dc75 100644 --- a/core/res/res/drawable-ldpi/toast_frame.9.png +++ b/core/res/res/drawable-ldpi/toast_frame.9.png diff --git a/core/res/res/drawable-mdpi/ic_media_group_collapse.png b/core/res/res/drawable-mdpi/ic_media_group_collapse.png Binary files differdeleted file mode 100644 index 34454ac..0000000 --- a/core/res/res/drawable-mdpi/ic_media_group_collapse.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/ic_media_group_expand.png b/core/res/res/drawable-mdpi/ic_media_group_expand.png Binary files differdeleted file mode 100644 index 8ce5a44..0000000 --- a/core/res/res/drawable-mdpi/ic_media_group_expand.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/ic_media_route_off_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_off_holo_dark.png Binary files differindex 6764598..9d92648 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_off_holo_dark.png +++ b/core/res/res/drawable-mdpi/ic_media_route_off_holo_dark.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_off_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_off_holo_light.png Binary files differindex 94e0bb6..3e27fc8 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_off_holo_light.png +++ b/core/res/res/drawable-mdpi/ic_media_route_off_holo_light.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_dark.png Binary files differindex 5ce2f20..72b9e78 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_dark.png +++ b/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_dark.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_light.png Binary files differindex 5105e90..bd462a2 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_light.png +++ b/core/res/res/drawable-mdpi/ic_media_route_on_0_holo_light.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_dark.png Binary files differindex 68c06ed..0a2cc89 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_dark.png +++ b/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_dark.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_light.png Binary files differindex 6e9b144..d162503 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_light.png +++ b/core/res/res/drawable-mdpi/ic_media_route_on_1_holo_light.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_dark.png Binary files differindex 45dc56f3d..997e32b 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_dark.png +++ b/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_dark.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_light.png Binary files differindex 46e743a..d314967 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_light.png +++ b/core/res/res/drawable-mdpi/ic_media_route_on_2_holo_light.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_holo_dark.png b/core/res/res/drawable-mdpi/ic_media_route_on_holo_dark.png Binary files differindex e384691..f15d7a9 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_on_holo_dark.png +++ b/core/res/res/drawable-mdpi/ic_media_route_on_holo_dark.png diff --git a/core/res/res/drawable-mdpi/ic_media_route_on_holo_light.png b/core/res/res/drawable-mdpi/ic_media_route_on_holo_light.png Binary files differindex e384691..26d46f8 100644 --- a/core/res/res/drawable-mdpi/ic_media_route_on_holo_light.png +++ b/core/res/res/drawable-mdpi/ic_media_route_on_holo_light.png diff --git a/core/res/res/drawable-mdpi/ic_notification_cast_0.png b/core/res/res/drawable-mdpi/ic_notification_cast_0.png Binary files differnew file mode 100644 index 0000000..d9cedbd --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_notification_cast_0.png diff --git a/core/res/res/drawable-mdpi/ic_notification_cast_1.png b/core/res/res/drawable-mdpi/ic_notification_cast_1.png Binary files differnew file mode 100644 index 0000000..414c67f --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_notification_cast_1.png diff --git a/core/res/res/drawable-mdpi/ic_notification_cast_2.png b/core/res/res/drawable-mdpi/ic_notification_cast_2.png Binary files differnew file mode 100644 index 0000000..280a888 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_notification_cast_2.png diff --git a/core/res/res/drawable-mdpi/ic_notification_cast_on.png b/core/res/res/drawable-mdpi/ic_notification_cast_on.png Binary files differnew file mode 100644 index 0000000..ab5f1d7 --- /dev/null +++ b/core/res/res/drawable-mdpi/ic_notification_cast_on.png diff --git a/core/res/res/drawable-mdpi/ic_notify_wifidisplay.png b/core/res/res/drawable-mdpi/ic_notify_wifidisplay.png Binary files differdeleted file mode 100644 index f9c8678..0000000 --- a/core/res/res/drawable-mdpi/ic_notify_wifidisplay.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/toast_frame.9.png b/core/res/res/drawable-mdpi/toast_frame.9.png Binary files differindex 9e93fe7..778e4e6 100644 --- a/core/res/res/drawable-mdpi/toast_frame.9.png +++ b/core/res/res/drawable-mdpi/toast_frame.9.png diff --git a/core/res/res/drawable-mdpi/toast_frame_holo.9.png b/core/res/res/drawable-mdpi/toast_frame_holo.9.png Binary files differdeleted file mode 100644 index 778e4e6..0000000 --- a/core/res/res/drawable-mdpi/toast_frame_holo.9.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/ic_media_group_collapse.png b/core/res/res/drawable-xhdpi/ic_media_group_collapse.png Binary files differdeleted file mode 100644 index 2fb7428..0000000 --- a/core/res/res/drawable-xhdpi/ic_media_group_collapse.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/ic_media_group_expand.png b/core/res/res/drawable-xhdpi/ic_media_group_expand.png Binary files differdeleted file mode 100644 index 5755b9d..0000000 --- a/core/res/res/drawable-xhdpi/ic_media_group_expand.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_dark.png Binary files differindex 1d48e12..045eee0 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_dark.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_dark.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_light.png Binary files differindex 2c8d1ec..6e14e29 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_light.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_disabled_holo_light.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_off_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_off_holo_dark.png Binary files differindex 00b2043..121bbf6 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_off_holo_dark.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_off_holo_dark.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_off_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_off_holo_light.png Binary files differindex ce1d939..468a0c3 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_off_holo_light.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_off_holo_light.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_dark.png Binary files differindex 3064b46..414a322 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_dark.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_dark.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_light.png Binary files differindex 4316686..6088a48 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_light.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_0_holo_light.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_dark.png Binary files differindex 25c4e31..363d7d4 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_dark.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_dark.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_light.png Binary files differindex 8e32bd2..edf731e 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_light.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_1_holo_light.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_dark.png Binary files differindex aeaa78f..85cba7b 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_dark.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_dark.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_light.png Binary files differindex 85277fa..e65ac31 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_light.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_2_holo_light.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_holo_dark.png b/core/res/res/drawable-xhdpi/ic_media_route_on_holo_dark.png Binary files differindex b01dbe8..d8e3e3a 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_on_holo_dark.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_holo_dark.png diff --git a/core/res/res/drawable-xhdpi/ic_media_route_on_holo_light.png b/core/res/res/drawable-xhdpi/ic_media_route_on_holo_light.png Binary files differindex c19a2ad..562dc9a 100644 --- a/core/res/res/drawable-xhdpi/ic_media_route_on_holo_light.png +++ b/core/res/res/drawable-xhdpi/ic_media_route_on_holo_light.png diff --git a/core/res/res/drawable-xhdpi/ic_notification_cast_0.png b/core/res/res/drawable-xhdpi/ic_notification_cast_0.png Binary files differnew file mode 100644 index 0000000..5fb23a0 --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_notification_cast_0.png diff --git a/core/res/res/drawable-xhdpi/ic_notification_cast_1.png b/core/res/res/drawable-xhdpi/ic_notification_cast_1.png Binary files differnew file mode 100644 index 0000000..f01d17d --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_notification_cast_1.png diff --git a/core/res/res/drawable-xhdpi/ic_notification_cast_2.png b/core/res/res/drawable-xhdpi/ic_notification_cast_2.png Binary files differnew file mode 100644 index 0000000..4f4ba7f --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_notification_cast_2.png diff --git a/core/res/res/drawable-xhdpi/ic_notification_cast_on.png b/core/res/res/drawable-xhdpi/ic_notification_cast_on.png Binary files differnew file mode 100644 index 0000000..38f15dd --- /dev/null +++ b/core/res/res/drawable-xhdpi/ic_notification_cast_on.png diff --git a/core/res/res/drawable-xhdpi/ic_notify_wifidisplay.png b/core/res/res/drawable-xhdpi/ic_notify_wifidisplay.png Binary files differdeleted file mode 100644 index 4cc0ee8..0000000 --- a/core/res/res/drawable-xhdpi/ic_notify_wifidisplay.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/toast_frame.9.png b/core/res/res/drawable-xhdpi/toast_frame.9.png Binary files differindex 1f63420..77e69c7 100644 --- a/core/res/res/drawable-xhdpi/toast_frame.9.png +++ b/core/res/res/drawable-xhdpi/toast_frame.9.png diff --git a/core/res/res/drawable-xhdpi/toast_frame_holo.9.png b/core/res/res/drawable-xhdpi/toast_frame_holo.9.png Binary files differdeleted file mode 100644 index 77e69c7..0000000 --- a/core/res/res/drawable-xhdpi/toast_frame_holo.9.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_dark.png Binary files differindex 7b0c383..178774c 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_dark.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_dark.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_light.png Binary files differindex efb624e..2dc2092 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_light.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_disabled_holo_light.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_dark.png Binary files differindex 5ee57e4..592ee8c 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_dark.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_dark.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_light.png Binary files differindex 6bc2e4a..f0549e2 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_light.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_off_holo_light.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_dark.png Binary files differindex c13af9c..91268f5 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_dark.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_dark.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_light.png Binary files differindex 744fb42..9d5436f 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_light.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_0_holo_light.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_dark.png Binary files differindex ca4d59c..8e77483 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_dark.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_dark.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_light.png Binary files differindex fde5688..f396d22 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_light.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_1_holo_light.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_dark.png Binary files differindex b8715c3..260bab4 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_dark.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_dark.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_light.png Binary files differindex 668bb25..2c9fb1d 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_light.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_2_holo_light.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_dark.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_dark.png Binary files differindex 7f54a62..bdbd59c 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_dark.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_dark.png diff --git a/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_light.png b/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_light.png Binary files differindex 2df924d..f5c33dd 100644 --- a/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_light.png +++ b/core/res/res/drawable-xxhdpi/ic_media_route_on_holo_light.png diff --git a/core/res/res/drawable-xxhdpi/ic_notification_cast_0.png b/core/res/res/drawable-xxhdpi/ic_notification_cast_0.png Binary files differnew file mode 100644 index 0000000..f5b16ed --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_notification_cast_0.png diff --git a/core/res/res/drawable-xxhdpi/ic_notification_cast_1.png b/core/res/res/drawable-xxhdpi/ic_notification_cast_1.png Binary files differnew file mode 100644 index 0000000..22efeec --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_notification_cast_1.png diff --git a/core/res/res/drawable-xxhdpi/ic_notification_cast_2.png b/core/res/res/drawable-xxhdpi/ic_notification_cast_2.png Binary files differnew file mode 100644 index 0000000..e24cd97 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_notification_cast_2.png diff --git a/core/res/res/drawable-xxhdpi/ic_notification_cast_on.png b/core/res/res/drawable-xxhdpi/ic_notification_cast_on.png Binary files differnew file mode 100644 index 0000000..da1a627 --- /dev/null +++ b/core/res/res/drawable-xxhdpi/ic_notification_cast_on.png diff --git a/core/res/res/drawable-xxhdpi/ic_notify_wifidisplay.png b/core/res/res/drawable-xxhdpi/ic_notify_wifidisplay.png Binary files differdeleted file mode 100644 index fea4774..0000000 --- a/core/res/res/drawable-xxhdpi/ic_notify_wifidisplay.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/toast_frame.9.png b/core/res/res/drawable-xxhdpi/toast_frame.9.png Binary files differindex 882b9c6..edecb63 100644 --- a/core/res/res/drawable-xxhdpi/toast_frame.9.png +++ b/core/res/res/drawable-xxhdpi/toast_frame.9.png diff --git a/core/res/res/drawable-xxhdpi/toast_frame_holo.9.png b/core/res/res/drawable-xxhdpi/toast_frame_holo.9.png Binary files differdeleted file mode 100644 index edecb63..0000000 --- a/core/res/res/drawable-xxhdpi/toast_frame_holo.9.png +++ /dev/null diff --git a/core/res/res/drawable/ic_notification_cast_connecting.xml b/core/res/res/drawable/ic_notification_cast_connecting.xml new file mode 100644 index 0000000..a390bce --- /dev/null +++ b/core/res/res/drawable/ic_notification_cast_connecting.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright 2013, 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. + */ +--> +<animation-list + xmlns:android="http://schemas.android.com/apk/res/android" + android:oneshot="false"> + <item android:drawable="@drawable/ic_notification_cast_0" android:duration="500" /> + <item android:drawable="@drawable/ic_notification_cast_1" android:duration="500" /> + <item android:drawable="@drawable/ic_notification_cast_2" android:duration="500" /> + <item android:drawable="@drawable/ic_notification_cast_1" android:duration="500" /> +</animation-list> diff --git a/core/res/res/layout/immersive_mode_cling.xml b/core/res/res/layout/immersive_mode_cling.xml index f97225e..c0cd93d 100644 --- a/core/res/res/layout/immersive_mode_cling.xml +++ b/core/res/res/layout/immersive_mode_cling.xml @@ -37,8 +37,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/cling_bg" - android:paddingLeft="14dp" - android:paddingRight="14dp" + android:paddingStart="14dp" + android:paddingEnd="14dp" android:paddingTop="24dp" android:paddingBottom="24dp"> <TextView diff --git a/core/res/res/layout/media_route_chooser_dialog.xml b/core/res/res/layout/media_route_chooser_dialog.xml new file mode 100644 index 0000000..d1c6267 --- /dev/null +++ b/core/res/res/layout/media_route_chooser_dialog.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:divider="?android:attr/dividerHorizontal" + android:showDividers="middle"> + <!-- List of routes. --> + <ListView android:id="@+id/media_route_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_weight="1" /> + + <!-- Content to show when list is empty. --> + <LinearLayout android:id="@android:id/empty" + android:layout_width="match_parent" + android:layout_height="64dp" + android:orientation="horizontal" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:visibility="gone"> + <ProgressBar android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" /> + <TextView android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:paddingLeft="16dp" + android:text="@string/media_route_chooser_searching" /> + </LinearLayout> + + <!-- Settings button. --> + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" + style="?attr/buttonBarStyle"> + <Button android:id="@+id/media_route_extended_settings_button" + android:layout_width="match_parent" + android:layout_height="match_parent" + style="?attr/buttonBarButtonStyle" + android:gravity="center" + android:text="@string/media_route_chooser_extended_settings" + android:visibility="gone" /> + </LinearLayout> +</LinearLayout> diff --git a/core/res/res/layout/media_route_chooser_layout.xml b/core/res/res/layout/media_route_chooser_layout.xml deleted file mode 100644 index 5fcb8c8..0000000 --- a/core/res/res/layout/media_route_chooser_layout.xml +++ /dev/null @@ -1,48 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:showDividers="middle" - android:divider="?android:attr/dividerHorizontal"> - <LinearLayout android:layout_width="match_parent" - android:layout_height="?android:attr/listPreferredItemHeight" - android:gravity="center_vertical" - android:padding="8dp"> - <ImageView android:id="@+id/volume_icon" - android:layout_width="48dp" - android:layout_height="48dp" - android:src="@android:drawable/ic_audio_vol" - android:gravity="center" - android:scaleType="center" /> - <SeekBar android:id="@+id/volume_slider" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_marginStart="8dp" - android:layout_marginEnd="8dp" /> - <ImageButton android:id="@+id/extended_settings" - android:layout_width="48dp" - android:layout_height="48dp" - android:background="?android:attr/selectableItemBackground" - android:src="@android:drawable/ic_sysbar_quicksettings" - android:visibility="gone" /> - </LinearLayout> - <ListView android:id="@id/list" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> -</LinearLayout> diff --git a/core/res/res/layout/media_route_controller_dialog.xml b/core/res/res/layout/media_route_controller_dialog.xml new file mode 100644 index 0000000..78287e0 --- /dev/null +++ b/core/res/res/layout/media_route_controller_dialog.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:divider="?android:attr/dividerHorizontal" + android:showDividers="middle"> + <!-- Optional volume slider section. --> + <LinearLayout android:id="@+id/media_route_volume_layout" + android:layout_width="match_parent" + android:layout_height="64dp" + android:gravity="center_vertical" + android:padding="8dp" + android:visibility="gone"> + <ImageView android:layout_width="48dp" + android:layout_height="48dp" + android:src="@drawable/ic_audio_vol" + android:gravity="center" + android:scaleType="center" /> + <SeekBar android:id="@+id/media_route_volume_slider" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_marginLeft="8dp" + android:layout_marginRight="8dp" /> + </LinearLayout> + + <!-- Optional content view section. --> + <FrameLayout android:id="@+id/media_route_control_frame" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" /> + + <!-- Disconnect button. --> + <LinearLayout android:layout_width="match_parent" + android:layout_height="wrap_content" + style="?attr/buttonBarStyle"> + <Button android:id="@+id/media_route_disconnect_button" + android:layout_width="match_parent" + android:layout_height="match_parent" + style="?attr/buttonBarButtonStyle" + android:gravity="center" + android:text="@string/media_route_controller_disconnect" /> + </LinearLayout> +</LinearLayout> diff --git a/core/res/res/layout/media_route_list_item.xml b/core/res/res/layout/media_route_list_item.xml index 423d544..bdca433 100644 --- a/core/res/res/layout/media_route_list_item.xml +++ b/core/res/res/layout/media_route_list_item.xml @@ -20,13 +20,6 @@ android:background="@drawable/item_background_activated_holo_dark" android:gravity="center_vertical"> - <ImageView android:layout_width="56dp" - android:layout_height="56dp" - android:scaleType="center" - android:id="@+id/icon" - android:visibility="gone" - android:duplicateParentState="true" /> - <LinearLayout android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" @@ -53,14 +46,4 @@ android:duplicateParentState="true" /> </LinearLayout> - <ImageButton - android:layout_width="56dp" - android:layout_height="56dp" - android:id="@+id/expand_button" - android:background="?android:attr/selectableItemBackground" - android:src="@drawable/ic_media_group_expand" - android:scaleType="center" - android:visibility="gone" - android:duplicateParentState="true" /> - </LinearLayout> diff --git a/core/res/res/layout/media_route_list_item_checkable.xml b/core/res/res/layout/media_route_list_item_checkable.xml deleted file mode 100644 index 5fb82f7..0000000 --- a/core/res/res/layout/media_route_list_item_checkable.xml +++ /dev/null @@ -1,60 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> - -<com.android.internal.view.CheckableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="?android:attr/listPreferredItemHeight" - android:background="?android:attr/selectableItemBackground" - android:gravity="center_vertical"> - - <ImageView android:layout_width="56dp" - android:layout_height="56dp" - android:scaleType="center" - android:id="@+id/icon" - android:visibility="gone" /> - - <LinearLayout android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1" - android:orientation="vertical" - android:gravity="start|center_vertical" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> - - <TextView android:id="@android:id/text1" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:singleLine="true" - android:ellipsize="marquee" - android:textAppearance="?android:attr/textAppearanceMedium" /> - - <TextView android:id="@android:id/text2" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:singleLine="true" - android:ellipsize="marquee" - android:textAppearance="?android:attr/textAppearanceSmall" /> - </LinearLayout> - - <CheckBox - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginEnd="16dp" - android:id="@+id/check" - android:focusable="false" - android:clickable="false" /> - -</com.android.internal.view.CheckableLinearLayout> diff --git a/core/res/res/layout/media_route_list_item_collapse_group.xml b/core/res/res/layout/media_route_list_item_collapse_group.xml deleted file mode 100644 index 323e24d..0000000 --- a/core/res/res/layout/media_route_list_item_collapse_group.xml +++ /dev/null @@ -1,44 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> - -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="?android:attr/selectableItemBackground"> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="?android:attr/listPreferredItemHeightSmall" - android:background="#19ffffff" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:gravity="center_vertical"> - - <TextView android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:singleLine="true" - android:ellipsize="marquee" - android:text="@string/media_route_chooser_grouping_done" - android:textAppearance="?android:attr/textAppearanceMedium" /> - - <ImageView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:src="@drawable/ic_media_group_collapse" - android:scaleType="center" /> - - </LinearLayout> -</FrameLayout>
\ No newline at end of file diff --git a/core/res/res/layout/media_route_list_item_section_header.xml b/core/res/res/layout/media_route_list_item_section_header.xml deleted file mode 100644 index 949635f..0000000 --- a/core/res/res/layout/media_route_list_item_section_header.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> - -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="16dp"> - <TextView - android:id="@android:id/text1" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceSmall" - android:background="#19ffffff" - android:textStyle="bold" - android:textAllCaps="true" - android:gravity="center_vertical" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:minHeight="24dp" - /> -</FrameLayout> diff --git a/core/res/res/values-mcc310-mnc260/config.xml b/core/res/res/values-mcc310-mnc260/config.xml index 886ecbe..d602c9f 100644 --- a/core/res/res/values-mcc310-mnc260/config.xml +++ b/core/res/res/values-mcc310-mnc260/config.xml @@ -21,22 +21,6 @@ for different hardware and product builds. --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering --> - <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or - <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH --> - <integer-array translatable="false" name="config_tether_upstream_types"> - <item>1</item> - <item>4</item> - <item>7</item> - <item>9</item> - </integer-array> - - <!-- String containing the apn value for tethering. May be overriden by secure settings - TETHER_DUN_APN. Value is a comma separated series of strings: - "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type" - note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" --> - <string translatable="false" name="config_tether_apndata">T-Mobile Tethering,pcweb.tmobile.com,,,,,,,,,310,260,,DUN</string> - <!-- Configure mobile network MTU. Carrier specific value is set here. --> <integer name="config_mobile_mtu">1440</integer> diff --git a/core/res/res/values-mcc311-mnc190/config.xml b/core/res/res/values-mcc311-mnc190/config.xml new file mode 100644 index 0000000..a6c4d1b --- /dev/null +++ b/core/res/res/values-mcc311-mnc190/config.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2013, 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 my 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. +*/ +--> + +<!-- These resources are around just to allow their values to be customized + for different hardware and product builds. --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering --> + <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or + <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH --> + <integer-array translatable="false" name="config_tether_upstream_types"> + <item>1</item> + <item>4</item> + <item>7</item> + <item>9</item> + </integer-array> + + <!-- String containing the apn value for tethering. May be overriden by secure settings + TETHER_DUN_APN. Value is a comma separated series of strings: + "name,apn,proxy,port,username,password,server,mmsc,mmsproxy,mmsport,mcc,mnc,auth,type" + note that empty fields can be ommitted: "name,apn,,,,,,,,,310,260,,DUN" --> + <string translatable="false" name="config_tether_apndata">Tether,broadband.cellular1.net,,,,,,,,,311,190,,DUN</string> + +</resources> diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index ef30b98..91af50a 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -22,7 +22,7 @@ <!-- Do not translate. These are all of the drawable resources that should be preloaded by the zygote process before it starts forking application processes. --> <array name="preloaded_drawables"> - <item>@drawable/toast_frame_holo</item> + <item>@drawable/toast_frame</item> <item>@drawable/btn_check_on_pressed_holo_light</item> <item>@drawable/btn_check_on_pressed_holo_dark</item> <item>@drawable/btn_check_on_holo_light</item> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 42ea384..b34c792 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -585,8 +585,8 @@ <!-- Disable lockscreen rotation by default --> <bool name="config_enableLockScreenRotation">false</bool> - <!-- Disable lockscreen translucent decor by default --> - <bool name="config_enableLockScreenTranslucentDecor">false</bool> + <!-- Enable lockscreen translucent decor by default --> + <bool name="config_enableLockScreenTranslucentDecor">true</bool> <!-- Enable translucent decor by default --> <bool name="config_enableTranslucentDecor">true</bool> @@ -1176,6 +1176,22 @@ where if the preferred is used we don't try the others. --> <bool name="config_dontPreferApn">false</bool> + <!-- The list of ril radio technologies (see ServiceState.java) which only support + a single data connection at one time. This may change by carrier via + overlays (some don't support multiple pdp on UMTS). All unlisted radio + tech types support unlimited types (practically only 2-4 used). --> + <integer-array name="config_onlySingleDcAllowed"> + <item>4</item> <!-- IS95A --> + <item>5</item> <!-- IS95B --> + <item>6</item> <!-- 1xRTT --> + <item>7</item> <!-- EVDO_0 --> + <item>8</item> <!-- EVDO_A --> + <item>12</item> <!-- EVDO_B --> + </integer-array> + + <!-- Set to true if after a provisioning apn the radio should be restarted --> + <bool name="config_restartRadioAfterProvisioning">false</bool> + <!-- Vibrator pattern to be used as the default for notifications that specify DEFAULT_VIBRATE. --> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 62f26c6..9025400 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1052,6 +1052,12 @@ interface of a wallpaper. Should never be needed for normal apps.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_bindRemoteDisplay">bind to a remote display</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_bindRemoteDisplay">Allows the holder to bind to the top-level + interface of a remote display. Should never be needed for normal apps.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_bindRemoteViews">bind to a widget service</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_bindRemoteViews">Allows the holder to bind to the top-level @@ -4090,12 +4096,24 @@ <!-- Description of a wireless display route. [CHAR LIMIT=50] --> <string name="wireless_display_route_description">Wireless display</string> - <!-- "Done" button for MediaRouter chooser dialog when grouping routes. [CHAR LIMIT=NONE] --> - <string name="media_route_chooser_grouping_done">Done</string> - <!-- Content description of a MediaRouteButton for accessibility support --> <string name="media_route_button_content_description">Media output</string> + <!-- Title of the media route chooser dialog. [CHAR LIMIT=40] --> + <string name="media_route_chooser_title">Connect to device</string> + + <!-- Title of the media route chooser dialog for selecting remote display routes. [CHAR LIMIT=40] --> + <string name="media_route_chooser_title_for_remote_display">Cast screen to device</string> + + <!-- Placeholder text to show when no devices have been found. [CHAR LIMIT=50] --> + <string name="media_route_chooser_searching">Searching for devices\u2026</string> + + <!-- Button to access extended settings. [CHAR LIMIT=30] --> + <string name="media_route_chooser_extended_settings">Settings</string> + + <!-- Button to disconnect from a media route. [CHAR LIMIT=30] --> + <string name="media_route_controller_disconnect">Disconnect</string> + <!-- Status message for remote routes attempting to scan/determine availability --> <string name="media_route_status_scanning">Scanning...</string> @@ -4128,10 +4146,14 @@ <!-- Title text to append when the display is secure. [CHAR LIMIT=30] --> <string name="display_manager_overlay_display_secure_suffix">, secure</string> + <!-- Title of the notification to indicate the process of connecting to a wifi display. [CHAR LIMIT=50] --> + <string name="wifi_display_notification_connecting_title">Casting screen</string> + <!-- Message of the notification to indicate the process of connecting to a wifi display. [CHAR LIMIT=80] --> + <string name="wifi_display_notification_connecting_message">Connecting to <xliff:g id="name">%1$s</xliff:g></string> <!-- Title of the notification to indicate an active wifi display connection. [CHAR LIMIT=50] --> - <string name="wifi_display_notification_title">Wireless display is connected</string> + <string name="wifi_display_notification_connected_title">Casting screen</string> <!-- Message of the notification to indicate an active wifi display connection. [CHAR LIMIT=80] --> - <string name="wifi_display_notification_message">This screen is showing on another device</string> + <string name="wifi_display_notification_connected_message">Connected to <xliff:g id="name">%1$s</xliff:g></string> <!-- Label of a button to disconnect an active wifi display connection. [CHAR LIMIT=25] --> <string name="wifi_display_notification_disconnect">Disconnect</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 22a9402..6d90973 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -283,6 +283,7 @@ <java-symbol type="bool" name="config_safe_media_volume_enabled" /> <java-symbol type="bool" name="config_camera_sound_forced" /> <java-symbol type="bool" name="config_dontPreferApn" /> + <java-symbol type="bool" name="config_restartRadioAfterProvisioning" /> <java-symbol type="bool" name="config_speed_up_audio_on_mt_calls" /> <java-symbol type="bool" name="config_useFixedVolume" /> <java-symbol type="bool" name="config_forceDefaultOrientation" /> @@ -1096,7 +1097,11 @@ <java-symbol type="drawable" name="notification_template_icon_bg" /> <java-symbol type="drawable" name="notification_template_icon_low_bg" /> <java-symbol type="drawable" name="ic_media_route_on_holo_dark" /> + <java-symbol type="drawable" name="ic_media_route_off_holo_dark" /> + <java-symbol type="drawable" name="ic_media_route_connecting_holo_dark" /> <java-symbol type="drawable" name="ic_media_route_disabled_holo_dark" /> + <java-symbol type="drawable" name="ic_notification_cast_connecting" /> + <java-symbol type="drawable" name="ic_notification_cast_on" /> <java-symbol type="drawable" name="cling_button" /> <java-symbol type="drawable" name="cling_arrow_up" /> <java-symbol type="drawable" name="cling_bg" /> @@ -1253,17 +1258,17 @@ <java-symbol type="attr" name="mediaRouteButtonStyle" /> <java-symbol type="attr" name="externalRouteEnabledDrawable" /> - <java-symbol type="id" name="extended_settings" /> - <java-symbol type="id" name="check" /> - <java-symbol type="id" name="volume_slider" /> - <java-symbol type="id" name="volume_icon" /> - <java-symbol type="drawable" name="ic_media_route_on_holo_dark" /> - <java-symbol type="layout" name="media_route_chooser_layout" /> - <java-symbol type="layout" name="media_route_list_item_top_header" /> - <java-symbol type="layout" name="media_route_list_item_section_header" /> + <java-symbol type="layout" name="media_route_chooser_dialog" /> + <java-symbol type="layout" name="media_route_controller_dialog" /> <java-symbol type="layout" name="media_route_list_item" /> - <java-symbol type="layout" name="media_route_list_item_checkable" /> - <java-symbol type="layout" name="media_route_list_item_collapse_group" /> + <java-symbol type="id" name="media_route_list" /> + <java-symbol type="id" name="media_route_volume_layout" /> + <java-symbol type="id" name="media_route_volume_slider" /> + <java-symbol type="id" name="media_route_control_frame" /> + <java-symbol type="id" name="media_route_disconnect_button" /> + <java-symbol type="id" name="media_route_extended_settings_button" /> + <java-symbol type="string" name="media_route_chooser_title" /> + <java-symbol type="string" name="media_route_chooser_title_for_remote_display" /> <java-symbol type="string" name="bluetooth_a2dp_audio_route_name" /> <java-symbol type="dimen" name="config_minScalingSpan" /> @@ -1435,6 +1440,7 @@ <java-symbol type="array" name="config_locationProviderPackageNames" /> <java-symbol type="array" name="config_defaultNotificationVibePattern" /> <java-symbol type="array" name="config_notificationFallbackVibePattern" /> + <java-symbol type="array" name="config_onlySingleDcAllowed" /> <java-symbol type="bool" name="config_animateScreenLights" /> <java-symbol type="bool" name="config_automatic_brightness_available" /> <java-symbol type="bool" name="config_enableFusedLocationOverlay" /> @@ -1450,7 +1456,6 @@ <java-symbol type="color" name="config_defaultNotificationColor" /> <java-symbol type="color" name="input_method_navigation_guard" /> <java-symbol type="drawable" name="ic_notification_ime_default" /> - <java-symbol type="drawable" name="ic_notify_wifidisplay" /> <java-symbol type="drawable" name="ic_menu_refresh" /> <java-symbol type="drawable" name="stat_notify_car_mode" /> <java-symbol type="drawable" name="stat_notify_disabled" /> @@ -1593,8 +1598,10 @@ <java-symbol type="string" name="vpn_lockdown_error" /> <java-symbol type="string" name="vpn_lockdown_config" /> <java-symbol type="string" name="wallpaper_binding_label" /> - <java-symbol type="string" name="wifi_display_notification_title" /> - <java-symbol type="string" name="wifi_display_notification_message" /> + <java-symbol type="string" name="wifi_display_notification_connecting_title" /> + <java-symbol type="string" name="wifi_display_notification_connecting_message" /> + <java-symbol type="string" name="wifi_display_notification_connected_title" /> + <java-symbol type="string" name="wifi_display_notification_connected_message" /> <java-symbol type="string" name="wifi_display_notification_disconnect" /> <java-symbol type="style" name="Theme.Dialog.AppError" /> <java-symbol type="style" name="Theme.Toast" /> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index 4c80e7d..c8d9fc6 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -1048,7 +1048,7 @@ please see themes_device_defaults.xml. <item name="presentationTheme">@android:style/Theme.Holo.Dialog.Presentation</item> <!-- Toast attributes --> - <item name="toastFrameBackground">@android:drawable/toast_frame_holo</item> + <item name="toastFrameBackground">@android:drawable/toast_frame</item> <!-- Panel attributes --> <item name="panelBackground">@android:drawable/menu_hardkey_panel_holo_dark</item> @@ -1363,7 +1363,7 @@ please see themes_device_defaults.xml. <item name="presentationTheme">@android:style/Theme.Holo.Light.Dialog.Presentation</item> <!-- Toast attributes --> - <item name="toastFrameBackground">@android:drawable/toast_frame_holo</item> + <item name="toastFrameBackground">@android:drawable/toast_frame</item> <!-- Panel attributes --> <item name="panelBackground">@android:drawable/menu_hardkey_panel_holo_light</item> diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml index 7af3b9c..3215e17 100644 --- a/core/res/res/xml/power_profile.xml +++ b/core/res/res/xml/power_profile.xml @@ -58,4 +58,12 @@ </array> <!-- This is the battery capacity in mAh (measured at nominal voltage) --> <item name="battery.capacity">1000</item> + + <array name="wifi.batchedscan"> <!-- mA --> + <value>.0002</value> <!-- 1-8/hr --> + <value>.002</value> <!-- 9-64/hr --> + <value>.02</value> <!-- 65-512/hr --> + <value>.2</value> <!-- 513-4,096/hr --> + <value>2</value> <!-- 4097-/hr --> + </array> </device> diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java index 76b702e..4a58f88 100644 --- a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java +++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java @@ -36,8 +36,6 @@ import com.android.bandwidthtest.util.BandwidthTestUtil; import com.android.bandwidthtest.util.ConnectionUtil; import java.io.File; -import java.util.HashMap; -import java.util.Map; /** * Test that downloads files from a test server and reports the bandwidth metrics collected. @@ -131,8 +129,8 @@ public class BandwidthTest extends InstrumentationTestCase { results.putString("device_id", mDeviceId); results.putString("timestamp", ts); results.putInt("size", FILE_SIZE); - AddStatsToResults(PROF_LABEL, prof_stats, results); - AddStatsToResults(PROC_LABEL, proc_stats, results); + addStatsToResults(PROF_LABEL, prof_stats, results, mUid); + addStatsToResults(PROC_LABEL, proc_stats, results, mUid); getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results); // Clean up. @@ -185,8 +183,8 @@ public class BandwidthTest extends InstrumentationTestCase { results.putString("device_id", mDeviceId); results.putString("timestamp", ts); results.putInt("size", FILE_SIZE); - AddStatsToResults(PROF_LABEL, prof_stats, results); - AddStatsToResults(PROC_LABEL, proc_stats, results); + addStatsToResults(PROF_LABEL, prof_stats, results, mUid); + addStatsToResults(PROC_LABEL, proc_stats, results, mUid); getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results); // Clean up. @@ -242,8 +240,9 @@ public class BandwidthTest extends InstrumentationTestCase { results.putString("device_id", mDeviceId); results.putString("timestamp", ts); results.putInt("size", FILE_SIZE); - AddStatsToResults(PROF_LABEL, prof_stats, results); - AddStatsToResults(PROC_LABEL, proc_stats, results); + addStatsToResults(PROF_LABEL, prof_stats, results, mUid); + // remember to use download manager uid for proc stats + addStatsToResults(PROC_LABEL, proc_stats, results, downloadManagerUid); getInstrumentation().sendStatus(INSTRUMENTATION_IN_PROGRESS, results); // Clean up. @@ -302,46 +301,35 @@ public class BandwidthTest extends InstrumentationTestCase { * @param label to attach to this given stats. * @param stats {@link NetworkStats} to add. * @param results {@link Bundle} to be added to. + * @param uid for which to report the results. */ - public void AddStatsToResults(String label, NetworkStats stats, Bundle results){ + public void addStatsToResults(String label, NetworkStats stats, Bundle results, int uid){ if (results == null || results.isEmpty()) { Log.e(LOG_TAG, "Empty bundle provided."); return; } - // Merge stats across all sets. - Map<Integer, Entry> totalStats = new HashMap<Integer, Entry>(); + Entry totalStats = null; for (int i = 0; i < stats.size(); ++i) { Entry statsEntry = stats.getValues(i, null); // We are only interested in the all inclusive stats. if (statsEntry.tag != 0) { continue; } - Entry mapEntry = null; - if (totalStats.containsKey(statsEntry.uid)) { - mapEntry = totalStats.get(statsEntry.uid); - switch (statsEntry.set) { - case NetworkStats.SET_ALL: - mapEntry.rxBytes = statsEntry.rxBytes; - mapEntry.txBytes = statsEntry.txBytes; - break; - case NetworkStats.SET_DEFAULT: - case NetworkStats.SET_FOREGROUND: - mapEntry.rxBytes += statsEntry.rxBytes; - mapEntry.txBytes += statsEntry.txBytes; - break; - default: - Log.w(LOG_TAG, "Invalid state found in NetworkStats."); - } + // skip stats for other uids + if (statsEntry.uid != uid) { + continue; + } + if (totalStats == null || statsEntry.set == NetworkStats.SET_ALL) { + totalStats = statsEntry; } else { - totalStats.put(statsEntry.uid, statsEntry); + totalStats.rxBytes += statsEntry.rxBytes; + totalStats.txBytes += statsEntry.txBytes; } } - // Ouput merged stats to bundle. - for (Entry entry : totalStats.values()) { - results.putInt(label + "uid", entry.uid); - results.putLong(label + "tx", entry.txBytes); - results.putLong(label + "rx", entry.rxBytes); - } + // Output merged stats to bundle. + results.putInt(label + "uid", totalStats.uid); + results.putLong(label + "tx", totalStats.txBytes); + results.putLong(label + "rx", totalStats.rxBytes); } /** diff --git a/data/keyboards/AVRCP.kl b/data/keyboards/AVRCP.kl index 736b43c..4c91ece 100644 --- a/data/keyboards/AVRCP.kl +++ b/data/keyboards/AVRCP.kl @@ -14,8 +14,8 @@ # Key layout used for Bluetooth AVRCP support. -key 200 MEDIA_PLAY WAKE -key 201 MEDIA_PAUSE WAKE +key 200 MEDIA_PLAY_PAUSE WAKE +key 201 MEDIA_PLAY_PAUSE WAKE key 166 MEDIA_STOP WAKE key 163 MEDIA_NEXT WAKE key 165 MEDIA_PREVIOUS WAKE diff --git a/data/sounds/AudioPackage10.mk b/data/sounds/AudioPackage10.mk index 783e1f8..ae4bc88 100644 --- a/data/sounds/AudioPackage10.mk +++ b/data/sounds/AudioPackage10.mk @@ -17,19 +17,19 @@ PRODUCT_COPY_FILES += \ $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:system/media/audio/alarms/Osmium.ogg \ $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \ $(LOCAL_PATH)/effects/ogg/Effect_Tick_48k.ogg:system/media/audio/ui/Effect_Tick.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressStandard_120_48k.ogg:system/media/audio/ui/KeypressStandard.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressDelete_120_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressInvalid_120_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressReturn_120_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressStandard_48k.ogg:system/media/audio/ui/KeypressStandard.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressDelete_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \ $(LOCAL_PATH)/effects/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \ $(LOCAL_PATH)/effects/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \ $(LOCAL_PATH)/effects/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \ $(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \ $(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \ - $(LOCAL_PATH)/effects/ogg/Lock.ogg:system/media/audio/ui/Lock.ogg \ - $(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \ + $(LOCAL_PATH)/effects/ogg/Lock_48k.ogg:system/media/audio/ui/Lock.ogg \ + $(LOCAL_PATH)/effects/ogg/Unlock_48k.ogg:system/media/audio/ui/Unlock.ogg \ $(LOCAL_PATH)/effects/ogg/WirelessChargingStarted.ogg:system/media/audio/ui/WirelessChargingStarted.ogg \ $(LOCAL_PATH)/notifications/ogg/Adara.ogg:system/media/audio/notifications/Adara.ogg \ $(LOCAL_PATH)/notifications/ogg/Alya.ogg:system/media/audio/notifications/Alya.ogg \ diff --git a/data/sounds/AudioPackage11.mk b/data/sounds/AudioPackage11.mk index b30be56..3c09297 100644 --- a/data/sounds/AudioPackage11.mk +++ b/data/sounds/AudioPackage11.mk @@ -17,19 +17,19 @@ PRODUCT_COPY_FILES += \ $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:system/media/audio/alarms/Osmium.ogg \ $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \ $(LOCAL_PATH)/effects/ogg/Effect_Tick_48k.ogg:system/media/audio/ui/Effect_Tick.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressStandard_120_48k.ogg:system/media/audio/ui/KeypressStandard.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressDelete_120_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressInvalid_120_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressReturn_120_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressStandard_48k.ogg:system/media/audio/ui/KeypressStandard.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_48k.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressDelete_48k.ogg:system/media/audio/ui/KeypressDelete.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressInvalid_48k.ogg:system/media/audio/ui/KeypressInvalid.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressReturn_48k.ogg:system/media/audio/ui/KeypressReturn.ogg \ $(LOCAL_PATH)/effects/ogg/VideoRecord_48k.ogg:system/media/audio/ui/VideoRecord.ogg \ $(LOCAL_PATH)/effects/ogg/camera_click_48k.ogg:system/media/audio/ui/camera_click.ogg \ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \ $(LOCAL_PATH)/effects/ogg/LowBattery.ogg:system/media/audio/ui/LowBattery.ogg \ $(LOCAL_PATH)/effects/ogg/Dock.ogg:system/media/audio/ui/Dock.ogg \ $(LOCAL_PATH)/effects/ogg/Undock.ogg:system/media/audio/ui/Undock.ogg \ - $(LOCAL_PATH)/effects/ogg/Lock.ogg:system/media/audio/ui/Lock.ogg \ - $(LOCAL_PATH)/effects/ogg/Unlock.ogg:system/media/audio/ui/Unlock.ogg \ + $(LOCAL_PATH)/effects/ogg/Lock_48k.ogg:system/media/audio/ui/Lock.ogg \ + $(LOCAL_PATH)/effects/ogg/Unlock_48k.ogg:system/media/audio/ui/Unlock.ogg \ $(LOCAL_PATH)/effects/ogg/WirelessChargingStarted.ogg:system/media/audio/ui/WirelessChargingStarted.ogg \ $(LOCAL_PATH)/notifications/ogg/Adara.ogg:system/media/audio/notifications/Adara.ogg \ $(LOCAL_PATH)/notifications/ogg/Alya.ogg:system/media/audio/notifications/Alya.ogg \ diff --git a/data/sounds/AudioPackage8.mk b/data/sounds/AudioPackage8.mk index 49b6154..0f4b8ad 100644 --- a/data/sounds/AudioPackage8.mk +++ b/data/sounds/AudioPackage8.mk @@ -17,11 +17,11 @@ PRODUCT_COPY_FILES += \ $(LOCAL_PATH)/alarms/ogg/Plutonium.ogg:system/media/audio/alarms/Plutonium.ogg \ $(LOCAL_PATH)/alarms/ogg/Promethium.ogg:system/media/audio/alarms/Promethium.ogg \ $(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressStandard_120.ogg:system/media/audio/ui/KeypressStandard.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressDelete_120.ogg:system/media/audio/ui/KeypressDelete.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:system/media/audio/ui/KeypressInvalid.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \ $(LOCAL_PATH)/effects/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \ $(LOCAL_PATH)/effects/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \ diff --git a/data/sounds/AudioPackage9.mk b/data/sounds/AudioPackage9.mk index 87b7764..36dc921 100644 --- a/data/sounds/AudioPackage9.mk +++ b/data/sounds/AudioPackage9.mk @@ -17,11 +17,11 @@ PRODUCT_COPY_FILES += \ $(LOCAL_PATH)/alarms/ogg/Osmium.ogg:system/media/audio/alarms/Osmium.ogg \ $(LOCAL_PATH)/alarms/ogg/Platinum.ogg:system/media/audio/alarms/Platinum.ogg \ $(LOCAL_PATH)/effects/ogg/Effect_Tick.ogg:system/media/audio/ui/Effect_Tick.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressStandard_120.ogg:system/media/audio/ui/KeypressStandard.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressSpacebar_120.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressDelete_120.ogg:system/media/audio/ui/KeypressDelete.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressInvalid_120.ogg:system/media/audio/ui/KeypressInvalid.ogg \ - $(LOCAL_PATH)/effects/ogg/KeypressReturn_120.ogg:system/media/audio/ui/KeypressReturn.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressStandard.ogg:system/media/audio/ui/KeypressStandard.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressSpacebar.ogg:system/media/audio/ui/KeypressSpacebar.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressDelete.ogg:system/media/audio/ui/KeypressDelete.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressInvalid.ogg:system/media/audio/ui/KeypressInvalid.ogg \ + $(LOCAL_PATH)/effects/ogg/KeypressReturn.ogg:system/media/audio/ui/KeypressReturn.ogg \ $(LOCAL_PATH)/effects/ogg/VideoRecord.ogg:system/media/audio/ui/VideoRecord.ogg \ $(LOCAL_PATH)/effects/ogg/camera_click.ogg:system/media/audio/ui/camera_click.ogg \ $(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \ diff --git a/data/sounds/notifications/ogg/Tethys_48k.ogg b/data/sounds/notifications/ogg/Tethys_48k.ogg Binary files differindex a9d8bbd..355d522 100644 --- a/data/sounds/notifications/ogg/Tethys_48k.ogg +++ b/data/sounds/notifications/ogg/Tethys_48k.ogg diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 6d60dd2..3c24683 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -1167,6 +1167,11 @@ public final class Bitmap implements Parcelable { * @see #reconfigure(int, int, Config) */ public final int getAllocationByteCount() { + if (mBuffer == null) { + // native backed bitmaps don't support reconfiguration, + // so alloc size is always content size + return getByteCount(); + } return mBuffer.length; } diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java index e350e8d..aac7876 100644 --- a/graphics/java/android/graphics/drawable/DrawableContainer.java +++ b/graphics/java/android/graphics/drawable/DrawableContainer.java @@ -23,6 +23,7 @@ import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.SystemClock; +import android.util.LayoutDirection; import android.util.SparseArray; /** @@ -59,6 +60,8 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { private long mExitAnimationEnd; private Drawable mLastDrawable; + private Insets mInsets = Insets.NONE; + // overrides from Drawable @Override @@ -78,18 +81,31 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { | mDrawableContainerState.mChildrenChangingConfigurations; } + private boolean needsMirroring() { + return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; + } + @Override public boolean getPadding(Rect padding) { final Rect r = mDrawableContainerState.getConstantPadding(); + boolean result; if (r != null) { padding.set(r); - return true; - } - if (mCurrDrawable != null) { - return mCurrDrawable.getPadding(padding); + result = (r.left | r.top | r.bottom | r.right) != 0; } else { - return super.getPadding(padding); + if (mCurrDrawable != null) { + result = mCurrDrawable.getPadding(padding); + } else { + result = super.getPadding(padding); + } } + if (needsMirroring()) { + final int left = padding.left; + final int right = padding.right; + padding.left = right; + padding.right = left; + } + return result; } /** @@ -97,7 +113,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { */ @Override public Insets getOpticalInsets() { - return (mCurrDrawable == null) ? Insets.NONE : mCurrDrawable.getOpticalInsets(); + return mInsets; } @Override @@ -334,6 +350,7 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { mCurrDrawable = d; mCurIndex = idx; if (d != null) { + mInsets = d.getOpticalInsets(); d.mutate(); if (mDrawableContainerState.mEnterFadeDuration > 0) { mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration; @@ -348,9 +365,12 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { d.setBounds(getBounds()); d.setLayoutDirection(getLayoutDirection()); d.setAutoMirrored(mDrawableContainerState.mAutoMirrored); + } else { + mInsets = Insets.NONE; } } else { mCurrDrawable = null; + mInsets = Insets.NONE; mCurIndex = -1; } diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java index ab34c0f..9c57a2c 100644 --- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java +++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java @@ -244,7 +244,7 @@ public class NinePatchDrawable extends Drawable { } else { padding.set(mPadding); } - return true; + return (padding.left | padding.top | padding.right | padding.bottom) != 0; } /** diff --git a/graphics/java/android/graphics/pdf/PdfDocument.java b/graphics/java/android/graphics/pdf/PdfDocument.java index 81e523d..29d14a2 100644 --- a/graphics/java/android/graphics/pdf/PdfDocument.java +++ b/graphics/java/android/graphics/pdf/PdfDocument.java @@ -18,6 +18,7 @@ package android.graphics.pdf; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.Rect; import dalvik.system.CloseGuard; @@ -69,6 +70,12 @@ import java.util.List; */ public class PdfDocument { + // TODO: We need a constructor that will take an OutputStream to + // support online data serialization as opposed to the current + // on demand one. The current approach is fine until Skia starts + // to support online PDF generation at which point we need to + // handle this. + private final byte[] mChunk = new byte[4096]; private final CloseGuard mCloseGuard = CloseGuard.get(); @@ -111,7 +118,7 @@ public class PdfDocument { if (pageInfo == null) { throw new IllegalArgumentException("page cannot be null"); } - Canvas canvas = new PdfCanvas(nativeCreatePage(pageInfo.mPageWidth, + Canvas canvas = new PdfCanvas(nativeStartPage(mNativeDocument, pageInfo.mPageWidth, pageInfo.mPageHeight, pageInfo.mContentRect.left, pageInfo.mContentRect.top, pageInfo.mContentRect.right, pageInfo.mContentRect.bottom)); mCurrentPage = new Page(canvas, pageInfo); @@ -142,7 +149,7 @@ public class PdfDocument { } mPages.add(page.getInfo()); mCurrentPage = null; - nativeAppendPage(mNativeDocument, page.mCanvas.mNativeCanvas); + nativeFinishPage(mNativeDocument); page.finish(); } @@ -204,7 +211,7 @@ public class PdfDocument { private void dispose() { if (mNativeDocument != 0) { - nativeFinalize(mNativeDocument); + nativeClose(mNativeDocument); mCloseGuard.close(); mNativeDocument = 0; } @@ -230,14 +237,14 @@ public class PdfDocument { private native int nativeCreateDocument(); - private native void nativeFinalize(int document); + private native void nativeClose(int document); - private native void nativeAppendPage(int document, int page); + private native void nativeFinishPage(int document); private native void nativeWriteTo(int document, OutputStream out, byte[] chunk); - private static native int nativeCreatePage(int pageWidth, int pageHeight, int contentLeft, - int contentTop, int contentRight, int contentBottom); + private static native int nativeStartPage(int documentPtr, int pageWidth, int pageHeight, + int contentLeft, int contentTop, int contentRight, int contentBottom); private final class PdfCanvas extends Canvas { @@ -392,28 +399,28 @@ public class PdfDocument { * Gets the {@link Canvas} of the page. * * <p> - * <strong>Note: </strong> There are some draw operations that are - * not yet supported by the canvas returned by this method. More - * specifically: + * <strong>Note: </strong> There are some draw operations that are not yet + * supported by the canvas returned by this method. More specifically: * <ul> - * <li>{@link Canvas#clipPath(android.graphics.Path) - * Canvas.clipPath(android.graphics.Path)}</li> - * <li>All flavors of {@link Canvas#drawText(String, float, float, - * android.graphics.Paint) Canvas.drawText(String, float, float, - * android.graphics.Paint)}</li> - * <li>All flavors of {@link Canvas#drawPosText(String, float[], - * android.graphics.Paint) Canvas.drawPosText(String, float[], - * android.graphics.Paint)}</li> + * <li>Inverse path clipping performed via {@link Canvas#clipPath(android.graphics.Path, + * android.graphics.Region.Op) Canvas.clipPath(android.graphics.Path, + * android.graphics.Region.Op)} for {@link + * android.graphics.Region.Op#REVERSE_DIFFERENCE + * Region.Op#REVERSE_DIFFERENCE} operations.</li> * <li>{@link Canvas#drawVertices(android.graphics.Canvas.VertexMode, int, * float[], int, float[], int, int[], int, short[], int, int, * android.graphics.Paint) Canvas.drawVertices( * android.graphics.Canvas.VertexMode, int, float[], int, float[], * int, int[], int, short[], int, int, android.graphics.Paint)}</li> - * <li>{@link android.graphics.PorterDuff.Mode#SRC_ATOP PorterDuff.Mode SRC}, + * <li>Color filters set via {@link Paint#setColorFilter( + * android.graphics.ColorFilter)}</li> + * <li>Mask filters set via {@link Paint#setMaskFilter( + * android.graphics.MaskFilter)}</li> + * <li>Some XFER modes such as + * {@link android.graphics.PorterDuff.Mode#SRC_ATOP PorterDuff.Mode SRC}, * {@link android.graphics.PorterDuff.Mode#DST_ATOP PorterDuff.DST_ATOP}, * {@link android.graphics.PorterDuff.Mode#XOR PorterDuff.XOR}, * {@link android.graphics.PorterDuff.Mode#ADD PorterDuff.ADD}</li> - * <li>Perspective transforms</li> * </ul> * * @return The canvas if the page is not finished, null otherwise. diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 1f5fefd..b836f50 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -2913,12 +2913,10 @@ public class AudioService extends IAudioService.Stub { int index; if (isMuted()) { index = 0; - } else if (mStreamVolumeAlias[mStreamType] == AudioSystem.STREAM_MUSIC && - (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && + } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && mAvrcpAbsVolSupported) { index = (mIndexMax + 5)/10; - } - else { + } else { index = (getIndex(device) + 5)/10; } AudioSystem.setStreamVolumeIndex(mStreamType, index, device); @@ -2943,6 +2941,9 @@ public class AudioService extends IAudioService.Stub { if (device != AudioSystem.DEVICE_OUT_DEFAULT) { if (isMuted()) { index = 0; + } else if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 && + mAvrcpAbsVolSupported) { + index = (mIndexMax + 5)/10; } else { index = ((Integer)entry.getValue() + 5)/10; } @@ -3215,7 +3216,14 @@ public class AudioService extends IAudioService.Stub { for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { if (streamType != streamState.mStreamType && mStreamVolumeAlias[streamType] == streamState.mStreamType) { - mStreamStates[streamType].applyDeviceVolume(getDeviceForStream(streamType)); + // Make sure volume is also maxed out on A2DP device for aliased stream + // that may have a different device selected + int streamDevice = getDeviceForStream(streamType); + if ((device != streamDevice) && mAvrcpAbsVolSupported && + ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) { + mStreamStates[streamType].applyDeviceVolume(device); + } + mStreamStates[streamType].applyDeviceVolume(streamDevice); } } @@ -3843,9 +3851,12 @@ public class AudioService extends IAudioService.Stub { // address is not used for now, but may be used when multiple a2dp devices are supported synchronized (mA2dpAvrcpLock) { mAvrcpAbsVolSupported = support; - VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC]; sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, streamState, 0); + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, + mStreamStates[AudioSystem.STREAM_MUSIC], 0); + sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE, + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0, + mStreamStates[AudioSystem.STREAM_RING], 0); } } diff --git a/media/java/android/media/IMediaRouterClient.aidl b/media/java/android/media/IMediaRouterClient.aidl new file mode 100644 index 0000000..9640dcb --- /dev/null +++ b/media/java/android/media/IMediaRouterClient.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2013 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 android.media; + +/** + * {@hide} + */ +oneway interface IMediaRouterClient { + void onStateChanged(); +} diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl new file mode 100644 index 0000000..f8f5fdf --- /dev/null +++ b/media/java/android/media/IMediaRouterService.aidl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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 android.media; + +import android.media.IMediaRouterClient; +import android.media.MediaRouterClientState; + +/** + * {@hide} + */ +interface IMediaRouterService { + void registerClientAsUser(IMediaRouterClient client, String packageName, int userId); + void unregisterClient(IMediaRouterClient client); + + MediaRouterClientState getState(IMediaRouterClient client); + + void setDiscoveryRequest(IMediaRouterClient client, int routeTypes, boolean activeScan); + void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit); + void requestSetVolume(IMediaRouterClient client, String routeId, int volume); + void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction); +} diff --git a/media/java/android/media/IRemoteDisplayCallback.aidl b/media/java/android/media/IRemoteDisplayCallback.aidl new file mode 100644 index 0000000..19cf070 --- /dev/null +++ b/media/java/android/media/IRemoteDisplayCallback.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2013 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 android.media; + +import android.media.RemoteDisplayState; + +/** + * {@hide} + */ +oneway interface IRemoteDisplayCallback { + void onStateChanged(in RemoteDisplayState state); +} diff --git a/media/java/android/media/IRemoteDisplayProvider.aidl b/media/java/android/media/IRemoteDisplayProvider.aidl new file mode 100644 index 0000000..b0d7379 --- /dev/null +++ b/media/java/android/media/IRemoteDisplayProvider.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2013 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 android.media; + +import android.media.IRemoteDisplayCallback; + +/** + * {@hide} + */ +oneway interface IRemoteDisplayProvider { + void setCallback(in IRemoteDisplayCallback callback); + void setDiscoveryMode(int mode); + void connect(String id); + void disconnect(String id); + void setVolume(String id, int volume); + void adjustVolume(String id, int delta); +} diff --git a/media/java/android/media/MediaFocusControl.java b/media/java/android/media/MediaFocusControl.java index 59d411d..34008bb 100644 --- a/media/java/android/media/MediaFocusControl.java +++ b/media/java/android/media/MediaFocusControl.java @@ -1930,7 +1930,6 @@ public class MediaFocusControl implements OnFinished { int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; synchronized(mAudioFocusLock) { synchronized(mRCStack) { - boolean wasCurrentRcController = isCurrentRcController(mediaIntent); // store the new display information try { for (int index = mRCStack.size()-1; index >= 0; index--) { @@ -1977,9 +1976,9 @@ public class MediaFocusControl implements OnFinished { Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); } - // if the eventReceiver is now at the top of the stack but wasn't before + // if the eventReceiver is at the top of the stack // then check for potential refresh of the remote controls - if (isCurrentRcController(mediaIntent) && !wasCurrentRcController) { + if (isCurrentRcController(mediaIntent)) { checkUpdateRemoteControlDisplay_syncAfRcs(RC_INFO_ALL); } }//synchronized(mRCStack) diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java index 9a79c94..525dc4f 100644 --- a/media/java/android/media/MediaRouter.java +++ b/media/java/android/media/MediaRouter.java @@ -16,11 +16,15 @@ package android.media; +import com.android.internal.util.Objects; + +import android.Manifest; import android.app.ActivityThread; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; @@ -28,8 +32,10 @@ import android.hardware.display.WifiDisplay; import android.hardware.display.WifiDisplayStatus; import android.os.Handler; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import android.view.Display; @@ -52,14 +58,17 @@ import java.util.concurrent.CopyOnWriteArrayList; */ public class MediaRouter { private static final String TAG = "MediaRouter"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); static class Static implements DisplayManager.DisplayListener { // Time between wifi display scans when actively scanning in milliseconds. - private static final int WIFI_DISPLAY_SCAN_INTERVAL = 15000; + private static final int WIFI_DISPLAY_SCAN_INTERVAL = 10000; + final Context mAppContext; final Resources mResources; final IAudioService mAudioService; final DisplayManager mDisplayService; + final IMediaRouterService mMediaRouterService; final Handler mHandler; final CopyOnWriteArrayList<CallbackInfo> mCallbacks = new CopyOnWriteArrayList<CallbackInfo>(); @@ -76,9 +85,16 @@ public class MediaRouter { RouteInfo mSelectedRoute; - WifiDisplayStatus mLastKnownWifiDisplayStatus; + final boolean mCanConfigureWifiDisplays; boolean mActivelyScanningWifiDisplays; + int mDiscoveryRequestRouteTypes; + boolean mDiscoverRequestActiveScan; + + int mCurrentUserId = -1; + IMediaRouterClient mClient; + MediaRouterClientState mClientState; + final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() { @Override public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) { @@ -101,6 +117,7 @@ public class MediaRouter { }; Static(Context appContext) { + mAppContext = appContext; mResources = Resources.getSystem(); mHandler = new Handler(appContext.getMainLooper()); @@ -109,10 +126,20 @@ public class MediaRouter { mDisplayService = (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE); + mMediaRouterService = IMediaRouterService.Stub.asInterface( + ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE)); + mSystemCategory = new RouteCategory( com.android.internal.R.string.default_audio_route_category_name, ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO, false); mSystemCategory.mIsSystem = true; + + // Only the system can configure wifi displays. The display manager + // enforces this with a permission check. Set a flag here so that we + // know whether this process is actually allowed to scan and connect. + mCanConfigureWifiDisplays = appContext.checkPermission( + Manifest.permission.CONFIGURE_WIFI_DISPLAY, + Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED; } // Called after sStatic is initialized @@ -120,8 +147,7 @@ public class MediaRouter { mDefaultAudioVideo = new RouteInfo(mSystemCategory); mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name; mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO; - mDefaultAudioVideo.mPresentationDisplay = choosePresentationDisplayForRoute( - mDefaultAudioVideo, getAllPresentationDisplays()); + mDefaultAudioVideo.updatePresentationDisplay(); addRouteStatic(mDefaultAudioVideo); // This will select the active wifi display route if there is one. @@ -146,10 +172,13 @@ public class MediaRouter { updateAudioRoutes(newAudioRoutes); } + // Bind to the media router service. + rebindAsUser(UserHandle.myUserId()); + // Select the default route if the above didn't sync us up // appropriately with relevant system state. if (mSelectedRoute == null) { - selectRouteStatic(mDefaultAudioVideo.getSupportedTypes(), mDefaultAudioVideo); + selectDefaultRouteStatic(); } } @@ -197,7 +226,7 @@ public class MediaRouter { dispatchRouteChanged(sStatic.mBluetoothA2dpRoute); } } else if (sStatic.mBluetoothA2dpRoute != null) { - removeRoute(sStatic.mBluetoothA2dpRoute); + removeRouteStatic(sStatic.mBluetoothA2dpRoute); sStatic.mBluetoothA2dpRoute = null; } } @@ -205,16 +234,52 @@ public class MediaRouter { if (mBluetoothA2dpRoute != null) { if (mainType != AudioRoutesInfo.MAIN_SPEAKER && mSelectedRoute == mBluetoothA2dpRoute && !a2dpEnabled) { - selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo); + selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo, false); } else if ((mSelectedRoute == mDefaultAudioVideo || mSelectedRoute == null) && a2dpEnabled) { - selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute); + selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute, false); } } } - void updateActiveScan() { - if (hasActiveScanCallbackOfType(ROUTE_TYPE_LIVE_VIDEO)) { + void updateDiscoveryRequest() { + // What are we looking for today? + int routeTypes = 0; + int passiveRouteTypes = 0; + boolean activeScan = false; + boolean activeScanWifiDisplay = false; + final int count = mCallbacks.size(); + for (int i = 0; i < count; i++) { + CallbackInfo cbi = mCallbacks.get(i); + if ((cbi.flags & (CALLBACK_FLAG_PERFORM_ACTIVE_SCAN + | CALLBACK_FLAG_REQUEST_DISCOVERY)) != 0) { + // Discovery explicitly requested. + routeTypes |= cbi.type; + } else if ((cbi.flags & CALLBACK_FLAG_PASSIVE_DISCOVERY) != 0) { + // Discovery only passively requested. + passiveRouteTypes |= cbi.type; + } else { + // Legacy case since applications don't specify the discovery flag. + // Unfortunately we just have to assume they always need discovery + // whenever they have a callback registered. + routeTypes |= cbi.type; + } + if ((cbi.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) { + activeScan = true; + if ((cbi.type & ROUTE_TYPE_REMOTE_DISPLAY) != 0) { + activeScanWifiDisplay = true; + } + } + } + if (routeTypes != 0 || activeScan) { + // If someone else requests discovery then enable the passive listeners. + // This is used by the MediaRouteButton and MediaRouteActionProvider since + // they don't receive lifecycle callbacks from the Activity. + routeTypes |= passiveRouteTypes; + } + + // Update wifi display scanning. + if (activeScanWifiDisplay && mCanConfigureWifiDisplays) { if (!mActivelyScanningWifiDisplays) { mActivelyScanningWifiDisplays = true; mHandler.post(mScanWifiDisplays); @@ -225,18 +290,14 @@ public class MediaRouter { mHandler.removeCallbacks(mScanWifiDisplays); } } - } - private boolean hasActiveScanCallbackOfType(int type) { - final int count = mCallbacks.size(); - for (int i = 0; i < count; i++) { - CallbackInfo cbi = mCallbacks.get(i); - if ((cbi.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0 - && (cbi.type & type) != 0) { - return true; - } + // Tell the media router service all about it. + if (routeTypes != mDiscoveryRequestRouteTypes + || activeScan != mDiscoverRequestActiveScan) { + mDiscoveryRequestRouteTypes = routeTypes; + mDiscoverRequestActiveScan = activeScan; + publishClientDiscoveryRequest(); } - return false; } @Override @@ -259,17 +320,267 @@ public class MediaRouter { } private void updatePresentationDisplays(int changedDisplayId) { - final Display[] displays = getAllPresentationDisplays(); final int count = mRoutes.size(); for (int i = 0; i < count; i++) { - final RouteInfo info = mRoutes.get(i); - Display display = choosePresentationDisplayForRoute(info, displays); - if (display != info.mPresentationDisplay - || (display != null && display.getDisplayId() == changedDisplayId)) { - info.mPresentationDisplay = display; - dispatchRoutePresentationDisplayChanged(info); + final RouteInfo route = mRoutes.get(i); + if (route.updatePresentationDisplay() || (route.mPresentationDisplay != null + && route.mPresentationDisplay.getDisplayId() == changedDisplayId)) { + dispatchRoutePresentationDisplayChanged(route); + } + } + } + + void setSelectedRoute(RouteInfo info, boolean explicit) { + // Must be non-reentrant. + mSelectedRoute = info; + publishClientSelectedRoute(explicit); + } + + void rebindAsUser(int userId) { + if (mCurrentUserId != userId || userId < 0 || mClient == null) { + if (mClient != null) { + try { + mMediaRouterService.unregisterClient(mClient); + } catch (RemoteException ex) { + Log.e(TAG, "Unable to unregister media router client.", ex); + } + mClient = null; + } + + mCurrentUserId = userId; + + try { + Client client = new Client(); + mMediaRouterService.registerClientAsUser(client, + mAppContext.getPackageName(), userId); + mClient = client; + } catch (RemoteException ex) { + Log.e(TAG, "Unable to register media router client.", ex); + } + + publishClientDiscoveryRequest(); + publishClientSelectedRoute(false); + updateClientState(); + } + } + + void publishClientDiscoveryRequest() { + if (mClient != null) { + try { + mMediaRouterService.setDiscoveryRequest(mClient, + mDiscoveryRequestRouteTypes, mDiscoverRequestActiveScan); + } catch (RemoteException ex) { + Log.e(TAG, "Unable to publish media router client discovery request.", ex); + } + } + } + + void publishClientSelectedRoute(boolean explicit) { + if (mClient != null) { + try { + mMediaRouterService.setSelectedRoute(mClient, + mSelectedRoute != null ? mSelectedRoute.mGlobalRouteId : null, + explicit); + } catch (RemoteException ex) { + Log.e(TAG, "Unable to publish media router client selected route.", ex); + } + } + } + + void updateClientState() { + // Update the client state. + mClientState = null; + if (mClient != null) { + try { + mClientState = mMediaRouterService.getState(mClient); + } catch (RemoteException ex) { + Log.e(TAG, "Unable to retrieve media router client state.", ex); + } + } + final ArrayList<MediaRouterClientState.RouteInfo> globalRoutes = + mClientState != null ? mClientState.routes : null; + final String globallySelectedRouteId = mClientState != null ? + mClientState.globallySelectedRouteId : null; + + // Add or update routes. + final int globalRouteCount = globalRoutes != null ? globalRoutes.size() : 0; + for (int i = 0; i < globalRouteCount; i++) { + final MediaRouterClientState.RouteInfo globalRoute = globalRoutes.get(i); + RouteInfo route = findGlobalRoute(globalRoute.id); + if (route == null) { + route = makeGlobalRoute(globalRoute); + addRouteStatic(route); + } else { + updateGlobalRoute(route, globalRoute); + } + } + + // Synchronize state with the globally selected route. + if (globallySelectedRouteId != null) { + final RouteInfo route = findGlobalRoute(globallySelectedRouteId); + if (route == null) { + Log.w(TAG, "Could not find new globally selected route: " + + globallySelectedRouteId); + } else if (route != mSelectedRoute) { + if (DEBUG) { + Log.d(TAG, "Selecting new globally selected route: " + route); + } + selectRouteStatic(route.mSupportedTypes, route, false); + } + } else if (mSelectedRoute != null && mSelectedRoute.mGlobalRouteId != null) { + if (DEBUG) { + Log.d(TAG, "Unselecting previous globally selected route: " + mSelectedRoute); + } + selectDefaultRouteStatic(); + } + + // Remove defunct routes. + outer: for (int i = mRoutes.size(); i-- > 0; ) { + final RouteInfo route = mRoutes.get(i); + final String globalRouteId = route.mGlobalRouteId; + if (globalRouteId != null) { + for (int j = 0; j < globalRouteCount; j++) { + MediaRouterClientState.RouteInfo globalRoute = globalRoutes.get(j); + if (globalRouteId.equals(globalRoute.id)) { + continue outer; // found + } + } + // not found + removeRouteStatic(route); + } + } + } + + void requestSetVolume(RouteInfo route, int volume) { + if (route.mGlobalRouteId != null && mClient != null) { + try { + mMediaRouterService.requestSetVolume(mClient, + route.mGlobalRouteId, volume); + } catch (RemoteException ex) { + Log.w(TAG, "Unable to request volume change.", ex); + } + } + } + + void requestUpdateVolume(RouteInfo route, int direction) { + if (route.mGlobalRouteId != null && mClient != null) { + try { + mMediaRouterService.requestUpdateVolume(mClient, + route.mGlobalRouteId, direction); + } catch (RemoteException ex) { + Log.w(TAG, "Unable to request volume change.", ex); + } + } + } + + RouteInfo makeGlobalRoute(MediaRouterClientState.RouteInfo globalRoute) { + RouteInfo route = new RouteInfo(sStatic.mSystemCategory); + route.mGlobalRouteId = globalRoute.id; + route.mName = globalRoute.name; + route.mDescription = globalRoute.description; + route.mSupportedTypes = globalRoute.supportedTypes; + route.mEnabled = globalRoute.enabled; + route.setRealStatusCode(globalRoute.statusCode); + route.mPlaybackType = globalRoute.playbackType; + route.mPlaybackStream = globalRoute.playbackStream; + route.mVolume = globalRoute.volume; + route.mVolumeMax = globalRoute.volumeMax; + route.mVolumeHandling = globalRoute.volumeHandling; + route.mPresentationDisplayId = globalRoute.presentationDisplayId; + route.updatePresentationDisplay(); + return route; + } + + void updateGlobalRoute(RouteInfo route, MediaRouterClientState.RouteInfo globalRoute) { + boolean changed = false; + boolean volumeChanged = false; + boolean presentationDisplayChanged = false; + + if (!Objects.equal(route.mName, globalRoute.name)) { + route.mName = globalRoute.name; + changed = true; + } + if (!Objects.equal(route.mDescription, globalRoute.description)) { + route.mDescription = globalRoute.description; + changed = true; + } + final int oldSupportedTypes = route.mSupportedTypes; + if (oldSupportedTypes != globalRoute.supportedTypes) { + route.mSupportedTypes = globalRoute.supportedTypes; + changed = true; + } + if (route.mEnabled != globalRoute.enabled) { + route.mEnabled = globalRoute.enabled; + changed = true; + } + if (route.mRealStatusCode != globalRoute.statusCode) { + route.setRealStatusCode(globalRoute.statusCode); + changed = true; + } + if (route.mPlaybackType != globalRoute.playbackType) { + route.mPlaybackType = globalRoute.playbackType; + changed = true; + } + if (route.mPlaybackStream != globalRoute.playbackStream) { + route.mPlaybackStream = globalRoute.playbackStream; + changed = true; + } + if (route.mVolume != globalRoute.volume) { + route.mVolume = globalRoute.volume; + changed = true; + volumeChanged = true; + } + if (route.mVolumeMax != globalRoute.volumeMax) { + route.mVolumeMax = globalRoute.volumeMax; + changed = true; + volumeChanged = true; + } + if (route.mVolumeHandling != globalRoute.volumeHandling) { + route.mVolumeHandling = globalRoute.volumeHandling; + changed = true; + volumeChanged = true; + } + if (route.mPresentationDisplayId != globalRoute.presentationDisplayId) { + route.mPresentationDisplayId = globalRoute.presentationDisplayId; + route.updatePresentationDisplay(); + changed = true; + presentationDisplayChanged = true; + } + + if (changed) { + dispatchRouteChanged(route, oldSupportedTypes); + } + if (volumeChanged) { + dispatchRouteVolumeChanged(route); + } + if (presentationDisplayChanged) { + dispatchRoutePresentationDisplayChanged(route); + } + } + + RouteInfo findGlobalRoute(String globalRouteId) { + final int count = mRoutes.size(); + for (int i = 0; i < count; i++) { + final RouteInfo route = mRoutes.get(i); + if (globalRouteId.equals(route.mGlobalRouteId)) { + return route; } } + return null; + } + + final class Client extends IMediaRouterClient.Stub { + @Override + public void onStateChanged() { + mHandler.post(new Runnable() { + @Override + public void run() { + if (Client.this == mClient) { + updateClientState(); + } + } + }); + } } } @@ -285,7 +596,7 @@ public class MediaRouter { * <p>Once initiated this routing is transparent to the application. All audio * played on the media stream will be routed to the selected destination.</p> */ - public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1; + public static final int ROUTE_TYPE_LIVE_AUDIO = 1 << 0; /** * Route type flag for live video. @@ -302,7 +613,13 @@ public class MediaRouter { * @see RouteInfo#getPresentationDisplay() * @see android.app.Presentation */ - public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2; + public static final int ROUTE_TYPE_LIVE_VIDEO = 1 << 1; + + /** + * Temporary interop constant to identify remote displays. + * @hide To be removed when media router API is updated. + */ + public static final int ROUTE_TYPE_REMOTE_DISPLAY = 1 << 2; /** * Route type flag for application-specific usage. @@ -312,7 +629,10 @@ public class MediaRouter { * is expected to interpret the meaning of these events and perform the requested * routing tasks.</p> */ - public static final int ROUTE_TYPE_USER = 0x00800000; + public static final int ROUTE_TYPE_USER = 1 << 23; + + static final int ROUTE_TYPE_ANY = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO + | ROUTE_TYPE_REMOTE_DISPLAY | ROUTE_TYPE_USER; /** * Flag for {@link #addCallback}: Actively scan for routes while this callback @@ -336,11 +656,40 @@ public class MediaRouter { * Flag for {@link #addCallback}: Do not filter route events. * <p> * When this flag is specified, the callback will be invoked for event that affect any - * route event if they do not match the callback's associated media route selector. + * route even if they do not match the callback's filter. * </p> */ public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 1 << 1; + /** + * Explicitly requests discovery. + * + * @hide Future API ported from support library. Revisit this later. + */ + public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 1 << 2; + + /** + * Requests that discovery be performed but only if there is some other active + * callback already registered. + * + * @hide Compatibility workaround for the fact that applications do not currently + * request discovery explicitly (except when using the support library API). + */ + public static final int CALLBACK_FLAG_PASSIVE_DISCOVERY = 1 << 3; + + /** + * Flag for {@link #isRouteAvailable}: Ignore the default route. + * <p> + * This flag is used to determine whether a matching non-default route is available. + * This constraint may be used to decide whether to offer the route chooser dialog + * to the user. There is no point offering the chooser if there are no + * non-default choices. + * </p> + * + * @hide Future API ported from support library. Revisit this later. + */ + public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1 << 0; + // Maps application contexts static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>(); @@ -352,6 +701,9 @@ public class MediaRouter { if ((types & ROUTE_TYPE_LIVE_VIDEO) != 0) { result.append("ROUTE_TYPE_LIVE_VIDEO "); } + if ((types & ROUTE_TYPE_REMOTE_DISPLAY) != 0) { + result.append("ROUTE_TYPE_REMOTE_DISPLAY "); + } if ((types & ROUTE_TYPE_USER) != 0) { result.append("ROUTE_TYPE_USER "); } @@ -388,6 +740,11 @@ public class MediaRouter { return sStatic.mSystemCategory; } + /** @hide */ + public RouteInfo getSelectedRoute() { + return getSelectedRoute(ROUTE_TYPE_ANY); + } + /** * Return the currently selected route for any of the given types * @@ -411,6 +768,38 @@ public class MediaRouter { } /** + * Returns true if there is a route that matches the specified types. + * <p> + * This method returns true if there are any available routes that match the types + * regardless of whether they are enabled or disabled. If the + * {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE} flag is specified, then + * the method will only consider non-default routes. + * </p> + * + * @param types The types to match. + * @param flags Flags to control the determination of whether a route may be available. + * May be zero or {@link #AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE}. + * @return True if a matching route may be available. + * + * @hide Future API ported from support library. Revisit this later. + */ + public boolean isRouteAvailable(int types, int flags) { + final int count = sStatic.mRoutes.size(); + for (int i = 0; i < count; i++) { + RouteInfo route = sStatic.mRoutes.get(i); + if (route.matchesTypes(types)) { + if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) == 0 + || route != sStatic.mDefaultAudioVideo) { + return true; + } + } + } + + // It doesn't look like we can find a matching route right now. + return false; + } + + /** * Add a callback to listen to events about specific kinds of media routes. * If the specified callback is already registered, its registration will be updated for any * additional route types specified. @@ -453,9 +842,7 @@ public class MediaRouter { info = new CallbackInfo(cb, types, flags, this); sStatic.mCallbacks.add(info); } - if ((info.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) { - sStatic.updateActiveScan(); - } + sStatic.updateDiscoveryRequest(); } /** @@ -466,10 +853,8 @@ public class MediaRouter { public void removeCallback(Callback cb) { int index = findCallbackInfo(cb); if (index >= 0) { - CallbackInfo info = sStatic.mCallbacks.remove(index); - if ((info.flags & CALLBACK_FLAG_PERFORM_ACTIVE_SCAN) != 0) { - sStatic.updateActiveScan(); - } + sStatic.mCallbacks.remove(index); + sStatic.updateDiscoveryRequest(); } else { Log.w(TAG, "removeCallback(" + cb + "): callback not registered"); } @@ -499,20 +884,20 @@ public class MediaRouter { * @param route Route to select */ public void selectRoute(int types, RouteInfo route) { - selectRouteStatic(types, route); + selectRouteStatic(types, route, true); } - + /** * @hide internal use */ - public void selectRouteInt(int types, RouteInfo route) { - selectRouteStatic(types, route); + public void selectRouteInt(int types, RouteInfo route, boolean explicit) { + selectRouteStatic(types, route, explicit); } - static void selectRouteStatic(int types, RouteInfo route) { + static void selectRouteStatic(int types, RouteInfo route, boolean explicit) { final RouteInfo oldRoute = sStatic.mSelectedRoute; if (oldRoute == route) return; - if ((route.getSupportedTypes() & types) == 0) { + if (!route.matchesTypes(types)) { Log.w(TAG, "selectRoute ignored; cannot select route with supported types " + typesToString(route.getSupportedTypes()) + " into route types " + typesToString(types)); @@ -535,21 +920,43 @@ public class MediaRouter { final boolean newRouteHasAddress = route != null && route.mDeviceAddress != null; if (activeDisplay != null || oldRouteHasAddress || newRouteHasAddress) { if (newRouteHasAddress && !matchesDeviceAddress(activeDisplay, route)) { - sStatic.mDisplayService.connectWifiDisplay(route.mDeviceAddress); + if (sStatic.mCanConfigureWifiDisplays) { + sStatic.mDisplayService.connectWifiDisplay(route.mDeviceAddress); + } else { + Log.e(TAG, "Cannot connect to wifi displays because this process " + + "is not allowed to do so."); + } } else if (activeDisplay != null && !newRouteHasAddress) { sStatic.mDisplayService.disconnectWifiDisplay(); } } + sStatic.setSelectedRoute(route, explicit); + if (oldRoute != null) { dispatchRouteUnselected(types & oldRoute.getSupportedTypes(), oldRoute); + if (oldRoute.resolveStatusCode()) { + dispatchRouteChanged(oldRoute); + } } - sStatic.mSelectedRoute = route; if (route != null) { + if (route.resolveStatusCode()) { + dispatchRouteChanged(route); + } dispatchRouteSelected(types & route.getSupportedTypes(), route); } } + static void selectDefaultRouteStatic() { + // TODO: Be smarter about the route types here; this selects for all valid. + if (sStatic.mSelectedRoute != sStatic.mBluetoothA2dpRoute + && sStatic.mBluetoothA2dpRoute != null) { + selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mBluetoothA2dpRoute, false); + } else { + selectRouteStatic(ROUTE_TYPE_ANY, sStatic.mDefaultAudioVideo, false); + } + } + /** * Compare the device address of a display and a route. * Nulls/no device address will match another null/no address. @@ -612,7 +1019,7 @@ public class MediaRouter { * @see #addUserRoute(UserRouteInfo) */ public void removeUserRoute(UserRouteInfo info) { - removeRoute(info); + removeRouteStatic(info); } /** @@ -626,7 +1033,7 @@ public class MediaRouter { // TODO Right now, RouteGroups only ever contain user routes. // The code below will need to change if this assumption does. if (info instanceof UserRouteInfo || info instanceof RouteGroup) { - removeRouteAt(i); + removeRouteStatic(info); i--; } } @@ -636,10 +1043,10 @@ public class MediaRouter { * @hide internal use only */ public void removeRouteInt(RouteInfo info) { - removeRoute(info); + removeRouteStatic(info); } - static void removeRoute(RouteInfo info) { + static void removeRouteStatic(RouteInfo info) { if (sStatic.mRoutes.remove(info)) { final RouteCategory removingCat = info.getCategory(); final int count = sStatic.mRoutes.size(); @@ -651,42 +1058,9 @@ public class MediaRouter { break; } } - if (info == sStatic.mSelectedRoute) { - // Removing the currently selected route? Select the default before we remove it. - // TODO: Be smarter about the route types here; this selects for all valid. - if (info != sStatic.mBluetoothA2dpRoute && sStatic.mBluetoothA2dpRoute != null) { - selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, - sStatic.mBluetoothA2dpRoute); - } else { - selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, - sStatic.mDefaultAudioVideo); - } - } - if (!found) { - sStatic.mCategories.remove(removingCat); - } - dispatchRouteRemoved(info); - } - } - - void removeRouteAt(int routeIndex) { - if (routeIndex >= 0 && routeIndex < sStatic.mRoutes.size()) { - final RouteInfo info = sStatic.mRoutes.remove(routeIndex); - final RouteCategory removingCat = info.getCategory(); - final int count = sStatic.mRoutes.size(); - boolean found = false; - for (int i = 0; i < count; i++) { - final RouteCategory cat = sStatic.mRoutes.get(i).getCategory(); - if (removingCat == cat) { - found = true; - break; - } - } - if (info == sStatic.mSelectedRoute) { + if (info.isSelected()) { // Removing the currently selected route? Select the default before we remove it. - // TODO: Be smarter about the route types here; this selects for all valid. - selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO | ROUTE_TYPE_USER, - sStatic.mDefaultAudioVideo); + selectDefaultRouteStatic(); } if (!found) { sStatic.mCategories.remove(removingCat); @@ -752,7 +1126,7 @@ public class MediaRouter { * * @see #addUserRoute(UserRouteInfo) * @see #removeUserRoute(UserRouteInfo) - * @see #createRouteCategory(CharSequence) + * @see #createRouteCategory(CharSequence, boolean) */ public UserRouteInfo createUserRoute(RouteCategory category) { return new UserRouteInfo(category); @@ -780,6 +1154,23 @@ public class MediaRouter { return new RouteCategory(nameResId, ROUTE_TYPE_USER, isGroupable); } + /** + * Rebinds the media router to handle routes that belong to the specified user. + * Requires the interact across users permission to access the routes of another user. + * <p> + * This method is a complete hack to work around the singleton nature of the + * media router when running inside of singleton processes like QuickSettings. + * This mechanism should be burned to the ground when MediaRouter is redesigned. + * Ideally the current user would be pulled from the Context but we need to break + * down MediaRouter.Static before we can get there. + * </p> + * + * @hide + */ + public void rebindAsUser(int userId) { + sStatic.rebindAsUser(userId); + } + static void updateRoute(final RouteInfo info) { dispatchRouteChanged(info); } @@ -801,10 +1192,34 @@ public class MediaRouter { } static void dispatchRouteChanged(RouteInfo info) { + dispatchRouteChanged(info, info.mSupportedTypes); + } + + static void dispatchRouteChanged(RouteInfo info, int oldSupportedTypes) { + final int newSupportedTypes = info.mSupportedTypes; for (CallbackInfo cbi : sStatic.mCallbacks) { - if (cbi.filterRouteEvent(info)) { + // Reconstruct some of the history for callbacks that may not have observed + // all of the events needed to correctly interpret the current state. + // FIXME: This is a strong signal that we should deprecate route type filtering + // completely in the future because it can lead to inconsistencies in + // applications. + final boolean oldVisibility = cbi.filterRouteEvent(oldSupportedTypes); + final boolean newVisibility = cbi.filterRouteEvent(newSupportedTypes); + if (!oldVisibility && newVisibility) { + cbi.cb.onRouteAdded(cbi.router, info); + if (info.isSelected()) { + cbi.cb.onRouteSelected(cbi.router, newSupportedTypes, info); + } + } + if (oldVisibility || newVisibility) { cbi.cb.onRouteChanged(cbi.router, info); } + if (oldVisibility && !newVisibility) { + if (info.isSelected()) { + cbi.cb.onRouteUnselected(cbi.router, oldSupportedTypes, info); + } + cbi.cb.onRouteRemoved(cbi.router, info); + } } } @@ -875,65 +1290,73 @@ public class MediaRouter { } } - static void updateWifiDisplayStatus(WifiDisplayStatus newStatus) { - final WifiDisplayStatus oldStatus = sStatic.mLastKnownWifiDisplayStatus; - - // TODO Naive implementation. Make this smarter later. + static void updateWifiDisplayStatus(WifiDisplayStatus status) { boolean wantScan = false; - boolean blockScan = false; - WifiDisplay[] oldDisplays = oldStatus != null ? - oldStatus.getDisplays() : WifiDisplay.EMPTY_ARRAY; - WifiDisplay[] newDisplays; + WifiDisplay[] displays; WifiDisplay activeDisplay; - if (newStatus.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) { - newDisplays = newStatus.getDisplays(); - activeDisplay = newStatus.getActiveDisplay(); + if (status.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) { + displays = status.getDisplays(); + activeDisplay = status.getActiveDisplay(); + + // Only the system is able to connect to wifi display routes. + // The display manager will enforce this with a permission check but it + // still publishes information about all available displays. + // Filter the list down to just the active display. + if (!sStatic.mCanConfigureWifiDisplays) { + if (activeDisplay != null) { + displays = new WifiDisplay[] { activeDisplay }; + } else { + displays = WifiDisplay.EMPTY_ARRAY; + } + } } else { - newDisplays = WifiDisplay.EMPTY_ARRAY; + displays = WifiDisplay.EMPTY_ARRAY; activeDisplay = null; } - for (int i = 0; i < newDisplays.length; i++) { - final WifiDisplay d = newDisplays[i]; - if (d.isRemembered()) { + // Add or update routes. + for (int i = 0; i < displays.length; i++) { + final WifiDisplay d = displays[i]; + if (shouldShowWifiDisplay(d, activeDisplay)) { RouteInfo route = findWifiDisplayRoute(d); if (route == null) { - route = makeWifiDisplayRoute(d, newStatus); + route = makeWifiDisplayRoute(d, status); addRouteStatic(route); wantScan = true; } else { - updateWifiDisplayRoute(route, d, newStatus); + updateWifiDisplayRoute(route, d, status); } if (d.equals(activeDisplay)) { - selectRouteStatic(route.getSupportedTypes(), route); - - // Don't scan if we're already connected to a wifi display, - // the scanning process can cause a hiccup with some configurations. - blockScan = true; + selectRouteStatic(route.getSupportedTypes(), route, false); } } } - for (int i = 0; i < oldDisplays.length; i++) { - final WifiDisplay d = oldDisplays[i]; - if (d.isRemembered()) { - final WifiDisplay newDisplay = findMatchingDisplay(d, newDisplays); - if (newDisplay == null || !newDisplay.isRemembered()) { - removeRoute(findWifiDisplayRoute(d)); + + // Remove stale routes. + for (int i = sStatic.mRoutes.size(); i-- > 0; ) { + RouteInfo route = sStatic.mRoutes.get(i); + if (route.mDeviceAddress != null) { + WifiDisplay d = findWifiDisplay(displays, route.mDeviceAddress); + if (d == null || !shouldShowWifiDisplay(d, activeDisplay)) { + removeRouteStatic(route); } } } - if (wantScan && !blockScan) { + // Don't scan if we're already connected to a wifi display, + // the scanning process can cause a hiccup with some configurations. + if (wantScan && activeDisplay != null && sStatic.mCanConfigureWifiDisplays) { sStatic.mDisplayService.scanWifiDisplays(); } + } - sStatic.mLastKnownWifiDisplayStatus = newStatus; + private static boolean shouldShowWifiDisplay(WifiDisplay d, WifiDisplay activeDisplay) { + return d.isRemembered() || d.equals(activeDisplay); } static int getWifiDisplayStatusCode(WifiDisplay d, WifiDisplayStatus wfdStatus) { - int newStatus = RouteInfo.STATUS_NONE; - + int newStatus; if (wfdStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING) { newStatus = RouteInfo.STATUS_SCANNING; } else if (d.isAvailable()) { @@ -947,7 +1370,7 @@ public class MediaRouter { final int activeState = wfdStatus.getActiveDisplayState(); switch (activeState) { case WifiDisplayStatus.DISPLAY_STATE_CONNECTED: - newStatus = RouteInfo.STATUS_NONE; + newStatus = RouteInfo.STATUS_CONNECTED; break; case WifiDisplayStatus.DISPLAY_STATE_CONNECTING: newStatus = RouteInfo.STATUS_CONNECTING; @@ -968,18 +1391,17 @@ public class MediaRouter { static RouteInfo makeWifiDisplayRoute(WifiDisplay display, WifiDisplayStatus wfdStatus) { final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory); newRoute.mDeviceAddress = display.getDeviceAddress(); - newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO; + newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO + | ROUTE_TYPE_REMOTE_DISPLAY; newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED; newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE; - newRoute.setStatusCode(getWifiDisplayStatusCode(display, wfdStatus)); + newRoute.setRealStatusCode(getWifiDisplayStatusCode(display, wfdStatus)); newRoute.mEnabled = isWifiDisplayEnabled(display, wfdStatus); newRoute.mName = display.getFriendlyDisplayName(); newRoute.mDescription = sStatic.mResources.getText( com.android.internal.R.string.wireless_display_route_description); - - newRoute.mPresentationDisplay = choosePresentationDisplayForRoute(newRoute, - sStatic.getAllPresentationDisplays()); + newRoute.updatePresentationDisplay(); return newRoute; } @@ -996,24 +1418,23 @@ public class MediaRouter { changed |= route.mEnabled != enabled; route.mEnabled = enabled; - changed |= route.setStatusCode(getWifiDisplayStatusCode(display, wfdStatus)); + changed |= route.setRealStatusCode(getWifiDisplayStatusCode(display, wfdStatus)); if (changed) { dispatchRouteChanged(route); } - if (!enabled && route == sStatic.mSelectedRoute) { + if (!enabled && route.isSelected()) { // Oops, no longer available. Reselect the default. - final RouteInfo defaultRoute = sStatic.mDefaultAudioVideo; - selectRouteStatic(defaultRoute.getSupportedTypes(), defaultRoute); + selectDefaultRouteStatic(); } } - private static WifiDisplay findMatchingDisplay(WifiDisplay d, WifiDisplay[] displays) { + private static WifiDisplay findWifiDisplay(WifiDisplay[] displays, String deviceAddress) { for (int i = 0; i < displays.length; i++) { - final WifiDisplay other = displays[i]; - if (d.hasSameAddress(other)) { - return other; + final WifiDisplay d = displays[i]; + if (d.getDeviceAddress().equals(deviceAddress)) { + return d; } } return null; @@ -1030,27 +1451,6 @@ public class MediaRouter { return null; } - private static Display choosePresentationDisplayForRoute(RouteInfo route, Display[] displays) { - if ((route.mSupportedTypes & ROUTE_TYPE_LIVE_VIDEO) != 0) { - if (route.mDeviceAddress != null) { - // Find the indicated Wifi display by its address. - for (Display display : displays) { - if (display.getType() == Display.TYPE_WIFI - && route.mDeviceAddress.equals(display.getAddress())) { - return display; - } - } - return null; - } - - if (route == sStatic.mDefaultAudioVideo && displays.length > 0) { - // Choose the first presentation display from the list. - return displays[0]; - } - } - return null; - } - /** * Information about a media route. */ @@ -1071,12 +1471,18 @@ public class MediaRouter { int mPlaybackStream = AudioManager.STREAM_MUSIC; VolumeCallbackInfo mVcb; Display mPresentationDisplay; + int mPresentationDisplayId = -1; String mDeviceAddress; boolean mEnabled = true; + // An id by which the route is known to the media router service. + // Null if this route only exists as an artifact within this process. + String mGlobalRouteId; + // A predetermined connection status that can override mStatus - private int mStatusCode; + private int mRealStatusCode; + private int mResolvedStatusCode; /** @hide */ public static final int STATUS_NONE = 0; /** @hide */ public static final int STATUS_SCANNING = 1; @@ -1084,19 +1490,20 @@ public class MediaRouter { /** @hide */ public static final int STATUS_AVAILABLE = 3; /** @hide */ public static final int STATUS_NOT_AVAILABLE = 4; /** @hide */ public static final int STATUS_IN_USE = 5; + /** @hide */ public static final int STATUS_CONNECTED = 6; private Object mTag; /** * The default playback type, "local", indicating the presentation of the media is happening * on the same device (e.g. a phone, a tablet) as where it is controlled from. - * @see #setPlaybackType(int) + * @see #getPlaybackType() */ public final static int PLAYBACK_TYPE_LOCAL = 0; /** * A playback type indicating the presentation of the media is happening on * a different device (i.e. the remote device) than where it is controlled from. - * @see #setPlaybackType(int) + * @see #getPlaybackType() */ public final static int PLAYBACK_TYPE_REMOTE = 1; /** @@ -1104,12 +1511,13 @@ public class MediaRouter { * controlled from this object. An example of fixed playback volume is a remote player, * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather * than attenuate at the source. - * @see #setVolumeHandling(int) + * @see #getVolumeHandling() */ public final static int PLAYBACK_VOLUME_FIXED = 0; /** * Playback information indicating the playback volume is variable and can be controlled * from this object. + * @see #getVolumeHandling() */ public final static int PLAYBACK_VOLUME_VARIABLE = 1; @@ -1178,38 +1586,71 @@ public class MediaRouter { * Set this route's status by predetermined status code. If the caller * should dispatch a route changed event this call will return true; */ - boolean setStatusCode(int statusCode) { - if (statusCode != mStatusCode) { - mStatusCode = statusCode; - int resId = 0; + boolean setRealStatusCode(int statusCode) { + if (mRealStatusCode != statusCode) { + mRealStatusCode = statusCode; + return resolveStatusCode(); + } + return false; + } + + /** + * Resolves the status code whenever the real status code or selection state + * changes. + */ + boolean resolveStatusCode() { + int statusCode = mRealStatusCode; + if (isSelected()) { switch (statusCode) { - case STATUS_SCANNING: - resId = com.android.internal.R.string.media_route_status_scanning; - break; - case STATUS_CONNECTING: - resId = com.android.internal.R.string.media_route_status_connecting; - break; + // If the route is selected and its status appears to be between states + // then report it as connecting even though it has not yet had a chance + // to officially move into the CONNECTING state. Note that routes in + // the NONE state are assumed to not require an explicit connection + // lifecycle whereas those that are AVAILABLE are assumed to have + // to eventually proceed to CONNECTED. case STATUS_AVAILABLE: - resId = com.android.internal.R.string.media_route_status_available; - break; - case STATUS_NOT_AVAILABLE: - resId = com.android.internal.R.string.media_route_status_not_available; - break; - case STATUS_IN_USE: - resId = com.android.internal.R.string.media_route_status_in_use; + case STATUS_SCANNING: + statusCode = STATUS_CONNECTING; break; } - mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null; - return true; } - return false; + if (mResolvedStatusCode == statusCode) { + return false; + } + + mResolvedStatusCode = statusCode; + int resId; + switch (statusCode) { + case STATUS_SCANNING: + resId = com.android.internal.R.string.media_route_status_scanning; + break; + case STATUS_CONNECTING: + resId = com.android.internal.R.string.media_route_status_connecting; + break; + case STATUS_AVAILABLE: + resId = com.android.internal.R.string.media_route_status_available; + break; + case STATUS_NOT_AVAILABLE: + resId = com.android.internal.R.string.media_route_status_not_available; + break; + case STATUS_IN_USE: + resId = com.android.internal.R.string.media_route_status_in_use; + break; + case STATUS_CONNECTED: + case STATUS_NONE: + default: + resId = 0; + break; + } + mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null; + return true; } /** * @hide */ public int getStatusCode() { - return mStatusCode; + return mResolvedStatusCode; } /** @@ -1219,6 +1660,11 @@ public class MediaRouter { return mSupportedTypes; } + /** @hide */ + public boolean matchesTypes(int types) { + return (mSupportedTypes & types) != 0; + } + /** * @return The group that this route belongs to. */ @@ -1317,9 +1763,7 @@ public class MediaRouter { Log.e(TAG, "Error setting local stream volume", e); } } else { - Log.e(TAG, getClass().getSimpleName() + ".requestSetVolume(): " + - "Non-local volume playback on system route? " + - "Could not request volume change."); + sStatic.requestSetVolume(this, volume); } } @@ -1338,9 +1782,7 @@ public class MediaRouter { Log.e(TAG, "Error setting local stream volume", e); } } else { - Log.e(TAG, getClass().getSimpleName() + ".requestChangeVolume(): " + - "Non-local volume playback on system route? " + - "Could not request volume change."); + sStatic.requestUpdateVolume(this, direction); } } @@ -1402,6 +1844,55 @@ public class MediaRouter { return mPresentationDisplay; } + boolean updatePresentationDisplay() { + Display display = choosePresentationDisplay(); + if (mPresentationDisplay != display) { + mPresentationDisplay = display; + return true; + } + return false; + } + + private Display choosePresentationDisplay() { + if ((mSupportedTypes & ROUTE_TYPE_LIVE_VIDEO) != 0) { + Display[] displays = sStatic.getAllPresentationDisplays(); + + // Ensure that the specified display is valid for presentations. + // This check will normally disallow the default display unless it was + // configured as a presentation display for some reason. + if (mPresentationDisplayId >= 0) { + for (Display display : displays) { + if (display.getDisplayId() == mPresentationDisplayId) { + return display; + } + } + return null; + } + + // Find the indicated Wifi display by its address. + if (mDeviceAddress != null) { + for (Display display : displays) { + if (display.getType() == Display.TYPE_WIFI + && mDeviceAddress.equals(display.getAddress())) { + return display; + } + } + return null; + } + + // For the default route, choose the first presentation display from the list. + if (this == sStatic.mDefaultAudioVideo && displays.length > 0) { + return displays[0]; + } + } + return null; + } + + /** @hide */ + public String getDeviceAddress() { + return mDeviceAddress; + } + /** * Returns true if this route is enabled and may be selected. * @@ -1418,7 +1909,22 @@ public class MediaRouter { * @return True if this route is in the process of connecting. */ public boolean isConnecting() { - return mStatusCode == STATUS_CONNECTING; + return mResolvedStatusCode == STATUS_CONNECTING; + } + + /** @hide */ + public boolean isSelected() { + return this == sStatic.mSelectedRoute; + } + + /** @hide */ + public boolean isDefault() { + return this == sStatic.mDefaultAudioVideo; + } + + /** @hide */ + public void select() { + selectRouteStatic(mSupportedTypes, this, true); } void setStatusInt(CharSequence status) { @@ -1432,6 +1938,7 @@ public class MediaRouter { } final IRemoteVolumeObserver.Stub mRemoteVolObserver = new IRemoteVolumeObserver.Stub() { + @Override public void dispatchRemoteVolumeUpdate(final int direction, final int value) { sStatic.mHandler.post(new Runnable() { @Override @@ -1460,7 +1967,7 @@ public class MediaRouter { ", status=" + getStatus() + ", category=" + getCategory() + ", supportedTypes=" + supportedTypes + - ", presentationDisplay=" + mPresentationDisplay + "}"; + ", presentationDisplay=" + mPresentationDisplay + " }"; } } @@ -1716,6 +2223,7 @@ public class MediaRouter { mVolumeHandling = PLAYBACK_VOLUME_FIXED; } + @Override CharSequence getName(Resources res) { if (mUpdateName) updateName(); return super.getName(res); @@ -1916,7 +2424,7 @@ public class MediaRouter { final int count = mRoutes.size(); if (count == 0) { // Don't keep empty groups in the router. - MediaRouter.removeRoute(this); + MediaRouter.removeRouteStatic(this); return; } @@ -2071,6 +2579,7 @@ public class MediaRouter { return mIsSystem; } + @Override public String toString() { return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) + " groupable=" + mGroupable + " }"; @@ -2091,8 +2600,12 @@ public class MediaRouter { } public boolean filterRouteEvent(RouteInfo route) { + return filterRouteEvent(route.mSupportedTypes); + } + + public boolean filterRouteEvent(int supportedTypes) { return (flags & CALLBACK_FLAG_UNFILTERED_EVENTS) != 0 - || (type & route.mSupportedTypes) != 0; + || (type & supportedTypes) != 0; } } diff --git a/media/java/android/media/MediaRouterClientState.aidl b/media/java/android/media/MediaRouterClientState.aidl new file mode 100644 index 0000000..70077119 --- /dev/null +++ b/media/java/android/media/MediaRouterClientState.aidl @@ -0,0 +1,18 @@ +/* Copyright 2013, 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 android.media; + +parcelable MediaRouterClientState; diff --git a/media/java/android/media/MediaRouterClientState.java b/media/java/android/media/MediaRouterClientState.java new file mode 100644 index 0000000..54b8276 --- /dev/null +++ b/media/java/android/media/MediaRouterClientState.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2013 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 android.media; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; + +/** + * Information available from MediaRouterService about the state perceived by + * a particular client and the routes that are available to it. + * + * Clients must not modify the contents of this object. + * @hide + */ +public final class MediaRouterClientState implements Parcelable { + /** + * A list of all known routes. + */ + public final ArrayList<RouteInfo> routes; + + /** + * The id of the current globally selected route, or null if none. + * Globally selected routes override any other route selections that applications + * may have made. Used for remote displays. + */ + public String globallySelectedRouteId; + + public MediaRouterClientState() { + routes = new ArrayList<RouteInfo>(); + } + + MediaRouterClientState(Parcel src) { + routes = src.createTypedArrayList(RouteInfo.CREATOR); + globallySelectedRouteId = src.readString(); + } + + public RouteInfo getRoute(String id) { + final int count = routes.size(); + for (int i = 0; i < count; i++) { + final RouteInfo route = routes.get(i); + if (route.id.equals(id)) { + return route; + } + } + return null; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedList(routes); + dest.writeString(globallySelectedRouteId); + } + + @Override + public String toString() { + return "MediaRouterClientState{ globallySelectedRouteId=" + + globallySelectedRouteId + ", routes=" + routes.toString() + " }"; + } + + public static final Parcelable.Creator<MediaRouterClientState> CREATOR = + new Parcelable.Creator<MediaRouterClientState>() { + @Override + public MediaRouterClientState createFromParcel(Parcel in) { + return new MediaRouterClientState(in); + } + + @Override + public MediaRouterClientState[] newArray(int size) { + return new MediaRouterClientState[size]; + } + }; + + public static final class RouteInfo implements Parcelable { + public String id; + public String name; + public String description; + public int supportedTypes; + public boolean enabled; + public int statusCode; + public int playbackType; + public int playbackStream; + public int volume; + public int volumeMax; + public int volumeHandling; + public int presentationDisplayId; + + public RouteInfo(String id) { + this.id = id; + enabled = true; + statusCode = MediaRouter.RouteInfo.STATUS_NONE; + playbackType = MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE; + playbackStream = -1; + volumeHandling = MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED; + presentationDisplayId = -1; + } + + public RouteInfo(RouteInfo other) { + id = other.id; + name = other.name; + description = other.description; + supportedTypes = other.supportedTypes; + enabled = other.enabled; + statusCode = other.statusCode; + playbackType = other.playbackType; + playbackStream = other.playbackStream; + volume = other.volume; + volumeMax = other.volumeMax; + volumeHandling = other.volumeHandling; + presentationDisplayId = other.presentationDisplayId; + } + + RouteInfo(Parcel in) { + id = in.readString(); + name = in.readString(); + description = in.readString(); + supportedTypes = in.readInt(); + enabled = in.readInt() != 0; + statusCode = in.readInt(); + playbackType = in.readInt(); + playbackStream = in.readInt(); + volume = in.readInt(); + volumeMax = in.readInt(); + volumeHandling = in.readInt(); + presentationDisplayId = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); + dest.writeString(name); + dest.writeString(description); + dest.writeInt(supportedTypes); + dest.writeInt(enabled ? 1 : 0); + dest.writeInt(statusCode); + dest.writeInt(playbackType); + dest.writeInt(playbackStream); + dest.writeInt(volume); + dest.writeInt(volumeMax); + dest.writeInt(volumeHandling); + dest.writeInt(presentationDisplayId); + } + + @Override + public String toString() { + return "RouteInfo{ id=" + id + + ", name=" + name + + ", description=" + description + + ", supportedTypes=0x" + Integer.toHexString(supportedTypes) + + ", enabled=" + enabled + + ", statusCode=" + statusCode + + ", playbackType=" + playbackType + + ", playbackStream=" + playbackStream + + ", volume=" + volume + + ", volumeMax=" + volumeMax + + ", volumeHandling=" + volumeHandling + + ", presentationDisplayId=" + presentationDisplayId + + " }"; + } + + @SuppressWarnings("hiding") + public static final Parcelable.Creator<RouteInfo> CREATOR = + new Parcelable.Creator<RouteInfo>() { + @Override + public RouteInfo createFromParcel(Parcel in) { + return new RouteInfo(in); + } + + @Override + public RouteInfo[] newArray(int size) { + return new RouteInfo[size]; + } + }; + } +} diff --git a/media/java/android/media/RemoteDisplayState.aidl b/media/java/android/media/RemoteDisplayState.aidl new file mode 100644 index 0000000..b3262fc --- /dev/null +++ b/media/java/android/media/RemoteDisplayState.aidl @@ -0,0 +1,18 @@ +/* Copyright 2013, 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 android.media; + +parcelable RemoteDisplayState; diff --git a/media/java/android/media/RemoteDisplayState.java b/media/java/android/media/RemoteDisplayState.java new file mode 100644 index 0000000..1197f65 --- /dev/null +++ b/media/java/android/media/RemoteDisplayState.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2013 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 android.media; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import java.util.ArrayList; + +/** + * Information available from IRemoteDisplayProvider about available remote displays. + * + * Clients must not modify the contents of this object. + * @hide + */ +public final class RemoteDisplayState implements Parcelable { + // Note: These constants are used by the remote display provider API. + // Do not change them! + public static final String SERVICE_INTERFACE = + "com.android.media.remotedisplay.RemoteDisplayProvider"; + public static final int DISCOVERY_MODE_NONE = 0; + public static final int DISCOVERY_MODE_PASSIVE = 1; + public static final int DISCOVERY_MODE_ACTIVE = 2; + + /** + * A list of all remote displays. + */ + public final ArrayList<RemoteDisplayInfo> displays; + + public RemoteDisplayState() { + displays = new ArrayList<RemoteDisplayInfo>(); + } + + RemoteDisplayState(Parcel src) { + displays = src.createTypedArrayList(RemoteDisplayInfo.CREATOR); + } + + public boolean isValid() { + if (displays == null) { + return false; + } + final int count = displays.size(); + for (int i = 0; i < count; i++) { + if (!displays.get(i).isValid()) { + return false; + } + } + return true; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedList(displays); + } + + public static final Parcelable.Creator<RemoteDisplayState> CREATOR = + new Parcelable.Creator<RemoteDisplayState>() { + @Override + public RemoteDisplayState createFromParcel(Parcel in) { + return new RemoteDisplayState(in); + } + + @Override + public RemoteDisplayState[] newArray(int size) { + return new RemoteDisplayState[size]; + } + }; + + public static final class RemoteDisplayInfo implements Parcelable { + // Note: These constants are used by the remote display provider API. + // Do not change them! + public static final int STATUS_NOT_AVAILABLE = 0; + public static final int STATUS_IN_USE = 1; + public static final int STATUS_AVAILABLE = 2; + public static final int STATUS_CONNECTING = 3; + public static final int STATUS_CONNECTED = 4; + + public static final int PLAYBACK_VOLUME_VARIABLE = + MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE; + public static final int PLAYBACK_VOLUME_FIXED = + MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED; + + public String id; + public String name; + public String description; + public int status; + public int volume; + public int volumeMax; + public int volumeHandling; + public int presentationDisplayId; + + public RemoteDisplayInfo(String id) { + this.id = id; + status = STATUS_NOT_AVAILABLE; + volumeHandling = MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED; + presentationDisplayId = -1; + } + + public RemoteDisplayInfo(RemoteDisplayInfo other) { + id = other.id; + name = other.name; + description = other.description; + status = other.status; + volume = other.volume; + volumeMax = other.volumeMax; + volumeHandling = other.volumeHandling; + presentationDisplayId = other.presentationDisplayId; + } + + RemoteDisplayInfo(Parcel in) { + id = in.readString(); + name = in.readString(); + description = in.readString(); + status = in.readInt(); + volume = in.readInt(); + volumeMax = in.readInt(); + volumeHandling = in.readInt(); + presentationDisplayId = in.readInt(); + } + + public boolean isValid() { + return !TextUtils.isEmpty(id) && !TextUtils.isEmpty(name); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); + dest.writeString(name); + dest.writeString(description); + dest.writeInt(status); + dest.writeInt(volume); + dest.writeInt(volumeMax); + dest.writeInt(volumeHandling); + dest.writeInt(presentationDisplayId); + } + + @Override + public String toString() { + return "RemoteDisplayInfo{ id=" + id + + ", name=" + name + + ", description=" + description + + ", status=" + status + + ", volume=" + volume + + ", volumeMax=" + volumeMax + + ", volumeHandling=" + volumeHandling + + ", presentationDisplayId=" + presentationDisplayId + + " }"; + } + + @SuppressWarnings("hiding") + public static final Parcelable.Creator<RemoteDisplayInfo> CREATOR = + new Parcelable.Creator<RemoteDisplayInfo>() { + @Override + public RemoteDisplayInfo createFromParcel(Parcel in) { + return new RemoteDisplayInfo(in); + } + + @Override + public RemoteDisplayInfo[] newArray(int size) { + return new RemoteDisplayInfo[size]; + } + }; + } +} diff --git a/media/lib/Android.mk b/media/lib/Android.mk new file mode 100644 index 0000000..50799a6 --- /dev/null +++ b/media/lib/Android.mk @@ -0,0 +1,46 @@ +# +# Copyright (C) 2013 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. +# +LOCAL_PATH := $(call my-dir) + +# the library +# ============================================================ +include $(CLEAR_VARS) + +LOCAL_MODULE:= com.android.media.remotedisplay +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := \ + $(call all-subdir-java-files) \ + $(call all-aidl-files-under, java) + +include $(BUILD_JAVA_LIBRARY) + + +# ==== com.android.media.remotedisplay.xml lib def ======================== +include $(CLEAR_VARS) + +LOCAL_MODULE := com.android.media.remotedisplay.xml +LOCAL_MODULE_TAGS := optional + +LOCAL_MODULE_CLASS := ETC + +# This will install the file in /system/etc/permissions +# +LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions + +LOCAL_SRC_FILES := $(LOCAL_MODULE) + +include $(BUILD_PREBUILT) diff --git a/media/lib/README.txt b/media/lib/README.txt new file mode 100644 index 0000000..cade3df --- /dev/null +++ b/media/lib/README.txt @@ -0,0 +1,28 @@ +This library (com.android.media.remotedisplay.jar) is a shared java library +containing classes required by unbundled remote display providers. + +--- Rules of this library --- +o This library is effectively a PUBLIC API for unbundled remote display providers + that may be distributed outside the system image. So it MUST BE API STABLE. + You can add but not remove. The rules are the same as for the + public platform SDK API. +o This library can see and instantiate internal platform classes, but it must not + expose them in any public method (or by extending them via inheritance). This would + break clients of the library because they cannot see the internal platform classes. + +This library is distributed in the system image, and loaded as +a shared library. So you can change the implementation, but not +the interface. In this way it is like framework.jar. + +--- Why does this library exists? --- + +Unbundled remote display providers (such as Cast) cannot use internal +platform classes. + +This library will eventually be replaced when the media route provider +infrastructure that is currently defined in the support library is reintegrated +with the framework in a new API. That API isn't ready yet so this +library is a compromise to make new capabilities available to the system +without exposing the full surface area of the support library media +route provider protocol. + diff --git a/core/res/res/layout/media_route_list_item_top_header.xml b/media/lib/com.android.media.remotedisplay.xml index 0c49b24..77a91d2 100644 --- a/core/res/res/layout/media_route_list_item_top_header.xml +++ b/media/lib/com.android.media.remotedisplay.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2012 The Android Open Source Project +<!-- Copyright (C) 2013 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. @@ -14,16 +14,7 @@ limitations under the License. --> -<TextView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@android:id/text1" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceSmall" - android:background="#19ffffff" - android:textStyle="bold" - android:textAllCaps="true" - android:gravity="center_vertical" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:minHeight="24dp" -/> +<permissions> + <library name="com.android.media.remotedisplay" + file="/system/framework/com.android.media.remotedisplay.jar" /> +</permissions> diff --git a/media/lib/java/com/android/media/remotedisplay/RemoteDisplay.java b/media/lib/java/com/android/media/remotedisplay/RemoteDisplay.java new file mode 100644 index 0000000..5e15702 --- /dev/null +++ b/media/lib/java/com/android/media/remotedisplay/RemoteDisplay.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2013 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.media.remotedisplay; + +import com.android.internal.util.Objects; + +import android.media.MediaRouter; +import android.media.RemoteDisplayState.RemoteDisplayInfo; +import android.text.TextUtils; + +/** + * Represents a remote display that has been discovered. + */ +public class RemoteDisplay { + private final RemoteDisplayInfo mMutableInfo; + private RemoteDisplayInfo mImmutableInfo; + + /** + * Status code: Indicates that the remote display is not available. + */ + public static final int STATUS_NOT_AVAILABLE = RemoteDisplayInfo.STATUS_NOT_AVAILABLE; + + /** + * Status code: Indicates that the remote display is in use by someone else. + */ + public static final int STATUS_IN_USE = RemoteDisplayInfo.STATUS_IN_USE; + + /** + * Status code: Indicates that the remote display is available for new connections. + */ + public static final int STATUS_AVAILABLE = RemoteDisplayInfo.STATUS_AVAILABLE; + + /** + * Status code: Indicates that the remote display is current connecting. + */ + public static final int STATUS_CONNECTING = RemoteDisplayInfo.STATUS_CONNECTING; + + /** + * Status code: Indicates that the remote display is connected and is mirroring + * display contents. + */ + public static final int STATUS_CONNECTED = RemoteDisplayInfo.STATUS_CONNECTED; + + /** + * Volume handling: Output volume can be changed. + */ + public static final int PLAYBACK_VOLUME_VARIABLE = + RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE; + + /** + * Volume handling: Output volume is fixed. + */ + public static final int PLAYBACK_VOLUME_FIXED = + RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED; + + /** + * Creates a remote display with the specified name and id. + */ + public RemoteDisplay(String id, String name) { + if (TextUtils.isEmpty(id)) { + throw new IllegalArgumentException("id must not be null or empty"); + } + mMutableInfo = new RemoteDisplayInfo(id); + setName(name); + } + + public String getId() { + return mMutableInfo.id; + } + + public String getName() { + return mMutableInfo.name; + } + + public void setName(String name) { + if (!Objects.equal(mMutableInfo.name, name)) { + mMutableInfo.name = name; + mImmutableInfo = null; + } + } + + public String getDescription() { + return mMutableInfo.description; + } + + public void setDescription(String description) { + if (!Objects.equal(mMutableInfo.description, description)) { + mMutableInfo.description = description; + mImmutableInfo = null; + } + } + + public int getStatus() { + return mMutableInfo.status; + } + + public void setStatus(int status) { + if (mMutableInfo.status != status) { + mMutableInfo.status = status; + mImmutableInfo = null; + } + } + + public int getVolume() { + return mMutableInfo.volume; + } + + public void setVolume(int volume) { + if (mMutableInfo.volume != volume) { + mMutableInfo.volume = volume; + mImmutableInfo = null; + } + } + + public int getVolumeMax() { + return mMutableInfo.volumeMax; + } + + public void setVolumeMax(int volumeMax) { + if (mMutableInfo.volumeMax != volumeMax) { + mMutableInfo.volumeMax = volumeMax; + mImmutableInfo = null; + } + } + + public int getVolumeHandling() { + return mMutableInfo.volumeHandling; + } + + public void setVolumeHandling(int volumeHandling) { + if (mMutableInfo.volumeHandling != volumeHandling) { + mMutableInfo.volumeHandling = volumeHandling; + mImmutableInfo = null; + } + } + + public int getPresentationDisplayId() { + return mMutableInfo.presentationDisplayId; + } + + public void setPresentationDisplayId(int presentationDisplayId) { + if (mMutableInfo.presentationDisplayId != presentationDisplayId) { + mMutableInfo.presentationDisplayId = presentationDisplayId; + mImmutableInfo = null; + } + } + + @Override + public String toString() { + return "RemoteDisplay{" + mMutableInfo.toString() + "}"; + } + + RemoteDisplayInfo getInfo() { + if (mImmutableInfo == null) { + mImmutableInfo = new RemoteDisplayInfo(mMutableInfo); + } + return mImmutableInfo; + } +} diff --git a/media/lib/java/com/android/media/remotedisplay/RemoteDisplayProvider.java b/media/lib/java/com/android/media/remotedisplay/RemoteDisplayProvider.java new file mode 100644 index 0000000..e2df77c --- /dev/null +++ b/media/lib/java/com/android/media/remotedisplay/RemoteDisplayProvider.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2013 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.media.remotedisplay; + +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.media.IRemoteDisplayCallback; +import android.media.IRemoteDisplayProvider; +import android.media.RemoteDisplayState; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.provider.Settings; +import android.util.ArrayMap; + +import java.util.Collection; + +/** + * Base class for remote display providers implemented as unbundled services. + * <p> + * To implement your remote display provider service, create a subclass of + * {@link Service} and override the {@link Service#onBind Service.onBind()} method + * to return the provider's binder when the {@link #SERVICE_INTERFACE} is requested. + * </p> + * <pre> + * public class SampleRemoteDisplayProviderService extends Service { + * private SampleProvider mProvider; + * + * public IBinder onBind(Intent intent) { + * if (intent.getAction().equals(RemoteDisplayProvider.SERVICE_INTERFACE)) { + * if (mProvider == null) { + * mProvider = new SampleProvider(this); + * } + * return mProvider.getBinder(); + * } + * return null; + * } + * + * class SampleProvider extends RemoteDisplayProvider { + * public SampleProvider() { + * super(SampleRemoteDisplayProviderService.this); + * } + * + * // --- Implementation goes here --- + * } + * } + * </pre> + * <p> + * Declare your remote display provider service in your application manifest + * like this: + * </p> + * <pre> + * <application> + * <uses-library android:name="com.android.media.remotedisplay" /> + * + * <service android:name=".SampleRemoteDisplayProviderService" + * android:label="@string/sample_remote_display_provider_service" + * android:exported="true" + * android:permission="android.permission.BIND_REMOTE_DISPLAY"> + * <intent-filter> + * <action android:name="com.android.media.remotedisplay.RemoteDisplayProvider" /> + * </intent-filter> + * </service> + * </application> + * </pre> + * <p> + * This object is not thread safe. It is only intended to be accessed on the + * {@link Context#getMainLooper main looper thread} of an application. + * </p><p> + * IMPORTANT: This class is effectively a public API for unbundled applications, and + * must remain API stable. See README.txt in the root of this package for more information. + * </p> + */ +public abstract class RemoteDisplayProvider { + private static final int MSG_SET_CALLBACK = 1; + private static final int MSG_SET_DISCOVERY_MODE = 2; + private static final int MSG_CONNECT = 3; + private static final int MSG_DISCONNECT = 4; + private static final int MSG_SET_VOLUME = 5; + private static final int MSG_ADJUST_VOLUME = 6; + + private final Context mContext; + private final ProviderStub mStub; + private final ProviderHandler mHandler; + private final ArrayMap<String, RemoteDisplay> mDisplays = + new ArrayMap<String, RemoteDisplay>(); + private IRemoteDisplayCallback mCallback; + private int mDiscoveryMode = DISCOVERY_MODE_NONE; + + private PendingIntent mSettingsPendingIntent; + + /** + * The {@link Intent} that must be declared as handled by the service. + * Put this in your manifest. + */ + public static final String SERVICE_INTERFACE = RemoteDisplayState.SERVICE_INTERFACE; + + /** + * Discovery mode: Do not perform any discovery. + */ + public static final int DISCOVERY_MODE_NONE = RemoteDisplayState.DISCOVERY_MODE_NONE; + + /** + * Discovery mode: Passive or low-power periodic discovery. + * <p> + * This mode indicates that an application is interested in knowing whether there + * are any remote displays paired or available but doesn't need the latest or + * most detailed information. The provider may scan at a lower rate or rely on + * knowledge of previously paired devices. + * </p> + */ + public static final int DISCOVERY_MODE_PASSIVE = RemoteDisplayState.DISCOVERY_MODE_PASSIVE; + + /** + * Discovery mode: Active discovery. + * <p> + * This mode indicates that the user is actively trying to connect to a route + * and we should perform continuous scans. This mode may use significantly more + * power but is intended to be short-lived. + * </p> + */ + public static final int DISCOVERY_MODE_ACTIVE = RemoteDisplayState.DISCOVERY_MODE_ACTIVE; + + /** + * Creates a remote display provider. + * + * @param context The application context for the remote display provider. + */ + public RemoteDisplayProvider(Context context) { + mContext = context; + mStub = new ProviderStub(); + mHandler = new ProviderHandler(context.getMainLooper()); + } + + /** + * Gets the context of the remote display provider. + */ + public final Context getContext() { + return mContext; + } + + /** + * Gets the Binder associated with the provider. + * <p> + * This is intended to be used for the onBind() method of a service that implements + * a remote display provider service. + * </p> + * + * @return The IBinder instance associated with the provider. + */ + public IBinder getBinder() { + return mStub; + } + + /** + * Called when the current discovery mode changes. + * + * @param mode The new discovery mode. + */ + public void onDiscoveryModeChanged(int mode) { + } + + /** + * Called when the system would like to connect to a display. + * + * @param display The remote display. + */ + public void onConnect(RemoteDisplay display) { + } + + /** + * Called when the system would like to disconnect from a display. + * + * @param display The remote display. + */ + public void onDisconnect(RemoteDisplay display) { + } + + /** + * Called when the system would like to set the volume of a display. + * + * @param display The remote display. + * @param volume The desired volume. + */ + public void onSetVolume(RemoteDisplay display, int volume) { + } + + /** + * Called when the system would like to adjust the volume of a display. + * + * @param display The remote display. + * @param delta An increment to add to the current volume, such as +1 or -1. + */ + public void onAdjustVolume(RemoteDisplay display, int delta) { + } + + /** + * Gets the current discovery mode. + * + * @return The current discovery mode. + */ + public int getDiscoveryMode() { + return mDiscoveryMode; + } + + /** + * Gets the current collection of displays. + * + * @return The current collection of displays, which must not be modified. + */ + public Collection<RemoteDisplay> getDisplays() { + return mDisplays.values(); + } + + /** + * Adds the specified remote display and notifies the system. + * + * @param display The remote display that was added. + * @throws IllegalStateException if there is already a display with the same id. + */ + public void addDisplay(RemoteDisplay display) { + if (display == null || mDisplays.containsKey(display.getId())) { + throw new IllegalArgumentException("display"); + } + mDisplays.put(display.getId(), display); + publishState(); + } + + /** + * Updates information about the specified remote display and notifies the system. + * + * @param display The remote display that was added. + * @throws IllegalStateException if the display was n + */ + public void updateDisplay(RemoteDisplay display) { + if (display == null || mDisplays.get(display.getId()) != display) { + throw new IllegalArgumentException("display"); + } + publishState(); + } + + /** + * Removes the specified remote display and tells the system about it. + * + * @param display The remote display that was removed. + */ + public void removeDisplay(RemoteDisplay display) { + if (display == null || mDisplays.get(display.getId()) != display) { + throw new IllegalArgumentException("display"); + } + mDisplays.remove(display.getId()); + publishState(); + } + + /** + * Finds the remote display with the specified id, returns null if not found. + * + * @param id Id of the remote display. + * @return The display, or null if none. + */ + public RemoteDisplay findRemoteDisplay(String id) { + return mDisplays.get(id); + } + + /** + * Gets a pending intent to launch the remote display settings activity. + * + * @return A pending intent to launch the settings activity. + */ + public PendingIntent getSettingsPendingIntent() { + if (mSettingsPendingIntent == null) { + Intent settingsIntent = new Intent(Settings.ACTION_WIFI_DISPLAY_SETTINGS); + settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mSettingsPendingIntent = PendingIntent.getActivity( + mContext, 0, settingsIntent, 0, null); + } + return mSettingsPendingIntent; + } + + void setCallback(IRemoteDisplayCallback callback) { + mCallback = callback; + publishState(); + } + + void setDiscoveryMode(int mode) { + if (mDiscoveryMode != mode) { + mDiscoveryMode = mode; + onDiscoveryModeChanged(mode); + } + } + + void publishState() { + if (mCallback != null) { + RemoteDisplayState state = new RemoteDisplayState(); + final int count = mDisplays.size(); + for (int i = 0; i < count; i++) { + final RemoteDisplay display = mDisplays.valueAt(i); + state.displays.add(display.getInfo()); + } + try { + mCallback.onStateChanged(state); + } catch (RemoteException ex) { + // system server died? + } + } + } + + final class ProviderStub extends IRemoteDisplayProvider.Stub { + @Override + public void setCallback(IRemoteDisplayCallback callback) { + mHandler.obtainMessage(MSG_SET_CALLBACK, callback).sendToTarget(); + } + + @Override + public void setDiscoveryMode(int mode) { + mHandler.obtainMessage(MSG_SET_DISCOVERY_MODE, mode, 0).sendToTarget(); + } + + @Override + public void connect(String id) { + mHandler.obtainMessage(MSG_CONNECT, id).sendToTarget(); + } + + @Override + public void disconnect(String id) { + mHandler.obtainMessage(MSG_DISCONNECT, id).sendToTarget(); + } + + @Override + public void setVolume(String id, int volume) { + mHandler.obtainMessage(MSG_SET_VOLUME, volume, 0, id).sendToTarget(); + } + + @Override + public void adjustVolume(String id, int delta) { + mHandler.obtainMessage(MSG_ADJUST_VOLUME, delta, 0, id).sendToTarget(); + } + } + + final class ProviderHandler extends Handler { + public ProviderHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SET_CALLBACK: { + setCallback((IRemoteDisplayCallback)msg.obj); + break; + } + case MSG_SET_DISCOVERY_MODE: { + setDiscoveryMode(msg.arg1); + break; + } + case MSG_CONNECT: { + RemoteDisplay display = findRemoteDisplay((String)msg.obj); + if (display != null) { + onConnect(display); + } + break; + } + case MSG_DISCONNECT: { + RemoteDisplay display = findRemoteDisplay((String)msg.obj); + if (display != null) { + onDisconnect(display); + } + break; + } + case MSG_SET_VOLUME: { + RemoteDisplay display = findRemoteDisplay((String)msg.obj); + if (display != null) { + onSetVolume(display, msg.arg1); + } + break; + } + case MSG_ADJUST_VOLUME: { + RemoteDisplay display = findRemoteDisplay((String)msg.obj); + if (display != null) { + onAdjustVolume(display, msg.arg1); + } + break; + } + } + } + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java index 55d73f2..1cbc221 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java +++ b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java @@ -51,7 +51,7 @@ public class FilteringCursorWrapper extends AbstractCursor { mPosition = new int[count]; cursor.moveToPosition(-1); - while (cursor.moveToNext()) { + while (cursor.moveToNext() && mCount < count) { final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED); if (rejectMimes != null && MimePredicate.mimeMatches(rejectMimes, mimeType)) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java index 3a8a3fb..34ce42d 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java @@ -55,6 +55,10 @@ import java.util.concurrent.TimeUnit; public class RecentLoader extends AsyncTaskLoader<DirectoryResult> { private static final boolean LOGD = true; + // TODO: clean up cursor ownership so background thread doesn't traverse + // previously returned cursors for filtering/sorting; this currently races + // with the UI thread. + private static final int MAX_OUTSTANDING_RECENTS = 4; private static final int MAX_OUTSTANDING_RECENTS_SVELTE = 2; diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml index 5169fef..edd6255 100644 --- a/packages/ExternalStorageProvider/AndroidManifest.xml +++ b/packages/ExternalStorageProvider/AndroidManifest.xml @@ -3,6 +3,7 @@ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> <application android:label="@string/app_label"> <provider diff --git a/packages/Keyguard/AndroidManifest.xml b/packages/Keyguard/AndroidManifest.xml index 9e296e2..66d1e75 100644 --- a/packages/Keyguard/AndroidManifest.xml +++ b/packages/Keyguard/AndroidManifest.xml @@ -38,6 +38,7 @@ <uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" /> <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" /> <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" /> + <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> <application android:label="@string/app_name" android:process="com.android.systemui" diff --git a/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml b/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml index 8be15cb..b4847f0 100644 --- a/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml +++ b/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml @@ -32,12 +32,10 @@ android:id="@+id/carrier_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:singleLine="true" android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/kg_status_line_font_size" - android:textColor="?android:attr/textColorSecondary" - android:textAllCaps="@bool/kg_use_all_caps" /> + android:textColor="?android:attr/textColorSecondary" /> <LinearLayout android:layout_width="match_parent" diff --git a/packages/Keyguard/res/layout/keyguard_presentation.xml b/packages/Keyguard/res/layout/keyguard_presentation.xml new file mode 100644 index 0000000..7df0b70 --- /dev/null +++ b/packages/Keyguard/res/layout/keyguard_presentation.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2013, 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. +*/ +--> + +<!-- This is a view that shows general status information in Keyguard. --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/res/com.android.keyguard" + android:id="@+id/presentation" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <com.android.keyguard.KeyguardStatusView + android:id="@+id/clock" + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@string/keyguard_accessibility_status"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal|top" + android:orientation="vertical" + android:focusable="true"> + <TextClock + android:id="@+id/clock_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal|top" + android:textColor="@color/clock_white" + android:singleLine="true" + style="@style/widget_big_thin" + android:format12Hour="@string/keyguard_widget_12_hours_format" + android:format24Hour="@string/keyguard_widget_24_hours_format" + android:baselineAligned="true" + android:layout_marginBottom="@dimen/bottom_text_spacing_digital" /> + + <include layout="@layout/keyguard_status_area" /> + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="10dip" + android:layout_gravity="center_horizontal" + android:src="@drawable/kg_security_lock_normal" /> + </LinearLayout> + </com.android.keyguard.KeyguardStatusView> + +</FrameLayout> diff --git a/packages/Keyguard/res/layout/keyguard_status_view.xml b/packages/Keyguard/res/layout/keyguard_status_view.xml index 5857fc2..a4d298a 100644 --- a/packages/Keyguard/res/layout/keyguard_status_view.xml +++ b/packages/Keyguard/res/layout/keyguard_status_view.xml @@ -26,7 +26,7 @@ android:layout_height="match_parent" androidprv:layout_maxWidth="@dimen/keyguard_security_width" androidprv:layout_maxHeight="@dimen/keyguard_security_height" - android:gravity="center_horizontal"> + android:gravity="center"> <com.android.keyguard.KeyguardStatusView android:id="@+id/keyguard_status_view_face_palm" diff --git a/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java b/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java index 7d1f24f..74e6f33 100644 --- a/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java +++ b/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java @@ -19,7 +19,6 @@ package com.android.keyguard; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Color; -import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; @@ -41,7 +40,7 @@ public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnCli private static final String TAG = CameraWidgetFrame.class.getSimpleName(); private static final boolean DEBUG = KeyguardHostView.DEBUG; private static final int WIDGET_ANIMATION_DURATION = 250; // ms - private static final int WIDGET_WAIT_DURATION = 650; // ms + private static final int WIDGET_WAIT_DURATION = 400; // ms private static final int RECOVERY_DELAY = 1000; // ms interface Callbacks { @@ -68,6 +67,7 @@ public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnCli private FixedSizeFrameLayout mPreview; private View mFullscreenPreview; private View mFakeNavBar; + private boolean mUseFastTransition; private final Runnable mTransitionToCameraRunnable = new Runnable() { @Override @@ -243,11 +243,12 @@ public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnCli final float pvTransX = pvWidth < thisWidth ? (thisWidth - pvWidth) / 2 : 0; final float pvTransY = pvHeight < thisHeight ? (thisHeight - pvHeight) / 2 : 0; - mPreview.setPivotX(0); + final boolean isRtl = mPreview.getLayoutDirection() == LAYOUT_DIRECTION_RTL; + mPreview.setPivotX(isRtl ? mPreview.width : 0); mPreview.setPivotY(0); mPreview.setScaleX(pvScale); mPreview.setScaleY(pvScale); - mPreview.setTranslationX(pvTransX); + mPreview.setTranslationX((isRtl ? -1 : 1) * pvTransX); mPreview.setTranslationY(pvTransY); mRenderedSize.set(width, height); @@ -417,7 +418,8 @@ public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnCli private void rescheduleTransitionToCamera() { if (DEBUG) Log.d(TAG, "rescheduleTransitionToCamera at " + SystemClock.uptimeMillis()); mHandler.removeCallbacks(mTransitionToCameraRunnable); - mHandler.postDelayed(mTransitionToCameraRunnable, WIDGET_WAIT_DURATION); + final long duration = mUseFastTransition ? 0 : WIDGET_WAIT_DURATION; + mHandler.postDelayed(mTransitionToCameraRunnable, duration); } private void cancelTransitionToCamera() { @@ -512,4 +514,8 @@ public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnCli if (DEBUG) Log.d(TAG, "setInsets: " + insets); mInsets.set(insets); } + + public void setUseFastTransition(boolean useFastTransition) { + mUseFastTransition = useFastTransition; + } } diff --git a/packages/Keyguard/src/com/android/keyguard/CarrierText.java b/packages/Keyguard/src/com/android/keyguard/CarrierText.java index c33f174..88558cd 100644 --- a/packages/Keyguard/src/com/android/keyguard/CarrierText.java +++ b/packages/Keyguard/src/com/android/keyguard/CarrierText.java @@ -17,14 +17,18 @@ package com.android.keyguard; import android.content.Context; +import android.text.method.SingleLineTransformationMethod; import android.text.TextUtils; import android.util.AttributeSet; +import android.view.View; import android.widget.TextView; import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.IccCardConstants.State; import com.android.internal.widget.LockPatternUtils; +import java.util.Locale; + public class CarrierText extends TextView { private static CharSequence mSeparator; @@ -77,6 +81,8 @@ public class CarrierText extends TextView { public CarrierText(Context context, AttributeSet attrs) { super(context, attrs); mLockPatternUtils = new LockPatternUtils(mContext); + boolean useAllCaps = mContext.getResources().getBoolean(R.bool.kg_use_all_caps); + setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps)); } protected void updateCarrierText(State simState, CharSequence plmn, CharSequence spn) { @@ -258,4 +264,25 @@ public class CarrierText extends TextView { return mContext.getText(carrierHelpTextId); } + + private class CarrierTextTransformationMethod extends SingleLineTransformationMethod { + private final Locale mLocale; + private final boolean mAllCaps; + + public CarrierTextTransformationMethod(Context context, boolean allCaps) { + mLocale = context.getResources().getConfiguration().locale; + mAllCaps = allCaps; + } + + @Override + public CharSequence getTransformation(CharSequence source, View view) { + source = super.getTransformation(source, view); + + if (mAllCaps && source != null) { + source = source.toString().toUpperCase(mLocale); + } + + return source; + } + } } diff --git a/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java b/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java index 677f1f1..2ee21ac 100644 --- a/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java +++ b/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java @@ -39,7 +39,7 @@ public interface ChallengeLayout { * * @param b true to show, false to hide */ - void showChallenge(boolean b); + void showChallenge(boolean show); /** * Show the bouncer challenge. This may block access to other child views. diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardActivityLauncher.java b/packages/Keyguard/src/com/android/keyguard/KeyguardActivityLauncher.java index 9a1aa5b..0a915ea 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardActivityLauncher.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardActivityLauncher.java @@ -99,6 +99,11 @@ public abstract class KeyguardActivityLauncher { public void launchCamera(Handler worker, Runnable onSecureCameraStarted) { LockPatternUtils lockPatternUtils = getLockPatternUtils(); + + // Workaround to avoid camera release/acquisition race when resuming face unlock + // after showing lockscreen camera (bug 11063890). + KeyguardUpdateMonitor.getInstance(getContext()).setAlternateUnlockEnabled(false); + if (lockPatternUtils.isSecure()) { // Launch the secure version of the camera if (wouldLaunchResolverActivity(SECURE_CAMERA_INTENT)) { diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardDisplayManager.java new file mode 100644 index 0000000..6bcbd6c --- /dev/null +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardDisplayManager.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2013 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.keyguard; + +import android.app.Presentation; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnDismissListener; +import android.graphics.Point; +import android.media.MediaRouter; +import android.media.MediaRouter.RouteInfo; +import android.os.Bundle; +import android.util.Slog; +import android.view.Display; +import android.view.View; +import android.view.WindowManager; + +public class KeyguardDisplayManager { + protected static final String TAG = "KeyguardDisplayManager"; + private static boolean DEBUG = KeyguardViewMediator.DEBUG; + Presentation mPresentation; + private MediaRouter mMediaRouter; + private Context mContext; + private boolean mShowing; + + KeyguardDisplayManager(Context context) { + mContext = context; + mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE); + } + + void show() { + if (!mShowing) { + if (DEBUG) Slog.v(TAG, "show"); + mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, + mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); + updateDisplays(true); + } + mShowing = true; + } + + void hide() { + if (mShowing) { + if (DEBUG) Slog.v(TAG, "hide"); + mMediaRouter.removeCallback(mMediaRouterCallback); + updateDisplays(false); + } + mShowing = false; + } + + private final MediaRouter.SimpleCallback mMediaRouterCallback = + new MediaRouter.SimpleCallback() { + @Override + public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { + if (DEBUG) Slog.d(TAG, "onRouteSelected: type=" + type + ", info=" + info); + updateDisplays(mShowing); + } + + @Override + public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { + if (DEBUG) Slog.d(TAG, "onRouteUnselected: type=" + type + ", info=" + info); + updateDisplays(mShowing); + } + + @Override + public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) { + if (DEBUG) Slog.d(TAG, "onRoutePresentationDisplayChanged: info=" + info); + updateDisplays(mShowing); + } + }; + + private OnDismissListener mOnDismissListener = new OnDismissListener() { + + @Override + public void onDismiss(DialogInterface dialog) { + mPresentation = null; + } + }; + + protected void updateDisplays(boolean showing) { + if (showing) { + MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute( + MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY); + boolean useDisplay = route != null + && route.getPlaybackType() == MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE; + Display presentationDisplay = useDisplay ? route.getPresentationDisplay() : null; + + if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) { + if (DEBUG) Slog.v(TAG, "Display gone: " + mPresentation.getDisplay()); + mPresentation.dismiss(); + mPresentation = null; + } + + if (mPresentation == null && presentationDisplay != null) { + if (DEBUG) Slog.i(TAG, "Keyguard enabled on display: " + presentationDisplay); + mPresentation = new KeyguardPresentation(mContext, presentationDisplay); + mPresentation.setOnDismissListener(mOnDismissListener); + try { + mPresentation.show(); + } catch (WindowManager.InvalidDisplayException ex) { + Slog.w(TAG, "Invalid display:", ex); + mPresentation = null; + } + } + } else { + if (mPresentation != null) { + mPresentation.dismiss(); + mPresentation = null; + } + } + } + + private final static class KeyguardPresentation extends Presentation { + private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height + private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s + private View mClock; + private int mUsableWidth; + private int mUsableHeight; + private int mMarginTop; + private int mMarginLeft; + Runnable mMoveTextRunnable = new Runnable() { + @Override + public void run() { + int x = mMarginLeft + (int) (Math.random() * (mUsableWidth - mClock.getWidth())); + int y = mMarginTop + (int) (Math.random() * (mUsableHeight - mClock.getHeight())); + mClock.setTranslationX(x); + mClock.setTranslationY(y); + mClock.postDelayed(mMoveTextRunnable, MOVE_CLOCK_TIMEOUT); + } + }; + + public KeyguardPresentation(Context context, Display display) { + super(context, display); + getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + + public void onDetachedFromWindow() { + mClock.removeCallbacks(mMoveTextRunnable); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Point p = new Point(); + getDisplay().getSize(p); + mUsableWidth = VIDEO_SAFE_REGION * p.x/100; + mUsableHeight = VIDEO_SAFE_REGION * p.y/100; + mMarginLeft = (100 - VIDEO_SAFE_REGION) * p.x / 200; + mMarginTop = (100 - VIDEO_SAFE_REGION) * p.y / 200; + + setContentView(R.layout.keyguard_presentation); + mClock = findViewById(R.id.clock); + + // Avoid screen burn in + mClock.post(mMoveTextRunnable); + } + } +} diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java index fdc06a6..1bae9b8 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java @@ -218,7 +218,7 @@ public class KeyguardHostView extends KeyguardViewBase { mTransportState = (dcs.clearing ? TRANSPORT_GONE : (isMusicPlaying(dcs.playbackState) ? TRANSPORT_VISIBLE : TRANSPORT_INVISIBLE)); - if (DEBUG) Log.v(TAG, "Initial transport state: " + if (DEBUGXPORT) Log.v(TAG, "Initial transport state: " + mTransportState + ", pbstate=" + dcs.playbackState); } @@ -1369,7 +1369,7 @@ public class KeyguardHostView extends KeyguardViewBase { } } - Runnable mSwitchPageRunnable = new Runnable() { + private final Runnable mSwitchPageRunnable = new Runnable() { @Override public void run() { showAppropriateWidgetPage(); @@ -1438,7 +1438,7 @@ public class KeyguardHostView extends KeyguardViewBase { mAppWidgetToShow = ss.appWidgetToShow; setInsets(ss.insets); if (DEBUG) Log.d(TAG, "onRestoreInstanceState, transport=" + mTransportState); - post(mSwitchPageRunnable); + mSwitchPageRunnable.run(); } @Override @@ -1472,10 +1472,21 @@ public class KeyguardHostView extends KeyguardViewBase { } private void showAppropriateWidgetPage() { - int state = mTransportState; - ensureTransportPresentOrRemoved(state); - int pageToShow = getAppropriateWidgetPage(state); - mAppWidgetContainer.setCurrentPage(pageToShow); + final int state = mTransportState; + final boolean transportAdded = ensureTransportPresentOrRemoved(state); + final int pageToShow = getAppropriateWidgetPage(state); + if (!transportAdded) { + mAppWidgetContainer.setCurrentPage(pageToShow); + } else if (state == TRANSPORT_VISIBLE) { + // If the transport was just added, we need to wait for layout to happen before + // we can set the current page. + post(new Runnable() { + @Override + public void run() { + mAppWidgetContainer.setCurrentPage(pageToShow); + } + }); + } } /** @@ -1499,12 +1510,11 @@ public class KeyguardHostView extends KeyguardViewBase { * * @param state */ - private void ensureTransportPresentOrRemoved(int state) { + private boolean ensureTransportPresentOrRemoved(int state) { final boolean showing = getWidgetPosition(R.id.keyguard_transport_control) != -1; final boolean visible = state == TRANSPORT_VISIBLE; final boolean shouldBeVisible = state == TRANSPORT_INVISIBLE && isMusicPlaying(state); if (!showing && (visible || shouldBeVisible)) { - if (DEBUGXPORT) Log.v(TAG, "add transport"); // insert to left of camera if it exists, otherwise after right-most widget int lastWidget = mAppWidgetContainer.getChildCount() - 1; int position = 0; // handle no widget case @@ -1512,13 +1522,16 @@ public class KeyguardHostView extends KeyguardViewBase { position = mAppWidgetContainer.isCameraPage(lastWidget) ? lastWidget : lastWidget + 1; } + if (DEBUGXPORT) Log.v(TAG, "add transport at " + position); mAppWidgetContainer.addWidget(getOrCreateTransportControl(), position); + return true; } else if (showing && state == TRANSPORT_GONE) { if (DEBUGXPORT) Log.v(TAG, "remove transport"); mAppWidgetContainer.removeWidget(getOrCreateTransportControl()); mTransportControl = null; KeyguardUpdateMonitor.getInstance(getContext()).dispatchSetBackground(null); } + return false; } private CameraWidgetFrame findCameraPage() { diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java index 69075ec..751572c 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java @@ -177,6 +177,7 @@ class KeyguardMessageArea extends TextView { public KeyguardMessageArea(Context context, AttributeSet attrs) { super(context, attrs); + setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug mLockPatternUtils = new LockPatternUtils(context); diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardService.java b/packages/Keyguard/src/com/android/keyguard/KeyguardService.java index 8ccd6fe..36b2446 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardService.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardService.java @@ -68,8 +68,6 @@ public class KeyguardService extends Service { } private final IKeyguardService.Stub mBinder = new IKeyguardService.Stub() { - private boolean mSetHiddenCalled; - private boolean mIsHidden; public boolean isShowing() { return mKeyguardViewMediator.isShowing(); } @@ -91,10 +89,7 @@ public class KeyguardService extends Service { } public void setHidden(boolean isHidden) { checkPermission(); - if (mSetHiddenCalled && mIsHidden == isHidden) return; mKeyguardViewMediator.setHidden(isHidden); - mSetHiddenCalled = true; - mIsHidden = isHidden; } public void dismiss() { mKeyguardViewMediator.dismiss(); diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java index e39622a..9accbb4 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java @@ -46,9 +46,10 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView implements KeyguardSecurityView, OnEditorActionListener, TextWatcher { private static final String LOG_TAG = "KeyguardSimPinView"; private static final boolean DEBUG = KeyguardViewMediator.DEBUG; + public static final String TAG = "KeyguardSimPinView"; private ProgressDialog mSimUnlockProgressDialog = null; - private volatile boolean mSimCheckInProgress; + private CheckSimPin mCheckSimPinThread; private AlertDialog mRemainingAttemptsDialog; @@ -169,14 +170,17 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView @Override public void run() { try { + Log.v(TAG, "call supplyPinReportResult()"); final int[] result = ITelephony.Stub.asInterface(ServiceManager .checkService("phone")).supplyPinReportResult(mPin); + Log.v(TAG, "supplyPinReportResult returned: " + result[0] + " " + result[1]); post(new Runnable() { public void run() { onSimCheckResponse(result[0], result[1]); } }); } catch (RemoteException e) { + Log.e(TAG, "RemoteException for supplyPinReportResult:", e); post(new Runnable() { public void run() { onSimCheckResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1); @@ -229,9 +233,8 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView getSimUnlockProgressDialog().show(); - if (!mSimCheckInProgress) { - mSimCheckInProgress = true; // there should be only one - new CheckSimPin(mPasswordEntry.getText().toString()) { + if (mCheckSimPinThread == null) { + mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText().toString()) { void onSimCheckResponse(final int result, final int attemptsRemaining) { post(new Runnable() { public void run() { @@ -263,11 +266,12 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView mPasswordEntry.setText(""); } mCallback.userActivity(0); - mSimCheckInProgress = false; + mCheckSimPinThread = null; } }); } - }.start(); + }; + mCheckSimPinThread.start(); } } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java index 31518a1..6e9e83e 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java @@ -45,9 +45,10 @@ public class KeyguardSimPukView extends KeyguardAbsKeyInputView implements KeyguardSecurityView, OnEditorActionListener, TextWatcher { private static final String LOG_TAG = "KeyguardSimPukView"; private static final boolean DEBUG = KeyguardViewMediator.DEBUG; + public static final String TAG = "KeyguardSimPukView"; private ProgressDialog mSimUnlockProgressDialog = null; - private volatile boolean mCheckInProgress; + private CheckSimPuk mCheckSimPukThread; private String mPukText; private String mPinText; private StateMachine mStateMachine = new StateMachine(); @@ -220,15 +221,17 @@ public class KeyguardSimPukView extends KeyguardAbsKeyInputView @Override public void run() { try { + Log.v(TAG, "call supplyPukReportResult()"); final int[] result = ITelephony.Stub.asInterface(ServiceManager .checkService("phone")).supplyPukReportResult(mPuk, mPin); - + Log.v(TAG, "supplyPukReportResult returned: " + result[0] + " " + result[1]); post(new Runnable() { public void run() { onSimLockChangedResponse(result[0], result[1]); } }); } catch (RemoteException e) { + Log.e(TAG, "RemoteException for supplyPukReportResult:", e); post(new Runnable() { public void run() { onSimLockChangedResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1); @@ -295,9 +298,8 @@ public class KeyguardSimPukView extends KeyguardAbsKeyInputView private void updateSim() { getSimUnlockProgressDialog().show(); - if (!mCheckInProgress) { - mCheckInProgress = true; - new CheckSimPuk(mPukText, mPinText) { + if (mCheckSimPukThread == null) { + mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText) { void onSimLockChangedResponse(final int result, final int attemptsRemaining) { post(new Runnable() { public void run() { @@ -326,11 +328,12 @@ public class KeyguardSimPukView extends KeyguardAbsKeyInputView + " attemptsRemaining=" + attemptsRemaining); mStateMachine.reset(); } - mCheckInProgress = false; + mCheckSimPukThread = null; } }); } - }.start(); + }; + mCheckSimPukThread.start(); } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java index d933275..0bfee38 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java @@ -98,26 +98,13 @@ public class KeyguardStatusView extends GridLayout { } protected void refresh() { - Resources res = mContext.getResources(); - Locale locale = Locale.getDefault(); - final String dateFormat = DateFormat.getBestDateTimePattern(locale, - res.getString(R.string.abbrev_wday_month_day_no_year)); - - mDateView.setFormat24Hour(dateFormat); - mDateView.setFormat12Hour(dateFormat); - - // 12-hour clock. - // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton - // format. The following code removes the AM/PM indicator if we didn't want it. - final String clock12skel = res.getString(R.string.clock_12hr_format); - String clock12hr = DateFormat.getBestDateTimePattern(locale, clock12skel); - clock12hr = clock12skel.contains("a") ? clock12hr : clock12hr.replaceAll("a", "").trim(); - mClockView.setFormat12Hour(clock12hr); - - // 24-hour clock - final String clock24skel = res.getString(R.string.clock_24hr_format); - final String clock24hr = DateFormat.getBestDateTimePattern(locale, clock24skel); - mClockView.setFormat24Hour(clock24hr); + Patterns.update(mContext); + + mDateView.setFormat24Hour(Patterns.dateView); + mDateView.setFormat12Hour(Patterns.dateView); + + mClockView.setFormat12Hour(Patterns.clockView12); + mClockView.setFormat24Hour(Patterns.clockView24); refreshAlarmStatus(); } @@ -149,4 +136,35 @@ public class KeyguardStatusView extends GridLayout { return LockPatternUtils.ID_DEFAULT_STATUS_WIDGET; } + // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often. + // This is an optimization to ensure we only recompute the patterns when the inputs change. + private static final class Patterns { + static String dateView; + static String clockView12; + static String clockView24; + static String cacheKey; + + static void update(Context context) { + final Locale locale = Locale.getDefault(); + final Resources res = context.getResources(); + final String dateViewSkel = res.getString(R.string.abbrev_wday_month_day_no_year); + final String clockView12Skel = res.getString(R.string.clock_12hr_format); + final String clockView24Skel = res.getString(R.string.clock_24hr_format); + final String key = locale.toString() + dateViewSkel + clockView12Skel + clockView24Skel; + if (key.equals(cacheKey)) return; + + dateView = DateFormat.getBestDateTimePattern(locale, dateViewSkel); + + clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel); + // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton + // format. The following code removes the AM/PM indicator if we didn't want it. + if (!clockView12Skel.contains("a")) { + clockView12 = clockView12.replaceAll("a", "").trim(); + } + + clockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel); + + cacheKey = key; + } + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java index 29ba60d..349078f 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java @@ -60,12 +60,12 @@ import java.util.TimeZone; */ public class KeyguardTransportControlView extends FrameLayout { - private static final int DISPLAY_TIMEOUT_MS = 5000; // 5s private static final int RESET_TO_METADATA_DELAY = 5000; protected static final boolean DEBUG = false; protected static final String TAG = "TransportControlView"; private static final boolean ANIMATE_TRANSITIONS = true; + protected static final long QUIESCENT_PLAYBACK_FACTOR = 1000; private ViewGroup mMetadataContainer; private ViewGroup mInfoContainer; @@ -89,11 +89,9 @@ public class KeyguardTransportControlView extends FrameLayout { private ImageView mBadge; private boolean mSeekEnabled; - private boolean mUserSeeking; private java.text.DateFormat mFormat; - private Date mTimeElapsed; - private Date mTrackDuration; + private Date mTempDate = new Date(); /** * The metadata which should be populated into the view once we've been attached @@ -104,23 +102,32 @@ public class KeyguardTransportControlView extends FrameLayout { new RemoteController.OnClientUpdateListener() { @Override public void onClientChange(boolean clearing) { - clearMetadata(); + if (clearing) { + clearMetadata(); + } } @Override public void onClientPlaybackStateUpdate(int state) { - setSeekBarsEnabled(false); updatePlayPauseState(state); } @Override public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, long currentPosMs, float speed) { - setSeekBarsEnabled(mMetadata != null && mMetadata.duration > 0); updatePlayPauseState(state); if (DEBUG) Log.d(TAG, "onClientPlaybackStateUpdate(state=" + state + ", stateChangeTimeMs=" + stateChangeTimeMs + ", currentPosMs=" + currentPosMs + ", speed=" + speed + ")"); + + removeCallbacks(mUpdateSeekBars); + // Since the music client may be responding to historical events that cause the + // playback state to change dramatically, wait until things become quiescent before + // resuming automatic scrub position update. + if (mTransientSeek.getVisibility() == View.VISIBLE + && playbackPositionShouldMove(mCurrentPlayState)) { + postDelayed(mUpdateSeekBars, QUIESCENT_PLAYBACK_FACTOR); + } } @Override @@ -134,15 +141,21 @@ public class KeyguardTransportControlView extends FrameLayout { } }; - private final Runnable mUpdateSeekBars = new Runnable() { + private class UpdateSeekBarRunnable implements Runnable { public void run() { - if (updateSeekBars()) { + boolean seekAble = updateOnce(); + if (seekAble) { removeCallbacks(this); postDelayed(this, 1000); } } + public boolean updateOnce() { + return updateSeekBars(); + } }; + private final UpdateSeekBarRunnable mUpdateSeekBars = new UpdateSeekBarRunnable(); + private final Runnable mResetToMetadata = new Runnable() { public void run() { resetToMetadata(); @@ -161,6 +174,7 @@ public class KeyguardTransportControlView extends FrameLayout { } if (keyCode != -1) { sendMediaButtonClick(keyCode); + delayResetToMetadata(); // if the scrub bar is showing, keep showing it. } } }; @@ -175,25 +189,67 @@ public class KeyguardTransportControlView extends FrameLayout { } }; + // This class is here to throttle scrub position updates to the music client + class FutureSeekRunnable implements Runnable { + private int mProgress; + private boolean mPending; + + public void run() { + scrubTo(mProgress); + mPending = false; + } + + void setProgress(int progress) { + mProgress = progress; + if (!mPending) { + mPending = true; + postDelayed(this, 30); + } + } + }; + + // This is here because RemoteControlClient's method isn't visible :/ + private final static boolean playbackPositionShouldMove(int playstate) { + switch(playstate) { + case RemoteControlClient.PLAYSTATE_STOPPED: + case RemoteControlClient.PLAYSTATE_PAUSED: + case RemoteControlClient.PLAYSTATE_BUFFERING: + case RemoteControlClient.PLAYSTATE_ERROR: + case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: + case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: + return false; + case RemoteControlClient.PLAYSTATE_PLAYING: + case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: + case RemoteControlClient.PLAYSTATE_REWINDING: + default: + return true; + } + } + + private final FutureSeekRunnable mFutureSeekRunnable = new FutureSeekRunnable(); + private final SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { - scrubTo(progress); + mFutureSeekRunnable.setProgress(progress); delayResetToMetadata(); + mTempDate.setTime(progress); + mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate)); + } else { + updateSeekDisplay(); } - updateSeekDisplay(); } @Override public void onStartTrackingTouch(SeekBar seekBar) { - mUserSeeking = true; + delayResetToMetadata(); + removeCallbacks(mUpdateSeekBars); // don't update during user interaction } @Override public void onStopTrackingTouch(SeekBar seekBar) { - mUserSeeking = false; } }; @@ -206,10 +262,10 @@ public class KeyguardTransportControlView extends FrameLayout { = new KeyguardUpdateMonitorCallback() { public void onScreenTurnedOff(int why) { setEnableMarquee(false); - }; + } public void onScreenTurnedOn() { setEnableMarquee(true); - }; + } }; public KeyguardTransportControlView(Context context, AttributeSet attrs) { @@ -245,17 +301,11 @@ public class KeyguardTransportControlView extends FrameLayout { if (enabled == mSeekEnabled) return; mSeekEnabled = enabled; - if (mTransientSeek.getVisibility() == VISIBLE) { + if (mTransientSeek.getVisibility() == VISIBLE && !enabled) { mTransientSeek.setVisibility(INVISIBLE); mMetadataContainer.setVisibility(VISIBLE); - mUserSeeking = false; cancelResetToMetadata(); } - if (enabled) { - mUpdateSeekBars.run(); - } else { - removeCallbacks(mUpdateSeekBars); - } } public void setTransportControlCallback(KeyguardHostView.TransportControlCallback @@ -292,6 +342,8 @@ public class KeyguardTransportControlView extends FrameLayout { } final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn(); setEnableMarquee(screenOn); + // Allow long-press anywhere else in this view to show the seek bar + setOnLongClickListener(mTransportShowSeekBarListener); } @Override @@ -324,10 +376,36 @@ public class KeyguardTransportControlView extends FrameLayout { mAudioManager.unregisterRemoteController(mRemoteController); KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitor); mMetadata.clear(); - mUserSeeking = false; removeCallbacks(mUpdateSeekBars); } + @Override + protected Parcelable onSaveInstanceState() { + SavedState ss = new SavedState(super.onSaveInstanceState()); + ss.artist = mMetadata.artist; + ss.trackTitle = mMetadata.trackTitle; + ss.albumTitle = mMetadata.albumTitle; + ss.duration = mMetadata.duration; + ss.bitmap = mMetadata.bitmap; + return ss; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (!(state instanceof SavedState)) { + super.onRestoreInstanceState(state); + return; + } + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + mMetadata.artist = ss.artist; + mMetadata.trackTitle = ss.trackTitle; + mMetadata.albumTitle = ss.albumTitle; + mMetadata.duration = ss.duration; + mMetadata.bitmap = ss.bitmap; + populateMetadata(); + } + void setBadgeIcon(Drawable bmp) { mBadge.setImageDrawable(bmp); @@ -455,18 +533,12 @@ public class KeyguardTransportControlView extends FrameLayout { void updateSeekDisplay() { if (mMetadata != null && mRemoteController != null && mFormat != null) { - if (mTimeElapsed == null) { - mTimeElapsed = new Date(); - } - if (mTrackDuration == null) { - mTrackDuration = new Date(); - } - mTimeElapsed.setTime(mRemoteController.getEstimatedMediaPosition()); - mTrackDuration.setTime(mMetadata.duration); - mTransientSeekTimeElapsed.setText(mFormat.format(mTimeElapsed)); - mTransientSeekTimeTotal.setText(mFormat.format(mTrackDuration)); + mTempDate.setTime(mRemoteController.getEstimatedMediaPosition()); + mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate)); + mTempDate.setTime(mMetadata.duration); + mTransientSeekTimeTotal.setText(mFormat.format(mTempDate)); - if (DEBUG) Log.d(TAG, "updateSeekDisplay timeElapsed=" + mTimeElapsed + + if (DEBUG) Log.d(TAG, "updateSeekDisplay timeElapsed=" + mTempDate + " duration=" + mMetadata.duration); } } @@ -479,10 +551,16 @@ public class KeyguardTransportControlView extends FrameLayout { mTransientSeek.setVisibility(INVISIBLE); mMetadataContainer.setVisibility(VISIBLE); cancelResetToMetadata(); + removeCallbacks(mUpdateSeekBars); // don't update if scrubber isn't visible } else { mTransientSeek.setVisibility(VISIBLE); mMetadataContainer.setVisibility(INVISIBLE); delayResetToMetadata(); + if (playbackPositionShouldMove(mCurrentPlayState)) { + mUpdateSeekBars.run(); + } else { + mUpdateSeekBars.updateOnce(); + } } mTransportControlCallback.userActivity(); return true; @@ -544,9 +622,6 @@ public class KeyguardTransportControlView extends FrameLayout { case RemoteControlClient.PLAYSTATE_PLAYING: imageResId = R.drawable.ic_media_pause; imageDescId = R.string.keyguard_transport_pause_description; - if (mSeekEnabled) { - mUpdateSeekBars.run(); - } break; case RemoteControlClient.PLAYSTATE_BUFFERING: @@ -561,10 +636,9 @@ public class KeyguardTransportControlView extends FrameLayout { break; } - if (state != RemoteControlClient.PLAYSTATE_PLAYING) { - removeCallbacks(mUpdateSeekBars); - updateSeekBars(); - } + boolean clientSupportsSeek = mMetadata != null && mMetadata.duration > 0; + setSeekBarsEnabled(clientSupportsSeek); + mBtnPlay.setImageResource(imageResId); mBtnPlay.setContentDescription(getResources().getString(imageDescId)); mCurrentPlayState = state; @@ -572,11 +646,9 @@ public class KeyguardTransportControlView extends FrameLayout { boolean updateSeekBars() { final int position = (int) mRemoteController.getEstimatedMediaPosition(); + if (DEBUG) Log.v(TAG, "Estimated time:" + position); if (position >= 0) { - if (DEBUG) Log.v(TAG, "Seek to " + position); - if (!mUserSeeking) { - mTransientSeekBar.setProgress(position); - } + mTransientSeekBar.setProgress(position); return true; } Log.w(TAG, "Updating seek bars; received invalid estimated media position (" + @@ -587,6 +659,11 @@ public class KeyguardTransportControlView extends FrameLayout { static class SavedState extends BaseSavedState { boolean clientPresent; + String artist; + String trackTitle; + String albumTitle; + long duration; + Bitmap bitmap; SavedState(Parcelable superState) { super(superState); @@ -594,13 +671,23 @@ public class KeyguardTransportControlView extends FrameLayout { private SavedState(Parcel in) { super(in); - this.clientPresent = in.readInt() != 0; + clientPresent = in.readInt() != 0; + artist = in.readString(); + trackTitle = in.readString(); + albumTitle = in.readString(); + duration = in.readLong(); + bitmap = Bitmap.CREATOR.createFromParcel(in); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); - out.writeInt(this.clientPresent ? 1 : 0); + out.writeInt(clientPresent ? 1 : 0); + out.writeString(artist); + out.writeString(trackTitle); + out.writeString(albumTitle); + out.writeLong(duration); + bitmap.writeToParcel(out, flags); } public static final Parcelable.Creator<SavedState> CREATOR @@ -627,34 +714,4 @@ public class KeyguardTransportControlView extends FrameLayout { public boolean providesClock() { return false; } - - private boolean wasPlayingRecently(int state, long stateChangeTimeMs) { - switch (state) { - case RemoteControlClient.PLAYSTATE_PLAYING: - case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: - case RemoteControlClient.PLAYSTATE_REWINDING: - case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: - case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: - case RemoteControlClient.PLAYSTATE_BUFFERING: - // actively playing or about to play - return true; - case RemoteControlClient.PLAYSTATE_NONE: - return false; - case RemoteControlClient.PLAYSTATE_STOPPED: - case RemoteControlClient.PLAYSTATE_PAUSED: - case RemoteControlClient.PLAYSTATE_ERROR: - // we have stopped playing, check how long ago - if (DEBUG) { - if ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS) { - Log.v(TAG, "wasPlayingRecently: time < TIMEOUT was playing recently"); - } else { - Log.v(TAG, "wasPlayingRecently: time > TIMEOUT"); - } - } - return ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS); - default: - Log.e(TAG, "Unknown playback state " + state + " in wasPlayingRecently()"); - return false; - } - } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java index 520cea3..a849316 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -815,7 +815,7 @@ public class KeyguardUpdateMonitor { for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { - cb.onKeyguardVisibilityChanged(isShowing); + cb.onKeyguardVisibilityChangedRaw(isShowing); } } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 76f9637..c08880d 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -19,6 +19,7 @@ import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; import android.graphics.Bitmap; import android.media.AudioManager; +import android.os.SystemClock; import android.view.WindowManagerPolicy; import com.android.internal.telephony.IccCardConstants; @@ -27,6 +28,11 @@ import com.android.internal.telephony.IccCardConstants; * Callback for general information relevant to lock screen. */ class KeyguardUpdateMonitorCallback { + + private static final long VISIBILITY_CHANGED_COLLAPSE_MS = 1000; + private long mVisibilityChangedCalled; + private boolean mShowing; + /** * Called when the battery status changes, e.g. when plugged in or unplugged, charge * level, etc. changes. @@ -70,6 +76,15 @@ class KeyguardUpdateMonitorCallback { */ void onKeyguardVisibilityChanged(boolean showing) { } + void onKeyguardVisibilityChangedRaw(boolean showing) { + final long now = SystemClock.elapsedRealtime(); + if (showing == mShowing + && (now - mVisibilityChangedCalled) < VISIBILITY_CHANGED_COLLAPSE_MS) return; + onKeyguardVisibilityChanged(showing); + mVisibilityChangedCalled = now; + mShowing = showing; + } + /** * Called when visibility of lockscreen clock changes, such as when * obscured by a widget. diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java index fd7cae6..6aa0a4b 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java @@ -61,9 +61,11 @@ import android.widget.FrameLayout; public class KeyguardViewManager { private final static boolean DEBUG = KeyguardViewMediator.DEBUG; private static String TAG = "KeyguardViewManager"; - public static boolean USE_UPPER_CASE = true; public final static String IS_SWITCHING_USER = "is_switching_user"; + // Delay dismissing keyguard to allow animations to complete. + private static final int HIDE_KEYGUARD_DELAY = 500; + // Timeout used for keypresses static final int DIGIT_PRESS_WAKE_MILLIS = 5000; @@ -509,9 +511,10 @@ public class KeyguardViewManager { mKeyguardHost.setCustomBackground(null); updateShowWallpaper(true); mKeyguardHost.removeView(lastView); + mViewMediatorCallback.keyguardGone(); } } - }, 500); + }, HIDE_KEYGUARD_DELAY); } } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java index b92ae90..4086f84 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java @@ -200,7 +200,7 @@ public class KeyguardViewMediator { // cached value of whether we are showing (need to know this to quickly // answer whether the input should be restricted) - private boolean mShowing = false; + private boolean mShowing; // true if the keyguard is hidden by another window private boolean mHidden = false; @@ -253,6 +253,11 @@ public class KeyguardViewMediator { private final float mLockSoundVolume; /** + * For managing external displays + */ + private KeyguardDisplayManager mKeyguardDisplayManager; + + /** * Cache of avatar drawables, for use by KeyguardMultiUserAvatar. */ private static MultiUserAvatarCache sMultiUserAvatarCache = new MultiUserAvatarCache(); @@ -304,6 +309,11 @@ public class KeyguardViewMediator { * Report that the keyguard is dismissable, pending the next keyguardDone call. */ void keyguardDonePending(); + + /** + * Report when keyguard is actually gone + */ + void keyguardGone(); } KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { @@ -457,6 +467,11 @@ public class KeyguardViewMediator { public void keyguardDonePending() { mKeyguardDonePending = true; } + + @Override + public void keyguardGone() { + mKeyguardDisplayManager.hide(); + } }; private void userActivity() { @@ -483,6 +498,8 @@ public class KeyguardViewMediator { mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(DELAYED_KEYGUARD_ACTION)); + mKeyguardDisplayManager = new KeyguardDisplayManager(context); + mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); mUpdateMonitor = KeyguardUpdateMonitor.getInstance(context); @@ -491,6 +508,10 @@ public class KeyguardViewMediator { ? lockPatternUtils : new LockPatternUtils(mContext); mLockPatternUtils.setCurrentUser(UserHandle.USER_OWNER); + // Assume keyguard is showing (unless it's disabled) until we know for sure... + mShowing = (mUpdateMonitor.isDeviceProvisioned() || mLockPatternUtils.isSecure()) + && !mLockPatternUtils.isLockScreenDisabled(); + WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); mKeyguardViewManager = new KeyguardViewManager(context, wm, mViewMediatorCallback, @@ -1218,6 +1239,7 @@ public class KeyguardViewMediator { mShowKeyguardWakeLock.release(); } + mKeyguardDisplayManager.show(); } /** diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java index 8e39628..169899f 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java @@ -15,6 +15,9 @@ */ package com.android.keyguard; +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.AnimatorListenerAdapter; import android.os.Handler; import android.os.Looper; import android.util.Log; @@ -46,6 +49,20 @@ public class KeyguardViewStateManager implements int mChallengeTop = 0; + private final AnimatorListener mPauseListener = new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + mKeyguardSecurityContainer.onPause(); + } + }; + + private final AnimatorListener mResumeListener = new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + if (((View)mKeyguardSecurityContainer).isShown()) { + mKeyguardSecurityContainer.onResume(0); + } + } + }; + public KeyguardViewStateManager(KeyguardHostView hostView) { mKeyguardHostView = hostView; } @@ -102,20 +119,20 @@ public class KeyguardViewStateManager implements } public void fadeOutSecurity(int duration) { - ((View) mKeyguardSecurityContainer).animate().alpha(0f).setDuration(duration).start(); + ((View) mKeyguardSecurityContainer).animate().alpha(0f).setDuration(duration) + .setListener(mPauseListener); } public void fadeInSecurity(int duration) { - ((View) mKeyguardSecurityContainer).animate().alpha(1f).setDuration(duration).start(); + ((View) mKeyguardSecurityContainer).animate().alpha(1f).setDuration(duration) + .setListener(mResumeListener); } public void onPageBeginMoving() { if (mChallengeLayout.isChallengeOverlapping() && mChallengeLayout instanceof SlidingChallengeLayout) { SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout; - if (!mKeyguardWidgetPager.isWarping()) { - scl.fadeOutChallenge(); - } + scl.fadeOutChallenge(); mPageIndexOnPageBeginMoving = mKeyguardWidgetPager.getCurrentPage(); } // We use mAppWidgetToShow to show a particular widget after you add it-- @@ -137,11 +154,12 @@ public class KeyguardViewStateManager implements public void onPageSwitching(View newPage, int newPageIndex) { if (mKeyguardWidgetPager != null && mChallengeLayout instanceof SlidingChallengeLayout) { boolean isCameraPage = newPage instanceof CameraWidgetFrame; - SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout; - scl.setChallengeInteractive(!isCameraPage); if (isCameraPage) { - scl.fadeOutChallenge(); + CameraWidgetFrame camera = (CameraWidgetFrame) newPage; + camera.setUseFastTransition(mKeyguardWidgetPager.isWarping()); } + SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout; + scl.setChallengeInteractive(!isCameraPage); final int currentFlags = mKeyguardWidgetPager.getSystemUiVisibility(); final int newFlags = isCameraPage ? (currentFlags | View.STATUS_BAR_DISABLE_SEARCH) : (currentFlags & ~View.STATUS_BAR_DISABLE_SEARCH); @@ -178,7 +196,7 @@ public class KeyguardViewStateManager implements boolean challengeOverlapping = mChallengeLayout.isChallengeOverlapping(); if (challengeOverlapping && !newCurPage.isSmall() && mPageListeningToSlider != newPageIndex) { - newCurPage.shrinkWidget(); + newCurPage.shrinkWidget(true); } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetFrame.java b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetFrame.java index ab8a759..8ee9b61 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetFrame.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetFrame.java @@ -375,10 +375,6 @@ public class KeyguardWidgetFrame extends FrameLayout { return mSmallFrameHeight; } - public void shrinkWidget() { - shrinkWidget(true); - } - public void setWidgetLockedSmall(boolean locked) { if (locked) { setWidgetHeight(mSmallWidgetHeight); diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java index 704af6e..99f7757 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java @@ -194,7 +194,9 @@ public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwit @Override public void onPageEndWarp() { - hideOutlinesAndSidePages(); + // if we're moving to the warp page, then immediately hide the other widgets. + int duration = getPageWarpIndex() == getNextPage() ? 0 : -1; + animateOutlinesAndSidePages(false, duration); mViewStateManager.onPageEndWarp(); } @@ -669,7 +671,7 @@ public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwit // On the very first measure pass, if the challenge is showing, we need to make sure // that the widget on the current page is small. if (challengeShowing && i == mCurrentPage && !mHasMeasure) { - frame.shrinkWidget(); + frame.shrinkWidget(true); } } } diff --git a/packages/Keyguard/src/com/android/keyguard/PagedView.java b/packages/Keyguard/src/com/android/keyguard/PagedView.java index 9d237dc..53c17a5 100644 --- a/packages/Keyguard/src/com/android/keyguard/PagedView.java +++ b/packages/Keyguard/src/com/android/keyguard/PagedView.java @@ -82,13 +82,13 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f; // The page is moved more than halfway, automatically move to the next page on touch up. - private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f; + private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.5f; // The following constants need to be scaled based on density. The scaled versions will be // assigned to the corresponding member variables below. - private static final int FLING_THRESHOLD_VELOCITY = 500; + private static final int FLING_THRESHOLD_VELOCITY = 1500; private static final int MIN_SNAP_VELOCITY = 1500; - private static final int MIN_FLING_VELOCITY = 250; + private static final int MIN_FLING_VELOCITY = 500; // We are disabling touch interaction of the widget region for factory ROM. private static final boolean DISABLE_TOUCH_INTERACTION = false; @@ -267,6 +267,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private boolean mIsCameraEvent; private float mWarpPeekAmount; + private boolean mOnPageEndWarpCalled; + private boolean mOnPageBeginWarpCalled; public interface PageSwitchListener { void onPageSwitching(View newPage, int newPageIndex); @@ -491,7 +493,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (!mIsPageMoving) { mIsPageMoving = true; if (isWarping()) { - onPageBeginWarp(); + dispatchOnPageBeginWarp(); if (mPageSwapIndex != -1) { swapPages(mPageSwapIndex, mPageWarpIndex); } @@ -500,6 +502,22 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } } + private void dispatchOnPageBeginWarp() { + if (!mOnPageBeginWarpCalled) { + onPageBeginWarp(); + mOnPageBeginWarpCalled = true; + } + mOnPageEndWarpCalled = false; + } + + private void dispatchOnPageEndWarp() { + if (!mOnPageEndWarpCalled) { + onPageEndWarp(); + mOnPageEndWarpCalled = true; + } + mOnPageBeginWarpCalled = false; + } + protected void pageEndMoving() { if (DEBUG_WARP) Log.v(TAG, "pageEndMoving(" + mIsPageMoving + ")"); if (mIsPageMoving) { @@ -508,7 +526,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (mPageSwapIndex != -1) { swapPages(mPageSwapIndex, mPageWarpIndex); } - onPageEndWarp(); + dispatchOnPageEndWarp(); resetPageWarp(); } onPageEndMoving(); @@ -1919,11 +1937,13 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } if (isWarping()) { - onPageEndWarp(); + dispatchOnPageEndWarp(); + notifyPageSwitching(whichPage); resetPageWarp(); + } else { + notifyPageSwitching(whichPage); } - notifyPageSwitching(whichPage); View focusedChild = getFocusedChild(); if (focusedChild != null && whichPage != mCurrentPage && focusedChild == getPageAt(mCurrentPage)) { @@ -2260,11 +2280,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mTempVisiblePagesRange[0] = 0; mTempVisiblePagesRange[1] = getPageCount() - 1; boundByReorderablePages(true, mTempVisiblePagesRange); - mReorderingStarted = true; // Check if we are within the reordering range if (mTempVisiblePagesRange[0] <= dragViewIndex && dragViewIndex <= mTempVisiblePagesRange[1]) { + mReorderingStarted = true; if (zoomOut()) { // Find the drag view under the pointer mDragView = getChildAt(dragViewIndex); @@ -2702,12 +2722,12 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc @Override public void onAnimationEnd(Animator animation) { mWarpAnimation = null; - mWarpPageExposed = true; + mWarpPageExposed = false; } }; private void cancelWarpAnimation(String msg, boolean abortAnimation) { - if (DEBUG_WARP) Log.v(TAG, "cancelWarpAnimation(" + msg + ")"); + if (DEBUG_WARP) Log.v(TAG, "cancelWarpAnimation(" + msg + ",abort=" + abortAnimation + ")"); if (abortAnimation) { // We're done with the animation and moving to a new page. Let the scroller // take over the animation. @@ -2727,9 +2747,9 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private void animateWarpPageOnScreen(String reason) { if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOnScreen(" + reason + ")"); - if (isWarping()) { + if (isWarping() && !mWarpPageExposed) { mWarpPageExposed = true; - onPageBeginWarp(); + dispatchOnPageBeginWarp(); KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(mPageWarpIndex); if (DEBUG_WARP) Log.v(TAG, "moving page on screen: Tx=" + v.getTranslationX()); DecelerateInterpolator interp = new DecelerateInterpolator(1.5f); @@ -2744,7 +2764,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private void animateWarpPageOffScreen(String reason, boolean animate) { if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOffScreen(" + reason + " anim:" + animate + ")"); if (isWarping()) { - onPageEndWarp(); + dispatchOnPageEndWarp(); KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(mPageWarpIndex); if (DEBUG_WARP) Log.v(TAG, "moving page off screen: Tx=" + v.getTranslationX()); AccelerateInterpolator interp = new AccelerateInterpolator(1.5f); diff --git a/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java b/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java index 7a9a1c8..3d515ce 100644 --- a/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java +++ b/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java @@ -1003,6 +1003,16 @@ public class SlidingChallengeLayout extends ViewGroup implements ChallengeLayout } } + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + // Focus security fileds before widgets. + if (mChallengeView != null && + mChallengeView.requestFocus(direction, previouslyFocusedRect)) { + return true; + } + return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); + } + public void computeScroll() { super.computeScroll(); diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml index f573d9d..d9f0a9a 100644 --- a/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml +++ b/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml @@ -35,7 +35,6 @@ android:layout_marginBottom="32dip" android:layout_gravity="center" style="?android:attr/buttonBarButtonStyle" - android:singleLine="true" android:ellipsize="end" android:text="@string/print_error_default_message" android:textColor="@color/important_text" diff --git a/packages/PrintSpooler/res/values-ja/arrays.xml b/packages/PrintSpooler/res/values-ja/arrays.xml index 57088c8..460bdb2 100644 --- a/packages/PrintSpooler/res/values-ja/arrays.xml +++ b/packages/PrintSpooler/res/values-ja/arrays.xml @@ -20,13 +20,13 @@ <item>JIS_B9</item> <item>JIS_B8</item> <item>JIS_B7</item> - <item>JIS_b6</item> - <item>JIS_b5</item> - <item>JIS_b4</item> - <item>JIS_b3</item> - <item>JIS_b2</item> - <item>JIS_b1</item> - <item>JIS_b0</item> + <item>JIS_B6</item> + <item>JIS_B5</item> + <item>JIS_B4</item> + <item>JIS_B3</item> + <item>JIS_B2</item> + <item>JIS_B1</item> + <item>JIS_B0</item> <item>JIS_EXEC</item> <item>JPN_CHOU4</item> <item>JPN_CHOU3</item> diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml index f2e768a..d2613d0 100644 --- a/packages/PrintSpooler/res/values/strings.xml +++ b/packages/PrintSpooler/res/values/strings.xml @@ -93,6 +93,12 @@ <!-- Title of the action bar button to got to add a printer. [CHAR LIMIT=25] --> <string name="print_add_printer">Add printer</string> + <!-- Title of the menu item to select a printer. [CHAR LIMIT=25] --> + <string name="print_select_printer">Select printer</string> + + <!-- Title of the menu item to forget a printer. [CHAR LIMIT=25] --> + <string name="print_forget_printer">Forget printer</string> + <!-- Utterance to announce a change in the number of matches during a search. This is spoken to a blind user. [CHAR LIMIT=none] --> <plurals name="print_search_result_count_utterance"> <item quantity="one"><xliff:g id="count" example="1">%1$s</xliff:g> printer found</item> diff --git a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java index 0601467..9831839 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java +++ b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java @@ -79,6 +79,8 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { private PrinterId mTrackedPrinter; + private boolean mPrintersUpdatedBefore; + public FusedPrintersProvider(Context context) { super(context); mPersistenceManager = new PersistenceManager(context); @@ -88,13 +90,14 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { mPersistenceManager.addPrinterAndWritePrinterHistory(printer); } - private void computeAndDeliverResult(Map<PrinterId, PrinterInfo> discoveredPrinters) { + private void computeAndDeliverResult(ArrayMap<PrinterId, PrinterInfo> discoveredPrinters, + ArrayMap<PrinterId, PrinterInfo> favoritePrinters) { List<PrinterInfo> printers = new ArrayList<PrinterInfo>(); // Add the updated favorite printers. - final int favoritePrinterCount = mFavoritePrinters.size(); + final int favoritePrinterCount = favoritePrinters.size(); for (int i = 0; i < favoritePrinterCount; i++) { - PrinterInfo favoritePrinter = mFavoritePrinters.get(i); + PrinterInfo favoritePrinter = favoritePrinters.valueAt(i); PrinterInfo updatedPrinter = discoveredPrinters.remove( favoritePrinter.getId()); if (updatedPrinter != null) { @@ -123,8 +126,11 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { mPrinters.addAll(printers); if (isStarted()) { - // Deliver the printers. + // If stated deliver the new printers. deliverResult(printers); + } else { + // Otherwise, take a note for the change. + onContentChanged(); } } @@ -165,6 +171,8 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { .getSystemService(Context.PRINT_SERVICE); mDiscoverySession = printManager.createPrinterDiscoverySession(); mPersistenceManager.readPrinterHistory(); + } else if (mPersistenceManager.isHistoryChanged()) { + mPersistenceManager.readPrinterHistory(); } if (mPersistenceManager.isReadHistoryCompleted() && !mDiscoverySession.isPrinterDiscoveryStarted()) { @@ -176,7 +184,7 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { + mDiscoverySession.getPrinters().size() + " " + FusedPrintersProvider.this.hashCode()); } - updatePrinters(mDiscoverySession.getPrinters()); + updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters); } }); final int favoriteCount = mFavoritePrinters.size(); @@ -187,15 +195,19 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { mDiscoverySession.startPrinterDisovery(printerIds); List<PrinterInfo> printers = mDiscoverySession.getPrinters(); if (!printers.isEmpty()) { - updatePrinters(printers); + updatePrinters(printers, mFavoritePrinters); } } } - private void updatePrinters(List<PrinterInfo> printers) { - if (mPrinters.equals(printers)) { + private void updatePrinters(List<PrinterInfo> printers, List<PrinterInfo> favoritePrinters) { + if (mPrintersUpdatedBefore && mPrinters.equals(printers) + && mFavoritePrinters.equals(favoritePrinters)) { return; } + + mPrintersUpdatedBefore = true; + ArrayMap<PrinterId, PrinterInfo> printersMap = new ArrayMap<PrinterId, PrinterInfo>(); final int printerCount = printers.size(); @@ -203,7 +215,16 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { PrinterInfo printer = printers.get(i); printersMap.put(printer.getId(), printer); } - computeAndDeliverResult(printersMap); + + ArrayMap<PrinterId, PrinterInfo> favoritePrintersMap = + new ArrayMap<PrinterId, PrinterInfo>(); + final int favoritePrinterCount = favoritePrinters.size(); + for (int i = 0; i < favoritePrinterCount; i++) { + PrinterInfo favoritePrinter = favoritePrinters.get(i); + favoritePrintersMap.put(favoritePrinter.getId(), favoritePrinter); + } + + computeAndDeliverResult(printersMap, favoritePrintersMap); } @Override @@ -264,6 +285,42 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { } } + public boolean isFavoritePrinter(PrinterId printerId) { + final int printerCount = mFavoritePrinters.size(); + for (int i = 0; i < printerCount; i++) { + PrinterInfo favoritePritner = mFavoritePrinters.get(i); + if (favoritePritner.getId().equals(printerId)) { + return true; + } + } + return false; + } + + public void forgetFavoritePrinter(PrinterId printerId) { + List<PrinterInfo> newFavoritePrinters = null; + + // Remove the printer from the favorites. + final int favoritePrinterCount = mFavoritePrinters.size(); + for (int i = 0; i < favoritePrinterCount; i++) { + PrinterInfo favoritePrinter = mFavoritePrinters.get(i); + if (favoritePrinter.getId().equals(printerId)) { + newFavoritePrinters = new ArrayList<PrinterInfo>(); + newFavoritePrinters.addAll(mPrinters); + newFavoritePrinters.remove(i); + break; + } + } + + // If we removed a favorite printer, we have work to do. + if (newFavoritePrinters != null) { + // Remove the printer from history and persist the latter. + mPersistenceManager.removeHistoricalPrinterAndWritePrinterHistory(printerId); + + // Recompute and deliver the printers. + updatePrinters(mDiscoverySession.getPrinters(), newFavoritePrinters); + } + } + private final class PersistenceManager { private static final String PERSIST_FILE_NAME = "printer_history.xml"; @@ -281,13 +338,15 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { private final AtomicFile mStatePersistFile; - private List<PrinterInfo> mHistoricalPrinters; + private List<PrinterInfo> mHistoricalPrinters = new ArrayList<PrinterInfo>(); private boolean mReadHistoryCompleted; private boolean mReadHistoryInProgress; private ReadTask mReadTask; + private volatile long mLastReadHistoryTimestamp; + private PersistenceManager(Context context) { mStatePersistFile = new AtomicFile(new File(context.getFilesDir(), PERSIST_FILE_NAME)); @@ -327,6 +386,27 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { new ArrayList<PrinterInfo>(mHistoricalPrinters)); } + @SuppressWarnings("unchecked") + public void removeHistoricalPrinterAndWritePrinterHistory(PrinterId printerId) { + boolean writeHistory = false; + final int printerCount = mHistoricalPrinters.size(); + for (int i = printerCount - 1; i >= 0; i--) { + PrinterInfo historicalPrinter = mHistoricalPrinters.get(i); + if (historicalPrinter.getId().equals(printerId)) { + mHistoricalPrinters.remove(i); + writeHistory = true; + } + } + if (writeHistory) { + new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, + new ArrayList<PrinterInfo>(mHistoricalPrinters)); + } + } + + public boolean isHistoryChanged() { + return mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified(); + } + private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) { Map<PrinterId, PrinterRecord> recordMap = new ArrayMap<PrinterId, PrinterRecord>(); @@ -423,11 +503,10 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { mReadHistoryInProgress = false; mReadHistoryCompleted = true; - // Deliver the favorites. - Map<PrinterId, PrinterInfo> discoveredPrinters = Collections.emptyMap(); - computeAndDeliverResult(discoveredPrinters); + // Deliver the printers. + updatePrinters(mDiscoverySession.getPrinters(), mHistoricalPrinters); - // Start loading the available printers. + // Loading the available printers if needed. loadInternal(); // We are done. @@ -450,6 +529,8 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, null); parseState(parser, printers); + // Take a note which version of the history was read. + mLastReadHistoryTimestamp = mStatePersistFile.getBaseFile().lastModified(); return printers; } catch (IllegalStateException ise) { Slog.w(LOG_TAG, "Failed parsing ", ise); diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java index 8f26361..c1c7a4e 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java @@ -357,6 +357,9 @@ public class PrintJobConfigActivity extends Activity { } public void cancel() { + if (isWorking()) { + mRemotePrintAdapter.cancel(); + } mControllerState = CONTROLLER_STATE_CANCELLED; } @@ -934,6 +937,7 @@ public class PrintJobConfigActivity extends Activity { mPrintJobId, mCurrentPrinter); if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) { + mCapabilitiesTimeout.post(); updateUi(); return; } @@ -1525,9 +1529,13 @@ public class PrintJobConfigActivity extends Activity { builder.append(','); } PageRange pageRange = pageRanges[i]; - builder.append(pageRange.getStart()); - builder.append('-'); - builder.append(pageRange.getEnd()); + final int shownStartPage = pageRange.getStart() + 1; + final int shownEndPage = pageRange.getEnd() + 1; + builder.append(shownStartPage); + if (shownStartPage != shownEndPage) { + builder.append('-'); + builder.append(shownEndPage); + } } mPageRangeEditText.setText(builder.toString()); } diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java index 609ae64..615d667 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java @@ -441,6 +441,7 @@ public final class PrintSpoolerService extends Service { private void removeObsoletePrintJobs() { synchronized (mLock) { + boolean persistState = false; final int printJobCount = mPrintJobs.size(); for (int i = printJobCount - 1; i >= 0; i--) { PrintJobInfo printJob = mPrintJobs.get(i); @@ -450,9 +451,12 @@ public final class PrintSpoolerService extends Service { Slog.i(LOG_TAG, "[REMOVE] " + printJob.getId().flattenToString()); } removePrintJobFileLocked(printJob.getId()); + persistState = true; } } - mPersistanceManager.writeStateLocked(); + if (persistState) { + mPersistanceManager.writeStateLocked(); + } } } @@ -544,7 +548,7 @@ public final class PrintSpoolerService extends Service { final int printJobCount = mPrintJobs.size(); for (int i = 0; i < printJobCount; i++) { PrintJobInfo printJob = mPrintJobs.get(i); - if (isActiveState(printJob.getState()) + if (isActiveState(printJob.getState()) && printJob.getPrinterId() != null && printJob.getPrinterId().getServiceName().equals(service)) { return true; } @@ -799,6 +803,10 @@ public final class PrintSpoolerService extends Service { for (int j = 0; j < printJobCount; j++) { PrintJobInfo printJob = printJobs.get(j); + if (!shouldPersistPrintJob(printJob)) { + continue; + } + serializer.startTag(null, TAG_JOB); serializer.attribute(null, ATTR_ID, printJob.getId().flattenToString()); diff --git a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java index fd14af9..d9ccb5d 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java +++ b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java @@ -137,4 +137,15 @@ final class RemotePrintDocumentAdapter { Log.e(LOG_TAG, "Error calling finish()", re); } } + + public void cancel() { + if (DEBUG) { + Log.i(LOG_TAG, "cancel()"); + } + try { + mRemoteInterface.cancel(); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling cancel()", re); + } + } } diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java index 204c152..fe5920c 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java +++ b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java @@ -46,6 +46,8 @@ import android.printservice.PrintServiceInfo; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -54,6 +56,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.ArrayAdapter; import android.widget.BaseAdapter; import android.widget.Filter; @@ -81,6 +84,8 @@ public final class SelectPrinterFragment extends Fragment { private static final String FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS = "FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS"; + private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID"; + private final ArrayList<PrintServiceInfo> mAddPrinterServices = new ArrayList<PrintServiceInfo>(); @@ -127,6 +132,9 @@ public final class SelectPrinterFragment extends Fragment { mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + if (!((DestinationAdapter) mListView.getAdapter()).isActionable(position)) { + return; + } PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position); Activity activity = getActivity(); if (activity instanceof OnPrinterSelectedListener) { @@ -138,6 +146,8 @@ public final class SelectPrinterFragment extends Fragment { } }); + registerForContextMenu(mListView); + return content; } @@ -185,6 +195,62 @@ public final class SelectPrinterFragment extends Fragment { } @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { + if (view == mListView) { + final int position = ((AdapterContextMenuInfo) menuInfo).position; + PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position); + + menu.setHeaderTitle(printer.getName()); + + // Add the select menu item if applicable. + if (printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) { + MenuItem selectItem = menu.add(Menu.NONE, R.string.print_select_printer, + Menu.NONE, R.string.print_select_printer); + Intent intent = new Intent(); + intent.putExtra(EXTRA_PRINTER_ID, printer.getId()); + selectItem.setIntent(intent); + } + + // Add the forget menu item if applicable. + FusedPrintersProvider provider = (FusedPrintersProvider) (Loader<?>) + getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER); + if (provider.isFavoritePrinter(printer.getId())) { + MenuItem forgetItem = menu.add(Menu.NONE, R.string.print_forget_printer, + Menu.NONE, R.string.print_forget_printer); + Intent intent = new Intent(); + intent.putExtra(EXTRA_PRINTER_ID, printer.getId()); + forgetItem.setIntent(intent); + } + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.string.print_select_printer: { + PrinterId printerId = (PrinterId) item.getIntent().getParcelableExtra( + EXTRA_PRINTER_ID); + Activity activity = getActivity(); + if (activity instanceof OnPrinterSelectedListener) { + ((OnPrinterSelectedListener) activity).onPrinterSelected(printerId); + } else { + throw new IllegalStateException("the host activity must implement" + + " OnPrinterSelectedListener"); + } + } return true; + + case R.string.print_forget_printer: { + PrinterId printerId = (PrinterId) item.getIntent().getParcelableExtra( + EXTRA_PRINTER_ID); + FusedPrintersProvider provider = (FusedPrintersProvider) (Loader<?>) + getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER); + provider.forgetFavoritePrinter(printerId); + } return true; + } + return false; + } + + @Override public void onResume() { updateAddPrintersAdapter(); getActivity().invalidateOptionsMenu(); @@ -464,7 +530,7 @@ public final class SelectPrinterFragment extends Fragment { R.layout.printer_list_item, parent, false); } - convertView.setEnabled(isEnabled(position)); + convertView.setEnabled(isActionable(position)); CharSequence title = null; CharSequence subtitle = null; @@ -506,8 +572,7 @@ public final class SelectPrinterFragment extends Fragment { return convertView; } - @Override - public boolean isEnabled(int position) { + public boolean isActionable(int position) { PrinterInfo printer = (PrinterInfo) getItem(position); return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE; } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index ffb4c20..29e8d1d 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -5,7 +5,6 @@ > <!-- Standard permissions granted to the shell. --> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> @@ -64,6 +63,7 @@ <uses-permission android:name="android.permission.SET_SCREEN_COMPATIBILITY" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.MANAGE_USERS" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 09ac2da..8d6fe41 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -71,6 +71,9 @@ <!-- Keyguard --> <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> + <!-- Wifi Display --> + <uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" /> + <application android:persistent="true" android:allowClearUserData="false" diff --git a/packages/SystemUI/res/drawable-hdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-hdpi/bg_protect.9.png Binary files differnew file mode 100644 index 0000000..5bbfa4f --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png Binary files differindex 2d8d074..693abf5 100644 --- a/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png +++ b/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_available.png Binary files differnew file mode 100644 index 0000000..1c3518a --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_available.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connected.png Binary files differnew file mode 100644 index 0000000..9dbc65e --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connected.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_0.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_0.png Binary files differnew file mode 100644 index 0000000..ddb002d --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_0.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_1.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_1.png Binary files differnew file mode 100644 index 0000000..43b7ef2 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_1.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_2.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_2.png Binary files differnew file mode 100644 index 0000000..1d8b7ee --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_2.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png Binary files differindex 7220968..e3b3eeb 100644 --- a/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png Binary files differdeleted file mode 100644 index 02d7fda..0000000 --- a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display_connected.png Binary files differdeleted file mode 100644 index 263f07c..0000000 --- a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display_connected.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png Binary files differindex 8f4cb64..c6f03c4 100644 --- a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png +++ b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png diff --git a/packages/SystemUI/res/drawable-hdpi/search_light.png b/packages/SystemUI/res/drawable-hdpi/search_light.png Binary files differindex 116b1f0..3c0dc4e 100644 --- a/packages/SystemUI/res/drawable-hdpi/search_light.png +++ b/packages/SystemUI/res/drawable-hdpi/search_light.png diff --git a/packages/SystemUI/res/drawable-hdpi/search_light_land.png b/packages/SystemUI/res/drawable-hdpi/search_light_land.png Binary files differnew file mode 100644 index 0000000..731f19b --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/search_light_land.png diff --git a/packages/SystemUI/res/drawable-land-hdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-hdpi/bg_protect.9.png Binary files differnew file mode 100644 index 0000000..1a58144 --- /dev/null +++ b/packages/SystemUI/res/drawable-land-hdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-land-mdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-mdpi/bg_protect.9.png Binary files differnew file mode 100644 index 0000000..a12519e --- /dev/null +++ b/packages/SystemUI/res/drawable-land-mdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-land-xhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-xhdpi/bg_protect.9.png Binary files differnew file mode 100644 index 0000000..ce41454 --- /dev/null +++ b/packages/SystemUI/res/drawable-land-xhdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-land-xxhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-xxhdpi/bg_protect.9.png Binary files differnew file mode 100644 index 0000000..b0b4561 --- /dev/null +++ b/packages/SystemUI/res/drawable-land-xxhdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-mdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-mdpi/bg_protect.9.png Binary files differnew file mode 100644 index 0000000..2856e09 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png Binary files differindex 399db00..15340d3 100644 --- a/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png +++ b/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_available.png Binary files differnew file mode 100644 index 0000000..11b2134 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_available.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connected.png Binary files differnew file mode 100644 index 0000000..a858573 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connected.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_0.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_0.png Binary files differnew file mode 100644 index 0000000..04de5d7 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_0.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_1.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_1.png Binary files differnew file mode 100644 index 0000000..caea37e --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_1.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_2.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_2.png Binary files differnew file mode 100644 index 0000000..b66aa46 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_2.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png Binary files differindex 8c2dc68..cc81794 100644 --- a/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png Binary files differdeleted file mode 100644 index 09ae409..0000000 --- a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display_connected.png Binary files differdeleted file mode 100644 index 780cfc8..0000000 --- a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display_connected.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png Binary files differindex 2142147..1c2d7aa 100644 --- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png +++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png diff --git a/packages/SystemUI/res/drawable-mdpi/search_light.png b/packages/SystemUI/res/drawable-mdpi/search_light.png Binary files differindex 7a70984..8010ce7 100644 --- a/packages/SystemUI/res/drawable-mdpi/search_light.png +++ b/packages/SystemUI/res/drawable-mdpi/search_light.png diff --git a/packages/SystemUI/res/drawable-mdpi/search_light_land.png b/packages/SystemUI/res/drawable-mdpi/search_light_land.png Binary files differnew file mode 100644 index 0000000..a4d82f0 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/search_light_land.png diff --git a/packages/SystemUI/res/drawable-xhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-xhdpi/bg_protect.9.png Binary files differnew file mode 100644 index 0000000..72269f2 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png Binary files differindex c0032e2..e3cc9b0 100644 --- a/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png +++ b/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_available.png Binary files differnew file mode 100644 index 0000000..10ebcd5 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_available.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connected.png Binary files differnew file mode 100644 index 0000000..fef43b8 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connected.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_0.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_0.png Binary files differnew file mode 100644 index 0000000..05e3267 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_0.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_1.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_1.png Binary files differnew file mode 100644 index 0000000..ef42b27 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_1.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_2.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_2.png Binary files differnew file mode 100644 index 0000000..fc1c95e --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_2.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png Binary files differindex bffbf55..65d15b5 100644 --- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png Binary files differdeleted file mode 100644 index 48f90ac..0000000 --- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display_connected.png Binary files differdeleted file mode 100644 index 621c045..0000000 --- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display_connected.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png Binary files differindex b0ea8e0..fbd4d6b 100644 --- a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png +++ b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png diff --git a/packages/SystemUI/res/drawable-xhdpi/search_light.png b/packages/SystemUI/res/drawable-xhdpi/search_light.png Binary files differindex e2aed09..6d46fdd 100644 --- a/packages/SystemUI/res/drawable-xhdpi/search_light.png +++ b/packages/SystemUI/res/drawable-xhdpi/search_light.png diff --git a/packages/SystemUI/res/drawable-xhdpi/search_light_land.png b/packages/SystemUI/res/drawable-xhdpi/search_light_land.png Binary files differnew file mode 100644 index 0000000..b62c74e --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/search_light_land.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-xxhdpi/bg_protect.9.png Binary files differnew file mode 100644 index 0000000..efc9b04 --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png Binary files differindex a3cc08d..e15981a 100644 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_available.png Binary files differnew file mode 100644 index 0000000..68b1b7c --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_available.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connected.png Binary files differnew file mode 100644 index 0000000..8a8f890 --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connected.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_0.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_0.png Binary files differnew file mode 100644 index 0000000..12d4a01 --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_0.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_1.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_1.png Binary files differnew file mode 100644 index 0000000..3cb4421 --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_1.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_2.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_2.png Binary files differnew file mode 100644 index 0000000..4620b3a --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_2.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png Binary files differindex ab841d2..1a5d26a 100644 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png Binary files differdeleted file mode 100644 index b07be828..0000000 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display_connected.png Binary files differdeleted file mode 100644 index f02d0ab..0000000 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display_connected.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png Binary files differindex aac3428..86df881 100644 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/search_light.png b/packages/SystemUI/res/drawable-xxhdpi/search_light.png Binary files differindex e5ef85d..7742207 100644 --- a/packages/SystemUI/res/drawable-xxhdpi/search_light.png +++ b/packages/SystemUI/res/drawable-xxhdpi/search_light.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png b/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png Binary files differnew file mode 100644 index 0000000..f364577 --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_connecting.xml b/packages/SystemUI/res/drawable/ic_qs_cast_connecting.xml new file mode 100644 index 0000000..70db2a9 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_qs_cast_connecting.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright 2013, 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. + */ +--> +<animation-list + xmlns:android="http://schemas.android.com/apk/res/android" + android:oneshot="false"> + <item android:drawable="@drawable/ic_qs_cast_connecting_0" android:duration="500" /> + <item android:drawable="@drawable/ic_qs_cast_connecting_1" android:duration="500" /> + <item android:drawable="@drawable/ic_qs_cast_connecting_2" android:duration="500" /> + <item android:drawable="@drawable/ic_qs_cast_connecting_1" android:duration="500" /> +</animation-list> diff --git a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml index b2ba25a..0c0be29 100644 --- a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml +++ b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml @@ -24,6 +24,7 @@ android:id="@+id/recents_root" android:layout_height="match_parent" android:layout_width="match_parent" + android:foreground="@drawable/bg_protect" systemui:recentItemLayout="@layout/status_bar_recent_item" > <FrameLayout diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml index aa365ae..5488a87 100644 --- a/packages/SystemUI/res/layout/navigation_bar.xml +++ b/packages/SystemUI/res/layout/navigation_bar.xml @@ -311,7 +311,7 @@ android:layout_height="80dp" android:layout_width="match_parent" android:layout_gravity="center_vertical" - android:src="@drawable/search_light" + android:src="@drawable/search_light_land" android:scaleType="center" android:visibility="gone" android:contentDescription="@string/accessibility_search_light" diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index d7312df..eb66908 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -95,12 +95,12 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" /> - <!-- battery must be padded below by 2px to match assets --> + <!-- battery must be padded below to match assets --> <com.android.systemui.BatteryMeterView android:id="@+id/battery" android:layout_height="16dp" android:layout_width="10.5dp" - android:layout_marginBottom="2px" + android:layout_marginBottom="0.33dp" android:layout_marginStart="4dip" /> </LinearLayout> diff --git a/packages/SystemUI/res/layout/status_bar_recent_panel.xml b/packages/SystemUI/res/layout/status_bar_recent_panel.xml index e41475b..2f3968d 100644 --- a/packages/SystemUI/res/layout/status_bar_recent_panel.xml +++ b/packages/SystemUI/res/layout/status_bar_recent_panel.xml @@ -24,6 +24,7 @@ android:id="@+id/recents_root" android:layout_height="match_parent" android:layout_width="match_parent" + android:foreground="@drawable/bg_protect" systemui:recentItemLayout="@layout/status_bar_recent_item" > <FrameLayout diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index e6fcdff..e36ca8e 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -490,10 +490,8 @@ <string name="quick_settings_wifi_no_network">No Network</string> <!-- QuickSettings: Wifi (Off) [CHAR LIMIT=NONE] --> <string name="quick_settings_wifi_off_label">Wi-Fi Off</string> - <!-- QuickSettings: Wifi display [CHAR LIMIT=NONE] --> - <string name="quick_settings_wifi_display_label">Wi-Fi Display</string> - <!-- QuickSettings: Wifi display [CHAR LIMIT=NONE] --> - <string name="quick_settings_wifi_display_no_connection_label">Wireless Display</string> + <!-- QuickSettings: Remote display [CHAR LIMIT=NONE] --> + <string name="quick_settings_remote_display_no_connection_label">Cast Screen</string> <!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] --> <string name="quick_settings_brightness_dialog_title">Brightness</string> <!-- QuickSettings: Brightness dialog auto brightness button [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index b6e03e1..13aafb2 100755 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -66,7 +66,7 @@ public class BatteryMeterView extends View implements DemoMode { private final RectF mFrame = new RectF(); private final RectF mButtonFrame = new RectF(); private final RectF mClipFrame = new RectF(); - private final Rect mBoltFrame = new Rect(); + private final RectF mBoltFrame = new RectF(); private class BatteryTracker extends BroadcastReceiver { public static final int UNKNOWN_LEVEL = -1; @@ -319,10 +319,10 @@ public class BatteryMeterView extends View implements DemoMode { if (tracker.plugged) { // draw the bolt - final int bl = (int)(mFrame.left + mFrame.width() / 4.5f); - final int bt = (int)(mFrame.top + mFrame.height() / 6f); - final int br = (int)(mFrame.right - mFrame.width() / 7f); - final int bb = (int)(mFrame.bottom - mFrame.height() / 10f); + final float bl = mFrame.left + mFrame.width() / 4.5f; + final float bt = mFrame.top + mFrame.height() / 6f; + final float br = mFrame.right - mFrame.width() / 7f; + final float bb = mFrame.bottom - mFrame.height() / 10f; if (mBoltFrame.left != bl || mBoltFrame.top != bt || mBoltFrame.right != br || mBoltFrame.bottom != bb) { mBoltFrame.set(bl, bt, br, bb); diff --git a/packages/SystemUI/src/com/android/systemui/DessertCase.java b/packages/SystemUI/src/com/android/systemui/DessertCase.java index dd4c018..d797e38 100644 --- a/packages/SystemUI/src/com/android/systemui/DessertCase.java +++ b/packages/SystemUI/src/com/android/systemui/DessertCase.java @@ -36,7 +36,8 @@ public class DessertCase extends Activity { if (pm.getComponentEnabledSetting(cn) != PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { Slog.v("DessertCase", "ACHIEVEMENT UNLOCKED"); pm.setComponentEnabledSetting(cn, - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP); } mView = new DessertCaseView(this); diff --git a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java index 6fce732..4147155 100644 --- a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java +++ b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java @@ -496,7 +496,6 @@ public class DessertCaseView extends FrameLayout { } public static class RescalingContainer extends FrameLayout { - private static final int SYSTEM_UI_MODE_800 = 0x00000800; private DessertCaseView mView; private float mDarkness; @@ -509,7 +508,7 @@ public class DessertCaseView extends FrameLayout { | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | SYSTEM_UI_MODE_800 + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY ); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java index 9839fe9..7d3e870 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java @@ -38,7 +38,7 @@ public class AnimatedImageView extends ImageView { } private void updateAnim() { - Drawable drawable = getDrawable(); + Drawable drawable = mAttached ? getDrawable() : null; if (mAttached && mAnim != null) { mAnim.stop(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 6a2bc5f..ed00398 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar; import android.app.ActivityManager; import android.app.ActivityManagerNative; -import android.app.KeyguardManager; import android.app.Notification; import android.app.PendingIntent; import android.app.TaskStackBuilder; @@ -70,6 +69,7 @@ import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.SearchPanelView; import com.android.systemui.SystemUI; +import com.android.systemui.statusbar.phone.KeyguardTouchDelegate; import com.android.systemui.statusbar.policy.NotificationRowLayout; import java.util.ArrayList; @@ -128,7 +128,6 @@ public abstract class BaseStatusBar extends SystemUI implements protected boolean mUseHeadsUp = false; protected IDreamManager mDreamManager; - KeyguardManager mKeyguardManager; PowerManager mPowerManager; protected int mRowHeight; @@ -221,7 +220,6 @@ public abstract class BaseStatusBar extends SystemUI implements mDreamManager = IDreamManager.Stub.asInterface( ServiceManager.checkService(DreamService.DREAM_SERVICE)); - mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mProvisioningObserver.onChange(false); // set up @@ -749,9 +747,7 @@ public abstract class BaseStatusBar extends SystemUI implements Log.w(TAG, "Sending contentIntent failed: " + e); } - KeyguardManager kgm = - (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); - if (kgm != null) kgm.exitKeyguardSecurely(null); + KeyguardTouchDelegate.getInstance(mContext).dismiss(); } try { @@ -1056,10 +1052,12 @@ public abstract class BaseStatusBar extends SystemUI implements boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP, Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER; + final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext); boolean interrupt = (isFullscreen || (isHighPriority && isNoisy)) && isAllowed && mPowerManager.isScreenOn() - && !mKeyguardManager.isKeyguardLocked(); + && !keyguard.isShowingAndNotHidden() + && !keyguard.isInputRestricted(); try { interrupt = interrupt && !mDreamManager.isDreaming(); } catch (RemoteException e) { @@ -1087,8 +1085,7 @@ public abstract class BaseStatusBar extends SystemUI implements } public boolean inKeyguardRestrictedInputMode() { - KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); - return km.inKeyguardRestrictedInputMode(); + return KeyguardTouchDelegate.getInstance(mContext).isInputRestricted(); } public void setInteracting(int barWindow, boolean interacting) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index e8173b7..39333d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -55,8 +55,7 @@ public class CommandQueue extends IStatusBar.Stub { private static final int MSG_TOGGLE_RECENT_APPS = 13 << MSG_SHIFT; private static final int MSG_PRELOAD_RECENT_APPS = 14 << MSG_SHIFT; private static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 15 << MSG_SHIFT; - private static final int MSG_SET_NAVIGATION_ICON_HINTS = 16 << MSG_SHIFT; - private static final int MSG_SET_WINDOW_STATE = 17 << MSG_SHIFT; + private static final int MSG_SET_WINDOW_STATE = 16 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -98,7 +97,6 @@ public class CommandQueue extends IStatusBar.Stub { public void showSearchPanel(); public void hideSearchPanel(); public void cancelPreloadRecentApps(); - public void setNavigationIconHints(int hints); public void setWindowState(int window, int state); } @@ -227,13 +225,6 @@ public class CommandQueue extends IStatusBar.Stub { } } - public void setNavigationIconHints(int hints) { - synchronized (mList) { - mHandler.removeMessages(MSG_SET_NAVIGATION_ICON_HINTS); - mHandler.obtainMessage(MSG_SET_NAVIGATION_ICON_HINTS, hints, 0, null).sendToTarget(); - } - } - public void setWindowState(int window, int state) { synchronized (mList) { // don't coalesce these @@ -318,9 +309,6 @@ public class CommandQueue extends IStatusBar.Stub { case MSG_CANCEL_PRELOAD_RECENT_APPS: mCallbacks.cancelPreloadRecentApps(); break; - case MSG_SET_NAVIGATION_ICON_HINTS: - mCallbacks.setNavigationIconHints(msg.arg1); - break; case MSG_SET_WINDOW_STATE: mCallbacks.setWindowState(msg.arg1, msg.arg2); break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java index 8ad538b..cb17ac6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java @@ -99,6 +99,10 @@ public class BarTransitions { mBarBackground.finishAnimation(); } + public void setContentVisible(boolean visible) { + // for subclasses + } + private static class BarBackgroundDrawable extends Drawable { private final int mOpaque; private final int mSemiTransparent; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java index 5c55f0d..c1646ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java @@ -77,10 +77,11 @@ public class KeyguardTouchDelegate { } public static KeyguardTouchDelegate getInstance(Context context) { - if (sInstance == null) { - sInstance = new KeyguardTouchDelegate(context); + KeyguardTouchDelegate instance = sInstance; + if (instance == null) { + instance = sInstance = new KeyguardTouchDelegate(context); } - return sInstance; + return instance; } public boolean isSecure() { @@ -165,7 +166,21 @@ public class KeyguardTouchDelegate { Slog.e(TAG, "RemoteException launching camera!", e); } } else { - Slog.w(TAG, "dispatch(event): NO SERVICE!"); + Slog.w(TAG, "launchCamera(): NO SERVICE!"); + } + } + + public void dismiss() { + final IKeyguardService service = mService; + if (service != null) { + try { + service.dismiss(); + } catch (RemoteException e) { + // What to do? + Slog.e(TAG, "RemoteException dismissing keyguard!", e); + } + } else { + Slog.w(TAG, "dismiss(): NO SERVICE!"); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java index 5d4b995..a74230b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java @@ -30,6 +30,9 @@ import com.android.systemui.statusbar.policy.KeyButtonView; public final class NavigationBarTransitions extends BarTransitions { + private static final float KEYGUARD_QUIESCENT_ALPHA = 0.5f; + private static final int CONTENT_FADE_DURATION = 200; + private final NavigationBarView mView; private final IStatusBarService mBarService; @@ -73,18 +76,57 @@ public final class NavigationBarTransitions extends BarTransitions { private void applyMode(int mode, boolean animate, boolean force) { // apply to key buttons - final boolean isOpaque = mode == MODE_OPAQUE || mode == MODE_LIGHTS_OUT; - final float alpha = isOpaque ? KeyButtonView.DEFAULT_QUIESCENT_ALPHA : 1f; - setKeyButtonViewQuiescentAlpha(mView.getBackButton(), alpha, animate); + final float alpha = alphaForMode(mode); setKeyButtonViewQuiescentAlpha(mView.getHomeButton(), alpha, animate); setKeyButtonViewQuiescentAlpha(mView.getRecentsButton(), alpha, animate); setKeyButtonViewQuiescentAlpha(mView.getMenuButton(), alpha, animate); - setKeyButtonViewQuiescentAlpha(mView.getCameraButton(), alpha, animate); + + setKeyButtonViewQuiescentAlpha(mView.getSearchLight(), KEYGUARD_QUIESCENT_ALPHA, animate); + setKeyButtonViewQuiescentAlpha(mView.getCameraButton(), KEYGUARD_QUIESCENT_ALPHA, animate); + + applyBackButtonQuiescentAlpha(mode, animate); // apply to lights out applyLightsOut(mode == MODE_LIGHTS_OUT, animate, force); } + private float alphaForMode(int mode) { + final boolean isOpaque = mode == MODE_OPAQUE || mode == MODE_LIGHTS_OUT; + return isOpaque ? KeyButtonView.DEFAULT_QUIESCENT_ALPHA : 1f; + } + + public void applyBackButtonQuiescentAlpha(int mode, boolean animate) { + float backAlpha = 0; + backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getSearchLight()); + backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getCameraButton()); + backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getHomeButton()); + backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getRecentsButton()); + backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getMenuButton()); + if (backAlpha > 0) { + setKeyButtonViewQuiescentAlpha(mView.getBackButton(), backAlpha, animate); + } + } + + private static float maxVisibleQuiescentAlpha(float max, View v) { + if ((v instanceof KeyButtonView) && v.isShown()) { + return Math.max(max, ((KeyButtonView)v).getQuiescentAlpha()); + } + return max; + } + + @Override + public void setContentVisible(boolean visible) { + final float alpha = visible ? 1 : 0; + fadeContent(mView.getCameraButton(), alpha); + fadeContent(mView.getSearchLight(), alpha); + } + + private void fadeContent(View v, float alpha) { + if (v != null) { + v.animate().alpha(alpha).setDuration(CONTENT_FADE_DURATION); + } + } + private void setKeyButtonViewQuiescentAlpha(View button, float alpha, boolean animate) { if (button instanceof KeyButtonView) { ((KeyButtonView) button).setQuiescentAlpha(alpha, animate); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index d1c4109..839016d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -17,6 +17,10 @@ package com.android.systemui.statusbar.phone; import android.animation.LayoutTransition; +import android.animation.LayoutTransition.TransitionListener; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; import android.app.ActivityManagerNative; import android.app.StatusBarManager; import android.app.admin.DevicePolicyManager; @@ -48,12 +52,12 @@ import com.android.systemui.R; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.DelegateViewHelper; import com.android.systemui.statusbar.policy.DeadZone; +import com.android.systemui.statusbar.policy.KeyButtonView; import java.io.FileDescriptor; import java.io.PrintWriter; public class NavigationBarView extends LinearLayout { - private static final int CAMERA_BUTTON_FADE_DURATION = 200; final static boolean DEBUG = false; final static String TAG = "PhoneStatusBar/NavigationBarView"; @@ -89,6 +93,54 @@ public class NavigationBarView extends LinearLayout { // used to disable the camera icon in navbar when disabled by DPM private boolean mCameraDisabledByDpm; + // performs manual animation in sync with layout transitions + private final NavTransitionListener mTransitionListener = new NavTransitionListener(); + + private class NavTransitionListener implements TransitionListener { + private boolean mBackTransitioning; + private boolean mHomeAppearing; + private long mStartDelay; + private long mDuration; + private TimeInterpolator mInterpolator; + + @Override + public void startTransition(LayoutTransition transition, ViewGroup container, + View view, int transitionType) { + if (view.getId() == R.id.back) { + mBackTransitioning = true; + } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) { + mHomeAppearing = true; + mStartDelay = transition.getStartDelay(transitionType); + mDuration = transition.getDuration(transitionType); + mInterpolator = transition.getInterpolator(transitionType); + } + } + + @Override + public void endTransition(LayoutTransition transition, ViewGroup container, + View view, int transitionType) { + if (view.getId() == R.id.back) { + mBackTransitioning = false; + } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) { + mHomeAppearing = false; + } + } + + public void onBackAltCleared() { + // When dismissing ime during unlock, force the back button to run the same appearance + // animation as home (if we catch this condition early enough). + if (!mBackTransitioning && getBackButton().getVisibility() == VISIBLE + && mHomeAppearing && getHomeButton().getAlpha() == 0) { + getBackButton().setAlpha(0); + ValueAnimator a = ObjectAnimator.ofFloat(getBackButton(), "alpha", 0, 1); + a.setStartDelay(mStartDelay); + a.setDuration(mDuration); + a.setInterpolator(mInterpolator); + a.start(); + } + } + } + // simplified click handler to be used when device is in accessibility mode private final OnClickListener mAccessibilityClickListener = new OnClickListener() { @Override @@ -108,12 +160,12 @@ public class NavigationBarView extends LinearLayout { case MotionEvent.ACTION_DOWN: // disable search gesture while interacting with camera mDelegateHelper.setDisabled(true); - transitionCameraAndSearchButtonAlpha(0.0f); + mBarTransitions.setContentVisible(false); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mDelegateHelper.setDisabled(false); - transitionCameraAndSearchButtonAlpha(1.0f); + mBarTransitions.setContentVisible(true); break; } return KeyguardTouchDelegate.getInstance(getContext()).dispatch(event); @@ -163,17 +215,6 @@ public class NavigationBarView extends LinearLayout { watchForDevicePolicyChanges(); } - protected void transitionCameraAndSearchButtonAlpha(float alpha) { - View cameraButtonView = getCameraButton(); - if (cameraButtonView != null) { - cameraButtonView.animate().alpha(alpha).setDuration(CAMERA_BUTTON_FADE_DURATION); - } - View searchLight = getSearchLight(); - if (searchLight != null) { - searchLight.animate().alpha(alpha).setDuration(CAMERA_BUTTON_FADE_DURATION); - } - } - private void watchForDevicePolicyChanges() { final IntentFilter filter = new IntentFilter(); filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); @@ -277,7 +318,10 @@ public class NavigationBarView extends LinearLayout { public void setNavigationIconHints(int hints, boolean force) { if (!force && hints == mNavigationIconHints) return; - + final boolean backAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; + if ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0 && !backAlt) { + mTransitionListener.onBackAltCleared(); + } if (DEBUG) { android.widget.Toast.makeText(mContext, "Navigation icon hints = " + hints, @@ -286,15 +330,7 @@ public class NavigationBarView extends LinearLayout { mNavigationIconHints = hints; - getBackButton().setAlpha( - (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_NOP)) ? 0.5f : 1.0f); - getHomeButton().setAlpha( - (0 != (hints & StatusBarManager.NAVIGATION_HINT_HOME_NOP)) ? 0.5f : 1.0f); - getRecentsButton().setAlpha( - (0 != (hints & StatusBarManager.NAVIGATION_HINT_RECENT_NOP)) ? 0.5f : 1.0f); - - ((ImageView)getBackButton()).setImageDrawable( - (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT)) + ((ImageView)getBackButton()).setImageDrawable(backAlt ? (mVertical ? mBackAltLandIcon : mBackAltIcon) : (mVertical ? mBackLandIcon : mBackIcon)); @@ -322,13 +358,20 @@ public class NavigationBarView extends LinearLayout { setSlippery(disableHome && disableRecent && disableBack && disableSearch); } - if (!mScreenOn && mCurrentView != null) { - ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons); - LayoutTransition lt = navButtons == null ? null : navButtons.getLayoutTransition(); + ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons); + if (navButtons != null) { + LayoutTransition lt = navButtons.getLayoutTransition(); if (lt != null) { - lt.disableTransitionType( - LayoutTransition.CHANGE_APPEARING | LayoutTransition.CHANGE_DISAPPEARING | - LayoutTransition.APPEARING | LayoutTransition.DISAPPEARING); + if (!lt.getTransitionListeners().contains(mTransitionListener)) { + lt.addTransitionListener(mTransitionListener); + } + if (!mScreenOn && mCurrentView != null) { + lt.disableTransitionType( + LayoutTransition.CHANGE_APPEARING | + LayoutTransition.CHANGE_DISAPPEARING | + LayoutTransition.APPEARING | + LayoutTransition.DISAPPEARING); + } } } @@ -336,12 +379,17 @@ public class NavigationBarView extends LinearLayout { getHomeButton() .setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); - final boolean shouldShowSearch = disableHome && !disableSearch; - getSearchLight().setVisibility(shouldShowSearch ? View.VISIBLE : View.GONE); - final View cameraButton = getCameraButton(); - if (cameraButton != null) { - cameraButton.setVisibility( - shouldShowSearch && !mCameraDisabledByDpm ? View.VISIBLE : View.GONE); + final boolean showSearch = disableHome && !disableSearch; + final boolean showCamera = showSearch && !mCameraDisabledByDpm; + setVisibleOrGone(getSearchLight(), showSearch); + setVisibleOrGone(getCameraButton(), showCamera); + + mBarTransitions.applyBackButtonQuiescentAlpha(mBarTransitions.getMode(), true /*animate*/); + } + + private void setVisibleOrGone(View view, boolean visible) { + if (view != null) { + view.setVisibility(visible ? VISIBLE : GONE); } } @@ -574,28 +622,31 @@ public class NavigationBarView extends LinearLayout { mVertical ? "true" : "false", mShowMenu ? "true" : "false")); - final View back = getBackButton(); - final View home = getHomeButton(); - final View recent = getRecentsButton(); - final View menu = getMenuButton(); - - pw.println(" back: " - + PhoneStatusBar.viewInfo(back) - + " " + visibilityToString(back.getVisibility()) - ); - pw.println(" home: " - + PhoneStatusBar.viewInfo(home) - + " " + visibilityToString(home.getVisibility()) - ); - pw.println(" rcnt: " - + PhoneStatusBar.viewInfo(recent) - + " " + visibilityToString(recent.getVisibility()) - ); - pw.println(" menu: " - + PhoneStatusBar.viewInfo(menu) - + " " + visibilityToString(menu.getVisibility()) - ); + dumpButton(pw, "back", getBackButton()); + dumpButton(pw, "home", getHomeButton()); + dumpButton(pw, "rcnt", getRecentsButton()); + dumpButton(pw, "menu", getMenuButton()); + dumpButton(pw, "srch", getSearchLight()); + dumpButton(pw, "cmra", getCameraButton()); + pw.println(" }"); } + private static void dumpButton(PrintWriter pw, String caption, View button) { + pw.print(" " + caption + ": "); + if (button == null) { + pw.print("null"); + } else { + pw.print(PhoneStatusBar.viewInfo(button) + + " " + visibilityToString(button.getVisibility()) + + " alpha=" + button.getAlpha() + ); + if (button instanceof KeyButtonView) { + pw.print(" drawingAlpha=" + ((KeyButtonView)button).getDrawingAlpha()); + pw.print(" quiescentAlpha=" + ((KeyButtonView)button).getQuiescentAlpha()); + } + } + pw.println(); + } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 39a9ba7..bbac4ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -53,6 +53,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; @@ -632,6 +633,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } } + PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mBroadcastReceiver.onReceive(mContext, + new Intent(pm.isScreenOn() ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF)); + // receive broadcasts IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); @@ -649,14 +654,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { @Override protected void onShowSearchPanel() { if (mNavigationBarView != null) { - mNavigationBarView.transitionCameraAndSearchButtonAlpha(0.0f); + mNavigationBarView.getBarTransitions().setContentVisible(false); } } @Override protected void onHideSearchPanel() { if (mNavigationBarView != null) { - mNavigationBarView.transitionCameraAndSearchButtonAlpha(1.0f); + mNavigationBarView.getBarTransitions().setContentVisible(true); } } @@ -802,7 +807,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } private void repositionNavigationBar() { - if (mNavigationBarView == null) return; + if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return; prepareNavigationBarView(); @@ -1807,8 +1812,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { return mGestureRec; } - @Override // CommandQueue - public void setNavigationIconHints(int hints) { + private void setNavigationIconHints(int hints) { if (hints == mNavigationIconHints) return; mNavigationIconHints = hints; @@ -2045,7 +2049,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { boolean altBack = (backDisposition == InputMethodService.BACK_DISPOSITION_WILL_DISMISS) || ((vis & InputMethodService.IME_VISIBLE) != 0); - mCommandQueue.setNavigationIconHints( + setNavigationIconHints( altBack ? (mNavigationIconHints | NAVIGATION_HINT_BACK_ALT) : (mNavigationIconHints & ~NAVIGATION_HINT_BACK_ALT)); if (mQS != null) mQS.setImeWindowStatus(vis > 0); @@ -2101,9 +2105,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } public void tickerHalting() { - mStatusBarContents.setVisibility(View.VISIBLE); + if (mStatusBarContents.getVisibility() != View.VISIBLE) { + mStatusBarContents.setVisibility(View.VISIBLE); + mStatusBarContents + .startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null)); + } mTickerView.setVisibility(View.GONE); - mStatusBarContents.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null)); // we do not animate the ticker away at this point, just get rid of it (b/6992707) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java index 37504fb..e7b8fa1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java @@ -37,9 +37,8 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.graphics.drawable.LevelListDrawable; import android.hardware.display.DisplayManager; -import android.hardware.display.WifiDisplayStatus; +import android.media.MediaRouter; import android.net.wifi.WifiManager; import android.os.AsyncTask; import android.os.Handler; @@ -62,6 +61,7 @@ import android.view.WindowManagerGlobal; import android.widget.ImageView; import android.widget.TextView; +import com.android.internal.app.MediaRouteDialogPresenter; import com.android.systemui.R; import com.android.systemui.statusbar.phone.QuickSettingsModel.ActivityState; import com.android.systemui.statusbar.phone.QuickSettingsModel.BluetoothState; @@ -92,9 +92,7 @@ class QuickSettings { private QuickSettingsModel mModel; private ViewGroup mContainerView; - private DisplayManager mDisplayManager; private DevicePolicyManager mDevicePolicyManager; - private WifiDisplayStatus mWifiDisplayStatus; private PhoneStatusBar mStatusBarService; private BluetoothState mBluetoothState; private BluetoothAdapter mBluetoothAdapter; @@ -118,13 +116,11 @@ class QuickSettings { new ArrayList<QuickSettingsTileView>(); public QuickSettings(Context context, QuickSettingsContainerView container) { - mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); mDevicePolicyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); mContext = context; mContainerView = container; mModel = new QuickSettingsModel(context); - mWifiDisplayStatus = new WifiDisplayStatus(); mBluetoothState = new QuickSettingsModel.BluetoothState(); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); @@ -171,7 +167,6 @@ class QuickSettings { mLocationController = locationController; setupQuickSettings(); - updateWifiDisplayStatus(); updateResources(); applyLocationEnabledStatus(); @@ -314,11 +309,19 @@ class QuickSettings { collapsePanels(); final UserManager um = UserManager.get(mContext); if (um.getUsers(true).size() > 1) { - try { - WindowManagerGlobal.getWindowManagerService().lockNow(null); - } catch (RemoteException e) { - Log.e(TAG, "Couldn't show user switcher", e); - } + // Since keyguard and systemui were merged into the same process to save + // memory, they share the same Looper and graphics context. As a result, + // there's no way to allow concurrent animation while keyguard inflates. + // The workaround is to add a slight delay to allow the animation to finish. + mHandler.postDelayed(new Runnable() { + public void run() { + try { + WindowManagerGlobal.getWindowManagerService().lockNow(null); + } catch (RemoteException e) { + Log.e(TAG, "Couldn't show user switcher", e); + } + } + }, 400); // TODO: ideally this would be tied to the collapse of the panel } else { Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent( mContext, v, ContactsContract.Profile.CONTENT_URI, @@ -668,20 +671,33 @@ class QuickSettings { }); parent.addView(alarmTile); - // Wifi Display - QuickSettingsBasicTile wifiDisplayTile + // Remote Display + QuickSettingsBasicTile remoteDisplayTile = new QuickSettingsBasicTile(mContext); - wifiDisplayTile.setImageResource(R.drawable.ic_qs_remote_display); - wifiDisplayTile.setOnClickListener(new View.OnClickListener() { + remoteDisplayTile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - startSettingsActivity(android.provider.Settings.ACTION_WIFI_DISPLAY_SETTINGS); + collapsePanels(); + + final Dialog[] dialog = new Dialog[1]; + dialog[0] = MediaRouteDialogPresenter.createDialog(mContext, + MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, + new View.OnClickListener() { + @Override + public void onClick(View v) { + dialog[0].dismiss(); + startSettingsActivity( + android.provider.Settings.ACTION_WIFI_DISPLAY_SETTINGS); + } + }); + dialog[0].getWindow().setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); + dialog[0].show(); } }); - mModel.addWifiDisplayTile(wifiDisplayTile, - new QuickSettingsModel.BasicRefreshCallback(wifiDisplayTile) + mModel.addRemoteDisplayTile(remoteDisplayTile, + new QuickSettingsModel.BasicRefreshCallback(remoteDisplayTile) .setShowWhenEnabled(true)); - parent.addView(wifiDisplayTile); + parent.addView(remoteDisplayTile); if (SHOW_IME_TILE || DEBUG_GONE_TILES) { // IME @@ -816,15 +832,6 @@ class QuickSettings { dialog.show(); } - private void updateWifiDisplayStatus() { - mWifiDisplayStatus = mDisplayManager.getWifiDisplayStatus(); - applyWifiDisplayStatus(); - } - - private void applyWifiDisplayStatus() { - mModel.onWifiDisplayStateChanged(mWifiDisplayStatus); - } - private void applyBluetoothStatus() { mModel.onBluetoothStateChange(mBluetoothState); } @@ -848,12 +855,7 @@ class QuickSettings { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); - if (DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED.equals(action)) { - WifiDisplayStatus status = (WifiDisplayStatus)intent.getParcelableExtra( - DisplayManager.EXTRA_WIFI_DISPLAY_STATUS); - mWifiDisplayStatus = status; - applyWifiDisplayStatus(); - } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { + if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); mBluetoothState.enabled = (state == BluetoothAdapter.STATE_ON); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java index 15d655c..e1a20ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java @@ -27,7 +27,8 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.drawable.Drawable; -import android.hardware.display.WifiDisplayStatus; +import android.media.MediaRouter; +import android.media.MediaRouter.RouteInfo; import android.net.ConnectivityManager; import android.os.Handler; import android.os.UserHandle; @@ -57,7 +58,6 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, BrightnessStateChangeCallback, RotationLockControllerCallback, LocationSettingsChangeCallback { - // Sett InputMethoManagerService private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher"; @@ -199,6 +199,30 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, } } + /** Callback for changes to remote display routes. */ + private class RemoteDisplayRouteCallback extends MediaRouter.SimpleCallback { + @Override + public void onRouteAdded(MediaRouter router, RouteInfo route) { + updateRemoteDisplays(); + } + @Override + public void onRouteChanged(MediaRouter router, RouteInfo route) { + updateRemoteDisplays(); + } + @Override + public void onRouteRemoved(MediaRouter router, RouteInfo route) { + updateRemoteDisplays(); + } + @Override + public void onRouteSelected(MediaRouter router, int type, RouteInfo route) { + updateRemoteDisplays(); + } + @Override + public void onRouteUnselected(MediaRouter router, int type, RouteInfo route) { + updateRemoteDisplays(); + } + } + private final Context mContext; private final Handler mHandler; private final CurrentUserTracker mUserTracker; @@ -206,6 +230,9 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, private final BugreportObserver mBugreportObserver; private final BrightnessObserver mBrightnessObserver; + private final MediaRouter mMediaRouter; + private final RemoteDisplayRouteCallback mRemoteDisplayRouteCallback; + private final boolean mHasMobileData; private QuickSettingsTileView mUserTile; @@ -228,9 +255,9 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, private RefreshCallback mWifiCallback; private WifiState mWifiState = new WifiState(); - private QuickSettingsTileView mWifiDisplayTile; - private RefreshCallback mWifiDisplayCallback; - private State mWifiDisplayState = new State(); + private QuickSettingsTileView mRemoteDisplayTile; + private RefreshCallback mRemoteDisplayCallback; + private State mRemoteDisplayState = new State(); private QuickSettingsTileView mRSSITile; private RefreshCallback mRSSICallback; @@ -278,12 +305,14 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, mContext = context; mHandler = new Handler(); mUserTracker = new CurrentUserTracker(mContext) { + @Override public void onUserSwitched(int newUserId) { mBrightnessObserver.startObserving(); refreshRotationLockTile(); onBrightnessLevelChanged(); onNextAlarmChanged(); onBugreportChanged(); + rebindMediaRouterAsCurrentUser(); } }; @@ -294,6 +323,11 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, mBrightnessObserver = new BrightnessObserver(mHandler); mBrightnessObserver.startObserving(); + mMediaRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE); + rebindMediaRouterAsCurrentUser(); + + mRemoteDisplayRouteCallback = new RemoteDisplayRouteCallback(); + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); mHasMobileData = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); @@ -621,24 +655,64 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, mBugreportCallback.refreshView(mBugreportTile, mBugreportState); } - // Wifi Display - void addWifiDisplayTile(QuickSettingsTileView view, RefreshCallback cb) { - mWifiDisplayTile = view; - mWifiDisplayCallback = cb; + // Remote Display + void addRemoteDisplayTile(QuickSettingsTileView view, RefreshCallback cb) { + mRemoteDisplayTile = view; + mRemoteDisplayCallback = cb; + final int[] count = new int[1]; + mRemoteDisplayTile.setOnPrepareListener(new QuickSettingsTileView.OnPrepareListener() { + @Override + public void onPrepare() { + mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, + mRemoteDisplayRouteCallback, + MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY); + updateRemoteDisplays(); + } + @Override + public void onUnprepare() { + mMediaRouter.removeCallback(mRemoteDisplayRouteCallback); + } + }); + + updateRemoteDisplays(); } - public void onWifiDisplayStateChanged(WifiDisplayStatus status) { - mWifiDisplayState.enabled = - (status.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON); - if (status.getActiveDisplay() != null) { - mWifiDisplayState.label = status.getActiveDisplay().getFriendlyDisplayName(); - mWifiDisplayState.iconId = R.drawable.ic_qs_remote_display_connected; + + private void rebindMediaRouterAsCurrentUser() { + mMediaRouter.rebindAsUser(mUserTracker.getCurrentUserId()); + } + + private void updateRemoteDisplays() { + MediaRouter.RouteInfo connectedRoute = mMediaRouter.getSelectedRoute( + MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY); + boolean enabled = connectedRoute != null + && connectedRoute.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY); + boolean connecting; + if (enabled) { + connecting = connectedRoute.isConnecting(); } else { - mWifiDisplayState.label = mContext.getString( - R.string.quick_settings_wifi_display_no_connection_label); - mWifiDisplayState.iconId = R.drawable.ic_qs_remote_display; + connectedRoute = null; + connecting = false; + final int count = mMediaRouter.getRouteCount(); + for (int i = 0; i < count; i++) { + MediaRouter.RouteInfo route = mMediaRouter.getRouteAt(i); + if (route.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)) { + enabled = true; + break; + } + } } - mWifiDisplayCallback.refreshView(mWifiDisplayTile, mWifiDisplayState); + mRemoteDisplayState.enabled = enabled; + if (connectedRoute != null) { + mRemoteDisplayState.label = connectedRoute.getName().toString(); + mRemoteDisplayState.iconId = connecting ? + R.drawable.ic_qs_cast_connecting : R.drawable.ic_qs_cast_connected; + } else { + mRemoteDisplayState.label = mContext.getString( + R.string.quick_settings_remote_display_no_connection_label); + mRemoteDisplayState.iconId = R.drawable.ic_qs_cast_available; + } + mRemoteDisplayCallback.refreshView(mRemoteDisplayTile, mRemoteDisplayState); } // IME diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java index 3d520f7..ad18294 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java @@ -21,6 +21,7 @@ import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewParent; import android.widget.FrameLayout; /** @@ -31,14 +32,14 @@ class QuickSettingsTileView extends FrameLayout { private int mContentLayoutId; private int mColSpan; - private int mRowSpan; + private boolean mPrepared; + private OnPrepareListener mOnPrepareListener; public QuickSettingsTileView(Context context, AttributeSet attrs) { super(context, attrs); mContentLayoutId = -1; mColSpan = 1; - mRowSpan = 1; } void setColumnSpan(int span) { @@ -77,4 +78,72 @@ class QuickSettingsTileView extends FrameLayout { } super.setVisibility(vis); } + + public void setOnPrepareListener(OnPrepareListener listener) { + if (mOnPrepareListener != listener) { + mOnPrepareListener = listener; + mPrepared = false; + post(new Runnable() { + @Override + public void run() { + updatePreparedState(); + } + }); + } + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + updatePreparedState(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + updatePreparedState(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + updatePreparedState(); + } + + private void updatePreparedState() { + if (mOnPrepareListener != null) { + if (isParentVisible()) { + if (!mPrepared) { + mPrepared = true; + mOnPrepareListener.onPrepare(); + } + } else if (mPrepared) { + mPrepared = false; + mOnPrepareListener.onUnprepare(); + } + } + } + + private boolean isParentVisible() { + if (!isAttachedToWindow()) { + return false; + } + for (ViewParent current = getParent(); current instanceof View; + current = current.getParent()) { + View view = (View)current; + if (view.getVisibility() != VISIBLE) { + return false; + } + } + return true; + } + + /** + * Called when the view's parent becomes visible or invisible to provide + * an opportunity for the client to provide new content. + */ + public interface OnPrepareListener { + void onPrepare(); + void onUnprepare(); + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index e77b420..4901823 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -113,7 +113,7 @@ public class StatusBarWindowView extends FrameLayout handled = super.onTouchEvent(ev); } final int action = ev.getAction(); - if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + if (!handled && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)) { mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); } return handled; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java index dca5e41..6eb88be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java @@ -22,7 +22,7 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.os.SystemClock; import android.util.AttributeSet; -import android.util.Log; +import android.util.Slog; import android.view.MotionEvent; import android.view.View; @@ -75,7 +75,7 @@ public class DeadZone extends View { mVertical = (index == VERTICAL); if (DEBUG) - Log.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold + Slog.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold + (mVertical ? " vertical" : " horizontal")); setFlashOnTouchCapture(context.getResources().getBoolean(R.bool.config_dead_zone_flash)); @@ -106,7 +106,7 @@ public class DeadZone extends View { @Override public boolean onTouchEvent(MotionEvent event) { if (DEBUG) { - Log.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction())); + Slog.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction())); } final int action = event.getAction(); @@ -114,12 +114,12 @@ public class DeadZone extends View { poke(event); } else if (action == MotionEvent.ACTION_DOWN) { if (DEBUG) { - Log.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY()); + Slog.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY()); } int size = (int) getSize(event.getEventTime()); if ((mVertical && event.getX() < size) || event.getY() < size) { if (CHATTY) { - Log.v(TAG, "consuming errant click: (" + event.getX() + "," + event.getY() + ")"); + Slog.v(TAG, "consuming errant click: (" + event.getX() + "," + event.getY() + ")"); } if (mShouldFlash) { post(mDebugFlash); @@ -134,7 +134,7 @@ public class DeadZone extends View { public void poke(MotionEvent event) { mLastPokeTime = event.getEventTime(); if (DEBUG) - Log.v(TAG, "poked! size=" + getSize(mLastPokeTime)); + Slog.v(TAG, "poked! size=" + getSize(mLastPokeTime)); if (mShouldFlash) postInvalidate(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java index 55fb95d..718acc3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java @@ -36,6 +36,7 @@ import android.view.MotionEvent; import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewDebug; import android.view.accessibility.AccessibilityEvent; import android.widget.ImageView; @@ -53,10 +54,13 @@ public class KeyButtonView extends ImageView { int mTouchSlop; Drawable mGlowBG; int mGlowWidth, mGlowHeight; - float mGlowAlpha = 0f, mGlowScale = 1f, mDrawingAlpha = 1f; + float mGlowAlpha = 0f, mGlowScale = 1f; + @ViewDebug.ExportedProperty(category = "drawing") + float mDrawingAlpha = 1f; + @ViewDebug.ExportedProperty(category = "drawing") float mQuiescentAlpha = DEFAULT_QUIESCENT_ALPHA; boolean mSupportsLongpress = true; - RectF mRect = new RectF(0f,0f,0f,0f); + RectF mRect = new RectF(); AnimatorSet mPressedAnim; Animator mAnimateToQuiescent = new ObjectAnimator(); @@ -90,8 +94,8 @@ public class KeyButtonView extends ImageView { mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true); mGlowBG = a.getDrawable(R.styleable.KeyButtonView_glowBackground); + setDrawingAlpha(mQuiescentAlpha); if (mGlowBG != null) { - setDrawingAlpha(mQuiescentAlpha); mGlowWidth = mGlowBG.getIntrinsicWidth(); mGlowHeight = mGlowBG.getIntrinsicHeight(); } @@ -126,16 +130,14 @@ public class KeyButtonView extends ImageView { public void setQuiescentAlpha(float alpha, boolean animate) { mAnimateToQuiescent.cancel(); alpha = Math.min(Math.max(alpha, 0), 1); - if (alpha == mQuiescentAlpha) return; + if (alpha == mQuiescentAlpha && alpha == mDrawingAlpha) return; mQuiescentAlpha = alpha; if (DEBUG) Log.d(TAG, "New quiescent alpha = " + mQuiescentAlpha); - if (mGlowBG != null) { - if (animate) { - mAnimateToQuiescent = animateToQuiescent(); - mAnimateToQuiescent.start(); - } else { - setDrawingAlpha(mQuiescentAlpha); - } + if (mGlowBG != null && animate) { + mAnimateToQuiescent = animateToQuiescent(); + mAnimateToQuiescent.start(); + } else { + setDrawingAlpha(mQuiescentAlpha); } } @@ -143,13 +145,15 @@ public class KeyButtonView extends ImageView { return ObjectAnimator.ofFloat(this, "drawingAlpha", mQuiescentAlpha); } + public float getQuiescentAlpha() { + return mQuiescentAlpha; + } + public float getDrawingAlpha() { - if (mGlowBG == null) return 0; return mDrawingAlpha; } public void setDrawingAlpha(float x) { - if (mGlowBG == null) return; // Calling setAlpha(int), which is an ImageView-specific // method that's different from setAlpha(float). This sets // the alpha on this ImageView's drawable directly diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index a53b25a..dd13e31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -89,10 +89,6 @@ public class TvStatusBar extends BaseStatusBar { } @Override // CommandQueue - public void setNavigationIconHints(int hints) { - } - - @Override // CommandQueue public void setWindowState(int window, int state) { } diff --git a/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java b/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java index e14f89a..8511de2 100644 --- a/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java +++ b/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java @@ -24,6 +24,9 @@ import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.net.Uri; import android.os.Build; @@ -53,7 +56,8 @@ class SimpleBitmapRegionDecoderWrapper implements SimpleBitmapRegionDecoder { private SimpleBitmapRegionDecoderWrapper(BitmapRegionDecoder decoder) { mDecoder = decoder; } - public static SimpleBitmapRegionDecoderWrapper newInstance(String pathName, boolean isShareable) { + public static SimpleBitmapRegionDecoderWrapper newInstance( + String pathName, boolean isShareable) { try { BitmapRegionDecoder d = BitmapRegionDecoder.newInstance(pathName, isShareable); if (d != null) { @@ -65,7 +69,8 @@ class SimpleBitmapRegionDecoderWrapper implements SimpleBitmapRegionDecoder { } return null; } - public static SimpleBitmapRegionDecoderWrapper newInstance(InputStream is, boolean isShareable) { + public static SimpleBitmapRegionDecoderWrapper newInstance( + InputStream is, boolean isShareable) { try { BitmapRegionDecoder d = BitmapRegionDecoder.newInstance(is, isShareable); if (d != null) { @@ -89,8 +94,9 @@ class SimpleBitmapRegionDecoderWrapper implements SimpleBitmapRegionDecoder { } class DumbBitmapRegionDecoder implements SimpleBitmapRegionDecoder { - //byte[] streamCopy; Bitmap mBuffer; + Canvas mTempCanvas; + Paint mTempPaint; private DumbBitmapRegionDecoder(Bitmap b) { mBuffer = b; } @@ -115,9 +121,23 @@ class DumbBitmapRegionDecoder implements SimpleBitmapRegionDecoder { return mBuffer.getHeight(); } public Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options) { - System.out.println("DECODING WITH SAMPLE LEVEL OF " + options.inSampleSize); - return Bitmap.createBitmap( - mBuffer, wantRegion.left, wantRegion.top, wantRegion.width(), wantRegion.height()); + if (mTempCanvas == null) { + mTempCanvas = new Canvas(); + mTempPaint = new Paint(); + mTempPaint.setFilterBitmap(true); + } + int sampleSize = Math.max(options.inSampleSize, 1); + Bitmap newBitmap = Bitmap.createBitmap( + wantRegion.width() / sampleSize, + wantRegion.height() / sampleSize, + Bitmap.Config.ARGB_8888); + mTempCanvas.setBitmap(newBitmap); + mTempCanvas.save(); + mTempCanvas.scale(1f / sampleSize, 1f / sampleSize); + mTempCanvas.drawBitmap(mBuffer, -wantRegion.left, -wantRegion.top, mTempPaint); + mTempCanvas.restore(); + mTempCanvas.setBitmap(null); + return newBitmap; } } @@ -256,6 +276,7 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { if (regionDecoder == null) { is = regenerateInputStream(); regionDecoder = DumbBitmapRegionDecoder.newInstance(is); + Utils.closeSilently(is); } return regionDecoder; } catch (FileNotFoundException e) { @@ -280,8 +301,9 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { } @Override public boolean readExif(ExifInterface ei) { + InputStream is = null; try { - InputStream is = regenerateInputStream(); + is = regenerateInputStream(); ei.readExif(is); Utils.closeSilently(is); return true; @@ -291,6 +313,8 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { } catch (IOException e) { Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e); return false; + } finally { + Utils.closeSilently(is); } } } @@ -316,6 +340,7 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { if (regionDecoder == null) { is = regenerateInputStream(); regionDecoder = DumbBitmapRegionDecoder.newInstance(is); + Utils.closeSilently(is); } return regionDecoder; } diff --git a/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java b/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java index d12140d..57c0581 100644 --- a/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java +++ b/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java @@ -247,19 +247,19 @@ public class WallpaperCropActivity extends Activity { private static int getRotationFromExifHelper( String path, Resources res, int resId, Context context, Uri uri) { ExifInterface ei = new ExifInterface(); + InputStream is = null; + BufferedInputStream bis = null; try { if (path != null) { ei.readExif(path); } else if (uri != null) { - InputStream is = context.getContentResolver().openInputStream(uri); - BufferedInputStream bis = new BufferedInputStream(is); + is = context.getContentResolver().openInputStream(uri); + bis = new BufferedInputStream(is); ei.readExif(bis); - bis.close(); } else { - InputStream is = res.openRawResource(resId); - BufferedInputStream bis = new BufferedInputStream(is); + is = res.openRawResource(resId); + bis = new BufferedInputStream(is); ei.readExif(bis); - bis.close(); } Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION); if (ori != null) { @@ -267,6 +267,9 @@ public class WallpaperCropActivity extends Activity { } } catch (IOException e) { Log.w(LOGTAG, "Getting exif data failed", e); + } finally { + Utils.closeSilently(bis); + Utils.closeSilently(is); } return 0; } @@ -326,40 +329,15 @@ public class WallpaperCropActivity extends Activity { // Get the crop boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR; - Point minDims = new Point(); - Point maxDims = new Point(); + Display d = getWindowManager().getDefaultDisplay(); - d.getCurrentSizeRange(minDims, maxDims); Point displaySize = new Point(); d.getSize(displaySize); - - int maxDim = Math.max(maxDims.x, maxDims.y); - final int minDim = Math.min(minDims.x, minDims.y); - int defaultWallpaperWidth; - if (isScreenLarge(getResources())) { - defaultWallpaperWidth = (int) (maxDim * - wallpaperTravelToScreenWidthRatio(maxDim, minDim)); - } else { - defaultWallpaperWidth = Math.max((int) - (minDim * WALLPAPER_SCREENS_SPAN), maxDim); - } - boolean isPortrait = displaySize.x < displaySize.y; - int portraitHeight; - if (isPortrait) { - portraitHeight = mCropView.getHeight(); - } else { - // TODO: how to actually get the proper portrait height? - // This is not quite right: - portraitHeight = Math.max(maxDims.x, maxDims.y); - } - if (android.os.Build.VERSION.SDK_INT >= - android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { - Point realSize = new Point(); - d.getRealSize(realSize); - portraitHeight = Math.max(realSize.x, realSize.y); - } + + Point defaultWallpaperSize = getDefaultWallpaperSize(getResources(), + getWindowManager()); // Get the crop RectF cropRect = mCropView.getCrop(); int cropRotation = mCropView.getImageRotation(); @@ -378,7 +356,7 @@ public class WallpaperCropActivity extends Activity { // (or all the way to the left, in RTL) float extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left; // Cap the amount of extra width - float maxExtraSpace = defaultWallpaperWidth / cropScale - cropRect.width(); + float maxExtraSpace = defaultWallpaperSize.x / cropScale - cropRect.width(); extraSpace = Math.min(extraSpace, maxExtraSpace); if (ltr) { @@ -389,10 +367,10 @@ public class WallpaperCropActivity extends Activity { // ADJUST CROP HEIGHT if (isPortrait) { - cropRect.bottom = cropRect.top + portraitHeight / cropScale; + cropRect.bottom = cropRect.top + defaultWallpaperSize.y / cropScale; } else { // LANDSCAPE float extraPortraitHeight = - portraitHeight / cropScale - cropRect.height(); + defaultWallpaperSize.y / cropScale - cropRect.height(); float expandHeight = Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top), extraPortraitHeight / 2); @@ -606,13 +584,13 @@ public class WallpaperCropActivity extends Activity { } // See how much we're reducing the size of the image - int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / mOutWidth, - roundedTrueCrop.height() / mOutHeight); - + int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / mOutWidth, + roundedTrueCrop.height() / mOutHeight)); // Attempt to open a region decoder BitmapRegionDecoder decoder = null; + InputStream is = null; try { - InputStream is = regenerateInputStream(); + is = regenerateInputStream(); if (is == null) { Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString()); failure = true; @@ -622,6 +600,9 @@ public class WallpaperCropActivity extends Activity { Utils.closeSilently(is); } catch (IOException e) { Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e); + } finally { + Utils.closeSilently(is); + is = null; } Bitmap crop = null; @@ -637,7 +618,7 @@ public class WallpaperCropActivity extends Activity { if (crop == null) { // BitmapRegionDecoder has failed, try to crop in-memory - InputStream is = regenerateInputStream(); + is = regenerateInputStream(); Bitmap fullSize = null; if (is != null) { BitmapFactory.Options options = new BitmapFactory.Options(); @@ -757,7 +738,7 @@ public class WallpaperCropActivity extends Activity { protected void updateWallpaperDimensions(int width, int height) { String spKey = getSharedPreferencesKey(); - SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE); + SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS); SharedPreferences.Editor editor = sp.edit(); if (width != 0 && height != 0) { editor.putInt(WALLPAPER_WIDTH_KEY, width); diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java index 596435a..10bcdad 100644 --- a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java +++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java @@ -117,8 +117,8 @@ public class ProxyServer extends Thread { if (!proxy.equals(Proxy.NO_PROXY)) { // Only Inets created by PacProxySelector. InetSocketAddress inetSocketAddress = - (InetSocketAddress)list.get(0).address(); - server = new Socket(inetSocketAddress.getAddress(), + (InetSocketAddress)proxy.address(); + server = new Socket(inetSocketAddress.getHostName(), inetSocketAddress.getPort()); sendLine(server, requestLine); } else { diff --git a/policy/src/com/android/internal/policy/impl/BarController.java b/policy/src/com/android/internal/policy/impl/BarController.java index 8d97fc8..0ce4b12 100644 --- a/policy/src/com/android/internal/policy/impl/BarController.java +++ b/policy/src/com/android/internal/policy/impl/BarController.java @@ -37,8 +37,9 @@ public class BarController { private static final boolean DEBUG = false; private static final int TRANSIENT_BAR_NONE = 0; - private static final int TRANSIENT_BAR_SHOWING = 1; - private static final int TRANSIENT_BAR_HIDING = 2; + private static final int TRANSIENT_BAR_SHOW_REQUESTED = 1; + private static final int TRANSIENT_BAR_SHOWING = 2; + private static final int TRANSIENT_BAR_HIDING = 3; private static final int TRANSLUCENT_ANIMATION_DELAY_MS = 1000; @@ -73,13 +74,9 @@ public class BarController { mWin = win; } - public boolean isHidden() { - return mState == StatusBarManager.WINDOW_STATE_HIDDEN; - } - public void showTransient() { if (mWin != null) { - setTransientBarState(TRANSIENT_BAR_SHOWING); + setTransientBarState(TRANSIENT_BAR_SHOW_REQUESTED); } } @@ -87,6 +84,10 @@ public class BarController { return mTransientBarState == TRANSIENT_BAR_SHOWING; } + public boolean isTransientShowRequested() { + return mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED; + } + public boolean wasRecentlyTranslucent() { return (SystemClock.uptimeMillis() - mLastTranslucent) < TRANSLUCENT_ANIMATION_DELAY_MS; } @@ -129,8 +130,8 @@ public class BarController { final boolean wasAnim = mWin.isAnimatingLw(); final boolean change = show ? mWin.showLw(true) : mWin.hideLw(true); final int state = computeStateLw(wasVis, wasAnim, mWin, change); - updateStateLw(state); - return change; + final boolean stateChanged = updateStateLw(state); + return change || stateChanged; } private int computeStateLw(boolean wasVis, boolean wasAnim, WindowState win, boolean change) { @@ -139,6 +140,8 @@ public class BarController { final boolean anim = win.isAnimatingLw(); if (mState == StatusBarManager.WINDOW_STATE_HIDING && !change && !vis) { return StatusBarManager.WINDOW_STATE_HIDDEN; + } else if (mState == StatusBarManager.WINDOW_STATE_HIDDEN && vis) { + return StatusBarManager.WINDOW_STATE_SHOWING; } else if (change) { if (wasVis && vis && !wasAnim && anim) { return StatusBarManager.WINDOW_STATE_HIDING; @@ -150,7 +153,7 @@ public class BarController { return mState; } - private void updateStateLw(final int state) { + private boolean updateStateLw(final int state) { if (state != mState) { mState = state; if (DEBUG) Slog.d(mTag, "mState: " + StatusBarManager.windowStateToString(state)); @@ -169,7 +172,9 @@ public class BarController { } } }); + return true; } + return false; } public boolean checkHiddenLw() { @@ -194,6 +199,9 @@ public class BarController { if (mTransientBarState == TRANSIENT_BAR_SHOWING) { if (DEBUG) Slog.d(mTag, "Not showing transient bar, already shown"); return false; + } else if (mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED) { + if (DEBUG) Slog.d(mTag, "Not showing transient bar, already requested"); + return false; } else if (mWin == null) { if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar doesn't exist"); return false; @@ -207,12 +215,13 @@ public class BarController { public int updateVisibilityLw(boolean transientAllowed, int oldVis, int vis) { if (mWin == null) return vis; - if (mTransientBarState == TRANSIENT_BAR_SHOWING) { // transient bar requested + if (isTransientShowing() || isTransientShowRequested()) { // transient bar requested if (transientAllowed) { vis |= mTransientFlag; if ((oldVis & mTransientFlag) == 0) { vis |= mUnhideFlag; // tell sysui we're ready to unhide } + setTransientBarState(TRANSIENT_BAR_SHOWING); // request accepted } else { setTransientBarState(TRANSIENT_BAR_NONE); // request denied } @@ -250,6 +259,7 @@ public class BarController { private static String transientBarStateToString(int state) { if (state == TRANSIENT_BAR_HIDING) return "TRANSIENT_BAR_HIDING"; if (state == TRANSIENT_BAR_SHOWING) return "TRANSIENT_BAR_SHOWING"; + if (state == TRANSIENT_BAR_SHOW_REQUESTED) return "TRANSIENT_BAR_SHOW_REQUESTED"; if (state == TRANSIENT_BAR_NONE) return "TRANSIENT_BAR_NONE"; throw new IllegalArgumentException("Unknown state " + state); } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index faca949..c33bd35 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -3468,6 +3468,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } // Maintain fullscreen layout until incoming animation is complete. topIsFullscreen = mTopIsFullscreen && mStatusBar.isAnimatingLw(); + // Transient status bar on the lockscreen is not allowed + if (mForceStatusBarFromKeyguard && mStatusBarController.isTransientShowing()) { + mStatusBarController.updateVisibilityLw(false /*transientAllowed*/, + mLastSystemUiFlags, mLastSystemUiFlags); + } } else if (mTopFullscreenOpaqueWindowState != null) { if (localLOGV) { Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw() @@ -5158,9 +5163,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { mNavigationBar != null && hideNavBarSysui && immersiveSticky; - boolean denyTransientStatus = mStatusBarController.isTransientShowing() + boolean denyTransientStatus = mStatusBarController.isTransientShowRequested() && !transientStatusBarAllowed && hideStatusBarSysui; - boolean denyTransientNav = mNavigationBarController.isTransientShowing() + boolean denyTransientNav = mNavigationBarController.isTransientShowRequested() && !transientNavBarAllowed; if (denyTransientStatus || denyTransientNav) { // clear the clearable flags instead diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java index bf22e2f..1357462 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java @@ -40,6 +40,14 @@ public class KeyguardServiceDelegate { private KeyguardState mKeyguardState = new KeyguardState(); /* package */ static final class KeyguardState { + KeyguardState() { + // Assume keyguard is showing and secure until we know for sure. This is here in + // the event something checks before the service is actually started. + // KeyguardService itself should default to this state until the real state is known. + showing = true; + showingAndNotHidden = true; + secure = true; + } boolean showing; boolean showingAndNotHidden; boolean inputRestricted; diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index a04ee14..00bfee7 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -46,6 +46,8 @@ import android.content.pm.IPackageInstallObserver; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.content.pm.Signature; import android.content.pm.PackageManager.NameNotFoundException; import android.database.ContentObserver; @@ -146,6 +148,7 @@ class BackupManagerService extends IBackupManager.Stub { static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup"; + static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST"; // How often we perform a backup pass. Privileged external callers can // trigger an immediate pass. @@ -158,6 +161,9 @@ class BackupManagerService extends IBackupManager.Stub { // the first backup pass. private static final long FIRST_BACKUP_INTERVAL = 12 * AlarmManager.INTERVAL_HOUR; + // Retry interval for clear/init when the transport is unavailable + private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR; + private static final String RUN_BACKUP_ACTION = "android.app.backup.intent.RUN"; private static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT"; private static final String RUN_CLEAR_ACTION = "android.app.backup.intent.CLEAR"; @@ -171,6 +177,8 @@ class BackupManagerService extends IBackupManager.Stub { private static final int MSG_RESTORE_TIMEOUT = 8; private static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9; private static final int MSG_RUN_FULL_RESTORE = 10; + private static final int MSG_RETRY_INIT = 11; + private static final int MSG_RETRY_CLEAR = 12; // backup task state machine tick static final int MSG_BACKUP_RESTORE_STEP = 20; @@ -251,10 +259,13 @@ class BackupManagerService extends IBackupManager.Stub { volatile boolean mClearingData; // Transport bookkeeping + final HashMap<String,String> mTransportNames + = new HashMap<String,String>(); // component name -> registration name final HashMap<String,IBackupTransport> mTransports - = new HashMap<String,IBackupTransport>(); + = new HashMap<String,IBackupTransport>(); // registration name -> binder + final ArrayList<TransportConnection> mTransportConnections + = new ArrayList<TransportConnection>(); String mCurrentTransport; - IBackupTransport mLocalTransport, mGoogleTransport; ActiveRestoreSession mActiveRestoreSession; // Watch the device provisioning operation during setup @@ -300,6 +311,7 @@ class BackupManagerService extends IBackupManager.Stub { class RestoreParams { public IBackupTransport transport; + public String dirName; public IRestoreObserver observer; public long token; public PackageInfo pkgInfo; @@ -307,9 +319,10 @@ class BackupManagerService extends IBackupManager.Stub { public boolean needFullBackup; public String[] filterSet; - RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, + RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs, long _token, PackageInfo _pkg, int _pmToken, boolean _needFullBackup) { transport = _transport; + dirName = _dirName; observer = _obs; token = _token; pkgInfo = _pkg; @@ -318,9 +331,10 @@ class BackupManagerService extends IBackupManager.Stub { filterSet = null; } - RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token, - boolean _needFullBackup) { + RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs, + long _token, boolean _needFullBackup) { transport = _transport; + dirName = _dirName; observer = _obs; token = _token; pkgInfo = null; @@ -329,9 +343,10 @@ class BackupManagerService extends IBackupManager.Stub { filterSet = null; } - RestoreParams(IBackupTransport _transport, IRestoreObserver _obs, long _token, - String[] _filterSet, boolean _needFullBackup) { + RestoreParams(IBackupTransport _transport, String _dirName, IRestoreObserver _obs, + long _token, String[] _filterSet, boolean _needFullBackup) { transport = _transport; + dirName = _dirName; observer = _obs; token = _token; pkgInfo = null; @@ -351,6 +366,16 @@ class BackupManagerService extends IBackupManager.Stub { } } + class ClearRetryParams { + public String transportName; + public String packageName; + + ClearRetryParams(String transport, String pkg) { + transportName = transport; + packageName = pkg; + } + } + class FullParams { public ParcelFileDescriptor fd; public final AtomicBoolean latch; @@ -510,13 +535,28 @@ class BackupManagerService extends IBackupManager.Stub { // When it completes successfully, that old journal file will be // deleted. If we crash prior to that, the old journal is parsed // at next boot and the journaled requests fulfilled. + boolean staged = true; if (queue.size() > 0) { // Spin up a backup state sequence and set it running - PerformBackupTask pbt = new PerformBackupTask(transport, queue, oldJournal); - Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt); - sendMessage(pbtMessage); + try { + String dirName = transport.transportDirName(); + PerformBackupTask pbt = new PerformBackupTask(transport, dirName, + queue, oldJournal); + Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt); + sendMessage(pbtMessage); + } catch (RemoteException e) { + // unable to ask the transport its dir name -- transient failure, since + // the above check succeeded. Try again next time. + Slog.e(TAG, "Transport became unavailable attempting backup"); + staged = false; + } } else { Slog.v(TAG, "Backup requested but nothing pending"); + staged = false; + } + + if (!staged) { + // if we didn't actually hand off the wakelock, rewind until next time synchronized (mQueueLock) { mBackupRunning = false; } @@ -566,7 +606,7 @@ class BackupManagerService extends IBackupManager.Stub { RestoreParams params = (RestoreParams)msg.obj; Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer); PerformRestoreTask task = new PerformRestoreTask( - params.transport, params.observer, + params.transport, params.dirName, params.observer, params.token, params.pkgInfo, params.pmToken, params.needFullBackup, params.filterSet); Message restoreMsg = obtainMessage(MSG_BACKUP_RESTORE_STEP, task); @@ -593,6 +633,14 @@ class BackupManagerService extends IBackupManager.Stub { break; } + case MSG_RETRY_CLEAR: + { + // reenqueues if the transport remains unavailable + ClearRetryParams params = (ClearRetryParams)msg.obj; + clearBackupData(params.transportName, params.packageName); + break; + } + case MSG_RUN_INITIALIZE: { HashSet<String> queue; @@ -607,6 +655,16 @@ class BackupManagerService extends IBackupManager.Stub { break; } + case MSG_RETRY_INIT: + { + synchronized (mQueueLock) { + recordInitPendingLocked(msg.arg1 != 0, (String)msg.obj); + mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), + mRunInitIntent); + } + break; + } + case MSG_RUN_GET_RESTORE_SETS: { // Like other async operations, this is entered with the wakelock held @@ -815,13 +873,7 @@ class BackupManagerService extends IBackupManager.Stub { } // Set up our transport options and initialize the default transport - // TODO: Have transports register themselves somehow? // TODO: Don't create transports that we don't need to? - mLocalTransport = new LocalTransport(context); // This is actually pretty cheap - ComponentName localName = new ComponentName(context, LocalTransport.class); - registerTransport(localName.flattenToShortString(), mLocalTransport); - - mGoogleTransport = null; mCurrentTransport = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT); if ("".equals(mCurrentTransport)) { @@ -829,28 +881,43 @@ class BackupManagerService extends IBackupManager.Stub { } if (DEBUG) Slog.v(TAG, "Starting with transport " + mCurrentTransport); - // Attach to the Google backup transport. When this comes up, it will set - // itself as the current transport because we explicitly reset mCurrentTransport - // to null. - ComponentName transportComponent = new ComponentName("com.google.android.backup", - "com.google.android.backup.BackupTransportService"); - try { - // If there's something out there that is supposed to be the Google - // backup transport, make sure it's legitimately part of the OS build - // and not an app lying about its package name. - ApplicationInfo info = mPackageManager.getApplicationInfo( - transportComponent.getPackageName(), 0); - if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - if (DEBUG) Slog.v(TAG, "Binding to Google transport"); - Intent intent = new Intent().setComponent(transportComponent); - context.bindServiceAsUser(intent, mGoogleConnection, Context.BIND_AUTO_CREATE, - UserHandle.OWNER); - } else { - Slog.w(TAG, "Possible Google transport spoof: ignoring " + info); + // Find transport hosts and bind to their services + Intent transportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST); + List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser( + transportServiceIntent, 0, UserHandle.USER_OWNER); + if (DEBUG) { + Slog.v(TAG, "Found transports: " + ((hosts == null) ? "null" : hosts.size())); + } + if (hosts != null) { + if (MORE_DEBUG) { + for (int i = 0; i < hosts.size(); i++) { + ServiceInfo info = hosts.get(i).serviceInfo; + Slog.v(TAG, " " + info.packageName + "/" + info.name); + } + } + for (int i = 0; i < hosts.size(); i++) { + try { + ServiceInfo info = hosts.get(i).serviceInfo; + PackageInfo packInfo = mPackageManager.getPackageInfo(info.packageName, 0); + if ((packInfo.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0) { + ComponentName svcName = new ComponentName(info.packageName, info.name); + if (DEBUG) { + Slog.i(TAG, "Binding to transport host " + svcName); + } + Intent intent = new Intent(transportServiceIntent); + intent.setComponent(svcName); + TransportConnection connection = new TransportConnection(); + mTransportConnections.add(connection); + context.bindServiceAsUser(intent, + connection, Context.BIND_AUTO_CREATE, + UserHandle.OWNER); + } else { + Slog.w(TAG, "Transport package not privileged: " + info.packageName); + } + } catch (Exception e) { + Slog.e(TAG, "Problem resolving transport service: " + e.getMessage()); + } } - } catch (PackageManager.NameNotFoundException nnf) { - // No such package? No binding. - if (DEBUG) Slog.v(TAG, "Google transport not present"); } // Now that we know about valid backup participants, parse any @@ -1235,29 +1302,47 @@ class BackupManagerService extends IBackupManager.Stub { void recordInitPendingLocked(boolean isPending, String transportName) { if (DEBUG) Slog.i(TAG, "recordInitPendingLocked: " + isPending + " on transport " + transportName); + mBackupHandler.removeMessages(MSG_RETRY_INIT); + try { IBackupTransport transport = getTransport(transportName); - String transportDirName = transport.transportDirName(); - File stateDir = new File(mBaseStateDir, transportDirName); - File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME); - - if (isPending) { - // We need an init before we can proceed with sending backup data. - // Record that with an entry in our set of pending inits, as well as - // journaling it via creation of a sentinel file. - mPendingInits.add(transportName); - try { - (new FileOutputStream(initPendingFile)).close(); - } catch (IOException ioe) { - // Something is badly wrong with our permissions; just try to move on + if (transport != null) { + String transportDirName = transport.transportDirName(); + File stateDir = new File(mBaseStateDir, transportDirName); + File initPendingFile = new File(stateDir, INIT_SENTINEL_FILE_NAME); + + if (isPending) { + // We need an init before we can proceed with sending backup data. + // Record that with an entry in our set of pending inits, as well as + // journaling it via creation of a sentinel file. + mPendingInits.add(transportName); + try { + (new FileOutputStream(initPendingFile)).close(); + } catch (IOException ioe) { + // Something is badly wrong with our permissions; just try to move on + } + } else { + // No more initialization needed; wipe the journal and reset our state. + initPendingFile.delete(); + mPendingInits.remove(transportName); } - } else { - // No more initialization needed; wipe the journal and reset our state. - initPendingFile.delete(); - mPendingInits.remove(transportName); + return; // done; don't fall through to the error case } } catch (RemoteException e) { - // can't happen; the transport is local + // transport threw when asked its name; fall through to the lookup-failed case + } + + // The named transport doesn't exist or threw. This operation is + // important, so we record the need for a an init and post a message + // to retry the init later. + if (isPending) { + mPendingInits.add(transportName); + mBackupHandler.sendMessageDelayed( + mBackupHandler.obtainMessage(MSG_RETRY_INIT, + (isPending ? 1 : 0), + 0, + transportName), + TRANSPORT_RETRY_INTERVAL); } } @@ -1298,13 +1383,16 @@ class BackupManagerService extends IBackupManager.Stub { // Add a transport to our set of available backends. If 'transport' is null, this // is an unregistration, and the transport's entry is removed from our bookkeeping. - private void registerTransport(String name, IBackupTransport transport) { + private void registerTransport(String name, String component, IBackupTransport transport) { synchronized (mTransports) { - if (DEBUG) Slog.v(TAG, "Registering transport " + name + " = " + transport); + if (DEBUG) Slog.v(TAG, "Registering transport " + + component + "::" + name + " = " + transport); if (transport != null) { mTransports.put(name, transport); + mTransportNames.put(component, name); } else { - mTransports.remove(name); + mTransports.remove(mTransportNames.get(component)); + mTransportNames.remove(component); // Nothing further to do in the unregistration case return; } @@ -1330,7 +1418,10 @@ class BackupManagerService extends IBackupManager.Stub { } } } catch (RemoteException e) { - // can't happen, the transport is local + // the transport threw when asked its file naming prefs; declare it invalid + Slog.e(TAG, "Unable to register transport as " + name); + mTransportNames.remove(component); + mTransports.remove(name); } } @@ -1390,18 +1481,23 @@ class BackupManagerService extends IBackupManager.Stub { } }; - // ----- Track connection to GoogleBackupTransport service ----- - ServiceConnection mGoogleConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName name, IBinder service) { - if (DEBUG) Slog.v(TAG, "Connected to Google transport"); - mGoogleTransport = IBackupTransport.Stub.asInterface(service); - registerTransport(name.flattenToShortString(), mGoogleTransport); + // ----- Track connection to transports service ----- + class TransportConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName component, IBinder service) { + if (DEBUG) Slog.v(TAG, "Connected to transport " + component); + try { + IBackupTransport transport = IBackupTransport.Stub.asInterface(service); + registerTransport(transport.name(), component.flattenToShortString(), transport); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to register transport " + component); + } } - public void onServiceDisconnected(ComponentName name) { - if (DEBUG) Slog.v(TAG, "Disconnected from Google transport"); - mGoogleTransport = null; - registerTransport(name.flattenToShortString(), null); + @Override + public void onServiceDisconnected(ComponentName component) { + if (DEBUG) Slog.v(TAG, "Disconnected from transport " + component); + registerTransport(null, component.flattenToShortString(), null); } }; @@ -1645,7 +1741,7 @@ class BackupManagerService extends IBackupManager.Stub { agent = mConnectedAgent; } } catch (RemoteException e) { - // can't happen + // can't happen - ActivityManager is local } } return agent; @@ -1821,17 +1917,13 @@ class BackupManagerService extends IBackupManager.Stub { int mStatus; boolean mFinished; - public PerformBackupTask(IBackupTransport transport, ArrayList<BackupRequest> queue, - File journal) { + public PerformBackupTask(IBackupTransport transport, String dirName, + ArrayList<BackupRequest> queue, File journal) { mTransport = transport; mOriginalQueue = queue; mJournal = journal; - try { - mStateDir = new File(mBaseStateDir, transport.transportDirName()); - } catch (RemoteException e) { - // can't happen; the transport is local - } + mStateDir = new File(mBaseStateDir, dirName); mCurrentState = BackupState.INITIAL; mFinished = false; @@ -2077,8 +2169,12 @@ class BackupManagerService extends IBackupManager.Stub { addBackupTrace("success; recording token"); try { mCurrentToken = mTransport.getCurrentRestoreSet(); - } catch (RemoteException e) {} // can't happen - writeRestoreTokens(); + writeRestoreTokens(); + } catch (RemoteException e) { + // nothing for it at this point, unfortunately, but this will be + // recorded the next time we fully succeed. + addBackupTrace("transport threw returning token"); + } } // Set up the next backup pass - at this point we can set mBackupRunning @@ -2299,7 +2395,7 @@ class BackupManagerService extends IBackupManager.Stub { addBackupTrace("unbinding " + mCurrentPackage.packageName); try { // unbind even on timeout, just in case mActivityManager.unbindBackupAgent(mCurrentPackage.applicationInfo); - } catch (RemoteException e) {} + } catch (RemoteException e) { /* can't happen; activity manager is local */ } } } @@ -4314,7 +4410,7 @@ class BackupManagerService extends IBackupManager.Stub { } } - PerformRestoreTask(IBackupTransport transport, IRestoreObserver observer, + PerformRestoreTask(IBackupTransport transport, String dirName, IRestoreObserver observer, long restoreSetToken, PackageInfo targetPackage, int pmToken, boolean needFullBackup, String[] filterSet) { mCurrentState = RestoreState.INITIAL; @@ -4337,11 +4433,7 @@ class BackupManagerService extends IBackupManager.Stub { mFilterSet = null; } - try { - mStateDir = new File(mBaseStateDir, transport.transportDirName()); - } catch (RemoteException e) { - // can't happen; the transport is local - } + mStateDir = new File(mBaseStateDir, dirName); } // Execute one tick of whatever state machine the task implements @@ -5067,8 +5159,8 @@ class BackupManagerService extends IBackupManager.Stub { } // Clear the given package's backup data from the current transport - public void clearBackupData(String packageName) { - if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName); + public void clearBackupData(String transportName, String packageName) { + if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName + " on " + transportName); PackageInfo info; try { info = mPackageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); @@ -5099,13 +5191,22 @@ class BackupManagerService extends IBackupManager.Stub { // Is the given app an available participant? if (apps.contains(packageName)) { - if (DEBUG) Slog.v(TAG, "Found the app - running clear process"); // found it; fire off the clear request + if (DEBUG) Slog.v(TAG, "Found the app - running clear process"); + mBackupHandler.removeMessages(MSG_RETRY_CLEAR); synchronized (mQueueLock) { + final IBackupTransport transport = getTransport(transportName); + if (transport == null) { + // transport is currently unavailable -- make sure to retry + Message msg = mBackupHandler.obtainMessage(MSG_RETRY_CLEAR, + new ClearRetryParams(transportName, packageName)); + mBackupHandler.sendMessageDelayed(msg, TRANSPORT_RETRY_INTERVAL); + return; + } long oldId = Binder.clearCallingIdentity(); mWakelock.acquire(); Message msg = mBackupHandler.obtainMessage(MSG_RUN_CLEAR, - new ClearParams(getTransport(mCurrentTransport), info)); + new ClearParams(transport, info)); mBackupHandler.sendMessage(msg); Binder.restoreCallingIdentity(oldId); } @@ -5603,21 +5704,36 @@ class BackupManagerService extends IBackupManager.Stub { + " restoreSet=" + Long.toHexString(restoreSet)); if (mAutoRestore && mProvisioned && restoreSet != 0) { - // okay, we're going to attempt a restore of this package from this restore set. - // The eventual message back into the Package Manager to run the post-install - // steps for 'token' will be issued from the restore handling code. + // Do we have a transport to fetch data for us? + IBackupTransport transport = getTransport(mCurrentTransport); + if (transport == null) { + if (DEBUG) Slog.w(TAG, "No transport for install-time restore"); + return; + } - // We can use a synthetic PackageInfo here because: - // 1. We know it's valid, since the Package Manager supplied the name - // 2. Only the packageName field will be used by the restore code - PackageInfo pkg = new PackageInfo(); - pkg.packageName = packageName; + try { + // okay, we're going to attempt a restore of this package from this restore set. + // The eventual message back into the Package Manager to run the post-install + // steps for 'token' will be issued from the restore handling code. - mWakelock.acquire(); - Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); - msg.obj = new RestoreParams(getTransport(mCurrentTransport), null, - restoreSet, pkg, token, true); - mBackupHandler.sendMessage(msg); + // This can throw and so *must* happen before the wakelock is acquired + String dirName = transport.transportDirName(); + + // We can use a synthetic PackageInfo here because: + // 1. We know it's valid, since the Package Manager supplied the name + // 2. Only the packageName field will be used by the restore code + PackageInfo pkg = new PackageInfo(); + pkg.packageName = packageName; + + mWakelock.acquire(); + Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); + msg.obj = new RestoreParams(transport, dirName, null, + restoreSet, pkg, token, true); + mBackupHandler.sendMessage(msg); + } catch (RemoteException e) { + // Binding to the transport broke; back off and proceed with the installation. + Slog.e(TAG, "Unable to contact transport for install-time restore"); + } } else { // Auto-restore disabled or no way to attempt a restore; just tell the Package // Manager to proceed with the post-install handling for this package. @@ -5774,13 +5890,23 @@ class BackupManagerService extends IBackupManager.Stub { return -1; } + String dirName; + try { + dirName = mRestoreTransport.transportDirName(); + } catch (RemoteException e) { + // Transport went AWOL; fail. + Slog.e(TAG, "Unable to contact transport for restore"); + return -1; + } + synchronized (mQueueLock) { for (int i = 0; i < mRestoreSets.length; i++) { if (token == mRestoreSets[i].token) { long oldId = Binder.clearCallingIdentity(); mWakelock.acquire(); Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); - msg.obj = new RestoreParams(mRestoreTransport, observer, token, true); + msg.obj = new RestoreParams(mRestoreTransport, dirName, + observer, token, true); mBackupHandler.sendMessage(msg); Binder.restoreCallingIdentity(oldId); return 0; @@ -5834,13 +5960,22 @@ class BackupManagerService extends IBackupManager.Stub { return -1; } + String dirName; + try { + dirName = mRestoreTransport.transportDirName(); + } catch (RemoteException e) { + // Transport went AWOL; fail. + Slog.e(TAG, "Unable to contact transport for restore"); + return -1; + } + synchronized (mQueueLock) { for (int i = 0; i < mRestoreSets.length; i++) { if (token == mRestoreSets[i].token) { long oldId = Binder.clearCallingIdentity(); mWakelock.acquire(); Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); - msg.obj = new RestoreParams(mRestoreTransport, observer, token, + msg.obj = new RestoreParams(mRestoreTransport, dirName, observer, token, packages, true); mBackupHandler.sendMessage(msg); Binder.restoreCallingIdentity(oldId); @@ -5906,11 +6041,21 @@ class BackupManagerService extends IBackupManager.Stub { return -1; } + String dirName; + try { + dirName = mRestoreTransport.transportDirName(); + } catch (RemoteException e) { + // Transport went AWOL; fail. + Slog.e(TAG, "Unable to contact transport for restore"); + return -1; + } + // Ready to go: enqueue the restore request and claim success long oldId = Binder.clearCallingIdentity(); mWakelock.acquire(); Message msg = mBackupHandler.obtainMessage(MSG_RUN_RESTORE); - msg.obj = new RestoreParams(mRestoreTransport, observer, token, app, 0, false); + msg.obj = new RestoreParams(mRestoreTransport, dirName, + observer, token, app, 0, false); mBackupHandler.sendMessage(msg); Binder.restoreCallingIdentity(oldId); return 0; diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 7c61c44..baff661 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -77,6 +77,7 @@ import android.net.wifi.WifiStateTracker; import android.net.wimax.WimaxManagerConstants; import android.os.AsyncTask; import android.os.Binder; +import android.os.Build; import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; @@ -141,6 +142,7 @@ import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.URL; +import java.net.URLConnection; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; @@ -154,6 +156,10 @@ import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSession; + /** * @hide */ @@ -3374,6 +3380,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { String pacFileUrl = ""; if (proxyProperties != null && (!TextUtils.isEmpty(proxyProperties.getHost()) || !TextUtils.isEmpty(proxyProperties.getPacFileUrl()))) { + if (!proxyProperties.isValid()) { + if (DBG) + log("Invalid proxy properties, ignoring: " + proxyProperties.toString()); + return; + } mGlobalProxy = new ProxyProperties(proxyProperties); host = mGlobalProxy.getHost(); port = mGlobalProxy.getPort(); @@ -3417,6 +3428,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { } else { proxyProperties = new ProxyProperties(host, port, exclList); } + if (!proxyProperties.isValid()) { + if (DBG) log("Invalid proxy properties, ignoring: " + proxyProperties.toString()); + return; + } + synchronized (mProxyLock) { mGlobalProxy = proxyProperties; } @@ -3441,6 +3457,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { synchronized (mProxyLock) { if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return; if (mDefaultProxy == proxy) return; // catches repeated nulls + if (proxy != null && !proxy.isValid()) { + if (DBG) log("Invalid proxy properties, ignoring: " + proxy.toString()); + return; + } mDefaultProxy = proxy; if (mGlobalProxy != null) return; @@ -4066,8 +4086,28 @@ public class ConnectivityService extends IConnectivityManager.Stub { static class CheckMp extends AsyncTask<CheckMp.Params, Void, Integer> { private static final String CHECKMP_TAG = "CheckMp"; + + // adb shell setprop persist.checkmp.testfailures 1 to enable testing failures + private static boolean mTestingFailures; + + // Choosing 4 loops as half of them will use HTTPS and the other half HTTP + private static final int MAX_LOOPS = 4; + + // Number of milli-seconds to complete all of the retires public static final int MAX_TIMEOUT_MS = 60000; + + // The socket should retry only 5 seconds, the default is longer private static final int SOCKET_TIMEOUT_MS = 5000; + + // Sleep time for network errors + private static final int NET_ERROR_SLEEP_SEC = 3; + + // Sleep time for network route establishment + private static final int NET_ROUTE_ESTABLISHMENT_SLEEP_SEC = 3; + + // Short sleep time for polling :( + private static final int POLLING_SLEEP_SEC = 1; + private Context mContext; private ConnectivityService mCs; private TelephonyManager mTm; @@ -4093,6 +4133,31 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } + // As explained to me by Brian Carlstrom and Kenny Root, Certificates can be + // issued by name or ip address, for Google its by name so when we construct + // this HostnameVerifier we'll pass the original Uri and use it to verify + // the host. If the host name in the original uril fails we'll test the + // hostname parameter just incase things change. + static class CheckMpHostnameVerifier implements HostnameVerifier { + Uri mOrgUri; + + CheckMpHostnameVerifier(Uri orgUri) { + mOrgUri = orgUri; + } + + @Override + public boolean verify(String hostname, SSLSession session) { + HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier(); + String orgUriHost = mOrgUri.getHost(); + boolean retVal = hv.verify(orgUriHost, session) || hv.verify(hostname, session); + if (DBG) { + log("isMobileOk: hostnameVerify retVal=" + retVal + " hostname=" + hostname + + " orgUriHost=" + orgUriHost); + } + return retVal; + } + } + /** * The call back object passed in Params. onComplete will be called * on the main thread. @@ -4103,6 +4168,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { } public CheckMp(Context context, ConnectivityService cs) { + if (Build.IS_DEBUGGABLE) { + mTestingFailures = + SystemProperties.getInt("persist.checkmp.testfailures", 0) == 1; + } else { + mTestingFailures = false; + } + mContext = context; mCs = cs; @@ -4174,7 +4246,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { mCs.setEnableFailFastMobileData(DctConstants.ENABLED); break; } - sleep(1); + sleep(POLLING_SLEEP_SEC); } } @@ -4192,7 +4264,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } if (VDBG) log("isMobileOk: hipri not started yet"); result = CMP_RESULT_CODE_NO_CONNECTION; - sleep(1); + sleep(POLLING_SLEEP_SEC); } // Continue trying to connect until time has run out @@ -4208,7 +4280,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { log("isMobileOk: not connected ni=" + mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI)); } - sleep(1); + sleep(POLLING_SLEEP_SEC); result = CMP_RESULT_CODE_NO_CONNECTION; continue; } @@ -4226,7 +4298,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // Get of the addresses associated with the url host. We need to use the // address otherwise HttpURLConnection object will use the name to get - // the addresses and is will try every address but that will bypass the + // the addresses and will try every address but that will bypass the // route to host we setup and the connection could succeed as the default // interface might be connected to the internet via wifi or other interface. InetAddress[] addresses; @@ -4263,14 +4335,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { int addrTried = 0; while (true) { - // Loop through at most 3 valid addresses or until + // Loop through at most MAX_LOOPS valid addresses or until // we run out of time - if (addrTried++ >= 3) { - log("too many loops tried - giving up"); + if (addrTried++ >= MAX_LOOPS) { + log("isMobileOk: too many loops tried - giving up"); break; } if (SystemClock.elapsedRealtime() >= endTime) { - log("spend too much time - giving up"); + log("isMobileOk: spend too much time - giving up"); break; } @@ -4283,25 +4355,37 @@ public class ConnectivityService extends IConnectivityManager.Stub { // Wait a short time to be sure the route is established ?? log("isMobileOk:" + " wait to establish route to hostAddr=" + hostAddr); - sleep(3); + sleep(NET_ROUTE_ESTABLISHMENT_SLEEP_SEC); } else { log("isMobileOk:" + " could not establish route to hostAddr=" + hostAddr); + // Wait a short time before the next attempt + sleep(NET_ERROR_SLEEP_SEC); continue; } - // Rewrite the url to have numeric address to use the specific route. - // Add a pointless random query param to fool proxies into not caching. - URL newUrl = new URL(orgUri.getScheme(), - hostAddr.getHostAddress(), - orgUri.getPath() + "?q=" + rand.nextInt(Integer.MAX_VALUE)); + // Rewrite the url to have numeric address to use the specific route + // using http for half the attempts and https for the other half. + // Doing https first and http second as on a redirected walled garden + // such as t-mobile uses we get a SocketTimeoutException: "SSL + // handshake timed out" which we declare as + // CMP_RESULT_CODE_NO_TCP_CONNECTION. We could change this, but by + // having http second we will be using logic used for some time. + URL newUrl; + String scheme = (addrTried <= (MAX_LOOPS/2)) ? "https" : "http"; + newUrl = new URL(scheme, hostAddr.getHostAddress(), + orgUri.getPath()); log("isMobileOk: newUrl=" + newUrl); HttpURLConnection urlConn = null; try { - // Open the connection set the request header and get the response - urlConn = (HttpURLConnection) newUrl.openConnection( + // Open the connection set the request headers and get the response + urlConn = (HttpURLConnection)newUrl.openConnection( java.net.Proxy.NO_PROXY); + if (scheme.equals("https")) { + ((HttpsURLConnection)urlConn).setHostnameVerifier( + new CheckMpHostnameVerifier(orgUri)); + } urlConn.setInstanceFollowRedirects(false); urlConn.setConnectTimeout(SOCKET_TIMEOUT_MS); urlConn.setReadTimeout(SOCKET_TIMEOUT_MS); @@ -4320,10 +4404,17 @@ public class ConnectivityService extends IConnectivityManager.Stub { urlConn.disconnect(); urlConn = null; + if (mTestingFailures) { + // Pretend no connection, this tests using http and https + result = CMP_RESULT_CODE_NO_CONNECTION; + log("isMobileOk: TESTING_FAILURES, pretend no connction"); + continue; + } + if (responseCode == 204) { // Return result = CMP_RESULT_CODE_CONNECTABLE; - log("isMobileOk: X expected responseCode=" + responseCode + log("isMobileOk: X got expected responseCode=" + responseCode + " result=" + result); return result; } else { @@ -4337,12 +4428,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { result = CMP_RESULT_CODE_REDIRECTED; } } catch (Exception e) { - log("isMobileOk: HttpURLConnection Exception e=" + e); + log("isMobileOk: HttpURLConnection Exception" + e); result = CMP_RESULT_CODE_NO_TCP_CONNECTION; if (urlConn != null) { urlConn.disconnect(); urlConn = null; } + sleep(NET_ERROR_SLEEP_SEC); + continue; } } log("isMobileOk: X loops|timed out result=" + result); @@ -4370,7 +4463,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { log("isMobileOk: connected ni=" + mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI)); } - sleep(1); + sleep(POLLING_SLEEP_SEC); continue; } } @@ -4435,7 +4528,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - private void log(String s) { + private static void log(String s) { Slog.d(ConnectivityService.TAG, "[" + CHECKMP_TAG + "] " + s); } } diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java index 8cc80f7..2bb99d6 100644 --- a/services/java/com/android/server/DevicePolicyManagerService.java +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -56,6 +56,7 @@ import android.content.pm.Signature; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; +import android.net.ProxyProperties; import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; @@ -522,6 +523,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || pm.getReceiverInfo(aa.info.getComponent(), 0, userHandle) == null) { removed = true; policy.mAdminList.remove(i); + policy.mAdminMap.remove(aa.info.getComponent()); } } catch (RemoteException re) { // Shouldn't happen @@ -2463,6 +2465,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } exclusionList = exclusionList.trim(); ContentResolver res = mContext.getContentResolver(); + + ProxyProperties proxyProperties = new ProxyProperties(data[0], proxyPort, exclusionList); + if (!proxyProperties.isValid()) { + Slog.e(TAG, "Invalid proxy properties, ignoring: " + proxyProperties.toString()); + return; + } Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST, data[0]); Settings.Global.putInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, proxyPort); Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, diff --git a/services/java/com/android/server/EventLogTags.logtags b/services/java/com/android/server/EventLogTags.logtags index 8eaa91d..399e7d1 100644 --- a/services/java/com/android/server/EventLogTags.logtags +++ b/services/java/com/android/server/EventLogTags.logtags @@ -123,6 +123,18 @@ option java_package com.android.server # --------------------------- # Out of memory for surfaces. 31000 wm_no_surface_memory (Window|3),(PID|1|5),(Operation|3) +# Task created. +31001 wm_task_created (TaskId|1|5),(StackId|1|5) +# Task moved to top (1) or bottom (0). +31002 wm_task_moved (TaskId|1|5),(ToTop|1),(Index|1) +# Task removed with source explanation. +31003 wm_task_removed (TaskId|1|5),(Reason|3) +# Stack created. +31004 wm_stack_created (StackId|1|5),(RelativeBoxId|1|5),(Position|1),(Weight|1|6) +# Home stack moved to top (1) or bottom (0). +31005 wm_home_stack_moved (ToTop|1) +# Stack removed. +31006 wm_stack_removed (StackId|1|5) # --------------------------- diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 562a50f..a996dbd 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -309,6 +309,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mShortcutInputMethodsAndSubtypes = new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>(); + // Was the keyguard locked when this client became current? + private boolean mCurClientInKeyguard; + /** * Set to true if our ServiceConnection is currently actively bound to * a service (whether or not we have gotten its IBinder back yet). @@ -385,7 +388,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private Locale mLastSystemLocale; private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor(); private final IPackageManager mIPackageManager; - private boolean mInputBoundToKeyguard; class SettingsObserver extends ContentObserver { String mLastEnabled = ""; @@ -874,12 +876,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final boolean hardKeyShown = haveHardKeyboard && conf.hardKeyboardHidden != Configuration.HARDKEYBOARDHIDDEN_YES; - final boolean isScreenLocked = - mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); - final boolean isScreenSecurelyLocked = - isScreenLocked && mKeyguardManager.isKeyguardSecure(); - final boolean inputShown = mInputShown && (!isScreenLocked || mInputBoundToKeyguard); - final boolean inputActive = !isScreenSecurelyLocked && (inputShown || hardKeyShown); + + final boolean isScreenLocked = isKeyguardLocked(); + final boolean inputActive = !isScreenLocked && (mInputShown || hardKeyShown); // We assume the softkeyboard is shown when the input is active as long as the // hard keyboard is not shown. final boolean inputVisible = inputActive && !hardKeyShown; @@ -1135,19 +1134,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return mNoBinding; } - if (mCurClient == null) { - mInputBoundToKeyguard = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); - if (DEBUG) { - Slog.v(TAG, "New bind. keyguard = " + mInputBoundToKeyguard); - } - } - if (mCurClient != cs) { + // Was the keyguard locked when switching over to the new client? + mCurClientInKeyguard = isKeyguardLocked(); // If the client is changing, we need to switch over to the new // one. unbindCurrentClientLocked(); if (DEBUG) Slog.v(TAG, "switching to client: client = " - + cs.client.asBinder()); + + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard); // If the screen is on, inform the new client it is active if (mScreenOn) { @@ -1499,6 +1493,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + private boolean isKeyguardLocked() { + return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); + } + // Caution! This method is called in this class. Handle multi-user carefully @SuppressWarnings("deprecation") @Override @@ -1510,8 +1508,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token); return; } - synchronized (mMethodMap) { + // apply policy for binder calls + if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) { + vis = 0; + } mImeWindowVis = vis; mBackDisposition = backDisposition; if (mStatusBar != null) { diff --git a/services/java/com/android/server/LockSettingsService.java b/services/java/com/android/server/LockSettingsService.java index cd746cf..35e7afa 100644 --- a/services/java/com/android/server/LockSettingsService.java +++ b/services/java/com/android/server/LockSettingsService.java @@ -154,11 +154,11 @@ public class LockSettingsService extends ILockSettings.Stub { } private final void checkWritePermission(int userId) { - mContext.checkCallingOrSelfPermission(PERMISSION); + mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsWrite"); } private final void checkPasswordReadPermission(int userId) { - mContext.checkCallingOrSelfPermission(PERMISSION); + mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsRead"); } private final void checkReadPermission(String requestedKey, int userId) { diff --git a/services/java/com/android/server/NsdService.java b/services/java/com/android/server/NsdService.java index e0f415b..16d2468 100644 --- a/services/java/com/android/server/NsdService.java +++ b/services/java/com/android/server/NsdService.java @@ -483,10 +483,14 @@ public class NsdService extends INsdManager.Stub { clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4])); stopResolveService(id); - if (!getAddrInfo(id, cooked[3])) { + removeRequestMap(clientId, id, clientInfo); + + int id2 = getUniqueId(); + if (getAddrInfo(id2, cooked[3])) { + storeRequestMap(clientId, id2, clientInfo); + } else { clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED, NsdManager.FAILURE_INTERNAL_ERROR, clientId); - removeRequestMap(clientId, id, clientInfo); clientInfo.mResolvedService = null; } break; diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 0e0f156..a42cbcf 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -55,6 +55,7 @@ import com.android.server.content.ContentService; import com.android.server.display.DisplayManagerService; import com.android.server.dreams.DreamManagerService; import com.android.server.input.InputManagerService; +import com.android.server.media.MediaRouterService; import com.android.server.net.NetworkPolicyManagerService; import com.android.server.net.NetworkStatsService; import com.android.server.os.SchedulingPolicyService; @@ -356,6 +357,7 @@ class ServerThread { DreamManagerService dreamy = null; AssetAtlasService atlas = null; PrintManagerService printManager = null; + MediaRouterService mediaRouter = null; // Bring up services needed for UI. if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { @@ -804,6 +806,16 @@ class ServerThread { } catch (Throwable e) { reportWtf("starting Print Service", e); } + + if (!disableNonCoreServices) { + try { + Slog.i(TAG, "Media Router Service"); + mediaRouter = new MediaRouterService(context); + ServiceManager.addService(Context.MEDIA_ROUTER_SERVICE, mediaRouter); + } catch (Throwable e) { + reportWtf("starting MediaRouterService", e); + } + } } // Before things start rolling, be sure we have decided whether @@ -916,6 +928,7 @@ class ServerThread { final InputManagerService inputManagerF = inputManager; final TelephonyRegistry telephonyRegistryF = telephonyRegistry; final PrintManagerService printManagerF = printManager; + final MediaRouterService mediaRouterF = mediaRouter; // 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 @@ -1063,6 +1076,12 @@ class ServerThread { } catch (Throwable e) { reportWtf("Notifying PrintManagerService running", e); } + + try { + if (mediaRouterF != null) mediaRouterF.systemRunning(); + } catch (Throwable e) { + reportWtf("Notifying MediaRouterService running", e); + } } }); @@ -1104,6 +1123,19 @@ public class SystemServer { private static native void nativeInit(); public static void main(String[] args) { + + /* + * In case the runtime switched since last boot (such as when + * the old runtime was removed in an OTA), set the system + * property so that it is in sync. We can't do this in + * libnativehelper's JniInvocation::Init code where we already + * had to fallback to a different runtime because it is + * running as root and we need to be the system user to set + * the property. http://b/11463182 + */ + SystemProperties.set("persist.sys.dalvik.vm.lib", + VMRuntime.getRuntime().vmLibrary()); + if (System.currentTimeMillis() < EARLIEST_SUPPORTED_TIME) { // If a device's clock is before 1970 (before 0), a lot of // APIs crash dealing with negative numbers, notably diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java index 6957bac..e6b6b93 100644 --- a/services/java/com/android/server/WallpaperManagerService.java +++ b/services/java/com/android/server/WallpaperManagerService.java @@ -40,6 +40,7 @@ import android.content.pm.ServiceInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; import android.content.res.Resources; +import android.graphics.Point; import android.os.Binder; import android.os.Bundle; import android.os.Environment; @@ -637,6 +638,14 @@ class WallpaperManagerService extends IWallpaperManager.Stub { return false; } + private Point getDefaultDisplaySize() { + Point p = new Point(); + WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); + Display d = wm.getDefaultDisplay(); + d.getRealSize(p); + return p; + } + public void setDimensionHints(int width, int height) throws RemoteException { checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS); synchronized (mLock) { @@ -648,6 +657,10 @@ class WallpaperManagerService extends IWallpaperManager.Stub { if (width <= 0 || height <= 0) { throw new IllegalArgumentException("width and height must be > 0"); } + // Make sure it is at least as large as the display. + Point displaySize = getDefaultDisplaySize(); + width = Math.max(width, displaySize.x); + height = Math.max(height, displaySize.y); if (width != wallpaper.width || height != wallpaper.height) { wallpaper.width = width; @@ -1146,9 +1159,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } // We always want to have some reasonable width hint. - WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); - Display d = wm.getDefaultDisplay(); - int baseSize = d.getMaximumSizeDimension(); + int baseSize = getMaximumSizeDimension(); if (wallpaper.width < baseSize) { wallpaper.width = baseSize; } @@ -1157,6 +1168,12 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } } + private int getMaximumSizeDimension() { + WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); + Display d = wm.getDefaultDisplay(); + return d.getMaximumSizeDimension(); + } + // Called by SystemBackupAgent after files are restored to disk. void settingsRestored() { // TODO: If necessary, make it work for secondary users as well. This currently assumes diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java index a99b58a..43f12eb 100644 --- a/services/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/java/com/android/server/accessibility/TouchExplorer.java @@ -76,7 +76,7 @@ class TouchExplorer implements EventStreamTransformation { private static final int STATE_DELEGATING = 0x00000004; private static final int STATE_GESTURE_DETECTING = 0x00000005; - // The minimum of the cosine between the vectors of two moving + // The maximum of the cosine between the vectors of two moving // pointers so they can be considered moving in the same direction. private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4) @@ -436,13 +436,19 @@ class TouchExplorer implements EventStreamTransformation { final int pointerIdBits = (1 << pointerId); mSendHoverEnterAndMoveDelayed.post(event, true, pointerIdBits, policyFlags); + } else { + // Cache the event until we discern exploration from gesturing. + mSendHoverEnterAndMoveDelayed.addEvent(event); } - // Cache the event until we discern exploration from gesturing. - mSendHoverEnterAndMoveDelayed.addEvent(event); } } break; case MotionEvent.ACTION_POINTER_DOWN: { - /* do nothing - let the code for ACTION_MOVE decide what to do */ + // Another finger down means that if we have not started to deliver + // hover events, we will not have to. The code for ACTION_MOVE will + // decide what we will actually do next. + mSendHoverEnterAndMoveDelayed.cancel(); + mSendHoverExitDelayed.cancel(); + mPerformLongPressDelayed.cancel(); } break; case MotionEvent.ACTION_MOVE: { final int pointerId = receivedTracker.getPrimaryPointerId(); @@ -518,14 +524,11 @@ class TouchExplorer implements EventStreamTransformation { mPerformLongPressDelayed.cancel(); } } - // The user is either double tapping or performing a long - // press, so do not send move events yet. - if (mDoubleTapDetector.firstTapDetected()) { - break; + if (mTouchExplorationInProgress) { + sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags); + sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, + policyFlags); } - sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags); - sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, - policyFlags); } } break; case 2: { @@ -539,22 +542,24 @@ class TouchExplorer implements EventStreamTransformation { mPerformLongPressDelayed.cancel(); } else { mPerformLongPressDelayed.cancel(); - // If the user is touch exploring the second pointer may be - // performing a double tap to activate an item without need - // for the user to lift his exploring finger. - // It is *important* to use the distance traveled by the pointers - // on the screen which may or may not be magnified. - final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId) - - rawEvent.getX(pointerIndex); - final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId) - - rawEvent.getY(pointerIndex); - final double moveDelta = Math.hypot(deltaX, deltaY); - if (moveDelta < mDoubleTapSlop) { - break; + if (mTouchExplorationInProgress) { + // If the user is touch exploring the second pointer may be + // performing a double tap to activate an item without need + // for the user to lift his exploring finger. + // It is *important* to use the distance traveled by the pointers + // on the screen which may or may not be magnified. + final float deltaX = receivedTracker.getReceivedPointerDownX( + pointerId) - rawEvent.getX(pointerIndex); + final float deltaY = receivedTracker.getReceivedPointerDownY( + pointerId) - rawEvent.getY(pointerIndex); + final double moveDelta = Math.hypot(deltaX, deltaY); + if (moveDelta < mDoubleTapSlop) { + break; + } + // We are sending events so send exit and gesture + // end since we transition to another state. + sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } - // We are sending events so send exit and gesture - // end since we transition to another state. - sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); } // We know that a new state transition is to happen and the @@ -736,20 +741,34 @@ class TouchExplorer implements EventStreamTransformation { + "there is at least one pointer down!"); } case MotionEvent.ACTION_UP: { - mAms.onTouchInteractionEnd(); + // Offset the event if we are doing a long press as the + // target is not necessarily under the user's finger. + if (mLongPressingPointerId >= 0) { + event = offsetEvent(event, - mLongPressingPointerDeltaX, + - mLongPressingPointerDeltaY); + // Clear the long press state. + mLongPressingPointerId = -1; + mLongPressingPointerDeltaX = 0; + mLongPressingPointerDeltaY = 0; + } + + // Deliver the event. + sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); + // Announce the end of a the touch interaction. + mAms.onTouchInteractionEnd(); sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); - mLongPressingPointerId = -1; - mLongPressingPointerDeltaX = 0; - mLongPressingPointerDeltaY = 0; + mCurrentState = STATE_TOUCH_EXPLORING; } break; case MotionEvent.ACTION_CANCEL: { clear(event, policyFlags); } break; + default: { + // Deliver the event. + sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); + } } - // Deliver the event. - sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); } private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) { @@ -959,27 +978,8 @@ class TouchExplorer implements EventStreamTransformation { // long press it, or even worse to avoid the user long pressing // on the wrong item since click and long press behave differently. if (mLongPressingPointerId >= 0) { - final int remappedIndex = event.findPointerIndex(mLongPressingPointerId); - final int pointerCount = event.getPointerCount(); - PointerProperties[] props = PointerProperties.createArray(pointerCount); - PointerCoords[] coords = PointerCoords.createArray(pointerCount); - for (int i = 0; i < pointerCount; i++) { - event.getPointerProperties(i, props[i]); - event.getPointerCoords(i, coords[i]); - if (i == remappedIndex) { - coords[i].x -= mLongPressingPointerDeltaX; - coords[i].y -= mLongPressingPointerDeltaY; - } - } - MotionEvent remapped = MotionEvent.obtain(event.getDownTime(), - event.getEventTime(), event.getAction(), event.getPointerCount(), - props, coords, event.getMetaState(), event.getButtonState(), - 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(), - event.getSource(), event.getFlags()); - if (event != prototype) { - event.recycle(); - } - event = remapped; + event = offsetEvent(event, - mLongPressingPointerDeltaX, + - mLongPressingPointerDeltaY); } if (DEBUG) { @@ -1004,6 +1004,39 @@ class TouchExplorer implements EventStreamTransformation { } /** + * Offsets all pointers in the given event by adding the specified X and Y + * offsets. + * + * @param event The event to offset. + * @param offsetX The X offset. + * @param offsetY The Y offset. + * @return An event with the offset pointers or the original event if both + * offsets are zero. + */ + private MotionEvent offsetEvent(MotionEvent event, int offsetX, int offsetY) { + if (offsetX == 0 && offsetY == 0) { + return event; + } + final int remappedIndex = event.findPointerIndex(mLongPressingPointerId); + final int pointerCount = event.getPointerCount(); + PointerProperties[] props = PointerProperties.createArray(pointerCount); + PointerCoords[] coords = PointerCoords.createArray(pointerCount); + for (int i = 0; i < pointerCount; i++) { + event.getPointerProperties(i, props[i]); + event.getPointerCoords(i, coords[i]); + if (i == remappedIndex) { + coords[i].x += offsetX; + coords[i].y += offsetY; + } + } + return MotionEvent.obtain(event.getDownTime(), + event.getEventTime(), event.getAction(), event.getPointerCount(), + props, coords, event.getMetaState(), event.getButtonState(), + 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(), + event.getSource(), event.getFlags()); + } + + /** * Computes the action for an injected event based on a masked action * and a pointer index. * @@ -1674,7 +1707,6 @@ class TouchExplorer implements EventStreamTransformation { // Keep track of the last up pointer data. private long mLastReceivedUpPointerDownTime; - private int mLastReceivedUpPointerId; private float mLastReceivedUpPointerDownX; private float mLastReceivedUpPointerDownY; @@ -1690,7 +1722,6 @@ class TouchExplorer implements EventStreamTransformation { mReceivedPointersDown = 0; mPrimaryPointerId = 0; mLastReceivedUpPointerDownTime = 0; - mLastReceivedUpPointerId = 0; mLastReceivedUpPointerDownX = 0; mLastReceivedUpPointerDownY = 0; } @@ -1823,7 +1854,6 @@ class TouchExplorer implements EventStreamTransformation { final int pointerId = event.getPointerId(pointerIndex); final int pointerFlag = (1 << pointerId); - mLastReceivedUpPointerId = 0; mLastReceivedUpPointerDownTime = 0; mLastReceivedUpPointerDownX = 0; mLastReceivedUpPointerDownX = 0; @@ -1848,7 +1878,6 @@ class TouchExplorer implements EventStreamTransformation { final int pointerId = event.getPointerId(pointerIndex); final int pointerFlag = (1 << pointerId); - mLastReceivedUpPointerId = pointerId; mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId); mLastReceivedUpPointerDownX = mReceivedPointerDownX[pointerId]; mLastReceivedUpPointerDownY = mReceivedPointerDownY[pointerId]; diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java index f972f70..aa9849e 100644 --- a/services/java/com/android/server/accounts/AccountManagerService.java +++ b/services/java/com/android/server/accounts/AccountManagerService.java @@ -190,10 +190,10 @@ public class AccountManagerService private final HashMap<String, Account[]> accountCache = new LinkedHashMap<String, Account[]>(); /** protected by the {@link #cacheLock} */ - private HashMap<Account, HashMap<String, String>> userDataCache = + private final HashMap<Account, HashMap<String, String>> userDataCache = new HashMap<Account, HashMap<String, String>>(); /** protected by the {@link #cacheLock} */ - private HashMap<Account, HashMap<String, String>> authTokenCache = + private final HashMap<Account, HashMap<String, String>> authTokenCache = new HashMap<Account, HashMap<String, String>>(); UserAccounts(Context context, int userId) { @@ -475,6 +475,7 @@ public class AccountManagerService validateAccountsInternal(getUserAccounts(userId), false /* invalidateAuthenticatorCache */); } + @Override public String getPassword(Account account) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "getPassword: " + account @@ -514,6 +515,7 @@ public class AccountManagerService } } + @Override public String getUserData(Account account, String key) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "getUserData: " + account @@ -533,6 +535,7 @@ public class AccountManagerService } } + @Override public AuthenticatorDescription[] getAuthenticatorTypes() { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "getAuthenticatorTypes: " @@ -763,6 +766,7 @@ public class AccountManagerService return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values); } + @Override public void hasFeatures(IAccountManagerResponse response, Account account, String[] features) { if (Log.isLoggable(TAG, Log.VERBOSE)) { @@ -840,6 +844,7 @@ public class AccountManagerService } } + @Override public void removeAccount(IAccountManagerResponse response, Account account) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "removeAccount: " + account @@ -1049,6 +1054,7 @@ public class AccountManagerService } } + @Override public String peekAuthToken(Account account, String authTokenType) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "peekAuthToken: " + account @@ -1068,6 +1074,7 @@ public class AccountManagerService } } + @Override public void setAuthToken(Account account, String authTokenType, String authToken) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "setAuthToken: " + account @@ -1087,6 +1094,7 @@ public class AccountManagerService } } + @Override public void setPassword(Account account, String password) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "setAuthToken: " + account @@ -1135,6 +1143,7 @@ public class AccountManagerService mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId)); } + @Override public void clearPassword(Account account) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "clearPassword: " + account @@ -1152,6 +1161,7 @@ public class AccountManagerService } } + @Override public void setUserData(Account account, String key, String value) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "setUserData: " + account @@ -1225,6 +1235,7 @@ public class AccountManagerService } } + @Override public void getAuthTokenLabel(IAccountManagerResponse response, final String accountType, final String authTokenType) throws RemoteException { @@ -1271,6 +1282,7 @@ public class AccountManagerService } } + @Override public void getAuthToken(IAccountManagerResponse response, final Account account, final String authTokenType, final boolean notifyOnAuthFailure, final boolean expectActivityLaunch, Bundle loginOptionsIn) { @@ -1284,8 +1296,22 @@ public class AccountManagerService + ", pid " + Binder.getCallingPid()); } if (response == null) throw new IllegalArgumentException("response is null"); - if (account == null) throw new IllegalArgumentException("account is null"); - if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); + try { + if (account == null) { + Slog.w(TAG, "getAuthToken called with null account"); + response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS, "account is null"); + return; + } + if (authTokenType == null) { + Slog.w(TAG, "getAuthToken called with null authTokenType"); + response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS, "authTokenType is null"); + return; + } + } catch (RemoteException e) { + Slog.w(TAG, "Failed to report error back to the client." + e); + return; + } + checkBinderPermission(Manifest.permission.USE_CREDENTIALS); final UserAccounts accounts = getUserAccountsForCaller(); final RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo; @@ -1294,11 +1320,6 @@ public class AccountManagerService final boolean customTokens = authenticatorInfo != null && authenticatorInfo.type.customTokens; - // Check to see that the app is authorized to access the account, in case it's a - // restricted account. - if (!ArrayUtils.contains(getAccounts((String) null), account)) { - throw new IllegalArgumentException("no such account"); - } // skip the check if customTokens final int callerUid = Binder.getCallingUid(); final boolean permissionGranted = customTokens || @@ -1472,6 +1493,7 @@ public class AccountManagerService return id; } + @Override public void addAccount(final IAccountManagerResponse response, final String accountType, final String authTokenType, final String[] requiredFeatures, final boolean expectActivityLaunch, final Bundle optionsIn) { @@ -1582,6 +1604,7 @@ public class AccountManagerService } } + @Override public void updateCredentials(IAccountManagerResponse response, final Account account, final String authTokenType, final boolean expectActivityLaunch, final Bundle loginOptions) { @@ -1620,6 +1643,7 @@ public class AccountManagerService } } + @Override public void editProperties(IAccountManagerResponse response, final String accountType, final boolean expectActivityLaunch) { if (Log.isLoggable(TAG, Log.VERBOSE)) { @@ -1657,7 +1681,7 @@ public class AccountManagerService private volatile Account[] mAccountsOfType = null; private volatile ArrayList<Account> mAccountsWithFeatures = null; private volatile int mCurrentAccount = 0; - private int mCallingUid; + private final int mCallingUid; public GetAccountsByTypeAndFeatureSession(UserAccounts accounts, IAccountManagerResponse response, String type, String[] features, int callingUid) { @@ -1941,6 +1965,7 @@ public class AccountManagerService return getAccountsAsUser(type, UserHandle.getCallingUserId(), packageName, packageUid); } + @Override public void getAccountsByFeatures(IAccountManagerResponse response, String type, String[] features) { if (Log.isLoggable(TAG, Log.VERBOSE)) { @@ -2069,6 +2094,7 @@ public class AccountManagerService unbind(); } + @Override public void binderDied() { mResponse = null; close(); @@ -2112,6 +2138,7 @@ public class AccountManagerService mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this); } + @Override public void onServiceConnected(ComponentName name, IBinder service) { mAuthenticator = IAccountAuthenticator.Stub.asInterface(service); try { @@ -2122,6 +2149,7 @@ public class AccountManagerService } } + @Override public void onServiceDisconnected(ComponentName name) { mAuthenticator = null; IAccountManagerResponse response = getResponseAndClose(); @@ -2217,7 +2245,14 @@ public class AccountManagerService Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " + response); } - response.onResult(result); + if ((result.getInt(AccountManager.KEY_ERROR_CODE, -1) > 0) && + (intent == null)) { + // All AccountManager error codes are greater than 0 + response.onError(result.getInt(AccountManager.KEY_ERROR_CODE), + result.getString(AccountManager.KEY_ERROR_MESSAGE)); + } else { + response.onResult(result); + } } } catch (RemoteException e) { // if the caller is dead then there is no one to care about remote exceptions @@ -2228,10 +2263,12 @@ public class AccountManagerService } } + @Override public void onRequestContinued() { mNumRequestContinued++; } + @Override public void onError(int errorCode, String errorMessage) { mNumErrors++; IAccountManagerResponse response = getResponseAndClose(); @@ -2731,6 +2768,7 @@ public class AccountManagerService return true; } + @Override public void updateAppPermission(Account account, String authTokenType, int uid, boolean value) throws RemoteException { final int callingUid = getCallingUid(); diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java index a64940c..933247e 100644 --- a/services/java/com/android/server/am/ActiveServices.java +++ b/services/java/com/android/server/am/ActiveServices.java @@ -26,6 +26,7 @@ import java.util.List; import android.os.Handler; import android.os.Looper; +import android.os.SystemProperties; import android.util.ArrayMap; import com.android.internal.app.ProcessStats; import com.android.internal.os.BatteryStatsImpl; @@ -76,7 +77,7 @@ public final class ActiveServices { // How long a service needs to be running until restarting its process // is no longer considered to be a relaunch of the service. - static final int SERVICE_RESTART_DURATION = 5*1000; + static final int SERVICE_RESTART_DURATION = 1*1000; // How long a service needs to be running until it will start back at // SERVICE_RESTART_DURATION after being killed. @@ -239,7 +240,12 @@ public final class ActiveServices { public ActiveServices(ActivityManagerService service) { mAm = service; - mMaxStartingBackground = ActivityManager.isLowRamDeviceStatic() ? 1 : 3; + int maxBg = 0; + try { + maxBg = Integer.parseInt(SystemProperties.get("ro.config.max_starting_bg", "0")); + } catch(RuntimeException e) { + } + mMaxStartingBackground = maxBg > 0 ? maxBg : ActivityManager.isLowRamDeviceStatic() ? 1 : 3; } ServiceRecord getServiceByName(ComponentName name, int callingUser) { @@ -301,7 +307,7 @@ public final class ActiveServices { ServiceRecord r = res.record; NeededUriGrants neededGrants = mAm.checkGrantUriPermissionFromIntentLocked( callingUid, r.packageName, service, service.getFlags(), null); - if (unscheduleServiceRestartLocked(r)) { + if (unscheduleServiceRestartLocked(r, callingUid, false)) { if (DEBUG_SERVICE) Slog.v(TAG, "START SERVICE WHILE RESTART PENDING: " + r); } r.lastActivity = SystemClock.uptimeMillis(); @@ -564,7 +570,7 @@ public final class ActiveServices { if (r.isForeground) { r.isForeground = false; if (r.app != null) { - mAm.updateLruProcessLocked(r.app, false, false); + mAm.updateLruProcessLocked(r.app, false, null); updateServiceForegroundLocked(r.app, true); } } @@ -597,6 +603,42 @@ public final class ActiveServices { } } + private boolean updateServiceClientActivitiesLocked(ProcessRecord proc, + ConnectionRecord modCr) { + if (modCr != null && modCr.binding.client != null) { + if (modCr.binding.client.activities.size() <= 0) { + // This connection is from a client without activities, so adding + // and removing is not interesting. + return false; + } + } + + boolean anyClientActivities = false; + for (int i=proc.services.size()-1; i>=0 && !anyClientActivities; i--) { + ServiceRecord sr = proc.services.valueAt(i); + for (int conni=sr.connections.size()-1; conni>=0 && !anyClientActivities; conni--) { + ArrayList<ConnectionRecord> clist = sr.connections.valueAt(conni); + for (int cri=clist.size()-1; cri>=0; cri--) { + ConnectionRecord cr = clist.get(cri); + if (cr.binding.client == null || cr.binding.client == proc) { + // Binding to ourself is not interesting. + continue; + } + if (cr.binding.client.activities.size() > 0) { + anyClientActivities = true; + break; + } + } + } + } + if (anyClientActivities != proc.hasClientActivities) { + proc.hasClientActivities = anyClientActivities; + mAm.updateLruProcessLocked(proc, anyClientActivities, null); + return true; + } + return false; + } + int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags, int userId) { @@ -659,7 +701,7 @@ public final class ActiveServices { final long origId = Binder.clearCallingIdentity(); try { - if (unscheduleServiceRestartLocked(s)) { + if (unscheduleServiceRestartLocked(s, callerApp.info.uid, false)) { if (DEBUG_SERVICE) Slog.v(TAG, "BIND SERVICE WHILE RESTART PENDING: " + s); } @@ -698,6 +740,9 @@ public final class ActiveServices { if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) { b.client.hasAboveClient = true; } + if (s.app != null) { + updateServiceClientActivitiesLocked(s.app, c); + } clist = mServiceConnections.get(binder); if (clist == null) { clist = new ArrayList<ConnectionRecord>(); @@ -714,6 +759,7 @@ public final class ActiveServices { if (s.app != null) { // This could have made the service more important. + mAm.updateLruProcessLocked(s.app, s.app.hasClientActivities, b.client); mAm.updateOomAdjLocked(s.app); } @@ -956,14 +1002,11 @@ public final class ActiveServices { smap.mServicesByIntent.put(filter, r); // Make sure this component isn't in the pending list. - int N = mPendingServices.size(); - for (int i=0; i<N; i++) { + for (int i=mPendingServices.size()-1; i>=0; i--) { ServiceRecord pr = mPendingServices.get(i); if (pr.serviceInfo.applicationInfo.uid == sInfo.applicationInfo.uid && pr.name.equals(name)) { mPendingServices.remove(i); - i--; - N--; } } } @@ -1055,6 +1098,14 @@ public final class ActiveServices { boolean allowCancel) { boolean canceled = false; + ServiceMap smap = getServiceMap(r.userId); + if (smap.mServicesByName.get(r.name) != r) { + ServiceRecord cur = smap.mServicesByName.get(r.name); + Slog.wtf(TAG, "Attempting to schedule restart of " + r + + " when found in map: " + cur); + return false; + } + final long now = SystemClock.uptimeMillis(); if ((r.serviceInfo.applicationInfo.flags @@ -1101,16 +1152,9 @@ public final class ActiveServices { r.restartCount = 1; r.restartDelay = minDuration; } else { - if ((r.serviceInfo.applicationInfo.flags - &ApplicationInfo.FLAG_PERSISTENT) != 0) { - // Services in peristent processes will restart much more - // quickly, since they are pretty important. (Think SystemUI). - r.restartDelay += minDuration/2; - } else { - r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR; - if (r.restartDelay < minDuration) { - r.restartDelay = minDuration; - } + r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR; + if (r.restartDelay < minDuration) { + r.restartDelay = minDuration; } } } @@ -1137,7 +1181,7 @@ public final class ActiveServices { } while (repeat); } else { - // Persistent processes are immediately restrted, so there is no + // Persistent processes are immediately restarted, so there is no // reason to hold of on restarting their services. r.totalRestartCount++; r.restartCount = 0; @@ -1148,6 +1192,7 @@ public final class ActiveServices { if (!mRestartingServices.contains(r)) { r.createdFromFg = false; mRestartingServices.add(r); + r.makeRestarting(mAm.mProcessStats.getMemFactorLocked(), now); } r.cancelNotification(); @@ -1170,16 +1215,44 @@ public final class ActiveServices { bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true); } - private final boolean unscheduleServiceRestartLocked(ServiceRecord r) { - if (r.restartDelay == 0) { + private final boolean unscheduleServiceRestartLocked(ServiceRecord r, int callingUid, + boolean force) { + if (!force && r.restartDelay == 0) { return false; } - r.resetRestartCounter(); - mRestartingServices.remove(r); + // Remove from the restarting list; if the service is currently on the + // restarting list, or the call is coming from another app, then this + // service has become of much more interest so we reset the restart interval. + boolean removed = mRestartingServices.remove(r); + if (removed || callingUid != r.appInfo.uid) { + r.resetRestartCounter(); + } + if (removed) { + clearRestartingIfNeededLocked(r); + } mAm.mHandler.removeCallbacks(r.restarter); return true; } + private void clearRestartingIfNeededLocked(ServiceRecord r) { + if (r.restartTracker != null) { + // If this is the last restarting record with this tracker, then clear + // the tracker's restarting state. + boolean stillTracking = false; + for (int i=mRestartingServices.size()-1; i>=0; i--) { + if (mRestartingServices.get(i).restartTracker == r.restartTracker) { + stillTracking = true; + break; + } + } + if (!stillTracking) { + r.restartTracker.setRestarting(false, mAm.mProcessStats.getMemFactorLocked(), + SystemClock.uptimeMillis()); + r.restartTracker = null; + } + } + } + private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg, boolean whileRestarting) { //Slog.i(TAG, "Bring up service:"); @@ -1199,7 +1272,9 @@ public final class ActiveServices { // We are now bringing the service up, so no longer in the // restarting state. - mRestartingServices.remove(r); + if (mRestartingServices.remove(r)) { + clearRestartingIfNeededLocked(r); + } // Make sure this service is no longer considered delayed, we are starting it now. if (r.delayed) { @@ -1316,7 +1391,8 @@ public final class ActiveServices { app.services.add(r); bumpServiceExecutingLocked(r, execInFg, "create"); - mAm.updateLruProcessLocked(app, true, false); + mAm.updateLruProcessLocked(app, false, null); + mAm.updateOomAdjLocked(); boolean created = false; try { @@ -1338,6 +1414,7 @@ public final class ActiveServices { } finally { if (!created) { app.services.remove(r); + r.app = null; scheduleServiceRestartLocked(r, false); } } @@ -1508,16 +1585,13 @@ public final class ActiveServices { smap.mServicesByName.remove(r.name); smap.mServicesByIntent.remove(r.intent); r.totalRestartCount = 0; - unscheduleServiceRestartLocked(r); + unscheduleServiceRestartLocked(r, 0, true); // Also make sure it is not on the pending list. - int N = mPendingServices.size(); - for (int i=0; i<N; i++) { + for (int i=mPendingServices.size()-1; i>=0; i--) { if (mPendingServices.get(i) == r) { mPendingServices.remove(i); if (DEBUG_SERVICE) Slog.v(TAG, "Removed pending: " + r); - i--; - N--; } } @@ -1536,6 +1610,7 @@ public final class ActiveServices { } r.app.services.remove(r); if (r.app.thread != null) { + updateServiceForegroundLocked(r.app, false); try { bumpServiceExecutingLocked(r, false, "destroy"); mDestroyingServices.add(r); @@ -1546,7 +1621,6 @@ public final class ActiveServices { + r.shortName, e); serviceProcessGoneLocked(r); } - updateServiceForegroundLocked(r.app, false); } else { if (DEBUG_SERVICE) Slog.v( TAG, "Removed service that has no process: " + r); @@ -1601,6 +1675,9 @@ public final class ActiveServices { if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) { b.client.updateHasAboveClientLocked(); } + if (s.app != null) { + updateServiceClientActivitiesLocked(s.app, c); + } } clist = mServiceConnections.get(binder); if (clist != null) { @@ -1621,6 +1698,13 @@ public final class ActiveServices { && b.intent.hasBound) { try { bumpServiceExecutingLocked(s, false, "unbind"); + if (b.client != s.app && (c.flags&Context.BIND_WAIVE_PRIORITY) == 0 + && s.app.setProcState <= ActivityManager.PROCESS_STATE_RECEIVER) { + // If this service's process is not already in the cached list, + // then update it in the LRU list here because this may be causing + // it to go down there and we want it to start out near the top. + mAm.updateLruProcessLocked(s.app, false, null); + } mAm.updateOomAdjLocked(s.app); b.intent.hasBound = false; // Assume the client doesn't want to know about a rebind; @@ -1714,6 +1798,7 @@ public final class ActiveServices { long now = SystemClock.uptimeMillis(); r.tracker.setExecuting(false, memFactor, now); r.tracker.setBound(false, memFactor, now); + r.tracker.setStarted(false, memFactor, now); } serviceDoneExecutingLocked(r, true, true); } @@ -1761,6 +1846,12 @@ public final class ActiveServices { r.tracker = null; } } + if (finishing) { + if (r.app != null) { + r.app.services.remove(r); + } + r.app = null; + } } } @@ -1839,6 +1930,7 @@ public final class ActiveServices { Slog.i(TAG, " Force stopping service " + service); if (service.app != null) { service.app.removed = true; + service.app.services.remove(service); } service.app = null; service.isolatedProc = null; @@ -1905,8 +1997,7 @@ public final class ActiveServices { } } - final void killServicesLocked(ProcessRecord app, - boolean allowRestart) { + final void killServicesLocked(ProcessRecord app, boolean allowRestart) { // Report disconnected services. if (false) { // XXX we are letting the client link to the service for @@ -1935,20 +2026,15 @@ public final class ActiveServices { } } - // Clean up any connections this application has to other services. - for (int i=app.connections.size()-1; i>=0; i--) { - ConnectionRecord r = app.connections.valueAt(i); - removeConnectionLocked(r, app, null); - } - app.connections.clear(); - + // First clear app state from services. for (int i=app.services.size()-1; i>=0; i--) { - // Any services running in the application need to be placed - // back in the pending list. ServiceRecord sr = app.services.valueAt(i); synchronized (sr.stats.getBatteryStats()) { sr.stats.stopLaunchedLocked(); } + if (sr.app != null) { + sr.app.services.remove(sr); + } sr.app = null; sr.isolatedProc = null; sr.executeNesting = 0; @@ -1965,8 +2051,33 @@ public final class ActiveServices { b.binder = null; b.requested = b.received = b.hasBound = false; } + } - if (sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags + // Clean up any connections this application has to other services. + for (int i=app.connections.size()-1; i>=0; i--) { + ConnectionRecord r = app.connections.valueAt(i); + removeConnectionLocked(r, app, null); + } + app.connections.clear(); + + ServiceMap smap = getServiceMap(app.userId); + + // Now do remaining service cleanup. + for (int i=app.services.size()-1; i>=0; i--) { + ServiceRecord sr = app.services.valueAt(i); + // Sanity check: if the service listed for the app is not one + // we actually are maintaining, drop it. + if (smap.mServicesByName.get(sr.name) != sr) { + ServiceRecord cur = smap.mServicesByName.get(sr.name); + Slog.wtf(TAG, "Service " + sr + " in process " + app + + " not same as in map: " + cur); + app.services.removeAt(i); + continue; + } + + // Any services running in the application may need to be placed + // back in the pending list. + if (allowRestart && sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags &ApplicationInfo.FLAG_PERSISTENT) == 0) { Slog.w(TAG, "Service crashed " + sr.crashCount + " times, stopping: " + sr); @@ -1999,6 +2110,23 @@ public final class ActiveServices { if (!allowRestart) { app.services.clear(); + + // Make sure there are no more restarting services for this process. + for (int i=mRestartingServices.size()-1; i>=0; i--) { + ServiceRecord r = mRestartingServices.get(i); + if (r.processName.equals(app.processName) && + r.serviceInfo.applicationInfo.uid == app.info.uid) { + mRestartingServices.remove(i); + clearRestartingIfNeededLocked(r); + } + } + for (int i=mPendingServices.size()-1; i>=0; i--) { + ServiceRecord r = mPendingServices.get(i); + if (r.processName.equals(app.processName) && + r.serviceInfo.applicationInfo.uid == app.info.uid) { + mPendingServices.remove(i); + } + } } // Make sure we have no more records on the stopping list. diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index d75fe5e..9201b1d 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -34,7 +34,6 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsService; import com.android.internal.app.ProcessStats; -import com.android.internal.app.ResolverActivity; import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.ProcessCpuTracker; @@ -218,6 +217,7 @@ public final class ActivityManagerService extends ActivityManagerNative static final boolean DEBUG_IMMERSIVE = localLOGV || false; static final boolean DEBUG_MU = localLOGV || false; static final boolean DEBUG_OOM_ADJ = localLOGV || false; + static final boolean DEBUG_LRU = localLOGV || false; static final boolean DEBUG_PAUSE = localLOGV || false; static final boolean DEBUG_POWER = localLOGV || false; static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false; @@ -457,6 +457,23 @@ public final class ActivityManagerService extends ActivityManagerNative final ProcessMap<Long> mProcessCrashTimes = new ProcessMap<Long>(); /** + * Information about a process that is currently marked as bad. + */ + static final class BadProcessInfo { + BadProcessInfo(long time, String shortMsg, String longMsg, String stack) { + this.time = time; + this.shortMsg = shortMsg; + this.longMsg = longMsg; + this.stack = stack; + } + + final long time; + final String shortMsg; + final String longMsg; + final String stack; + } + + /** * Set of applications that we consider to be bad, and will reject * incoming broadcasts from (which the user has no control over). * Processes are added to this set when they have crashed twice within @@ -464,7 +481,7 @@ public final class ActivityManagerService extends ActivityManagerNative * later restarted (hopefully due to some user action). The value is the * time it was added to the list. */ - final ProcessMap<Long> mBadProcesses = new ProcessMap<Long>(); + final ProcessMap<BadProcessInfo> mBadProcesses = new ProcessMap<BadProcessInfo>(); /** * All of the processes we currently have running organized by pid. @@ -1743,7 +1760,8 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (mSelf.mPidsSelfLocked) { mSelf.mPidsSelfLocked.put(app.pid, app); } - mSelf.updateLruProcessLocked(app, true, false); + mSelf.updateLruProcessLocked(app, false, null); + mSelf.updateOomAdjLocked(); } } catch (PackageManager.NameNotFoundException e) { throw new RuntimeException( @@ -2131,7 +2149,8 @@ public final class ActivityManagerService extends ActivityManagerNative totalUTime += otherUTime; totalSTime += otherSTime; if (pr != null) { - BatteryStatsImpl.Uid.Proc ps = pr.batteryStats; + BatteryStatsImpl.Uid.Proc ps = bstats.getProcessStatsLocked( + st.name, st.pid); ps.addCpuTimeLocked(st.rel_utime-otherUTime, st.rel_stime-otherSTime); ps.addSpeedStepTimes(cpuSpeedTimes); @@ -2269,7 +2288,7 @@ public final class ActivityManagerService extends ActivityManagerNative int lrui = mLruProcesses.lastIndexOf(app); if (lrui < 0) { - Log.wtf(TAG, "Adding dependent process " + app + " not on LRU list: " + Slog.wtf(TAG, "Adding dependent process " + app + " not on LRU list: " + what + " " + obj + " from " + srcApp); return index; } @@ -2289,6 +2308,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (index > 0) { index--; } + if (DEBUG_LRU) Slog.d(TAG, "Moving dep from " + lrui + " to " + index + + " in LRU list: " + app); mLruProcesses.add(index, app); return index; } @@ -2306,8 +2327,9 @@ public final class ActivityManagerService extends ActivityManagerNative } } - final void updateLruProcessLocked(ProcessRecord app, boolean oomAdj, boolean activityChange) { - final boolean hasActivity = app.activities.size() > 0; + final void updateLruProcessLocked(ProcessRecord app, boolean activityChange, + ProcessRecord client) { + final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities; final boolean hasService = false; // not impl yet. app.services.size() > 0; if (!activityChange && hasActivity) { // The process has activties, so we are only going to allow activity-based @@ -2321,8 +2343,65 @@ public final class ActivityManagerService extends ActivityManagerNative final long now = SystemClock.uptimeMillis(); app.lastActivityTime = now; + // First a quick reject: if the app is already at the position we will + // put it, then there is nothing to do. + if (hasActivity) { + final int N = mLruProcesses.size(); + if (N > 0 && mLruProcesses.get(N-1) == app) { + if (DEBUG_LRU) Slog.d(TAG, "Not moving, already top activity: " + app); + return; + } + } else { + if (mLruProcessServiceStart > 0 + && mLruProcesses.get(mLruProcessServiceStart-1) == app) { + if (DEBUG_LRU) Slog.d(TAG, "Not moving, already top other: " + app); + return; + } + } + int lrui = mLruProcesses.lastIndexOf(app); + if (app.persistent && lrui >= 0) { + // We don't care about the position of persistent processes, as long as + // they are in the list. + if (DEBUG_LRU) Slog.d(TAG, "Not moving, persistent: " + app); + return; + } + + /* In progress: compute new position first, so we can avoid doing work + if the process is not actually going to move. Not yet working. + int addIndex; + int nextIndex; + boolean inActivity = false, inService = false; + if (hasActivity) { + // Process has activities, put it at the very tipsy-top. + addIndex = mLruProcesses.size(); + nextIndex = mLruProcessServiceStart; + inActivity = true; + } else if (hasService) { + // Process has services, put it at the top of the service list. + addIndex = mLruProcessActivityStart; + nextIndex = mLruProcessServiceStart; + inActivity = true; + inService = true; + } else { + // Process not otherwise of interest, it goes to the top of the non-service area. + addIndex = mLruProcessServiceStart; + if (client != null) { + int clientIndex = mLruProcesses.lastIndexOf(client); + if (clientIndex < 0) Slog.d(TAG, "Unknown client " + client + " when updating " + + app); + if (clientIndex >= 0 && addIndex > clientIndex) { + addIndex = clientIndex; + } + } + nextIndex = addIndex > 0 ? addIndex-1 : addIndex; + } + + Slog.d(TAG, "Update LRU at " + lrui + " to " + addIndex + " (act=" + + mLruProcessActivityStart + "): " + app); + */ + if (lrui >= 0) { if (lrui < mLruProcessActivityStart) { mLruProcessActivityStart--; @@ -2330,23 +2409,91 @@ public final class ActivityManagerService extends ActivityManagerNative if (lrui < mLruProcessServiceStart) { mLruProcessServiceStart--; } + /* + if (addIndex > lrui) { + addIndex--; + } + if (nextIndex > lrui) { + nextIndex--; + } + */ mLruProcesses.remove(lrui); } + /* + mLruProcesses.add(addIndex, app); + if (inActivity) { + mLruProcessActivityStart++; + } + if (inService) { + mLruProcessActivityStart++; + } + */ + int nextIndex; if (hasActivity) { - // Process has activities, put it at the very tipsy-top. - mLruProcesses.add(app); - nextIndex = mLruProcessActivityStart; + final int N = mLruProcesses.size(); + if (app.activities.size() == 0 && mLruProcessActivityStart < (N-1)) { + // Process doesn't have activities, but has clients with + // activities... move it up, but one below the top (the top + // should always have a real activity). + if (DEBUG_LRU) Slog.d(TAG, "Adding to second-top of LRU activity list: " + app); + mLruProcesses.add(N-1, app); + // To keep it from spamming the LRU list (by making a bunch of clients), + // we will push down any other entries owned by the app. + final int uid = app.info.uid; + for (int i=N-2; i>mLruProcessActivityStart; i--) { + ProcessRecord subProc = mLruProcesses.get(i); + if (subProc.info.uid == uid) { + // We want to push this one down the list. If the process after + // it is for the same uid, however, don't do so, because we don't + // want them internally to be re-ordered. + if (mLruProcesses.get(i-1).info.uid != uid) { + if (DEBUG_LRU) Slog.d(TAG, "Pushing uid " + uid + " swapping at " + i + + ": " + mLruProcesses.get(i) + " : " + mLruProcesses.get(i-1)); + ProcessRecord tmp = mLruProcesses.get(i); + mLruProcesses.set(i, mLruProcesses.get(i-1)); + mLruProcesses.set(i-1, tmp); + i--; + } + } else { + // A gap, we can stop here. + break; + } + } + } else { + // Process has activities, put it at the very tipsy-top. + if (DEBUG_LRU) Slog.d(TAG, "Adding to top of LRU activity list: " + app); + mLruProcesses.add(app); + } + nextIndex = mLruProcessServiceStart; } else if (hasService) { // Process has services, put it at the top of the service list. + if (DEBUG_LRU) Slog.d(TAG, "Adding to top of LRU service list: " + app); mLruProcesses.add(mLruProcessActivityStart, app); nextIndex = mLruProcessServiceStart; mLruProcessActivityStart++; } else { // Process not otherwise of interest, it goes to the top of the non-service area. - mLruProcesses.add(mLruProcessServiceStart, app); - nextIndex = mLruProcessServiceStart-1; + int index = mLruProcessServiceStart; + if (client != null) { + // If there is a client, don't allow the process to be moved up higher + // in the list than that client. + int clientIndex = mLruProcesses.lastIndexOf(client); + if (DEBUG_LRU && clientIndex < 0) Slog.d(TAG, "Unknown client " + client + + " when updating " + app); + if (clientIndex <= lrui) { + // Don't allow the client index restriction to push it down farther in the + // list than it already is. + clientIndex = lrui; + } + if (clientIndex >= 0 && index > clientIndex) { + index = clientIndex; + } + } + if (DEBUG_LRU) Slog.d(TAG, "Adding at " + index + " of LRU list: " + app); + mLruProcesses.add(index, app); + nextIndex = index-1; mLruProcessActivityStart++; mLruProcessServiceStart++; } @@ -2357,23 +2504,19 @@ public final class ActivityManagerService extends ActivityManagerNative ConnectionRecord cr = app.connections.valueAt(j); if (cr.binding != null && !cr.serviceDead && cr.binding.service != null && cr.binding.service.app != null - && cr.binding.service.app.lruSeq != mLruSeq) { + && cr.binding.service.app.lruSeq != mLruSeq + && !cr.binding.service.app.persistent) { nextIndex = updateLruProcessInternalLocked(cr.binding.service.app, now, nextIndex, "service connection", cr, app); } } for (int j=app.conProviders.size()-1; j>=0; j--) { ContentProviderRecord cpr = app.conProviders.get(j).provider; - if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq) { + if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq && !cpr.proc.persistent) { nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex, "provider reference", cpr, app); } } - - //Slog.i(TAG, "Putting proc to front: " + app.processName); - if (oomAdj) { - updateOomAdjLocked(); - } } final ProcessRecord getProcessRecordLocked(String processName, int uid, boolean keepIfLarge) { @@ -2627,10 +2770,10 @@ public final class ActivityManagerService extends ActivityManagerNative app.processName, uid, uid, gids, debugFlags, mountExternal, app.info.targetSdkVersion, app.info.seinfo, null); - BatteryStatsImpl bs = app.batteryStats.getBatteryStats(); + BatteryStatsImpl bs = mBatteryStatsService.getActiveStatistics(); synchronized (bs) { if (bs.isOnBattery()) { - app.batteryStats.incStartsLocked(); + bs.getProcessStatsLocked(app.uid, app.processName).incStartsLocked(); } } @@ -4831,7 +4974,7 @@ public final class ActivityManagerService extends ActivityManagerNative isRestrictedBackupMode || !normalMode, app.persistent, new Configuration(mConfiguration), app.compat, getCommonServicesLocked(), mCoreSettingsObserver.getCoreSettingsLocked()); - updateLruProcessLocked(app, false, false); + updateLruProcessLocked(app, false, null); app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis(); } catch (Exception e) { // todo: Yikes! What should we do? For now we will try to @@ -7419,7 +7562,7 @@ public final class ActivityManagerService extends ActivityManagerNative // make sure to count it as being accessed and thus // back up on the LRU list. This is good because // content providers are often expensive to start. - updateLruProcessLocked(cpr.proc, false, false); + updateLruProcessLocked(cpr.proc, false, null); } } @@ -8008,10 +8151,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - synchronized (stats) { - ps = stats.getProcessStatsLocked(info.uid, proc); - } - return new ProcessRecord(ps, info, proc, uid); + return new ProcessRecord(stats, info, proc, uid); } final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated) { @@ -8028,7 +8168,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (isolated) { mIsolatedProcesses.put(app.uid, app); } - updateLruProcessLocked(app, true, false); + updateLruProcessLocked(app, false, null); + updateOomAdjLocked(); } // This package really, really can not be stopped. @@ -9292,7 +9433,7 @@ public final class ActivityManagerService extends ActivityManagerNative ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace); startAppProblemLocked(app); app.stopFreezingAllLocked(); - return handleAppCrashLocked(app); + return handleAppCrashLocked(app, shortMsg, longMsg, stackTrace); } private void makeAppNotRespondingLocked(ProcessRecord app, @@ -9347,13 +9488,14 @@ public final class ActivityManagerService extends ActivityManagerNative app.waitDialog = null; } if (app.pid > 0 && app.pid != MY_PID) { - handleAppCrashLocked(app); + handleAppCrashLocked(app, null, null, null); killUnneededProcessLocked(app, "user request after error"); } } } - private boolean handleAppCrashLocked(ProcessRecord app) { + private boolean handleAppCrashLocked(ProcessRecord app, String shortMsg, String longMsg, + String stackTrace) { if (mHeadless) { Log.e(TAG, "handleAppCrashLocked: " + app.processName); return false; @@ -9383,7 +9525,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (!app.isolated) { // XXX We don't have a way to mark isolated processes // as bad, since they don't have a peristent identity. - mBadProcesses.put(app.info.processName, app.uid, now); + mBadProcesses.put(app.info.processName, app.uid, + new BadProcessInfo(now, shortMsg, longMsg, stackTrace)); mProcessCrashTimes.remove(app.info.processName, app.uid); } app.bad = true; @@ -10101,7 +10244,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.persistent) { outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_PERSISTENT; } - if (app.hasActivities) { + if (app.activities.size() > 0) { outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_HAS_ACTIVITIES; } outInfo.lastTrimLevel = app.trimMemoryLevel; @@ -10645,11 +10788,11 @@ public final class ActivityManagerService extends ActivityManagerNative if (mBadProcesses.getMap().size() > 0) { boolean printed = false; - final ArrayMap<String, SparseArray<Long>> pmap = mBadProcesses.getMap(); + final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = mBadProcesses.getMap(); final int NP = pmap.size(); for (int ip=0; ip<NP; ip++) { String pname = pmap.keyAt(ip); - SparseArray<Long> uids = pmap.valueAt(ip); + SparseArray<BadProcessInfo> uids = pmap.valueAt(ip); final int N = uids.size(); for (int i=0; i<N; i++) { int puid = uids.keyAt(i); @@ -10664,10 +10807,33 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" Bad processes:"); printedAnything = true; } + BadProcessInfo info = uids.valueAt(i); pw.print(" Bad process "); pw.print(pname); pw.print(" uid "); pw.print(puid); - pw.print(": crashed at time "); - pw.println(uids.valueAt(i)); + pw.print(": crashed at time "); pw.println(info.time); + if (info.shortMsg != null) { + pw.print(" Short msg: "); pw.println(info.shortMsg); + } + if (info.longMsg != null) { + pw.print(" Long msg: "); pw.println(info.longMsg); + } + if (info.stack != null) { + pw.println(" Stack:"); + int lastPos = 0; + for (int pos=0; pos<info.stack.length(); pos++) { + if (info.stack.charAt(pos) == '\n') { + pw.print(" "); + pw.write(info.stack, lastPos, pos-lastPos); + pw.println(); + lastPos = pos+1; + } + } + if (lastPos < info.stack.length()) { + pw.print(" "); + pw.write(info.stack, lastPos, info.stack.length()-lastPos); + pw.println(); + } + } } } } @@ -11733,6 +11899,7 @@ public final class ActivityManagerService extends ActivityManagerNative boolean dumpDalvik = false; boolean oomOnly = false; boolean isCompact = false; + boolean localOnly = false; int opti = 0; while (opti < args.length) { @@ -11751,12 +11918,15 @@ public final class ActivityManagerService extends ActivityManagerNative isCompact = true; } else if ("--oom".equals(opt)) { oomOnly = true; + } else if ("--local".equals(opt)) { + localOnly = true; } else if ("-h".equals(opt)) { pw.println("meminfo dump options: [-a] [-d] [-c] [--oom] [process]"); pw.println(" -a: include all available information for each process."); pw.println(" -d: include dalvik details when dumping process details."); pw.println(" -c: dump in a compact machine-parseable representation."); pw.println(" --oom: only show processes organized by oom adj."); + pw.println(" --local: only collect details locally, don't call process."); pw.println("If [process] is specified it can be the name or "); pw.println("pid of a specific process to dump."); return; @@ -11857,7 +12027,7 @@ public final class ActivityManagerService extends ActivityManagerNative thread = r.thread; pid = r.pid; oomAdj = r.getSetAdjWithServices(); - hasActivities = r.hasActivities; + hasActivities = r.activities.size() > 0; } if (thread != null) { if (!isCheckinRequest && dumpDetails) { @@ -11873,14 +12043,22 @@ public final class ActivityManagerService extends ActivityManagerNative mi.dalvikPrivateDirty = (int)tmpLong[0]; } if (dumpDetails) { - try { - pw.flush(); - thread.dumpMemInfo(fd, mi, isCheckinRequest, dumpFullDetails, - dumpDalvik, innerArgs); - } catch (RemoteException e) { - if (!isCheckinRequest) { - pw.println("Got RemoteException!"); + if (localOnly) { + ActivityThread.dumpMemInfoTable(pw, mi, isCheckinRequest, dumpFullDetails, + dumpDalvik, pid, r.processName, 0, 0, 0, 0, 0, 0); + if (isCheckinRequest) { + pw.println(); + } + } else { + try { pw.flush(); + thread.dumpMemInfo(fd, mi, isCheckinRequest, dumpFullDetails, + dumpDalvik, innerArgs); + } catch (RemoteException e) { + if (!isCheckinRequest) { + pw.println("Got RemoteException!"); + pw.flush(); + } } } } @@ -14079,7 +14257,6 @@ public final class ActivityManagerService extends ActivityManagerNative app.adjTarget = null; app.empty = false; app.cached = false; - app.hasClientActivities = false; final int activitiesSize = app.activities.size(); @@ -14089,7 +14266,6 @@ public final class ActivityManagerService extends ActivityManagerNative app.adjType = "fixed"; app.adjSeq = mAdjSeq; app.curRawAdj = app.maxAdj; - app.hasActivities = false; app.foregroundActivities = false; app.keeping = true; app.curSchedGroup = Process.THREAD_GROUP_DEFAULT; @@ -14101,16 +14277,12 @@ public final class ActivityManagerService extends ActivityManagerNative app.systemNoUi = true; if (app == TOP_APP) { app.systemNoUi = false; - app.hasActivities = true; } else if (activitiesSize > 0) { for (int j = 0; j < activitiesSize; j++) { final ActivityRecord r = app.activities.get(j); if (r.visible) { app.systemNoUi = false; } - if (r.app == app) { - app.hasActivities = true; - } } } if (!app.systemNoUi) { @@ -14121,7 +14293,6 @@ public final class ActivityManagerService extends ActivityManagerNative app.keeping = false; app.systemNoUi = false; - app.hasActivities = false; // Determine the importance of the process, starting with most // important to least, and assign an appropriate OOM adjustment. @@ -14138,7 +14309,6 @@ public final class ActivityManagerService extends ActivityManagerNative app.adjType = "top-activity"; foregroundActivities = true; interesting = true; - app.hasActivities = true; procState = ActivityManager.PROCESS_STATE_TOP; } else if (app.instrumentationClass != null) { // Don't want to kill running instrumentation. @@ -14187,7 +14357,6 @@ public final class ActivityManagerService extends ActivityManagerNative + app + "?!?"); continue; } - app.hasActivities = true; if (r.visible) { // App has a visible activity; only upgrade adjustment. if (adj > ProcessList.VISIBLE_APP_ADJ) { @@ -14436,27 +14605,6 @@ public final class ActivityManagerService extends ActivityManagerNative clientAdj = adj; } } - } else if ((cr.flags&Context.BIND_AUTO_CREATE) != 0) { - if ((cr.flags&Context.BIND_NOT_VISIBLE) == 0) { - // If this connection is keeping the service - // created, then we want to try to better follow - // its memory management semantics for activities. - // That is, if it is sitting in the background - // LRU list as a cached process (with activities), - // we don't want the service it is connected to - // to go into the empty LRU and quickly get killed, - // because all we'll do is just end up restarting - // the service. - if (client.hasActivities) { - if (procState > - ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT) { - procState = - ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT; - app.adjType = "cch-client-act"; - } - app.hasClientActivities = true; - } - } } if (adj > clientAdj) { // If this process has recently shown UI, and @@ -14674,6 +14822,12 @@ public final class ActivityManagerService extends ActivityManagerNative } } + if (procState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY && app.hasClientActivities) { + // This is a cached process, but with client activities. Mark it so. + procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT; + app.adjType = "cch-client-act"; + } + if (adj == ProcessList.SERVICE_ADJ) { if (doingAll) { app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3); @@ -15302,7 +15456,6 @@ public final class ActivityManagerService extends ActivityManagerNative // application processes based on their current state. int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ; int nextCachedAdj = curCachedAdj+1; - int curClientCachedAdj = curCachedAdj+1; int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ; int nextEmptyAdj = curEmptyAdj+2; for (int i=N-1; i>=0; i--) { @@ -15317,11 +15470,15 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.curAdj >= ProcessList.UNKNOWN_ADJ) { switch (app.curProcState) { case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: + case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: // This process is a cached process holding activities... // assign it the next cached value for that type, and then // step that cached level. app.curRawAdj = curCachedAdj; app.curAdj = app.modifyRawOomAdj(curCachedAdj); + if (DEBUG_LRU && false) Slog.d(TAG, "Assigning activity LRU #" + i + + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj + + ")"); if (curCachedAdj != nextCachedAdj) { stepCached++; if (stepCached >= cachedFactor) { @@ -15331,25 +15488,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) { nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ; } - if (curClientCachedAdj <= curCachedAdj) { - curClientCachedAdj = curCachedAdj + 1; - if (curClientCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) { - curClientCachedAdj = ProcessList.CACHED_APP_MAX_ADJ; - } - } } } break; - case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: - // Special case for cached client processes... just step - // down from after regular cached processes. - app.curRawAdj = curClientCachedAdj; - app.curAdj = app.modifyRawOomAdj(curClientCachedAdj); - curClientCachedAdj++; - if (curClientCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) { - curClientCachedAdj = ProcessList.CACHED_APP_MAX_ADJ; - } - break; default: // For everything else, assign next empty cached process // level and bump that up. Note that this means that @@ -15358,6 +15499,9 @@ public final class ActivityManagerService extends ActivityManagerNative // state is still as a service), which is what we want. app.curRawAdj = curEmptyAdj; app.curAdj = app.modifyRawOomAdj(curEmptyAdj); + if (DEBUG_LRU && false) Slog.d(TAG, "Assigning empty LRU #" + i + + " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj + + ")"); if (curEmptyAdj != nextEmptyAdj) { stepEmpty++; if (stepEmpty >= emptyFactor) { diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 44ff3bc..0e15261 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -36,7 +36,6 @@ import static com.android.server.am.ActivityStackSupervisor.DEBUG_SAVED_STATE; import static com.android.server.am.ActivityStackSupervisor.DEBUG_STATES; import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; -import android.os.Trace; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.util.Objects; import com.android.server.Watchdog; @@ -61,16 +60,17 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; import android.net.Uri; import android.os.Binder; import android.os.Bundle; +import android.os.Debug; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; +import android.os.Trace; import android.os.UserHandle; import android.util.EventLog; import android.util.Slog; @@ -566,7 +566,7 @@ final class ActivityStack { // Move userId's tasks to the top. int index = mTaskHistory.size(); - for (int i = 0; i < index; ++i) { + for (int i = 0; i < index; ) { TaskRecord task = mTaskHistory.get(i); if (task.userId == userId) { if (DEBUG_TASKS) Slog.d(TAG, "switchUserLocked: stack=" + getStackId() + @@ -574,6 +574,9 @@ final class ActivityStack { mTaskHistory.remove(i); mTaskHistory.add(task); --index; + // Use same value for i. + } else { + ++i; } } if (VALIDATE_TOKENS) { @@ -997,8 +1000,8 @@ final class ActivityStack { if (r.isHomeActivity()) { return true; } - if (!r.finishing && r.visible && r.fullscreen) { - // Passed activity is over a visible fullscreen activity. + if (!r.finishing && r.fullscreen) { + // Passed activity is over a fullscreen activity. return false; } } @@ -1141,7 +1144,7 @@ final class ActivityStack { } else if (isActivityOverHome(r)) { if (DEBUG_VISBILITY) Slog.v(TAG, "Showing home: at " + r); showHomeBehindStack = true; - behindFullscreen = true; + behindFullscreen = !isHomeStack(); } } else { if (DEBUG_VISBILITY) Slog.v( @@ -1398,7 +1401,7 @@ final class ActivityStack { if (next.app != null && next.app.thread != null) { // No reason to do full oom adj update here; we'll let that // happen whenever it needs to later. - mService.updateLruProcessLocked(next.app, false, true); + mService.updateLruProcessLocked(next.app, true, null); } if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); return true; @@ -1526,8 +1529,9 @@ final class ActivityStack { mResumedActivity = next; next.task.touchActiveTime(); mService.addRecentTaskLocked(next.task); - mService.updateLruProcessLocked(next.app, true, true); + mService.updateLruProcessLocked(next.app, true, null); updateLRUListLocked(next); + mService.updateOomAdjLocked(); // Have the window manager re-evaluate the orientation of // the screen based on the new activity order. @@ -1713,7 +1717,7 @@ final class ActivityStack { mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, - r.userId); + r.userId, r.info.configChanges); if (VALIDATE_TOKENS) { validateAppTokensLocked(); } @@ -1774,7 +1778,8 @@ final class ActivityStack { r.updateOptionsLocked(options); mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, - (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId); + (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId, + r.info.configChanges); boolean doShow = true; if (newTask) { // Even though this activity is starting fresh, we still need @@ -1817,7 +1822,8 @@ final class ActivityStack { // because there is nothing for it to animate on top of. mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, - (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId); + (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId, + r.info.configChanges); ActivityOptions.abort(options); } if (VALIDATE_TOKENS) { @@ -1907,26 +1913,38 @@ final class ActivityStack { // bottom of the activity stack. This also keeps it // correctly ordered with any activities we previously // moved. + final ThumbnailHolder newThumbHolder; + final TaskRecord targetTask; final ActivityRecord bottom = !mTaskHistory.isEmpty() && !mTaskHistory.get(0).mActivities.isEmpty() ? - mTaskHistory.get(0).mActivities.get(0) : null; + mTaskHistory.get(0).mActivities.get(0) : null; if (bottom != null && target.taskAffinity != null && target.taskAffinity.equals(bottom.task.affinity)) { // If the activity currently at the bottom has the // same task affinity as the one we are moving, // then merge it into the same task. - target.setTask(bottom.task, bottom.thumbHolder, false); + targetTask = bottom.task; + newThumbHolder = bottom.thumbHolder == null ? targetTask : bottom.thumbHolder; if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + " out to bottom task " + bottom.task); } else { - target.setTask(createTaskRecord(mStackSupervisor.getNextTaskId(), target.info, - null, false), null, false); - target.task.affinityIntent = target.intent; + targetTask = createTaskRecord(mStackSupervisor.getNextTaskId(), target.info, + null, false); + newThumbHolder = targetTask; + targetTask.affinityIntent = target.intent; if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + " out to new task " + target.task); } - final TaskRecord targetTask = target.task; + if (clearWhenTaskReset) { + // This is the start of a new sub-task. + if (target.thumbHolder == null) { + target.thumbHolder = new ThumbnailHolder(); + } + } else { + target.thumbHolder = newThumbHolder; + } + final int targetTaskId = targetTask.taskId; mWindowManager.setAppGroupId(target.appToken, targetTaskId); @@ -1947,8 +1965,8 @@ final class ActivityStack { } } if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing activity " + p + " from task=" - + task + " adding to task=" + targetTask, - new RuntimeException("here").fillInStackTrace()); + + task + " adding to task=" + targetTask + + " Callers=" + Debug.getCallers(4)); if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p + " out to target's task " + target.task); p.setTask(targetTask, curThumbHolder, false); @@ -2781,7 +2799,7 @@ final class ActivityStack { } if (r.app.activities.isEmpty()) { // No longer have activities, so update LRU list and oom adj. - mService.updateLruProcessLocked(r.app, false, false); + mService.updateLruProcessLocked(r.app, false, null); mService.updateOomAdjLocked(); } } @@ -3142,7 +3160,9 @@ final class ActivityStack { final TaskRecord task = mResumedActivity != null ? mResumedActivity.task : null; if (task == tr && task.mOnTopOfHome || numTasks <= 1) { - task.mOnTopOfHome = false; + if (task != null) { + task.mOnTopOfHome = false; + } return mStackSupervisor.resumeHomeActivity(null); } diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java index 7650a65..483b4a0 100644 --- a/services/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/java/com/android/server/am/ActivityStackSupervisor.java @@ -905,7 +905,8 @@ public final class ActivityStackSupervisor { if (idx < 0) { app.activities.add(r); } - mService.updateLruProcessLocked(app, true, true); + mService.updateLruProcessLocked(app, true, null); + mService.updateOomAdjLocked(); final ActivityStack stack = r.task.stack; try { @@ -1385,17 +1386,22 @@ public final class ActivityStackSupervisor { launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; } + ActivityInfo newTaskInfo = null; + Intent newTaskIntent = null; final ActivityStack sourceStack; if (sourceRecord != null) { if (sourceRecord.finishing) { // If the source is finishing, we can't further count it as our source. This // is because the task it is associated with may now be empty and on its way out, // so we don't want to blindly throw it in to that task. Instead we will take - // the NEW_TASK flow and try to find a task for it. + // the NEW_TASK flow and try to find a task for it. But save the task information + // so it can be used when creating the new task. if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { Slog.w(TAG, "startActivity called from finishing " + sourceRecord + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + newTaskInfo = sourceRecord.info; + newTaskIntent = sourceRecord.task.intent; } sourceRecord = null; sourceStack = null; @@ -1667,8 +1673,10 @@ public final class ActivityStackSupervisor { targetStack = adjustStackFocus(r); moveHomeStack(targetStack.isHomeStack()); if (reuseTask == null) { - r.setTask(targetStack.createTaskRecord(getNextTaskId(), r.info, intent, true), - null, true); + r.setTask(targetStack.createTaskRecord(getNextTaskId(), + newTaskInfo != null ? newTaskInfo : r.info, + newTaskIntent != null ? newTaskIntent : intent, + true), null, true); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " + r.task); } else { diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index 0dd950e..2d59678 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -419,6 +419,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub { } } + public void noteWifiBatchedScanStartedFromSource(WorkSource ws, int csph) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteWifiBatchedScanStartedFromSourceLocked(ws, csph); + } + } + + public void noteWifiBatchedScanStoppedFromSource(WorkSource ws) { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteWifiBatchedScanStoppedFromSourceLocked(ws); + } + } + public void noteWifiMulticastEnabledFromSource(WorkSource ws) { enforceCallingPermission(); synchronized (mStats) { diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java index 5e80135..bfb667f 100644 --- a/services/java/com/android/server/am/BroadcastQueue.java +++ b/services/java/com/android/server/am/BroadcastQueue.java @@ -27,6 +27,7 @@ import android.content.ComponentName; import android.content.IIntentReceiver; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; @@ -220,7 +221,8 @@ public final class BroadcastQueue { r.curApp = app; app.curReceiver = r; app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER); - mService.updateLruProcessLocked(app, true, false); + mService.updateLruProcessLocked(app, false, null); + mService.updateOomAdjLocked(); // Tell the application to launch this receiver. r.intent.setComponent(r.curComponent); @@ -813,6 +815,26 @@ public final class BroadcastQueue { + " to " + r.curApp + ": process crashing"); skip = true; } + if (!skip) { + boolean isAvailable = false; + try { + isAvailable = AppGlobals.getPackageManager().isPackageAvailable( + info.activityInfo.packageName, + UserHandle.getUserId(info.activityInfo.applicationInfo.uid)); + } catch (Exception e) { + // all such failures mean we skip this receiver + Slog.w(TAG, "Exception getting recipient info for " + + info.activityInfo.packageName, e); + } + if (!isAvailable) { + if (DEBUG_BROADCAST) { + Slog.v(TAG, "Skipping delivery to " + info.activityInfo.packageName + + " / " + info.activityInfo.applicationInfo.uid + + " : package no longer available"); + } + skip = true; + } + } if (skip) { if (DEBUG_BROADCAST) Slog.v(TAG, diff --git a/services/java/com/android/server/am/ConnectionRecord.java b/services/java/com/android/server/am/ConnectionRecord.java index 576adc2..423e540 100644 --- a/services/java/com/android/server/am/ConnectionRecord.java +++ b/services/java/com/android/server/am/ConnectionRecord.java @@ -27,7 +27,7 @@ import java.io.PrintWriter; */ final class ConnectionRecord { final AppBindRecord binding; // The application/service binding. - final ActivityRecord activity; // If non-null, the owning activity. + final ActivityRecord activity; // If non-null, the owning activity. final IServiceConnection conn; // The client connection. final int flags; // Binding options. final int clientLabel; // String resource labeling this client. diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java index 486e916..217a8d6 100644 --- a/services/java/com/android/server/am/ProcessRecord.java +++ b/services/java/com/android/server/am/ProcessRecord.java @@ -46,7 +46,7 @@ import java.util.ArrayList; * is currently running. */ final class ProcessRecord { - final BatteryStatsImpl.Uid.Proc batteryStats; // where to collect runtime statistics + private final BatteryStatsImpl mBatteryStats; // where to collect runtime statistics final ApplicationInfo info; // all about the first app in the process final boolean isolated; // true if this is a special isolated process final int uid; // uid of process; may be different from 'info' if isolated @@ -86,7 +86,6 @@ final class ProcessRecord { boolean keeping; // Actively running code so don't kill due to that? boolean setIsForeground; // Running foreground UI when last set? boolean notCachedSinceIdle; // Has this process not been in a cached state since last idle? - boolean hasActivities; // Are there any activities running in this process? boolean hasClientActivities; // Are there any client services with activities? boolean hasStartedServices; // Are there any started services running in this process? boolean foregroundServices; // Running any services that are foreground? @@ -265,9 +264,8 @@ final class ProcessRecord { pw.print(prefix); pw.print("persistent="); pw.print(persistent); pw.print(" removed="); pw.println(removed); } - if (hasActivities || hasClientActivities || foregroundActivities) { - pw.print(prefix); pw.print("hasActivities="); pw.print(hasActivities); - pw.print(" hasClientActivities="); pw.print(hasClientActivities); + if (hasClientActivities || foregroundActivities) { + pw.print(prefix); pw.print("hasClientActivities="); pw.print(hasClientActivities); pw.print(" foregroundActivities="); pw.println(foregroundActivities); } if (hasStartedServices) { @@ -275,8 +273,8 @@ final class ProcessRecord { } if (!keeping) { long wtime; - synchronized (batteryStats.getBatteryStats()) { - wtime = batteryStats.getBatteryStats().getProcessWakeTime(info.uid, + synchronized (mBatteryStats) { + wtime = mBatteryStats.getProcessWakeTime(info.uid, pid, SystemClock.elapsedRealtime()); } long timeUsed = wtime - lastWakeTime; @@ -361,9 +359,9 @@ final class ProcessRecord { } } - ProcessRecord(BatteryStatsImpl.Uid.Proc _batteryStats, ApplicationInfo _info, + ProcessRecord(BatteryStatsImpl _batteryStats, ApplicationInfo _info, String _processName, int _uid) { - batteryStats = _batteryStats; + mBatteryStats = _batteryStats; info = _info; isolated = _info.uid != _uid; uid = _uid; diff --git a/services/java/com/android/server/am/ProcessStatsService.java b/services/java/com/android/server/am/ProcessStatsService.java index 8d16880..e05fcda 100644 --- a/services/java/com/android/server/am/ProcessStatsService.java +++ b/services/java/com/android/server/am/ProcessStatsService.java @@ -750,23 +750,12 @@ public final class ProcessStatsService extends IProcessStats.Stub { return; } else { // Not an option, last argument must be a package name. - try { - IPackageManager pm = AppGlobals.getPackageManager(); - if (pm.getPackageUid(arg, UserHandle.getCallingUserId()) >= 0) { - reqPackage = arg; - // Include all details, since we know we are only going to - // be dumping a smaller set of data. In fact only the details - // container per-package data, so that are needed to be able - // to dump anything at all when filtering by package. - dumpDetails = true; - } - } catch (RemoteException e) { - } - if (reqPackage == null) { - pw.println("Unknown package: " + arg); - dumpHelp(pw); - return; - } + reqPackage = arg; + // Include all details, since we know we are only going to + // be dumping a smaller set of data. In fact only the details + // container per-package data, so that are needed to be able + // to dump anything at all when filtering by package. + dumpDetails = true; } } } @@ -816,13 +805,14 @@ public final class ProcessStatsService extends IProcessStats.Stub { } return; } else if (aggregateHours != 0) { + pw.print("AGGREGATED OVER LAST "); pw.print(aggregateHours); pw.println(" HOURS:"); dumpAggregatedStats(pw, aggregateHours, now, reqPackage, isCompact, dumpDetails, dumpFullDetails, dumpAll, activeOnly); return; } boolean sepNeeded = false; - if (!currentOnly || isCheckin) { + if (dumpAll || isCheckin) { mWriteLock.lock(); try { ArrayList<String> files = getCommittedFiles(0, false, !isCheckin); @@ -882,11 +872,11 @@ public final class ProcessStatsService extends IProcessStats.Stub { } } if (!isCheckin) { - if (dumpAll) { + if (!currentOnly) { if (sepNeeded) { pw.println(); - pw.println("AGGREGATED OVER LAST 24 HOURS:"); } + pw.println("AGGREGATED OVER LAST 24 HOURS:"); dumpAggregatedStats(pw, 24, now, reqPackage, isCompact, dumpDetails, dumpFullDetails, dumpAll, activeOnly); pw.println(); @@ -901,8 +891,8 @@ public final class ProcessStatsService extends IProcessStats.Stub { } else { if (sepNeeded) { pw.println(); - pw.println("CURRENT STATS:"); } + pw.println("CURRENT STATS:"); if (dumpDetails || dumpFullDetails) { mProcessStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll, activeOnly); diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index cc1172a..80e6e94 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -85,6 +85,7 @@ final class ServiceRecord extends Binder { ProcessRecord app; // where this service is running or null. ProcessRecord isolatedProc; // keep track of isolated process, if requested ProcessStats.ServiceState tracker; // tracking service execution, may be null + ProcessStats.ServiceState restartTracker; // tracking service restart boolean delayed; // are we waiting to start this service in the background? boolean isForeground; // is service currently in foreground mode? int foregroundId; // Notification ID of last foreground req. @@ -340,6 +341,19 @@ final class ServiceRecord extends Binder { } } + public void makeRestarting(int memFactor, long now) { + if (restartTracker == null) { + if ((serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) { + restartTracker = ams.mProcessStats.getServiceStateLocked(serviceInfo.packageName, + serviceInfo.applicationInfo.uid, serviceInfo.processName, serviceInfo.name); + } + if (restartTracker == null) { + return; + } + } + restartTracker.setRestarting(true, memFactor, now); + } + public AppBindRecord retrieveAppBindingLocked(Intent intent, ProcessRecord app) { Intent.FilterComparison filter = new Intent.FilterComparison(intent); diff --git a/services/java/com/android/server/content/ContentService.java b/services/java/com/android/server/content/ContentService.java index cb35ef1..023bf2b 100644 --- a/services/java/com/android/server/content/ContentService.java +++ b/services/java/com/android/server/content/ContentService.java @@ -660,7 +660,7 @@ public final class ContentService extends IContentService.Stub { int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { - return getSyncManager().getSyncStorageEngine().getCurrentSyncs(userId); + return getSyncManager().getSyncStorageEngine().getCurrentSyncsCopy(userId); } finally { restoreCallingIdentity(identityToken); } diff --git a/services/java/com/android/server/content/SyncStorageEngine.java b/services/java/com/android/server/content/SyncStorageEngine.java index 41ef229..5ebf9ea 100644 --- a/services/java/com/android/server/content/SyncStorageEngine.java +++ b/services/java/com/android/server/content/SyncStorageEngine.java @@ -1295,20 +1295,40 @@ public class SyncStorageEngine extends Handler { } /** - * Return a list of the currently active syncs. Note that the returned items are the - * real, live active sync objects, so be careful what you do with it. + * Return a list of the currently active syncs. Note that the returned + * items are the real, live active sync objects, so be careful what you do + * with it. */ - public List<SyncInfo> getCurrentSyncs(int userId) { + private List<SyncInfo> getCurrentSyncs(int userId) { synchronized (mAuthorities) { - ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId); - if (syncs == null) { - syncs = new ArrayList<SyncInfo>(); - mCurrentSyncs.put(userId, syncs); + return getCurrentSyncsLocked(userId); + } + } + + /** + * @return a copy of the current syncs data structure. Will not return + * null. + */ + public List<SyncInfo> getCurrentSyncsCopy(int userId) { + synchronized (mAuthorities) { + final List<SyncInfo> syncs = getCurrentSyncsLocked(userId); + final List<SyncInfo> syncsCopy = new ArrayList<SyncInfo>(); + for (SyncInfo sync : syncs) { + syncsCopy.add(new SyncInfo(sync)); } - return syncs; + return syncsCopy; } } + private List<SyncInfo> getCurrentSyncsLocked(int userId) { + ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId); + if (syncs == null) { + syncs = new ArrayList<SyncInfo>(); + mCurrentSyncs.put(userId, syncs); + } + return syncs; + } + /** * Return an array of the current sync status for all authorities. Note * that the objects inside the array are the real, live status objects, diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java index 249c8b0..02f26b3 100644 --- a/services/java/com/android/server/display/DisplayManagerService.java +++ b/services/java/com/android/server/display/DisplayManagerService.java @@ -466,6 +466,9 @@ public final class DisplayManagerService extends IDisplayManager.Stub { @Override // Binder call public void scanWifiDisplays() { + mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY, + "Permission required to scan wifi displays"); + final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { @@ -483,13 +486,14 @@ public final class DisplayManagerService extends IDisplayManager.Stub { if (address == null) { throw new IllegalArgumentException("address must not be null"); } + mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY, + "Permission required to connect to a wifi display"); - final boolean trusted = canCallerConfigureWifiDisplay(); final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { if (mWifiDisplayAdapter != null) { - mWifiDisplayAdapter.requestConnectLocked(address, trusted); + mWifiDisplayAdapter.requestConnectLocked(address); } } } finally { @@ -499,12 +503,8 @@ public final class DisplayManagerService extends IDisplayManager.Stub { @Override public void pauseWifiDisplay() { - if (mContext.checkCallingPermission( - android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY" - + "permission to pause a wifi display session."); - } + mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY, + "Permission required to pause a wifi display session"); final long token = Binder.clearCallingIdentity(); try { @@ -520,12 +520,8 @@ public final class DisplayManagerService extends IDisplayManager.Stub { @Override public void resumeWifiDisplay() { - if (mContext.checkCallingPermission( - android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY" - + "permission to resume a wifi display session."); - } + mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY, + "Permission required to resume a wifi display session"); final long token = Binder.clearCallingIdentity(); try { @@ -541,6 +537,11 @@ public final class DisplayManagerService extends IDisplayManager.Stub { @Override // Binder call public void disconnectWifiDisplay() { + // This request does not require special permissions. + // Any app can request disconnection from the currently active wifi display. + // This exception should no longer be needed once wifi display control moves + // to the media router service. + final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { @@ -558,10 +559,8 @@ public final class DisplayManagerService extends IDisplayManager.Stub { if (address == null) { throw new IllegalArgumentException("address must not be null"); } - if (!canCallerConfigureWifiDisplay()) { - throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission to " - + "rename a wifi display."); - } + mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY, + "Permission required to rename to a wifi display"); final long token = Binder.clearCallingIdentity(); try { @@ -580,10 +579,8 @@ public final class DisplayManagerService extends IDisplayManager.Stub { if (address == null) { throw new IllegalArgumentException("address must not be null"); } - if (!canCallerConfigureWifiDisplay()) { - throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY permission to " - + "forget a wifi display."); - } + mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY, + "Permission required to forget to a wifi display"); final long token = Binder.clearCallingIdentity(); try { @@ -599,6 +596,9 @@ public final class DisplayManagerService extends IDisplayManager.Stub { @Override // Binder call public WifiDisplayStatus getWifiDisplayStatus() { + // This request does not require special permissions. + // Any app can get information about available wifi displays. + final long token = Binder.clearCallingIdentity(); try { synchronized (mSyncRoot) { @@ -612,11 +612,6 @@ public final class DisplayManagerService extends IDisplayManager.Stub { } } - private boolean canCallerConfigureWifiDisplay() { - return mContext.checkCallingPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) - == PackageManager.PERMISSION_GRANTED; - } - @Override // Binder call public int createVirtualDisplay(IBinder appToken, String packageName, String name, int width, int height, int densityDpi, Surface surface, int flags) { diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java index f7bbdf8..fdef039 100644 --- a/services/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/java/com/android/server/display/WifiDisplayAdapter.java @@ -172,19 +172,9 @@ final class WifiDisplayAdapter extends DisplayAdapter { }); } - public void requestConnectLocked(final String address, final boolean trusted) { + public void requestConnectLocked(final String address) { if (DEBUG) { - Slog.d(TAG, "requestConnectLocked: address=" + address + ", trusted=" + trusted); - } - - if (!trusted) { - synchronized (getSyncRoot()) { - if (!isRememberedDisplayLocked(address)) { - Slog.w(TAG, "Ignoring request by an untrusted client to connect to " - + "an unknown wifi display: " + address); - return; - } - } + Slog.d(TAG, "requestConnectLocked: address=" + address); } getHandler().post(new Runnable() { @@ -400,8 +390,6 @@ final class WifiDisplayAdapter extends DisplayAdapter { mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height, refreshRate, deviceFlags, address, surface); sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED); - - scheduleUpdateNotificationLocked(); } private void removeDisplayDeviceLocked() { @@ -409,8 +397,6 @@ final class WifiDisplayAdapter extends DisplayAdapter { mDisplayDevice.destroyLocked(); sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED); mDisplayDevice = null; - - scheduleUpdateNotificationLocked(); } } @@ -457,21 +443,24 @@ final class WifiDisplayAdapter extends DisplayAdapter { // Runs on the handler. private void handleUpdateNotification() { - final boolean isConnected; + final int state; + final WifiDisplay display; synchronized (getSyncRoot()) { if (!mPendingNotificationUpdate) { return; } mPendingNotificationUpdate = false; - isConnected = (mDisplayDevice != null); + state = mActiveDisplayState; + display = mActiveDisplay; } // Cancel the old notification if there is one. mNotificationManager.cancelAsUser(null, - R.string.wifi_display_notification_title, UserHandle.ALL); + R.string.wifi_display_notification_disconnect, UserHandle.ALL); - if (isConnected) { + if (state == WifiDisplayStatus.DISPLAY_STATE_CONNECTING + || state == WifiDisplayStatus.DISPLAY_STATE_CONNECTED) { Context context = getContext(); // Initialize pending intents for the notification outside of the lock because @@ -493,20 +482,38 @@ final class WifiDisplayAdapter extends DisplayAdapter { // Post the notification. Resources r = context.getResources(); - Notification notification = new Notification.Builder(context) - .setContentTitle(r.getString( - R.string.wifi_display_notification_title)) - .setContentText(r.getString( - R.string.wifi_display_notification_message)) - .setContentIntent(mSettingsPendingIntent) - .setSmallIcon(R.drawable.ic_notify_wifidisplay) - .setOngoing(true) - .addAction(android.R.drawable.ic_menu_close_clear_cancel, - r.getString(R.string.wifi_display_notification_disconnect), - mDisconnectPendingIntent) - .build(); + Notification notification; + if (state == WifiDisplayStatus.DISPLAY_STATE_CONNECTING) { + notification = new Notification.Builder(context) + .setContentTitle(r.getString( + R.string.wifi_display_notification_connecting_title)) + .setContentText(r.getString( + R.string.wifi_display_notification_connecting_message, + display.getFriendlyDisplayName())) + .setContentIntent(mSettingsPendingIntent) + .setSmallIcon(R.drawable.ic_notification_cast_connecting) + .setOngoing(true) + .addAction(android.R.drawable.ic_menu_close_clear_cancel, + r.getString(R.string.wifi_display_notification_disconnect), + mDisconnectPendingIntent) + .build(); + } else { + notification = new Notification.Builder(context) + .setContentTitle(r.getString( + R.string.wifi_display_notification_connected_title)) + .setContentText(r.getString( + R.string.wifi_display_notification_connected_message, + display.getFriendlyDisplayName())) + .setContentIntent(mSettingsPendingIntent) + .setSmallIcon(R.drawable.ic_notification_cast_on) + .setOngoing(true) + .addAction(android.R.drawable.ic_menu_close_clear_cancel, + r.getString(R.string.wifi_display_notification_disconnect), + mDisconnectPendingIntent) + .build(); + } mNotificationManager.notifyAsUser(null, - R.string.wifi_display_notification_title, + R.string.wifi_display_notification_disconnect, notification, UserHandle.ALL); } } @@ -578,6 +585,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTING; mActiveDisplay = display; scheduleStatusChangedBroadcastLocked(); + scheduleUpdateNotificationLocked(); } } } @@ -590,6 +598,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED; mActiveDisplay = null; scheduleStatusChangedBroadcastLocked(); + scheduleUpdateNotificationLocked(); } } } @@ -607,6 +616,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTED; mActiveDisplay = display; scheduleStatusChangedBroadcastLocked(); + scheduleUpdateNotificationLocked(); } } } @@ -629,6 +639,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mActiveDisplay = display; renameDisplayDeviceLocked(display.getFriendlyDisplayName()); scheduleStatusChangedBroadcastLocked(); + scheduleUpdateNotificationLocked(); } } } @@ -644,6 +655,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED; mActiveDisplay = null; scheduleStatusChangedBroadcastLocked(); + scheduleUpdateNotificationLocked(); } } } diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java index 9a4cfb7..b2939fe 100644 --- a/services/java/com/android/server/display/WifiDisplayController.java +++ b/services/java/com/android/server/display/WifiDisplayController.java @@ -76,7 +76,7 @@ final class WifiDisplayController implements DumpUtils.Dump { private static final int DEFAULT_CONTROL_PORT = 7236; private static final int MAX_THROUGHPUT = 50; private static final int CONNECTION_TIMEOUT_SECONDS = 60; - private static final int RTSP_TIMEOUT_SECONDS = 15; + private static final int RTSP_TIMEOUT_SECONDS = 30; private static final int RTSP_TIMEOUT_SECONDS_CERT_MODE = 120; private static final int DISCOVER_PEERS_MAX_RETRIES = 10; diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java index d749e6c..3145805 100644 --- a/services/java/com/android/server/input/InputManagerService.java +++ b/services/java/com/android/server/input/InputManagerService.java @@ -294,6 +294,7 @@ public class InputManagerService extends IInputManager.Stub IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addAction(Intent.ACTION_PACKAGE_REPLACED); filter.addDataScheme("package"); mContext.registerReceiver(new BroadcastReceiver() { @Override diff --git a/services/java/com/android/server/media/MediaRouterService.java b/services/java/com/android/server/media/MediaRouterService.java new file mode 100644 index 0000000..a31695b --- /dev/null +++ b/services/java/com/android/server/media/MediaRouterService.java @@ -0,0 +1,1423 @@ +/* + * Copyright (C) 2013 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.media; + +import com.android.internal.util.Objects; +import com.android.server.Watchdog; + +import android.Manifest; +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.media.AudioSystem; +import android.media.IMediaRouterClient; +import android.media.IMediaRouterService; +import android.media.MediaRouter; +import android.media.MediaRouterClientState; +import android.media.RemoteDisplayState; +import android.media.RemoteDisplayState.RemoteDisplayInfo; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import android.util.TimeUtils; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Provides a mechanism for discovering media routes and manages media playback + * behalf of applications. + * <p> + * Currently supports discovering remote displays via remote display provider + * services that have been registered by applications. + * </p> + */ +public final class MediaRouterService extends IMediaRouterService.Stub + implements Watchdog.Monitor { + private static final String TAG = "MediaRouterService"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + /** + * Timeout in milliseconds for a selected route to transition from a + * disconnected state to a connecting state. If we don't observe any + * progress within this interval, then we will give up and unselect the route. + */ + static final long CONNECTING_TIMEOUT = 5000; + + /** + * Timeout in milliseconds for a selected route to transition from a + * connecting state to a connected state. If we don't observe any + * progress within this interval, then we will give up and unselect the route. + */ + static final long CONNECTED_TIMEOUT = 60000; + + private final Context mContext; + + // State guarded by mLock. + private final Object mLock = new Object(); + private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>(); + private final ArrayMap<IBinder, ClientRecord> mAllClientRecords = + new ArrayMap<IBinder, ClientRecord>(); + private int mCurrentUserId = -1; + + public MediaRouterService(Context context) { + mContext = context; + Watchdog.getInstance().addMonitor(this); + } + + public void systemRunning() { + IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); + mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) { + switchUser(); + } + } + }, filter); + + switchUser(); + } + + @Override + public void monitor() { + synchronized (mLock) { /* check for deadlock */ } + } + + // Binder call + @Override + public void registerClientAsUser(IMediaRouterClient client, String packageName, int userId) { + if (client == null) { + throw new IllegalArgumentException("client must not be null"); + } + + final int uid = Binder.getCallingUid(); + if (!validatePackageName(uid, packageName)) { + throw new SecurityException("packageName must match the calling uid"); + } + + final int pid = Binder.getCallingPid(); + final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId, + false /*allowAll*/, true /*requireFull*/, "registerClientAsUser", packageName); + final boolean trusted = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) == + PackageManager.PERMISSION_GRANTED; + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + registerClientLocked(client, pid, packageName, resolvedUserId, trusted); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Binder call + @Override + public void unregisterClient(IMediaRouterClient client) { + if (client == null) { + throw new IllegalArgumentException("client must not be null"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + unregisterClientLocked(client, false); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Binder call + @Override + public MediaRouterClientState getState(IMediaRouterClient client) { + if (client == null) { + throw new IllegalArgumentException("client must not be null"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + return getStateLocked(client); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Binder call + @Override + public void setDiscoveryRequest(IMediaRouterClient client, + int routeTypes, boolean activeScan) { + if (client == null) { + throw new IllegalArgumentException("client must not be null"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + setDiscoveryRequestLocked(client, routeTypes, activeScan); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Binder call + // A null routeId means that the client wants to unselect its current route. + // The explicit flag indicates whether the change was explicitly requested by the + // user or the application which may cause changes to propagate out to the rest + // of the system. Should be false when the change is in response to a new globally + // selected route or a default selection. + @Override + public void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit) { + if (client == null) { + throw new IllegalArgumentException("client must not be null"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + setSelectedRouteLocked(client, routeId, explicit); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Binder call + @Override + public void requestSetVolume(IMediaRouterClient client, String routeId, int volume) { + if (client == null) { + throw new IllegalArgumentException("client must not be null"); + } + if (routeId == null) { + throw new IllegalArgumentException("routeId must not be null"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + requestSetVolumeLocked(client, routeId, volume); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Binder call + @Override + public void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction) { + if (client == null) { + throw new IllegalArgumentException("client must not be null"); + } + if (routeId == null) { + throw new IllegalArgumentException("routeId must not be null"); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + requestUpdateVolumeLocked(client, routeId, direction); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + // Binder call + @Override + public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump MediaRouterService from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + pw.println("MEDIA ROUTER SERVICE (dumpsys media_router)"); + pw.println(); + pw.println("Global state"); + pw.println(" mCurrentUserId=" + mCurrentUserId); + + synchronized (mLock) { + final int count = mUserRecords.size(); + for (int i = 0; i < count; i++) { + UserRecord userRecord = mUserRecords.valueAt(i); + pw.println(); + userRecord.dump(pw, ""); + } + } + } + + void switchUser() { + synchronized (mLock) { + int userId = ActivityManager.getCurrentUser(); + if (mCurrentUserId != userId) { + final int oldUserId = mCurrentUserId; + mCurrentUserId = userId; // do this first + + UserRecord oldUser = mUserRecords.get(oldUserId); + if (oldUser != null) { + oldUser.mHandler.sendEmptyMessage(UserHandler.MSG_STOP); + disposeUserIfNeededLocked(oldUser); // since no longer current user + } + + UserRecord newUser = mUserRecords.get(userId); + if (newUser != null) { + newUser.mHandler.sendEmptyMessage(UserHandler.MSG_START); + } + } + } + } + + void clientDied(ClientRecord clientRecord) { + synchronized (mLock) { + unregisterClientLocked(clientRecord.mClient, true); + } + } + + private void registerClientLocked(IMediaRouterClient client, + int pid, String packageName, int userId, boolean trusted) { + final IBinder binder = client.asBinder(); + ClientRecord clientRecord = mAllClientRecords.get(binder); + if (clientRecord == null) { + boolean newUser = false; + UserRecord userRecord = mUserRecords.get(userId); + if (userRecord == null) { + userRecord = new UserRecord(userId); + newUser = true; + } + clientRecord = new ClientRecord(userRecord, client, pid, packageName, trusted); + try { + binder.linkToDeath(clientRecord, 0); + } catch (RemoteException ex) { + throw new RuntimeException("Media router client died prematurely.", ex); + } + + if (newUser) { + mUserRecords.put(userId, userRecord); + initializeUserLocked(userRecord); + } + + userRecord.mClientRecords.add(clientRecord); + mAllClientRecords.put(binder, clientRecord); + initializeClientLocked(clientRecord); + } + } + + private void unregisterClientLocked(IMediaRouterClient client, boolean died) { + ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder()); + if (clientRecord != null) { + UserRecord userRecord = clientRecord.mUserRecord; + userRecord.mClientRecords.remove(clientRecord); + disposeClientLocked(clientRecord, died); + disposeUserIfNeededLocked(userRecord); // since client removed from user + } + } + + private MediaRouterClientState getStateLocked(IMediaRouterClient client) { + ClientRecord clientRecord = mAllClientRecords.get(client.asBinder()); + if (clientRecord != null) { + return clientRecord.getState(); + } + return null; + } + + private void setDiscoveryRequestLocked(IMediaRouterClient client, + int routeTypes, boolean activeScan) { + final IBinder binder = client.asBinder(); + ClientRecord clientRecord = mAllClientRecords.get(binder); + if (clientRecord != null) { + // Only let the system discover remote display routes for now. + if (!clientRecord.mTrusted) { + routeTypes &= ~MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; + } + + if (clientRecord.mRouteTypes != routeTypes + || clientRecord.mActiveScan != activeScan) { + if (DEBUG) { + Slog.d(TAG, clientRecord + ": Set discovery request, routeTypes=0x" + + Integer.toHexString(routeTypes) + ", activeScan=" + activeScan); + } + clientRecord.mRouteTypes = routeTypes; + clientRecord.mActiveScan = activeScan; + clientRecord.mUserRecord.mHandler.sendEmptyMessage( + UserHandler.MSG_UPDATE_DISCOVERY_REQUEST); + } + } + } + + private void setSelectedRouteLocked(IMediaRouterClient client, + String routeId, boolean explicit) { + ClientRecord clientRecord = mAllClientRecords.get(client.asBinder()); + if (clientRecord != null) { + final String oldRouteId = clientRecord.mSelectedRouteId; + if (!Objects.equal(routeId, oldRouteId)) { + if (DEBUG) { + Slog.d(TAG, clientRecord + ": Set selected route, routeId=" + routeId + + ", oldRouteId=" + oldRouteId + + ", explicit=" + explicit); + } + + clientRecord.mSelectedRouteId = routeId; + if (explicit) { + // Any app can disconnect from the globally selected route. + if (oldRouteId != null) { + clientRecord.mUserRecord.mHandler.obtainMessage( + UserHandler.MSG_UNSELECT_ROUTE, oldRouteId).sendToTarget(); + } + // Only let the system connect to new global routes for now. + // A similar check exists in the display manager for wifi display. + if (routeId != null && clientRecord.mTrusted) { + clientRecord.mUserRecord.mHandler.obtainMessage( + UserHandler.MSG_SELECT_ROUTE, routeId).sendToTarget(); + } + } + } + } + } + + private void requestSetVolumeLocked(IMediaRouterClient client, + String routeId, int volume) { + final IBinder binder = client.asBinder(); + ClientRecord clientRecord = mAllClientRecords.get(binder); + if (clientRecord != null) { + clientRecord.mUserRecord.mHandler.obtainMessage( + UserHandler.MSG_REQUEST_SET_VOLUME, volume, 0, routeId).sendToTarget(); + } + } + + private void requestUpdateVolumeLocked(IMediaRouterClient client, + String routeId, int direction) { + final IBinder binder = client.asBinder(); + ClientRecord clientRecord = mAllClientRecords.get(binder); + if (clientRecord != null) { + clientRecord.mUserRecord.mHandler.obtainMessage( + UserHandler.MSG_REQUEST_UPDATE_VOLUME, direction, 0, routeId).sendToTarget(); + } + } + + private void initializeUserLocked(UserRecord userRecord) { + if (DEBUG) { + Slog.d(TAG, userRecord + ": Initialized"); + } + if (userRecord.mUserId == mCurrentUserId) { + userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START); + } + } + + private void disposeUserIfNeededLocked(UserRecord userRecord) { + // If there are no records left and the user is no longer current then go ahead + // and purge the user record and all of its associated state. If the user is current + // then leave it alone since we might be connected to a route or want to query + // the same route information again soon. + if (userRecord.mUserId != mCurrentUserId + && userRecord.mClientRecords.isEmpty()) { + if (DEBUG) { + Slog.d(TAG, userRecord + ": Disposed"); + } + mUserRecords.remove(userRecord.mUserId); + // Note: User already stopped (by switchUser) so no need to send stop message here. + } + } + + private void initializeClientLocked(ClientRecord clientRecord) { + if (DEBUG) { + Slog.d(TAG, clientRecord + ": Registered"); + } + } + + private void disposeClientLocked(ClientRecord clientRecord, boolean died) { + if (DEBUG) { + if (died) { + Slog.d(TAG, clientRecord + ": Died!"); + } else { + Slog.d(TAG, clientRecord + ": Unregistered"); + } + } + if (clientRecord.mRouteTypes != 0 || clientRecord.mActiveScan) { + clientRecord.mUserRecord.mHandler.sendEmptyMessage( + UserHandler.MSG_UPDATE_DISCOVERY_REQUEST); + } + clientRecord.dispose(); + } + + private boolean validatePackageName(int uid, String packageName) { + if (packageName != null) { + String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); + if (packageNames != null) { + for (String n : packageNames) { + if (n.equals(packageName)) { + return true; + } + } + } + } + return false; + } + + /** + * Information about a particular client of the media router. + * The contents of this object is guarded by mLock. + */ + final class ClientRecord implements DeathRecipient { + public final UserRecord mUserRecord; + public final IMediaRouterClient mClient; + public final int mPid; + public final String mPackageName; + public final boolean mTrusted; + + public int mRouteTypes; + public boolean mActiveScan; + public String mSelectedRouteId; + + public ClientRecord(UserRecord userRecord, IMediaRouterClient client, + int pid, String packageName, boolean trusted) { + mUserRecord = userRecord; + mClient = client; + mPid = pid; + mPackageName = packageName; + mTrusted = trusted; + } + + public void dispose() { + mClient.asBinder().unlinkToDeath(this, 0); + } + + @Override + public void binderDied() { + clientDied(this); + } + + MediaRouterClientState getState() { + return mTrusted ? mUserRecord.mTrustedState : mUserRecord.mUntrustedState; + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + this); + + final String indent = prefix + " "; + pw.println(indent + "mTrusted=" + mTrusted); + pw.println(indent + "mRouteTypes=0x" + Integer.toHexString(mRouteTypes)); + pw.println(indent + "mActiveScan=" + mActiveScan); + pw.println(indent + "mSelectedRouteId=" + mSelectedRouteId); + } + + @Override + public String toString() { + return "Client " + mPackageName + " (pid " + mPid + ")"; + } + } + + /** + * Information about a particular user. + * The contents of this object is guarded by mLock. + */ + final class UserRecord { + public final int mUserId; + public final ArrayList<ClientRecord> mClientRecords = new ArrayList<ClientRecord>(); + public final UserHandler mHandler; + public MediaRouterClientState mTrustedState; + public MediaRouterClientState mUntrustedState; + + public UserRecord(int userId) { + mUserId = userId; + mHandler = new UserHandler(MediaRouterService.this, this); + } + + public void dump(final PrintWriter pw, String prefix) { + pw.println(prefix + this); + + final String indent = prefix + " "; + final int clientCount = mClientRecords.size(); + if (clientCount != 0) { + for (int i = 0; i < clientCount; i++) { + mClientRecords.get(i).dump(pw, indent); + } + } else { + pw.println(indent + "<no clients>"); + } + + pw.println(indent + "State"); + pw.println(indent + "mTrustedState=" + mTrustedState); + pw.println(indent + "mUntrustedState=" + mUntrustedState); + + if (!mHandler.runWithScissors(new Runnable() { + @Override + public void run() { + mHandler.dump(pw, indent); + } + }, 1000)) { + pw.println(indent + "<could not dump handler state>"); + } + } + + @Override + public String toString() { + return "User " + mUserId; + } + } + + /** + * Media router handler + * <p> + * Since remote display providers are designed to be single-threaded by nature, + * this class encapsulates all of the associated functionality and exports state + * to the service as it evolves. + * </p><p> + * One important task of this class is to keep track of the current globally selected + * route id for certain routes that have global effects, such as remote displays. + * Global route selections override local selections made within apps. The change + * is propagated to all apps so that they are all in sync. Synchronization works + * both ways. Whenever the globally selected route is explicitly unselected by any + * app, then it becomes unselected globally and all apps are informed. + * </p><p> + * This class is currently hardcoded to work with remote display providers but + * it is intended to be eventually extended to support more general route providers + * similar to the support library media router. + * </p> + */ + static final class UserHandler extends Handler + implements RemoteDisplayProviderWatcher.Callback, + RemoteDisplayProviderProxy.Callback { + public static final int MSG_START = 1; + public static final int MSG_STOP = 2; + public static final int MSG_UPDATE_DISCOVERY_REQUEST = 3; + public static final int MSG_SELECT_ROUTE = 4; + public static final int MSG_UNSELECT_ROUTE = 5; + public static final int MSG_REQUEST_SET_VOLUME = 6; + public static final int MSG_REQUEST_UPDATE_VOLUME = 7; + private static final int MSG_UPDATE_CLIENT_STATE = 8; + private static final int MSG_CONNECTION_TIMED_OUT = 9; + + private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1; + private static final int TIMEOUT_REASON_CONNECTION_LOST = 2; + private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 3; + private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTED = 4; + + // The relative order of these constants is important and expresses progress + // through the process of connecting to a route. + private static final int PHASE_NOT_AVAILABLE = -1; + private static final int PHASE_NOT_CONNECTED = 0; + private static final int PHASE_CONNECTING = 1; + private static final int PHASE_CONNECTED = 2; + + private final MediaRouterService mService; + private final UserRecord mUserRecord; + private final RemoteDisplayProviderWatcher mWatcher; + private final ArrayList<ProviderRecord> mProviderRecords = + new ArrayList<ProviderRecord>(); + private final ArrayList<IMediaRouterClient> mTempClients = + new ArrayList<IMediaRouterClient>(); + + private boolean mRunning; + private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE; + private RouteRecord mGloballySelectedRouteRecord; + private int mConnectionPhase = PHASE_NOT_AVAILABLE; + private int mConnectionTimeoutReason; + private long mConnectionTimeoutStartTime; + private boolean mClientStateUpdateScheduled; + + public UserHandler(MediaRouterService service, UserRecord userRecord) { + super(Looper.getMainLooper(), null, true); + mService = service; + mUserRecord = userRecord; + mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this, + this, mUserRecord.mUserId); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_START: { + start(); + break; + } + case MSG_STOP: { + stop(); + break; + } + case MSG_UPDATE_DISCOVERY_REQUEST: { + updateDiscoveryRequest(); + break; + } + case MSG_SELECT_ROUTE: { + selectRoute((String)msg.obj); + break; + } + case MSG_UNSELECT_ROUTE: { + unselectRoute((String)msg.obj); + break; + } + case MSG_REQUEST_SET_VOLUME: { + requestSetVolume((String)msg.obj, msg.arg1); + break; + } + case MSG_REQUEST_UPDATE_VOLUME: { + requestUpdateVolume((String)msg.obj, msg.arg1); + break; + } + case MSG_UPDATE_CLIENT_STATE: { + updateClientState(); + break; + } + case MSG_CONNECTION_TIMED_OUT: { + connectionTimedOut(); + break; + } + } + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "Handler"); + + final String indent = prefix + " "; + pw.println(indent + "mRunning=" + mRunning); + pw.println(indent + "mDiscoveryMode=" + mDiscoveryMode); + pw.println(indent + "mGloballySelectedRouteRecord=" + mGloballySelectedRouteRecord); + pw.println(indent + "mConnectionPhase=" + mConnectionPhase); + pw.println(indent + "mConnectionTimeoutReason=" + mConnectionTimeoutReason); + pw.println(indent + "mConnectionTimeoutStartTime=" + (mConnectionTimeoutReason != 0 ? + TimeUtils.formatUptime(mConnectionTimeoutStartTime) : "<n/a>")); + + mWatcher.dump(pw, prefix); + + final int providerCount = mProviderRecords.size(); + if (providerCount != 0) { + for (int i = 0; i < providerCount; i++) { + mProviderRecords.get(i).dump(pw, prefix); + } + } else { + pw.println(indent + "<no providers>"); + } + } + + private void start() { + if (!mRunning) { + mRunning = true; + mWatcher.start(); // also starts all providers + } + } + + private void stop() { + if (mRunning) { + mRunning = false; + unselectGloballySelectedRoute(); + mWatcher.stop(); // also stops all providers + } + } + + private void updateDiscoveryRequest() { + int routeTypes = 0; + boolean activeScan = false; + synchronized (mService.mLock) { + final int count = mUserRecord.mClientRecords.size(); + for (int i = 0; i < count; i++) { + ClientRecord clientRecord = mUserRecord.mClientRecords.get(i); + routeTypes |= clientRecord.mRouteTypes; + activeScan |= clientRecord.mActiveScan; + } + } + + final int newDiscoveryMode; + if ((routeTypes & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) { + if (activeScan) { + newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_ACTIVE; + } else { + newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_PASSIVE; + } + } else { + newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE; + } + + if (mDiscoveryMode != newDiscoveryMode) { + mDiscoveryMode = newDiscoveryMode; + final int count = mProviderRecords.size(); + for (int i = 0; i < count; i++) { + mProviderRecords.get(i).getProvider().setDiscoveryMode(mDiscoveryMode); + } + } + } + + private void selectRoute(String routeId) { + if (routeId != null + && (mGloballySelectedRouteRecord == null + || !routeId.equals(mGloballySelectedRouteRecord.getUniqueId()))) { + RouteRecord routeRecord = findRouteRecord(routeId); + if (routeRecord != null) { + unselectGloballySelectedRoute(); + + Slog.i(TAG, "Selected global route:" + routeRecord); + mGloballySelectedRouteRecord = routeRecord; + checkGloballySelectedRouteState(); + routeRecord.getProvider().setSelectedDisplay(routeRecord.getDescriptorId()); + + scheduleUpdateClientState(); + } + } + } + + private void unselectRoute(String routeId) { + if (routeId != null + && mGloballySelectedRouteRecord != null + && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) { + unselectGloballySelectedRoute(); + } + } + + private void unselectGloballySelectedRoute() { + if (mGloballySelectedRouteRecord != null) { + Slog.i(TAG, "Unselected global route:" + mGloballySelectedRouteRecord); + mGloballySelectedRouteRecord.getProvider().setSelectedDisplay(null); + mGloballySelectedRouteRecord = null; + checkGloballySelectedRouteState(); + + scheduleUpdateClientState(); + } + } + + private void requestSetVolume(String routeId, int volume) { + if (mGloballySelectedRouteRecord != null + && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) { + mGloballySelectedRouteRecord.getProvider().setDisplayVolume(volume); + } + } + + private void requestUpdateVolume(String routeId, int direction) { + if (mGloballySelectedRouteRecord != null + && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) { + mGloballySelectedRouteRecord.getProvider().adjustDisplayVolume(direction); + } + } + + @Override + public void addProvider(RemoteDisplayProviderProxy provider) { + provider.setCallback(this); + provider.setDiscoveryMode(mDiscoveryMode); + provider.setSelectedDisplay(null); // just to be safe + + ProviderRecord providerRecord = new ProviderRecord(provider); + mProviderRecords.add(providerRecord); + providerRecord.updateDescriptor(provider.getDisplayState()); + + scheduleUpdateClientState(); + } + + @Override + public void removeProvider(RemoteDisplayProviderProxy provider) { + int index = findProviderRecord(provider); + if (index >= 0) { + ProviderRecord providerRecord = mProviderRecords.remove(index); + providerRecord.updateDescriptor(null); // mark routes invalid + provider.setCallback(null); + provider.setDiscoveryMode(RemoteDisplayState.DISCOVERY_MODE_NONE); + + checkGloballySelectedRouteState(); + scheduleUpdateClientState(); + } + } + + @Override + public void onDisplayStateChanged(RemoteDisplayProviderProxy provider, + RemoteDisplayState state) { + updateProvider(provider, state); + } + + private void updateProvider(RemoteDisplayProviderProxy provider, + RemoteDisplayState state) { + int index = findProviderRecord(provider); + if (index >= 0) { + ProviderRecord providerRecord = mProviderRecords.get(index); + if (providerRecord.updateDescriptor(state)) { + checkGloballySelectedRouteState(); + scheduleUpdateClientState(); + } + } + } + + /** + * This function is called whenever the state of the globally selected route + * may have changed. It checks the state and updates timeouts or unselects + * the route as appropriate. + */ + private void checkGloballySelectedRouteState() { + // Unschedule timeouts when the route is unselected. + if (mGloballySelectedRouteRecord == null) { + mConnectionPhase = PHASE_NOT_AVAILABLE; + updateConnectionTimeout(0); + return; + } + + // Ensure that the route is still present and enabled. + if (!mGloballySelectedRouteRecord.isValid() + || !mGloballySelectedRouteRecord.isEnabled()) { + updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE); + return; + } + + // Make sure we haven't lost our connection. + final int oldPhase = mConnectionPhase; + mConnectionPhase = getConnectionPhase(mGloballySelectedRouteRecord.getStatus()); + if (oldPhase >= PHASE_CONNECTING && mConnectionPhase < PHASE_CONNECTING) { + updateConnectionTimeout(TIMEOUT_REASON_CONNECTION_LOST); + return; + } + + // Check the route status. + switch (mConnectionPhase) { + case PHASE_CONNECTED: + if (oldPhase != PHASE_CONNECTED) { + Slog.i(TAG, "Connected to global route: " + + mGloballySelectedRouteRecord); + } + updateConnectionTimeout(0); + break; + case PHASE_CONNECTING: + if (oldPhase != PHASE_CONNECTING) { + Slog.i(TAG, "Connecting to global route: " + + mGloballySelectedRouteRecord); + } + updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTED); + break; + case PHASE_NOT_CONNECTED: + updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTING); + break; + case PHASE_NOT_AVAILABLE: + default: + updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE); + break; + } + } + + private void updateConnectionTimeout(int reason) { + if (reason != mConnectionTimeoutReason) { + if (mConnectionTimeoutReason != 0) { + removeMessages(MSG_CONNECTION_TIMED_OUT); + } + mConnectionTimeoutReason = reason; + mConnectionTimeoutStartTime = SystemClock.uptimeMillis(); + switch (reason) { + case TIMEOUT_REASON_NOT_AVAILABLE: + case TIMEOUT_REASON_CONNECTION_LOST: + // Route became unavailable or connection lost. + // Unselect it immediately. + sendEmptyMessage(MSG_CONNECTION_TIMED_OUT); + break; + case TIMEOUT_REASON_WAITING_FOR_CONNECTING: + // Waiting for route to start connecting. + sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTING_TIMEOUT); + break; + case TIMEOUT_REASON_WAITING_FOR_CONNECTED: + // Waiting for route to complete connection. + sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTED_TIMEOUT); + break; + } + } + } + + private void connectionTimedOut() { + if (mConnectionTimeoutReason == 0 || mGloballySelectedRouteRecord == null) { + // Shouldn't get here. There must be a bug somewhere. + Log.wtf(TAG, "Handled connection timeout for no reason."); + return; + } + + switch (mConnectionTimeoutReason) { + case TIMEOUT_REASON_NOT_AVAILABLE: + Slog.i(TAG, "Global route no longer available: " + + mGloballySelectedRouteRecord); + break; + case TIMEOUT_REASON_CONNECTION_LOST: + Slog.i(TAG, "Global route connection lost: " + + mGloballySelectedRouteRecord); + break; + case TIMEOUT_REASON_WAITING_FOR_CONNECTING: + Slog.i(TAG, "Global route timed out while waiting for " + + "connection attempt to begin after " + + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime) + + " ms: " + mGloballySelectedRouteRecord); + break; + case TIMEOUT_REASON_WAITING_FOR_CONNECTED: + Slog.i(TAG, "Global route timed out while connecting after " + + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime) + + " ms: " + mGloballySelectedRouteRecord); + break; + } + mConnectionTimeoutReason = 0; + + unselectGloballySelectedRoute(); + } + + private void scheduleUpdateClientState() { + if (!mClientStateUpdateScheduled) { + mClientStateUpdateScheduled = true; + sendEmptyMessage(MSG_UPDATE_CLIENT_STATE); + } + } + + private void updateClientState() { + mClientStateUpdateScheduled = false; + + final String globallySelectedRouteId = mGloballySelectedRouteRecord != null ? + mGloballySelectedRouteRecord.getUniqueId() : null; + + // Build a new client state for trusted clients. + MediaRouterClientState trustedState = new MediaRouterClientState(); + trustedState.globallySelectedRouteId = globallySelectedRouteId; + final int providerCount = mProviderRecords.size(); + for (int i = 0; i < providerCount; i++) { + mProviderRecords.get(i).appendClientState(trustedState); + } + + // Build a new client state for untrusted clients that can only see + // the currently selected route. + MediaRouterClientState untrustedState = new MediaRouterClientState(); + untrustedState.globallySelectedRouteId = globallySelectedRouteId; + if (globallySelectedRouteId != null) { + untrustedState.routes.add(trustedState.getRoute(globallySelectedRouteId)); + } + + try { + synchronized (mService.mLock) { + // Update the UserRecord. + mUserRecord.mTrustedState = trustedState; + mUserRecord.mUntrustedState = untrustedState; + + // Collect all clients. + final int count = mUserRecord.mClientRecords.size(); + for (int i = 0; i < count; i++) { + mTempClients.add(mUserRecord.mClientRecords.get(i).mClient); + } + } + + // Notify all clients (outside of the lock). + final int count = mTempClients.size(); + for (int i = 0; i < count; i++) { + try { + mTempClients.get(i).onStateChanged(); + } catch (RemoteException ex) { + // ignore errors, client probably died + } + } + } finally { + // Clear the list in preparation for the next time. + mTempClients.clear(); + } + } + + private int findProviderRecord(RemoteDisplayProviderProxy provider) { + final int count = mProviderRecords.size(); + for (int i = 0; i < count; i++) { + ProviderRecord record = mProviderRecords.get(i); + if (record.getProvider() == provider) { + return i; + } + } + return -1; + } + + private RouteRecord findRouteRecord(String uniqueId) { + final int count = mProviderRecords.size(); + for (int i = 0; i < count; i++) { + RouteRecord record = mProviderRecords.get(i).findRouteByUniqueId(uniqueId); + if (record != null) { + return record; + } + } + return null; + } + + private static int getConnectionPhase(int status) { + switch (status) { + case MediaRouter.RouteInfo.STATUS_NONE: + case MediaRouter.RouteInfo.STATUS_CONNECTED: + return PHASE_CONNECTED; + case MediaRouter.RouteInfo.STATUS_CONNECTING: + return PHASE_CONNECTING; + case MediaRouter.RouteInfo.STATUS_SCANNING: + case MediaRouter.RouteInfo.STATUS_AVAILABLE: + return PHASE_NOT_CONNECTED; + case MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE: + case MediaRouter.RouteInfo.STATUS_IN_USE: + default: + return PHASE_NOT_AVAILABLE; + } + } + + static final class ProviderRecord { + private final RemoteDisplayProviderProxy mProvider; + private final String mUniquePrefix; + private final ArrayList<RouteRecord> mRoutes = new ArrayList<RouteRecord>(); + private RemoteDisplayState mDescriptor; + + public ProviderRecord(RemoteDisplayProviderProxy provider) { + mProvider = provider; + mUniquePrefix = provider.getFlattenedComponentName() + ":"; + } + + public RemoteDisplayProviderProxy getProvider() { + return mProvider; + } + + public String getUniquePrefix() { + return mUniquePrefix; + } + + public boolean updateDescriptor(RemoteDisplayState descriptor) { + boolean changed = false; + if (mDescriptor != descriptor) { + mDescriptor = descriptor; + + // Update all existing routes and reorder them to match + // the order of their descriptors. + int targetIndex = 0; + if (descriptor != null) { + if (descriptor.isValid()) { + final List<RemoteDisplayInfo> routeDescriptors = descriptor.displays; + final int routeCount = routeDescriptors.size(); + for (int i = 0; i < routeCount; i++) { + final RemoteDisplayInfo routeDescriptor = + routeDescriptors.get(i); + final String descriptorId = routeDescriptor.id; + final int sourceIndex = findRouteByDescriptorId(descriptorId); + if (sourceIndex < 0) { + // Add the route to the provider. + String uniqueId = assignRouteUniqueId(descriptorId); + RouteRecord route = + new RouteRecord(this, descriptorId, uniqueId); + mRoutes.add(targetIndex++, route); + route.updateDescriptor(routeDescriptor); + changed = true; + } else if (sourceIndex < targetIndex) { + // Ignore route with duplicate id. + Slog.w(TAG, "Ignoring route descriptor with duplicate id: " + + routeDescriptor); + } else { + // Reorder existing route within the list. + RouteRecord route = mRoutes.get(sourceIndex); + Collections.swap(mRoutes, sourceIndex, targetIndex++); + changed |= route.updateDescriptor(routeDescriptor); + } + } + } else { + Slog.w(TAG, "Ignoring invalid descriptor from media route provider: " + + mProvider.getFlattenedComponentName()); + } + } + + // Dispose all remaining routes that do not have matching descriptors. + for (int i = mRoutes.size() - 1; i >= targetIndex; i--) { + RouteRecord route = mRoutes.remove(i); + route.updateDescriptor(null); // mark route invalid + changed = true; + } + } + return changed; + } + + public void appendClientState(MediaRouterClientState state) { + final int routeCount = mRoutes.size(); + for (int i = 0; i < routeCount; i++) { + state.routes.add(mRoutes.get(i).getInfo()); + } + } + + public RouteRecord findRouteByUniqueId(String uniqueId) { + final int routeCount = mRoutes.size(); + for (int i = 0; i < routeCount; i++) { + RouteRecord route = mRoutes.get(i); + if (route.getUniqueId().equals(uniqueId)) { + return route; + } + } + return null; + } + + private int findRouteByDescriptorId(String descriptorId) { + final int routeCount = mRoutes.size(); + for (int i = 0; i < routeCount; i++) { + RouteRecord route = mRoutes.get(i); + if (route.getDescriptorId().equals(descriptorId)) { + return i; + } + } + return -1; + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + this); + + final String indent = prefix + " "; + mProvider.dump(pw, indent); + + final int routeCount = mRoutes.size(); + if (routeCount != 0) { + for (int i = 0; i < routeCount; i++) { + mRoutes.get(i).dump(pw, indent); + } + } else { + pw.println(indent + "<no routes>"); + } + } + + @Override + public String toString() { + return "Provider " + mProvider.getFlattenedComponentName(); + } + + private String assignRouteUniqueId(String descriptorId) { + return mUniquePrefix + descriptorId; + } + } + + static final class RouteRecord { + private final ProviderRecord mProviderRecord; + private final String mDescriptorId; + private final MediaRouterClientState.RouteInfo mMutableInfo; + private MediaRouterClientState.RouteInfo mImmutableInfo; + private RemoteDisplayInfo mDescriptor; + + public RouteRecord(ProviderRecord providerRecord, + String descriptorId, String uniqueId) { + mProviderRecord = providerRecord; + mDescriptorId = descriptorId; + mMutableInfo = new MediaRouterClientState.RouteInfo(uniqueId); + } + + public RemoteDisplayProviderProxy getProvider() { + return mProviderRecord.getProvider(); + } + + public ProviderRecord getProviderRecord() { + return mProviderRecord; + } + + public String getDescriptorId() { + return mDescriptorId; + } + + public String getUniqueId() { + return mMutableInfo.id; + } + + public MediaRouterClientState.RouteInfo getInfo() { + if (mImmutableInfo == null) { + mImmutableInfo = new MediaRouterClientState.RouteInfo(mMutableInfo); + } + return mImmutableInfo; + } + + public boolean isValid() { + return mDescriptor != null; + } + + public boolean isEnabled() { + return mMutableInfo.enabled; + } + + public int getStatus() { + return mMutableInfo.statusCode; + } + + public boolean updateDescriptor(RemoteDisplayInfo descriptor) { + boolean changed = false; + if (mDescriptor != descriptor) { + mDescriptor = descriptor; + if (descriptor != null) { + final String name = computeName(descriptor); + if (!Objects.equal(mMutableInfo.name, name)) { + mMutableInfo.name = name; + changed = true; + } + final String description = computeDescription(descriptor); + if (!Objects.equal(mMutableInfo.description, description)) { + mMutableInfo.description = description; + changed = true; + } + final int supportedTypes = computeSupportedTypes(descriptor); + if (mMutableInfo.supportedTypes != supportedTypes) { + mMutableInfo.supportedTypes = supportedTypes; + changed = true; + } + final boolean enabled = computeEnabled(descriptor); + if (mMutableInfo.enabled != enabled) { + mMutableInfo.enabled = enabled; + changed = true; + } + final int statusCode = computeStatusCode(descriptor); + if (mMutableInfo.statusCode != statusCode) { + mMutableInfo.statusCode = statusCode; + changed = true; + } + final int playbackType = computePlaybackType(descriptor); + if (mMutableInfo.playbackType != playbackType) { + mMutableInfo.playbackType = playbackType; + changed = true; + } + final int playbackStream = computePlaybackStream(descriptor); + if (mMutableInfo.playbackStream != playbackStream) { + mMutableInfo.playbackStream = playbackStream; + changed = true; + } + final int volume = computeVolume(descriptor); + if (mMutableInfo.volume != volume) { + mMutableInfo.volume = volume; + changed = true; + } + final int volumeMax = computeVolumeMax(descriptor); + if (mMutableInfo.volumeMax != volumeMax) { + mMutableInfo.volumeMax = volumeMax; + changed = true; + } + final int volumeHandling = computeVolumeHandling(descriptor); + if (mMutableInfo.volumeHandling != volumeHandling) { + mMutableInfo.volumeHandling = volumeHandling; + changed = true; + } + final int presentationDisplayId = computePresentationDisplayId(descriptor); + if (mMutableInfo.presentationDisplayId != presentationDisplayId) { + mMutableInfo.presentationDisplayId = presentationDisplayId; + changed = true; + } + } + } + if (changed) { + mImmutableInfo = null; + } + return changed; + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + this); + + final String indent = prefix + " "; + pw.println(indent + "mMutableInfo=" + mMutableInfo); + pw.println(indent + "mDescriptorId=" + mDescriptorId); + pw.println(indent + "mDescriptor=" + mDescriptor); + } + + @Override + public String toString() { + return "Route " + mMutableInfo.name + " (" + mMutableInfo.id + ")"; + } + + private static String computeName(RemoteDisplayInfo descriptor) { + // Note that isValid() already ensures the name is non-empty. + return descriptor.name; + } + + private static String computeDescription(RemoteDisplayInfo descriptor) { + final String description = descriptor.description; + return TextUtils.isEmpty(description) ? null : description; + } + + private static int computeSupportedTypes(RemoteDisplayInfo descriptor) { + return MediaRouter.ROUTE_TYPE_LIVE_AUDIO + | MediaRouter.ROUTE_TYPE_LIVE_VIDEO + | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; + } + + private static boolean computeEnabled(RemoteDisplayInfo descriptor) { + switch (descriptor.status) { + case RemoteDisplayInfo.STATUS_CONNECTED: + case RemoteDisplayInfo.STATUS_CONNECTING: + case RemoteDisplayInfo.STATUS_AVAILABLE: + return true; + default: + return false; + } + } + + private static int computeStatusCode(RemoteDisplayInfo descriptor) { + switch (descriptor.status) { + case RemoteDisplayInfo.STATUS_NOT_AVAILABLE: + return MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE; + case RemoteDisplayInfo.STATUS_AVAILABLE: + return MediaRouter.RouteInfo.STATUS_AVAILABLE; + case RemoteDisplayInfo.STATUS_IN_USE: + return MediaRouter.RouteInfo.STATUS_IN_USE; + case RemoteDisplayInfo.STATUS_CONNECTING: + return MediaRouter.RouteInfo.STATUS_CONNECTING; + case RemoteDisplayInfo.STATUS_CONNECTED: + return MediaRouter.RouteInfo.STATUS_CONNECTED; + default: + return MediaRouter.RouteInfo.STATUS_NONE; + } + } + + private static int computePlaybackType(RemoteDisplayInfo descriptor) { + return MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE; + } + + private static int computePlaybackStream(RemoteDisplayInfo descriptor) { + return AudioSystem.STREAM_MUSIC; + } + + private static int computeVolume(RemoteDisplayInfo descriptor) { + final int volume = descriptor.volume; + final int volumeMax = descriptor.volumeMax; + if (volume < 0) { + return 0; + } else if (volume > volumeMax) { + return volumeMax; + } + return volume; + } + + private static int computeVolumeMax(RemoteDisplayInfo descriptor) { + final int volumeMax = descriptor.volumeMax; + return volumeMax > 0 ? volumeMax : 0; + } + + private static int computeVolumeHandling(RemoteDisplayInfo descriptor) { + final int volumeHandling = descriptor.volumeHandling; + switch (volumeHandling) { + case RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE: + return MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE; + case RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED: + default: + return MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED; + } + } + + private static int computePresentationDisplayId(RemoteDisplayInfo descriptor) { + // The MediaRouter class validates that the id corresponds to an extant + // presentation display. So all we do here is canonicalize the null case. + final int displayId = descriptor.presentationDisplayId; + return displayId < 0 ? -1 : displayId; + } + } + } +} diff --git a/services/java/com/android/server/media/RemoteDisplayProviderProxy.java b/services/java/com/android/server/media/RemoteDisplayProviderProxy.java new file mode 100644 index 0000000..b248ee0 --- /dev/null +++ b/services/java/com/android/server/media/RemoteDisplayProviderProxy.java @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2013 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.media; + +import com.android.internal.util.Objects; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.media.IRemoteDisplayCallback; +import android.media.IRemoteDisplayProvider; +import android.media.RemoteDisplayState; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.IBinder.DeathRecipient; +import android.os.UserHandle; +import android.util.Log; +import android.util.Slog; + +import java.io.PrintWriter; +import java.lang.ref.WeakReference; + +/** + * Maintains a connection to a particular remote display provider service. + */ +final class RemoteDisplayProviderProxy implements ServiceConnection { + private static final String TAG = "RemoteDisplayProvider"; // max. 23 chars + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final Context mContext; + private final ComponentName mComponentName; + private final int mUserId; + private final Handler mHandler; + + private Callback mDisplayStateCallback; + + // Connection state + private boolean mRunning; + private boolean mBound; + private Connection mActiveConnection; + private boolean mConnectionReady; + + // Logical state + private int mDiscoveryMode; + private String mSelectedDisplayId; + private RemoteDisplayState mDisplayState; + private boolean mScheduledDisplayStateChangedCallback; + + public RemoteDisplayProviderProxy(Context context, ComponentName componentName, + int userId) { + mContext = context; + mComponentName = componentName; + mUserId = userId; + mHandler = new Handler(); + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "Proxy"); + pw.println(prefix + " mUserId=" + mUserId); + pw.println(prefix + " mRunning=" + mRunning); + pw.println(prefix + " mBound=" + mBound); + pw.println(prefix + " mActiveConnection=" + mActiveConnection); + pw.println(prefix + " mConnectionReady=" + mConnectionReady); + pw.println(prefix + " mDiscoveryMode=" + mDiscoveryMode); + pw.println(prefix + " mSelectedDisplayId=" + mSelectedDisplayId); + pw.println(prefix + " mDisplayState=" + mDisplayState); + } + + public void setCallback(Callback callback) { + mDisplayStateCallback = callback; + } + + public RemoteDisplayState getDisplayState() { + return mDisplayState; + } + + public void setDiscoveryMode(int mode) { + if (mDiscoveryMode != mode) { + mDiscoveryMode = mode; + if (mConnectionReady) { + mActiveConnection.setDiscoveryMode(mode); + } + updateBinding(); + } + } + + public void setSelectedDisplay(String id) { + if (!Objects.equal(mSelectedDisplayId, id)) { + if (mConnectionReady && mSelectedDisplayId != null) { + mActiveConnection.disconnect(mSelectedDisplayId); + } + mSelectedDisplayId = id; + if (mConnectionReady && id != null) { + mActiveConnection.connect(id); + } + updateBinding(); + } + } + + public void setDisplayVolume(int volume) { + if (mConnectionReady && mSelectedDisplayId != null) { + mActiveConnection.setVolume(mSelectedDisplayId, volume); + } + } + + public void adjustDisplayVolume(int delta) { + if (mConnectionReady && mSelectedDisplayId != null) { + mActiveConnection.adjustVolume(mSelectedDisplayId, delta); + } + } + + public boolean hasComponentName(String packageName, String className) { + return mComponentName.getPackageName().equals(packageName) + && mComponentName.getClassName().equals(className); + } + + public String getFlattenedComponentName() { + return mComponentName.flattenToShortString(); + } + + public void start() { + if (!mRunning) { + if (DEBUG) { + Slog.d(TAG, this + ": Starting"); + } + + mRunning = true; + updateBinding(); + } + } + + public void stop() { + if (mRunning) { + if (DEBUG) { + Slog.d(TAG, this + ": Stopping"); + } + + mRunning = false; + updateBinding(); + } + } + + public void rebindIfDisconnected() { + if (mActiveConnection == null && shouldBind()) { + unbind(); + bind(); + } + } + + private void updateBinding() { + if (shouldBind()) { + bind(); + } else { + unbind(); + } + } + + private boolean shouldBind() { + if (mRunning) { + // Bind whenever there is a discovery request or selected display. + if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE + || mSelectedDisplayId != null) { + return true; + } + } + return false; + } + + private void bind() { + if (!mBound) { + if (DEBUG) { + Slog.d(TAG, this + ": Binding"); + } + + Intent service = new Intent(RemoteDisplayState.SERVICE_INTERFACE); + service.setComponent(mComponentName); + try { + mBound = mContext.bindServiceAsUser(service, this, Context.BIND_AUTO_CREATE, + new UserHandle(mUserId)); + if (!mBound && DEBUG) { + Slog.d(TAG, this + ": Bind failed"); + } + } catch (SecurityException ex) { + if (DEBUG) { + Slog.d(TAG, this + ": Bind failed", ex); + } + } + } + } + + private void unbind() { + if (mBound) { + if (DEBUG) { + Slog.d(TAG, this + ": Unbinding"); + } + + mBound = false; + disconnect(); + mContext.unbindService(this); + } + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (DEBUG) { + Slog.d(TAG, this + ": Connected"); + } + + if (mBound) { + disconnect(); + + IRemoteDisplayProvider provider = IRemoteDisplayProvider.Stub.asInterface(service); + if (provider != null) { + Connection connection = new Connection(provider); + if (connection.register()) { + mActiveConnection = connection; + } else { + if (DEBUG) { + Slog.d(TAG, this + ": Registration failed"); + } + } + } else { + Slog.e(TAG, this + ": Service returned invalid remote display provider binder"); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + if (DEBUG) { + Slog.d(TAG, this + ": Service disconnected"); + } + disconnect(); + } + + private void onConnectionReady(Connection connection) { + if (mActiveConnection == connection) { + mConnectionReady = true; + + if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE) { + mActiveConnection.setDiscoveryMode(mDiscoveryMode); + } + if (mSelectedDisplayId != null) { + mActiveConnection.connect(mSelectedDisplayId); + } + } + } + + private void onConnectionDied(Connection connection) { + if (mActiveConnection == connection) { + if (DEBUG) { + Slog.d(TAG, this + ": Service connection died"); + } + disconnect(); + } + } + + private void onDisplayStateChanged(Connection connection, RemoteDisplayState state) { + if (mActiveConnection == connection) { + if (DEBUG) { + Slog.d(TAG, this + ": State changed, state=" + state); + } + setDisplayState(state); + } + } + + private void disconnect() { + if (mActiveConnection != null) { + if (mSelectedDisplayId != null) { + mActiveConnection.disconnect(mSelectedDisplayId); + } + mConnectionReady = false; + mActiveConnection.dispose(); + mActiveConnection = null; + setDisplayState(null); + } + } + + private void setDisplayState(RemoteDisplayState state) { + if (!Objects.equal(mDisplayState, state)) { + mDisplayState = state; + if (!mScheduledDisplayStateChangedCallback) { + mScheduledDisplayStateChangedCallback = true; + mHandler.post(mDisplayStateChanged); + } + } + } + + @Override + public String toString() { + return "Service connection " + mComponentName.flattenToShortString(); + } + + private final Runnable mDisplayStateChanged = new Runnable() { + @Override + public void run() { + mScheduledDisplayStateChangedCallback = false; + if (mDisplayStateCallback != null) { + mDisplayStateCallback.onDisplayStateChanged( + RemoteDisplayProviderProxy.this, mDisplayState); + } + } + }; + + public interface Callback { + void onDisplayStateChanged(RemoteDisplayProviderProxy provider, RemoteDisplayState state); + } + + private final class Connection implements DeathRecipient { + private final IRemoteDisplayProvider mProvider; + private final ProviderCallback mCallback; + + public Connection(IRemoteDisplayProvider provider) { + mProvider = provider; + mCallback = new ProviderCallback(this); + } + + public boolean register() { + try { + mProvider.asBinder().linkToDeath(this, 0); + mProvider.setCallback(mCallback); + mHandler.post(new Runnable() { + @Override + public void run() { + onConnectionReady(Connection.this); + } + }); + return true; + } catch (RemoteException ex) { + binderDied(); + } + return false; + } + + public void dispose() { + mProvider.asBinder().unlinkToDeath(this, 0); + mCallback.dispose(); + } + + public void setDiscoveryMode(int mode) { + try { + mProvider.setDiscoveryMode(mode); + } catch (RemoteException ex) { + Slog.e(TAG, "Failed to deliver request to set discovery mode.", ex); + } + } + + public void connect(String id) { + try { + mProvider.connect(id); + } catch (RemoteException ex) { + Slog.e(TAG, "Failed to deliver request to connect to display.", ex); + } + } + + public void disconnect(String id) { + try { + mProvider.disconnect(id); + } catch (RemoteException ex) { + Slog.e(TAG, "Failed to deliver request to disconnect from display.", ex); + } + } + + public void setVolume(String id, int volume) { + try { + mProvider.setVolume(id, volume); + } catch (RemoteException ex) { + Slog.e(TAG, "Failed to deliver request to set display volume.", ex); + } + } + + public void adjustVolume(String id, int volume) { + try { + mProvider.adjustVolume(id, volume); + } catch (RemoteException ex) { + Slog.e(TAG, "Failed to deliver request to adjust display volume.", ex); + } + } + + @Override + public void binderDied() { + mHandler.post(new Runnable() { + @Override + public void run() { + onConnectionDied(Connection.this); + } + }); + } + + void postStateChanged(final RemoteDisplayState state) { + mHandler.post(new Runnable() { + @Override + public void run() { + onDisplayStateChanged(Connection.this, state); + } + }); + } + } + + /** + * Receives callbacks from the service. + * <p> + * This inner class is static and only retains a weak reference to the connection + * to prevent the client from being leaked in case the service is holding an + * active reference to the client's callback. + * </p> + */ + private static final class ProviderCallback extends IRemoteDisplayCallback.Stub { + private final WeakReference<Connection> mConnectionRef; + + public ProviderCallback(Connection connection) { + mConnectionRef = new WeakReference<Connection>(connection); + } + + public void dispose() { + mConnectionRef.clear(); + } + + @Override + public void onStateChanged(RemoteDisplayState state) throws RemoteException { + Connection connection = mConnectionRef.get(); + if (connection != null) { + connection.postStateChanged(state); + } + } + } +} diff --git a/services/java/com/android/server/media/RemoteDisplayProviderWatcher.java b/services/java/com/android/server/media/RemoteDisplayProviderWatcher.java new file mode 100644 index 0000000..6a5f563 --- /dev/null +++ b/services/java/com/android/server/media/RemoteDisplayProviderWatcher.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2013 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.media; + +import android.Manifest; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.media.RemoteDisplayState; +import android.os.Handler; +import android.os.UserHandle; +import android.util.Log; +import android.util.Slog; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; + +/** + * Watches for remote display provider services to be installed. + * Adds a provider to the media router for each registered service. + * + * @see RemoteDisplayProviderProxy + */ +public final class RemoteDisplayProviderWatcher { + private static final String TAG = "RemoteDisplayProvider"; // max. 23 chars + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final Context mContext; + private final Callback mCallback; + private final Handler mHandler; + private final int mUserId; + private final PackageManager mPackageManager; + + private final ArrayList<RemoteDisplayProviderProxy> mProviders = + new ArrayList<RemoteDisplayProviderProxy>(); + private boolean mRunning; + + public RemoteDisplayProviderWatcher(Context context, + Callback callback, Handler handler, int userId) { + mContext = context; + mCallback = callback; + mHandler = handler; + mUserId = userId; + mPackageManager = context.getPackageManager(); + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "Watcher"); + pw.println(prefix + " mUserId=" + mUserId); + pw.println(prefix + " mRunning=" + mRunning); + pw.println(prefix + " mProviders.size()=" + mProviders.size()); + } + + public void start() { + if (!mRunning) { + mRunning = true; + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addAction(Intent.ACTION_PACKAGE_REPLACED); + filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); + filter.addDataScheme("package"); + mContext.registerReceiverAsUser(mScanPackagesReceiver, + new UserHandle(mUserId), filter, null, mHandler); + + // Scan packages. + // Also has the side-effect of restarting providers if needed. + mHandler.post(mScanPackagesRunnable); + } + } + + public void stop() { + if (mRunning) { + mRunning = false; + + mContext.unregisterReceiver(mScanPackagesReceiver); + mHandler.removeCallbacks(mScanPackagesRunnable); + + // Stop all providers. + for (int i = mProviders.size() - 1; i >= 0; i--) { + mProviders.get(i).stop(); + } + } + } + + private void scanPackages() { + if (!mRunning) { + return; + } + + // Add providers for all new services. + // Reorder the list so that providers left at the end will be the ones to remove. + int targetIndex = 0; + Intent intent = new Intent(RemoteDisplayState.SERVICE_INTERFACE); + for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser( + intent, 0, mUserId)) { + ServiceInfo serviceInfo = resolveInfo.serviceInfo; + if (serviceInfo != null && verifyServiceTrusted(serviceInfo)) { + int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name); + if (sourceIndex < 0) { + RemoteDisplayProviderProxy provider = + new RemoteDisplayProviderProxy(mContext, + new ComponentName(serviceInfo.packageName, serviceInfo.name), + mUserId); + provider.start(); + mProviders.add(targetIndex++, provider); + mCallback.addProvider(provider); + } else if (sourceIndex >= targetIndex) { + RemoteDisplayProviderProxy provider = mProviders.get(sourceIndex); + provider.start(); // restart the provider if needed + provider.rebindIfDisconnected(); + Collections.swap(mProviders, sourceIndex, targetIndex++); + } + } + } + + // Remove providers for missing services. + if (targetIndex < mProviders.size()) { + for (int i = mProviders.size() - 1; i >= targetIndex; i--) { + RemoteDisplayProviderProxy provider = mProviders.get(i); + mCallback.removeProvider(provider); + mProviders.remove(provider); + provider.stop(); + } + } + } + + private boolean verifyServiceTrusted(ServiceInfo serviceInfo) { + if (serviceInfo.permission == null || !serviceInfo.permission.equals( + Manifest.permission.BIND_REMOTE_DISPLAY)) { + // If the service does not require this permission then any app could + // potentially bind to it and cause the remote display service to + // misbehave. So we only want to trust providers that require the + // correct permissions. + Slog.w(TAG, "Ignoring remote display provider service because it did not " + + "require the BIND_REMOTE_DISPLAY permission in its manifest: " + + serviceInfo.packageName + "/" + serviceInfo.name); + return false; + } + if (!hasCaptureVideoPermission(serviceInfo.packageName)) { + // If the service does not have permission to capture video then it + // isn't going to be terribly useful as a remote display, is it? + // Kind of makes you wonder what it's doing there in the first place. + Slog.w(TAG, "Ignoring remote display provider service because it does not " + + "have the CAPTURE_VIDEO_OUTPUT or CAPTURE_SECURE_VIDEO_OUTPUT " + + "permission: " + serviceInfo.packageName + "/" + serviceInfo.name); + return false; + } + // Looks good. + return true; + } + + private boolean hasCaptureVideoPermission(String packageName) { + if (mPackageManager.checkPermission(Manifest.permission.CAPTURE_VIDEO_OUTPUT, + packageName) == PackageManager.PERMISSION_GRANTED) { + return true; + } + if (mPackageManager.checkPermission(Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT, + packageName) == PackageManager.PERMISSION_GRANTED) { + return true; + } + return false; + } + + private int findProvider(String packageName, String className) { + int count = mProviders.size(); + for (int i = 0; i < count; i++) { + RemoteDisplayProviderProxy provider = mProviders.get(i); + if (provider.hasComponentName(packageName, className)) { + return i; + } + } + return -1; + } + + private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DEBUG) { + Slog.d(TAG, "Received package manager broadcast: " + intent); + } + scanPackages(); + } + }; + + private final Runnable mScanPackagesRunnable = new Runnable() { + @Override + public void run() { + scanPackages(); + } + }; + + public interface Callback { + void addProvider(RemoteDisplayProviderProxy provider); + void removeProvider(RemoteDisplayProviderProxy provider); + } +} diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index 3d6b3c9..5450fd0 100755 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -1278,7 +1278,8 @@ public class PackageManagerService extends IPackageManager.Stub { frameworkDir.getPath(), OBSERVER_EVENTS, true, false); mFrameworkInstallObserver.startWatching(); scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR, + | PackageParser.PARSE_IS_SYSTEM_DIR + | PackageParser.PARSE_IS_PRIVILEGED, scanMode | SCAN_NO_DEX, 0); // Collected privileged system packages. @@ -1771,6 +1772,24 @@ public class PackageManagerService extends IPackageManager.Stub { state, userId); } + public boolean isPackageAvailable(String packageName, int userId) { + if (!sUserManager.exists(userId)) return false; + enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "is package available"); + synchronized (mPackages) { + PackageParser.Package p = mPackages.get(packageName); + if (p != null) { + final PackageSetting ps = (PackageSetting) p.mExtras; + if (ps != null) { + final PackageUserState state = ps.readUserState(userId); + if (state != null) { + return PackageParser.isAvailable(state); + } + } + } + } + return false; + } + @Override public PackageInfo getPackageInfo(String packageName, int flags, int userId) { if (!sUserManager.exists(userId)) return null; @@ -4959,6 +4978,18 @@ public class PackageManagerService extends IPackageManager.Stub { permissionMap.put(p.info.name, bp); } if (bp.perm == null) { + if (bp.sourcePackage != null + && !bp.sourcePackage.equals(p.info.packageName)) { + // If this is a permission that was formerly defined by a non-system + // app, but is now defined by a system app (following an upgrade), + // discard the previous declaration and consider the system's to be + // canonical. + if (isSystemApp(p.owner)) { + Slog.i(TAG, "New decl " + p.owner + " of permission " + + p.info.name + " is system"); + bp.sourcePackage = null; + } + } if (bp.sourcePackage == null || bp.sourcePackage.equals(p.info.packageName)) { BasePermission tree = findPermissionTreeLP(p.info.name); @@ -9999,11 +10030,11 @@ public class PackageManagerService extends IPackageManager.Stub { } if (filter.countDataAuthorities() != 0 || filter.countDataPaths() != 0 - || filter.countDataSchemes() != 0 + || filter.countDataSchemes() > 1 || filter.countDataTypes() != 0) { throw new IllegalArgumentException( "replacePreferredActivity expects filter to have no data authorities, " + - "paths, schemes or types."); + "paths, or types; and at most one scheme."); } synchronized (mPackages) { if (mContext.checkCallingOrSelfPermission( @@ -10020,33 +10051,27 @@ public class PackageManagerService extends IPackageManager.Stub { } final int callingUserId = UserHandle.getCallingUserId(); - ArrayList<PreferredActivity> removed = null; PreferredIntentResolver pir = mSettings.mPreferredActivities.get(callingUserId); if (pir != null) { - Iterator<PreferredActivity> it = pir.filterIterator(); - String action = filter.getAction(0); - String category = filter.getCategory(0); - while (it.hasNext()) { - PreferredActivity pa = it.next(); - if ((pa.countActions() == 0) || (pa.countCategories() == 0) - || (pa.getAction(0).equals(action) - && pa.getCategory(0).equals(category))) { - if (removed == null) { - removed = new ArrayList<PreferredActivity>(); - } - removed.add(pa); - if (DEBUG_PREFERRED) { - Slog.i(TAG, "Removing preferred activity " - + pa.mPref.mComponent + ":"); - filter.dump(new LogPrinter(Log.INFO, TAG), " "); - } - } - } - if (removed != null) { - for (int i=0; i<removed.size(); i++) { - PreferredActivity pa = removed.get(i); - pir.removeFilter(pa); + Intent intent = new Intent(filter.getAction(0)).addCategory(filter.getCategory(0)); + if (filter.countDataSchemes() == 1) { + Uri.Builder builder = new Uri.Builder(); + builder.scheme(filter.getDataScheme(0)); + intent.setData(builder.build()); + } + List<PreferredActivity> matches = pir.queryIntent( + intent, null, true, callingUserId); + if (DEBUG_PREFERRED) { + Slog.i(TAG, matches.size() + " preferred matches for " + intent); + } + for (int i = 0; i < matches.size(); i++) { + PreferredActivity pa = matches.get(i); + if (DEBUG_PREFERRED) { + Slog.i(TAG, "Removing preferred activity " + + pa.mPref.mComponent + ":"); + filter.dump(new LogPrinter(Log.INFO, TAG), " "); } + pir.removeFilter(pa); } } addPreferredActivityInternal(filter, match, set, activity, true, callingUserId); diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java index d3ccba6..0079b54 100644 --- a/services/java/com/android/server/pm/Settings.java +++ b/services/java/com/android/server/pm/Settings.java @@ -1959,10 +1959,14 @@ final class Settings { } boolean doNonData = true; + boolean hasSchemes = false; for (int ischeme=0; ischeme<tmpPa.countDataSchemes(); ischeme++) { boolean doScheme = true; String scheme = tmpPa.getDataScheme(ischeme); + if (scheme != null && !scheme.isEmpty()) { + hasSchemes = true; + } for (int issp=0; issp<tmpPa.countDataSchemeSpecificParts(); issp++) { Uri.Builder builder = new Uri.Builder(); builder.scheme(scheme); @@ -2016,11 +2020,25 @@ final class Settings { } for (int idata=0; idata<tmpPa.countDataTypes(); idata++) { - Intent finalIntent = new Intent(intent); String mimeType = tmpPa.getDataType(idata); - finalIntent.setType(mimeType); - applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn, - null, null, null, null, mimeType, userId); + if (hasSchemes) { + Uri.Builder builder = new Uri.Builder(); + for (int ischeme=0; ischeme<tmpPa.countDataSchemes(); ischeme++) { + String scheme = tmpPa.getDataScheme(ischeme); + if (scheme != null && !scheme.isEmpty()) { + Intent finalIntent = new Intent(intent); + builder.scheme(scheme); + finalIntent.setDataAndType(builder.build(), mimeType); + applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn, + scheme, null, null, null, mimeType, userId); + } + } + } else { + Intent finalIntent = new Intent(intent); + finalIntent.setType(mimeType); + applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn, + null, null, null, null, mimeType, userId); + } doNonData = false; } diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java index 976a328..60d44c7 100644 --- a/services/java/com/android/server/power/DisplayPowerController.java +++ b/services/java/com/android/server/power/DisplayPowerController.java @@ -298,6 +298,10 @@ final class DisplayPowerController { // True if mAmbientLux holds a valid value. private boolean mAmbientLuxValid; + // The ambient light level threshold at which to brighten or darken the screen. + private float mBrighteningLuxThreshold; + private float mDarkeningLuxThreshold; + // The most recent light sample. private float mLastObservedLux; @@ -945,12 +949,24 @@ final class DisplayPowerController { mLastObservedLuxTime = time; } + private void setAmbientLux(float lux) { + mAmbientLux = lux; + mBrighteningLuxThreshold = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS); + mDarkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS); + } + private void updateAmbientLux(long time) { // If the light sensor was just turned on then immediately update our initial // estimate of the current ambient light level. - if (!mAmbientLuxValid - || (time - mLightSensorEnableTime) < mLightSensorWarmUpTimeConfig) { - mAmbientLux = mRecentShortTermAverageLux; + if (!mAmbientLuxValid) { + final long timeWhenSensorWarmedUp = + mLightSensorWarmUpTimeConfig + mLightSensorEnableTime; + if (time < timeWhenSensorWarmedUp) { + mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, + timeWhenSensorWarmedUp); + return; + } + setAmbientLux(mRecentShortTermAverageLux); mAmbientLuxValid = true; mDebounceLuxDirection = 0; mDebounceLuxTime = time; @@ -961,98 +977,90 @@ final class DisplayPowerController { + ", mAmbientLux=" + mAmbientLux); } updateAutoBrightness(true); - return; - } - - // Determine whether the ambient environment appears to be brightening. - float brighteningLuxThreshold = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS); - if (mRecentShortTermAverageLux > brighteningLuxThreshold - && mRecentLongTermAverageLux > brighteningLuxThreshold) { + } else if (mRecentShortTermAverageLux > mBrighteningLuxThreshold + && mRecentLongTermAverageLux > mBrighteningLuxThreshold) { + // The ambient environment appears to be brightening. if (mDebounceLuxDirection <= 0) { mDebounceLuxDirection = 1; mDebounceLuxTime = time; if (DEBUG) { Slog.d(TAG, "updateAmbientLux: Possibly brightened, waiting for " + BRIGHTENING_LIGHT_DEBOUNCE + " ms: " - + "brighteningLuxThreshold=" + brighteningLuxThreshold + + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + ", mAmbientLux=" + mAmbientLux); } } long debounceTime = mDebounceLuxTime + BRIGHTENING_LIGHT_DEBOUNCE; - if (time >= debounceTime) { - mAmbientLux = mRecentShortTermAverageLux; - if (DEBUG) { - Slog.d(TAG, "updateAmbientLux: Brightened: " - + "brighteningLuxThreshold=" + brighteningLuxThreshold - + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux - + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux - + ", mAmbientLux=" + mAmbientLux); - } - updateAutoBrightness(true); - } else { + if (time < debounceTime) { mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, debounceTime); + return; } - return; - } - - // Determine whether the ambient environment appears to be darkening. - float darkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS); - if (mRecentShortTermAverageLux < darkeningLuxThreshold - && mRecentLongTermAverageLux < darkeningLuxThreshold) { + setAmbientLux(mRecentShortTermAverageLux); + if (DEBUG) { + Slog.d(TAG, "updateAmbientLux: Brightened: " + + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold + + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux + + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + + ", mAmbientLux=" + mAmbientLux); + } + updateAutoBrightness(true); + } else if (mRecentShortTermAverageLux < mDarkeningLuxThreshold + && mRecentLongTermAverageLux < mDarkeningLuxThreshold) { + // The ambient environment appears to be darkening. if (mDebounceLuxDirection >= 0) { mDebounceLuxDirection = -1; mDebounceLuxTime = time; if (DEBUG) { Slog.d(TAG, "updateAmbientLux: Possibly darkened, waiting for " + DARKENING_LIGHT_DEBOUNCE + " ms: " - + "darkeningLuxThreshold=" + darkeningLuxThreshold + + "mDarkeningLuxThreshold=" + mDarkeningLuxThreshold + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + ", mAmbientLux=" + mAmbientLux); } } long debounceTime = mDebounceLuxTime + DARKENING_LIGHT_DEBOUNCE; - if (time >= debounceTime) { - // Be conservative about reducing the brightness, only reduce it a little bit - // at a time to avoid having to bump it up again soon. - mAmbientLux = Math.max(mRecentShortTermAverageLux, mRecentLongTermAverageLux); - if (DEBUG) { - Slog.d(TAG, "updateAmbientLux: Darkened: " - + "darkeningLuxThreshold=" + darkeningLuxThreshold - + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux - + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux - + ", mAmbientLux=" + mAmbientLux); - } - updateAutoBrightness(true); - } else { + if (time < debounceTime) { mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, debounceTime); + return; } - return; - } - - // No change or change is within the hysteresis thresholds. - if (mDebounceLuxDirection != 0) { + // Be conservative about reducing the brightness, only reduce it a little bit + // at a time to avoid having to bump it up again soon. + setAmbientLux(Math.max(mRecentShortTermAverageLux, mRecentLongTermAverageLux)); + if (DEBUG) { + Slog.d(TAG, "updateAmbientLux: Darkened: " + + "mDarkeningLuxThreshold=" + mDarkeningLuxThreshold + + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux + + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + + ", mAmbientLux=" + mAmbientLux); + } + updateAutoBrightness(true); + } else if (mDebounceLuxDirection != 0) { + // No change or change is within the hysteresis thresholds. mDebounceLuxDirection = 0; mDebounceLuxTime = time; if (DEBUG) { Slog.d(TAG, "updateAmbientLux: Canceled debounce: " - + "brighteningLuxThreshold=" + brighteningLuxThreshold - + ", darkeningLuxThreshold=" + darkeningLuxThreshold + + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold + + ", mDarkeningLuxThreshold=" + mDarkeningLuxThreshold + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux + ", mAmbientLux=" + mAmbientLux); } } - // If the light level does not change, then the sensor may not report - // a new value. This can cause problems for the auto-brightness algorithm - // because the filters might not be updated. To work around it, we want to - // make sure to update the filters whenever the observed light level could - // possibly exceed one of the hysteresis thresholds. - if (mLastObservedLux > brighteningLuxThreshold - || mLastObservedLux < darkeningLuxThreshold) { + // Now that we've done all of that, we haven't yet posted a debounce + // message. So consider the case where current lux is beyond the + // threshold. It's possible that the light sensor may not report values + // if the light level does not change, so we need to occasionally + // synthesize sensor readings in order to make sure the brightness is + // adjusted accordingly. Note these thresholds may have changed since + // we entered the function because we called setAmbientLux and + // updateAutoBrightness along the way. + if (mLastObservedLux > mBrighteningLuxThreshold + || mLastObservedLux < mDarkeningLuxThreshold) { mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, time + SYNTHETIC_LIGHT_SENSOR_RATE_MILLIS); } diff --git a/services/java/com/android/server/print/PrintManagerService.java b/services/java/com/android/server/print/PrintManagerService.java index 8a3997a..98acc27 100644 --- a/services/java/com/android/server/print/PrintManagerService.java +++ b/services/java/com/android/server/print/PrintManagerService.java @@ -366,7 +366,7 @@ public final class PrintManagerService extends IPrintManager.Stub { pw.println("PRINT MANAGER STATE (dumpsys print)"); final int userStateCount = mUserStates.size(); for (int i = 0; i < userStateCount; i++) { - UserState userState = mUserStates.get(i); + UserState userState = mUserStates.valueAt(i); userState.dump(fd, pw, ""); pw.println(); } diff --git a/services/java/com/android/server/wifi/WifiService.java b/services/java/com/android/server/wifi/WifiService.java index d471b57..f2efde1 100644 --- a/services/java/com/android/server/wifi/WifiService.java +++ b/services/java/com/android/server/wifi/WifiService.java @@ -369,15 +369,17 @@ public final class WifiService extends IWifiManager.Stub { } private class BatchedScanRequest extends DeathRecipient { - BatchedScanSettings settings; - int uid; - int pid; + final BatchedScanSettings settings; + final int uid; + final int pid; + final WorkSource workSource; - BatchedScanRequest(BatchedScanSettings settings, IBinder binder) { + BatchedScanRequest(BatchedScanSettings settings, IBinder binder, WorkSource ws) { super(0, null, binder, null); this.settings = settings; this.uid = getCallingUid(); this.pid = getCallingPid(); + workSource = ws; } public void binderDied() { stopBatchedScan(settings, uid, pid); @@ -406,12 +408,19 @@ public final class WifiService extends IWifiManager.Stub { /** * see {@link android.net.wifi.WifiManager#requestBatchedScan()} */ - public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder) { + public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder, + WorkSource workSource) { enforceChangePermission(); + if (workSource != null) { + enforceWorkSourcePermission(); + // WifiManager currently doesn't use names, so need to clear names out of the + // supplied WorkSource to allow future WorkSource combining. + workSource.clearNames(); + } if (mBatchedScanSupported == false) return false; requested = new BatchedScanSettings(requested); if (requested.isInvalid()) return false; - BatchedScanRequest r = new BatchedScanRequest(requested, binder); + BatchedScanRequest r = new BatchedScanRequest(requested, binder, workSource); synchronized(mBatchedScanners) { mBatchedScanners.add(r); resolveBatchedScannersLocked(); @@ -468,18 +477,48 @@ public final class WifiService extends IWifiManager.Stub { private void resolveBatchedScannersLocked() { BatchedScanSettings setting = new BatchedScanSettings(); + WorkSource responsibleWorkSource = null; int responsibleUid = 0; + double responsibleCsph = 0; // Channel Scans Per Hour if (mBatchedScanners.size() == 0) { - mWifiStateMachine.setBatchedScanSettings(null, 0); + mWifiStateMachine.setBatchedScanSettings(null, 0, 0, null); return; } for (BatchedScanRequest r : mBatchedScanners) { BatchedScanSettings s = r.settings; + + // evaluate responsibility + int currentChannelCount; + int currentScanInterval; + double currentCsph; + + if (s.channelSet == null || s.channelSet.isEmpty()) { + // all channels - 11 B and 9 A channels roughly. + currentChannelCount = 9 + 11; + } else { + currentChannelCount = s.channelSet.size(); + // these are rough est - no real need to correct for reg-domain; + if (s.channelSet.contains("A")) currentChannelCount += (9 - 1); + if (s.channelSet.contains("B")) currentChannelCount += (11 - 1); + + } + if (s.scanIntervalSec == BatchedScanSettings.UNSPECIFIED) { + currentScanInterval = BatchedScanSettings.DEFAULT_INTERVAL_SEC; + } else { + currentScanInterval = s.scanIntervalSec; + } + currentCsph = 60 * 60 * currentChannelCount / currentScanInterval; + + if (currentCsph > responsibleCsph) { + responsibleUid = r.uid; + responsibleWorkSource = r.workSource; + responsibleCsph = currentCsph; + } + if (s.maxScansPerBatch != BatchedScanSettings.UNSPECIFIED && s.maxScansPerBatch < setting.maxScansPerBatch) { setting.maxScansPerBatch = s.maxScansPerBatch; - responsibleUid = r.uid; } if (s.maxApPerScan != BatchedScanSettings.UNSPECIFIED && (setting.maxApPerScan == BatchedScanSettings.UNSPECIFIED || @@ -489,7 +528,6 @@ public final class WifiService extends IWifiManager.Stub { if (s.scanIntervalSec != BatchedScanSettings.UNSPECIFIED && s.scanIntervalSec < setting.scanIntervalSec) { setting.scanIntervalSec = s.scanIntervalSec; - responsibleUid = r.uid; } if (s.maxApForDistance != BatchedScanSettings.UNSPECIFIED && (setting.maxApForDistance == BatchedScanSettings.UNSPECIFIED || @@ -511,7 +549,8 @@ public final class WifiService extends IWifiManager.Stub { } setting.constrain(); - mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid); + mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid, (int)responsibleCsph, + responsibleWorkSource); } private void enforceAccessPermission() { diff --git a/services/java/com/android/server/wm/AppWindowToken.java b/services/java/com/android/server/wm/AppWindowToken.java index 8cc1d02..e98014b 100644 --- a/services/java/com/android/server/wm/AppWindowToken.java +++ b/services/java/com/android/server/wm/AppWindowToken.java @@ -53,6 +53,7 @@ class AppWindowToken extends WindowToken { int groupId = -1; boolean appFullscreen; int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + boolean layoutConfigChanges; boolean showWhenLocked; // The input dispatching timeout for this application token in nanoseconds. diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java index 52f2325..d358b4c 100644 --- a/services/java/com/android/server/wm/DisplayContent.java +++ b/services/java/com/android/server/wm/DisplayContent.java @@ -25,9 +25,11 @@ import android.app.ActivityManager.StackBoxInfo; import android.graphics.Rect; import android.graphics.Region; import android.os.Debug; +import android.util.EventLog; import android.util.Slog; import android.view.Display; import android.view.DisplayInfo; +import com.android.server.EventLogTags; import java.io.PrintWriter; import java.util.ArrayList; @@ -97,9 +99,6 @@ class DisplayContent { /** True when the home StackBox is at the top of mStackBoxes, false otherwise. */ private TaskStack mHomeStack = null; - /** Sorted most recent at top, oldest at [0]. */ - ArrayList<TaskStack> mStackHistory = new ArrayList<TaskStack>(); - /** Detect user tapping outside of current focused stack bounds .*/ StackTapPointerEventListener mTapDetector; @@ -107,7 +106,7 @@ class DisplayContent { Region mTouchExcludeRegion = new Region(); /** Save allocating when retrieving tasks */ - ArrayList<Task> mTaskHistory = new ArrayList<Task>(); + private ArrayList<Task> mTaskHistory = new ArrayList<Task>(); /** Save allocating when calculating rects */ Rect mTmpRect = new Rect(); @@ -160,12 +159,6 @@ class DisplayContent { return mStackBoxes.get(0).mStack != mHomeStack; } - void moveStack(TaskStack stack, boolean toTop) { - mStackHistory.remove(stack); - mStackHistory.add(toTop ? mStackHistory.size() : 0, stack); - mService.moveStackWindowsLocked(this); - } - public boolean isPrivate() { return (mDisplay.getFlags() & Display.FLAG_PRIVATE) != 0; } @@ -200,6 +193,7 @@ class DisplayContent { } mTaskHistory.add(taskNdx, task); + EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, task.taskId, toTop ? 1 : 0, taskNdx); } void removeTask(Task task) { @@ -277,6 +271,8 @@ class DisplayContent { if (newStack != null) { layoutNeeded = true; } + EventLog.writeEvent(EventLogTags.WM_STACK_CREATED, stackId, relativeStackBoxId, position, + (int)(weight * 100 + 0.5)); return newStack; } @@ -345,6 +341,7 @@ class DisplayContent { boolean moveHomeStackBox(boolean toTop) { if (DEBUG_STACK) Slog.d(TAG, "moveHomeStackBox: toTop=" + toTop + " Callers=" + Debug.getCallers(4)); + EventLog.writeEvent(EventLogTags.WM_HOME_STACK_MOVED, toTop ? 1 : 0); switch (mStackBoxes.size()) { case 0: throw new RuntimeException("moveHomeStackBox: No home StackBox!"); case 1: return false; // Only the home StackBox exists. diff --git a/services/java/com/android/server/wm/StackBox.java b/services/java/com/android/server/wm/StackBox.java index d054e9a..d351925 100644 --- a/services/java/com/android/server/wm/StackBox.java +++ b/services/java/com/android/server/wm/StackBox.java @@ -243,10 +243,6 @@ public class StackBox { /** Remove this box and propagate its sibling's content up to their parent. * @return The first stackId of the resulting StackBox. */ int remove() { - if (mStack != null) { - if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: removing stackId=" + mStack.mStackId); - mDisplayContent.mStackHistory.remove(mStack); - } mDisplayContent.layoutNeeded = true; if (mParent == null) { diff --git a/services/java/com/android/server/wm/Task.java b/services/java/com/android/server/wm/Task.java index d9acbb9..13fdbc8 100644 --- a/services/java/com/android/server/wm/Task.java +++ b/services/java/com/android/server/wm/Task.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import android.util.EventLog; +import com.android.server.EventLogTags; + class Task { // private final String TAG = "TaskGroup"; TaskStack mStack; @@ -41,6 +44,8 @@ class Task { boolean removeAppToken(AppWindowToken wtoken) { mAppTokens.remove(wtoken); if (mAppTokens.size() == 0) { + EventLog.writeEvent(com.android.server.EventLogTags.WM_TASK_REMOVED, taskId, + "removeAppToken: last token"); mStack.removeTask(this); return true; } diff --git a/services/java/com/android/server/wm/TaskStack.java b/services/java/com/android/server/wm/TaskStack.java index 34bef68..cb29df4 100644 --- a/services/java/com/android/server/wm/TaskStack.java +++ b/services/java/com/android/server/wm/TaskStack.java @@ -21,8 +21,10 @@ import static com.android.server.wm.WindowManagerService.TAG; import android.graphics.Rect; import android.os.Debug; +import android.util.EventLog; import android.util.Slog; import android.util.TypedValue; +import com.android.server.EventLogTags; import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; @@ -45,7 +47,7 @@ public class TaskStack { /** The Tasks that define this stack. Oldest Tasks are at the bottom. The ordering must match * mTaskHistory in the ActivityStack with the same mStackId */ - private ArrayList<Task> mTasks = new ArrayList<Task>(); + private final ArrayList<Task> mTasks = new ArrayList<Task>(); /** The StackBox this sits in. */ StackBox mStackBox; @@ -70,7 +72,6 @@ public class TaskStack { mService = service; mStackId = stackId; mDisplayContent = displayContent; - final int displayId = displayContent.getDisplayId(); mDimLayer = new DimLayer(service, this); mAnimationBackgroundSurface = new DimLayer(service, this); } @@ -152,6 +153,7 @@ public class TaskStack { int remove() { mAnimationBackgroundSurface.destroySurface(); mDimLayer.destroySurface(); + EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId); return mStackBox.remove(); } @@ -269,6 +271,8 @@ public class TaskStack { for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) { final WindowState win = windows.get(winNdx); if (!resizingWindows.contains(win)) { + if (WindowManagerService.DEBUG_RESIZE) Slog.d(TAG, + "setBounds: Resizing " + win); resizingWindows.add(win); } win.mUnderStatusBar = underStatusBar; diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java index cd46bb8..91f15f3 100644 --- a/services/java/com/android/server/wm/WindowAnimator.java +++ b/services/java/com/android/server/wm/WindowAnimator.java @@ -245,7 +245,7 @@ public class WindowAnimator { mForceHiding = KEYGUARD_ANIMATING_OUT; } } else { - mForceHiding = KEYGUARD_SHOWN; + mForceHiding = win.isDrawnLw() ? KEYGUARD_SHOWN : KEYGUARD_NOT_SHOWN; } } if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 63e09db..3ed5076 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -578,10 +578,13 @@ public class WindowManagerService extends IWindowManager.Stub private boolean mUpdateRotation = false; boolean mWallpaperActionPending = false; - private static final int DISPLAY_CONTENT_UNKNOWN = 0; - private static final int DISPLAY_CONTENT_MIRROR = 1; - private static final int DISPLAY_CONTENT_UNIQUE = 2; - private int mDisplayHasContent = DISPLAY_CONTENT_UNKNOWN; + // Set to true when the display contains content to show the user. + // When false, the display manager may choose to mirror or blank the display. + boolean mDisplayHasContent = false; + + // Only set while traversing the default display based on its content. + // Affects the behavior of mirroring on secondary displays. + boolean mObscureApplicationContentOnSecondaryDisplays = false; } final LayoutFields mInnerFields = new LayoutFields(); @@ -3394,16 +3397,17 @@ public class WindowManagerService extends IWindowManager.Stub if (stack == null) { throw new IllegalArgumentException("addAppToken: invalid stackId=" + stackId); } + EventLog.writeEvent(EventLogTags.WM_TASK_CREATED, taskId, stackId); Task task = new Task(atoken, stack, userId); mTaskIdToTask.put(taskId, task); stack.addTask(task, true); - stack.getDisplayContent().moveStack(stack, true); return task; } @Override public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId, - int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId) { + int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId, + int configChanges) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "addAppToken()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); @@ -3435,6 +3439,8 @@ public class WindowManagerService extends IWindowManager.Stub atoken.appFullscreen = fullscreen; atoken.showWhenLocked = showWhenLocked; atoken.requestedOrientation = requestedOrientation; + atoken.layoutConfigChanges = (configChanges & + (ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) != 0; if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "addAppToken: " + atoken + " to stack=" + stackId + " task=" + taskId + " at " + addPos); @@ -4303,10 +4309,6 @@ public class WindowManagerService extends IWindowManager.Stub // If we are preparing an app transition, then delay changing // the visibility of this token until we execute that transition. if (okToDisplay() && mAppTransition.isTransitionSet()) { - // Already in requested state, don't do anything more. - if (wtoken.hiddenRequested != visible) { - return; - } wtoken.hiddenRequested = !visible; if (!wtoken.startingDisplayed) { @@ -4792,7 +4794,6 @@ public class WindowManagerService extends IWindowManager.Stub displayContent.moveHomeStackBox(isHomeStackTask); } stack.moveTaskToTop(task); - displayContent.moveStack(stack, true); } } finally { Binder.restoreCallingIdentity(origId); @@ -4846,7 +4847,6 @@ public class WindowManagerService extends IWindowManager.Stub weight); if (stack != null) { mStackIdToStack.put(stackId, stack); - displayContent.moveStack(stack, true); performLayoutAndPlaceSurfacesLocked(); return; } @@ -4878,6 +4878,7 @@ public class WindowManagerService extends IWindowManager.Stub return; } final TaskStack stack = task.mStack; + EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, taskId, "removeTask"); stack.removeTask(task); stack.getDisplayContent().layoutNeeded = true; } @@ -8269,7 +8270,9 @@ public class WindowManagerService extends IWindowManager.Stub // windows, since that means "perform layout as normal, // just don't display"). if (!gone || !win.mHaveFrame || win.mLayoutNeeded - || (win.mAttrs.type == TYPE_KEYGUARD && win.isConfigChanged()) + || ((win.isConfigChanged() || win.setInsetsChanged()) && + (win.mAttrs.type == TYPE_KEYGUARD || + win.mAppToken != null && win.mAppToken.layoutConfigChanges)) || win.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) { if (!win.mLayoutAttached) { if (initial) { @@ -8703,12 +8706,7 @@ public class WindowManagerService extends IWindowManager.Stub private void updateResizingWindows(final WindowState w) { final WindowStateAnimator winAnimator = w.mWinAnimator; if (w.mHasSurface && w.mLayoutSeq == mLayoutSeq) { - w.mOverscanInsetsChanged |= - !w.mLastOverscanInsets.equals(w.mOverscanInsets); - w.mContentInsetsChanged |= - !w.mLastContentInsets.equals(w.mContentInsets); - w.mVisibleInsetsChanged |= - !w.mLastVisibleInsets.equals(w.mVisibleInsets); + w.setInsetsChanged(); boolean configChanged = w.isConfigChanged(); if (DEBUG_CONFIGURATION && configChanged) { Slog.v(TAG, "Win " + w + " config changed: " @@ -8783,6 +8781,14 @@ public class WindowManagerService extends IWindowManager.Stub final WindowManager.LayoutParams attrs = w.mAttrs; final int attrFlags = attrs.flags; final boolean canBeSeen = w.isDisplayedLw(); + final boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn(); + + if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) { + // This window completely covers everything behind it, + // so we want to leave all of them as undimmed (for + // performance reasons). + mInnerFields.mObscured = true; + } if (w.mHasSurface) { if ((attrFlags&FLAG_KEEP_SCREEN_ON) != 0) { @@ -8811,22 +8817,24 @@ public class WindowManagerService extends IWindowManager.Stub } if (canBeSeen) { - if (type == TYPE_DREAM || type == TYPE_KEYGUARD) { - mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_MIRROR; - } else if (mInnerFields.mDisplayHasContent - == LayoutFields.DISPLAY_CONTENT_UNKNOWN) { - mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_UNIQUE; + // This function assumes that the contents of the default display are + // processed first before secondary displays. + if (w.mDisplayContent.isDefaultDisplay) { + // While a dream or keyguard is showing, obscure ordinary application + // content on secondary displays (by forcibly enabling mirroring unless + // there is other content we want to show) but still allow opaque + // keyguard dialogs to be shown. + if (type == TYPE_DREAM || type == TYPE_KEYGUARD) { + mInnerFields.mObscureApplicationContentOnSecondaryDisplays = true; + } + mInnerFields.mDisplayHasContent = true; + } else if (!mInnerFields.mObscureApplicationContentOnSecondaryDisplays + || (mInnerFields.mObscured && type == TYPE_KEYGUARD_DIALOG)) { + // Allow full screen keyguard presentation dialogs to be seen. + mInnerFields.mDisplayHasContent = true; } } } - - boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn(); - if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) { - // This window completely covers everything behind it, - // so we want to leave all of them as undimmed (for - // performance reasons). - mInnerFields.mObscured = true; - } } private void handleFlagDimBehind(WindowState w, int innerDw, int innerDh) { @@ -8904,7 +8912,7 @@ public class WindowManagerService extends IWindowManager.Stub mInnerFields.mScreenBrightness = -1; mInnerFields.mButtonBrightness = -1; mInnerFields.mUserActivityTimeout = -1; - mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_UNKNOWN; + mInnerFields.mObscureApplicationContentOnSecondaryDisplays = false; mTransactionSequence++; @@ -8939,10 +8947,8 @@ public class WindowManagerService extends IWindowManager.Stub final int innerDh = displayInfo.appHeight; final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); - // Reset for each display unless we are forcing mirroring. - if (mInnerFields.mDisplayHasContent != LayoutFields.DISPLAY_CONTENT_MIRROR) { - mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_UNKNOWN; - } + // Reset for each display. + mInnerFields.mDisplayHasContent = false; int repeats = 0; do { @@ -9151,20 +9157,8 @@ public class WindowManagerService extends IWindowManager.Stub updateResizingWindows(w); } - final boolean hasUniqueContent; - switch (mInnerFields.mDisplayHasContent) { - case LayoutFields.DISPLAY_CONTENT_MIRROR: - hasUniqueContent = isDefaultDisplay; - break; - case LayoutFields.DISPLAY_CONTENT_UNIQUE: - hasUniqueContent = true; - break; - case LayoutFields.DISPLAY_CONTENT_UNKNOWN: - default: - hasUniqueContent = false; - break; - } - mDisplayManagerService.setDisplayHasContent(displayId, hasUniqueContent, + mDisplayManagerService.setDisplayHasContent(displayId, + mInnerFields.mDisplayHasContent, true /* inTraversal, must call performTraversalInTrans... below */); getDisplayContentLocked(displayId).stopDimmingIfNeeded(); diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index 2d08792..4d53cea 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -701,6 +701,13 @@ final class WindowState implements WindowManagerPolicy.WindowState { return mAppToken != null ? mAppToken.appToken : null; } + boolean setInsetsChanged() { + mOverscanInsetsChanged |= !mLastOverscanInsets.equals(mOverscanInsets); + mContentInsetsChanged |= !mLastContentInsets.equals(mContentInsets); + mVisibleInsetsChanged |= !mLastVisibleInsets.equals(mVisibleInsets); + return mOverscanInsetsChanged || mContentInsetsChanged || mVisibleInsetsChanged; + } + public int getDisplayId() { return mDisplayContent.getDisplayId(); } diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index ab429fd..e3a1aa6 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -1957,6 +1957,27 @@ public class PhoneNumberUtils } /** + * Process phone number for CDMA, converting plus code using the home network number format. + * This is used for outgoing SMS messages. + * + * @param dialStr the original dial string + * @return the converted dial string + * @hide for internal use + */ + public static String cdmaCheckAndProcessPlusCodeForSms(String dialStr) { + if (!TextUtils.isEmpty(dialStr)) { + if (isReallyDialable(dialStr.charAt(0)) && isNonSeparator(dialStr)) { + String defaultIso = SystemProperties.get(PROPERTY_ICC_OPERATOR_ISO_COUNTRY, ""); + if (!TextUtils.isEmpty(defaultIso)) { + int format = getFormatTypeFromCountryCode(defaultIso); + return cdmaCheckAndProcessPlusCodeByNumberFormat(dialStr, format, format); + } + } + } + return dialStr; + } + + /** * This function should be called from checkAndProcessPlusCode only * And it is used for test purpose also. * diff --git a/tests/RemoteDisplayProvider/Android.mk b/tests/RemoteDisplayProvider/Android.mk new file mode 100644 index 0000000..2f4b343 --- /dev/null +++ b/tests/RemoteDisplayProvider/Android.mk @@ -0,0 +1,26 @@ +# Copyright (C) 2013 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. + +LOCAL_PATH := $(call my-dir) + +# Build the application. +include $(CLEAR_VARS) +LOCAL_PACKAGE_NAME := RemoteDisplayProviderTest +LOCAL_MODULE_TAGS := tests +LOCAL_SDK_VERSION := current +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_RESOURCE_DIR = $(LOCAL_PATH)/res +LOCAL_JAVA_LIBRARIES := com.android.media.remotedisplay +LOCAL_CERTIFICATE := platform +include $(BUILD_PACKAGE) diff --git a/tests/RemoteDisplayProvider/AndroidManifest.xml b/tests/RemoteDisplayProvider/AndroidManifest.xml new file mode 100644 index 0000000..afb7c78 --- /dev/null +++ b/tests/RemoteDisplayProvider/AndroidManifest.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.media.remotedisplay.test" > + + <uses-sdk android:minSdkVersion="19" /> + <uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT"/> + + <application android:label="@string/app_name" + android:icon="@drawable/ic_app"> + <uses-library android:name="com.android.media.remotedisplay" + android:required="true" /> + + <service android:name=".RemoteDisplayProviderService" + android:label="@string/app_name" + android:exported="true" + android:permission="android.permission.BIND_REMOTE_DISPLAY"> + <intent-filter> + <action android:name="com.android.media.remotedisplay.RemoteDisplayProvider"/> + </intent-filter> + </service> + + </application> +</manifest> diff --git a/tests/RemoteDisplayProvider/README b/tests/RemoteDisplayProvider/README new file mode 100644 index 0000000..8bf0130 --- /dev/null +++ b/tests/RemoteDisplayProvider/README @@ -0,0 +1,16 @@ +This directory contains sample code to test system integration with +remote display providers using the API declared by the +com.android.media.remotedisplay.jar library. + +--- DESCRIPTION --- + +The application registers a service that publishes a few different +remote display routes. Behavior can be controlled by modifying the +code. + +To exercise the provider, use System UI features for connecting to +wireless displays or launch an activity that uses the MediaRouter, +such as the PresentationWithMediaRouterActivity in ApiDemos. + +This code is mainly intended for development and not meant to be +used as an example implementation of a robust remote display provider. diff --git a/tests/RemoteDisplayProvider/res/drawable-hdpi/ic_app.png b/tests/RemoteDisplayProvider/res/drawable-hdpi/ic_app.png Binary files differnew file mode 100755 index 0000000..66a1984 --- /dev/null +++ b/tests/RemoteDisplayProvider/res/drawable-hdpi/ic_app.png diff --git a/tests/RemoteDisplayProvider/res/drawable-mdpi/ic_app.png b/tests/RemoteDisplayProvider/res/drawable-mdpi/ic_app.png Binary files differnew file mode 100644 index 0000000..5ae7701 --- /dev/null +++ b/tests/RemoteDisplayProvider/res/drawable-mdpi/ic_app.png diff --git a/tests/RemoteDisplayProvider/res/values/strings.xml b/tests/RemoteDisplayProvider/res/values/strings.xml new file mode 100644 index 0000000..dd82d2c --- /dev/null +++ b/tests/RemoteDisplayProvider/res/values/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 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. +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="app_name">Remote Display Provider Test</string> +</resources> diff --git a/tests/RemoteDisplayProvider/src/com/android/media/remotedisplay/test/RemoteDisplayProviderService.java b/tests/RemoteDisplayProvider/src/com/android/media/remotedisplay/test/RemoteDisplayProviderService.java new file mode 100644 index 0000000..611d7e4 --- /dev/null +++ b/tests/RemoteDisplayProvider/src/com/android/media/remotedisplay/test/RemoteDisplayProviderService.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2013 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.media.remotedisplay.test; + +import com.android.media.remotedisplay.RemoteDisplay; +import com.android.media.remotedisplay.RemoteDisplayProvider; + +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.util.Log; + +/** + * Remote display provider implementation that publishes working routes. + */ +public class RemoteDisplayProviderService extends Service { + private static final String TAG = "RemoteDisplayProviderTest"; + + private Provider mProvider; + + @Override + public IBinder onBind(Intent intent) { + if (intent.getAction().equals(RemoteDisplayProvider.SERVICE_INTERFACE)) { + if (mProvider == null) { + mProvider = new Provider(); + return mProvider.getBinder(); + } + } + return null; + } + + final class Provider extends RemoteDisplayProvider { + private RemoteDisplay mTestDisplay1; // variable volume + private RemoteDisplay mTestDisplay2; // fixed volume + private RemoteDisplay mTestDisplay3; // not available + private RemoteDisplay mTestDisplay4; // in use + private RemoteDisplay mTestDisplay5; // available but ignores request to connect + private RemoteDisplay mTestDisplay6; // available but never finishes connecting + private RemoteDisplay mTestDisplay7; // blinks in and out of existence + private RemoteDisplay mTestDisplay8; // available but connecting attempt flakes out + private RemoteDisplay mTestDisplay9; // available but connection flakes out + private RemoteDisplay mTestDisplay10; // available and reconnects periodically + + private final Handler mHandler; + private boolean mBlinking; + + public Provider() { + super(RemoteDisplayProviderService.this); + mHandler = new Handler(getMainLooper()); + } + + @Override + public void onDiscoveryModeChanged(int mode) { + Log.d(TAG, "onDiscoveryModeChanged: mode=" + mode); + + if (mode != DISCOVERY_MODE_NONE) { + // When discovery begins, go find all of the routes. + if (mTestDisplay1 == null) { + mTestDisplay1 = new RemoteDisplay("testDisplay1", + "Test Display 1 (variable)"); + mTestDisplay1.setDescription("Variable volume"); + mTestDisplay1.setStatus(RemoteDisplay.STATUS_AVAILABLE); + mTestDisplay1.setVolume(10); + mTestDisplay1.setVolumeHandling(RemoteDisplay.PLAYBACK_VOLUME_VARIABLE); + mTestDisplay1.setVolumeMax(15); + addDisplay(mTestDisplay1); + } + if (mTestDisplay2 == null) { + mTestDisplay2 = new RemoteDisplay("testDisplay2", + "Test Display 2 (fixed)"); + mTestDisplay2.setDescription("Fixed volume"); + mTestDisplay2.setStatus(RemoteDisplay.STATUS_AVAILABLE); + addDisplay(mTestDisplay2); + } + if (mTestDisplay3 == null) { + mTestDisplay3 = new RemoteDisplay("testDisplay3", + "Test Display 3 (unavailable)"); + mTestDisplay3.setDescription("Always unavailable"); + mTestDisplay3.setStatus(RemoteDisplay.STATUS_NOT_AVAILABLE); + addDisplay(mTestDisplay3); + } + if (mTestDisplay4 == null) { + mTestDisplay4 = new RemoteDisplay("testDisplay4", + "Test Display 4 (in-use)"); + mTestDisplay4.setDescription("Always in-use"); + mTestDisplay4.setStatus(RemoteDisplay.STATUS_IN_USE); + addDisplay(mTestDisplay4); + } + if (mTestDisplay5 == null) { + mTestDisplay5 = new RemoteDisplay("testDisplay5", + "Test Display 5 (connect ignored)"); + mTestDisplay5.setDescription("Ignores connect"); + mTestDisplay5.setStatus(RemoteDisplay.STATUS_AVAILABLE); + addDisplay(mTestDisplay5); + } + if (mTestDisplay6 == null) { + mTestDisplay6 = new RemoteDisplay("testDisplay6", + "Test Display 6 (connect hangs)"); + mTestDisplay6.setDescription("Never finishes connecting"); + mTestDisplay6.setStatus(RemoteDisplay.STATUS_AVAILABLE); + addDisplay(mTestDisplay6); + } + if (mTestDisplay8 == null) { + mTestDisplay8 = new RemoteDisplay("testDisplay8", + "Test Display 8 (flaky when connecting)"); + mTestDisplay8.setDescription("Aborts spontaneously while connecting"); + mTestDisplay8.setStatus(RemoteDisplay.STATUS_AVAILABLE); + addDisplay(mTestDisplay8); + } + if (mTestDisplay9 == null) { + mTestDisplay9 = new RemoteDisplay("testDisplay9", + "Test Display 9 (flaky when connected)"); + mTestDisplay9.setDescription("Aborts spontaneously while connected"); + mTestDisplay9.setStatus(RemoteDisplay.STATUS_AVAILABLE); + addDisplay(mTestDisplay9); + } + if (mTestDisplay10 == null) { + mTestDisplay10 = new RemoteDisplay("testDisplay10", + "Test Display 10 (reconnects periodically)"); + mTestDisplay10.setDescription("Reconnects spontaneously"); + mTestDisplay10.setStatus(RemoteDisplay.STATUS_AVAILABLE); + addDisplay(mTestDisplay10); + } + } else { + // When discovery ends, go hide some of the routes we can't actually use. + // This isn't something a normal route provider would do though. + // The routes will usually stay published. + if (mTestDisplay3 != null) { + removeDisplay(mTestDisplay3); + mTestDisplay3 = null; + } + if (mTestDisplay4 != null) { + removeDisplay(mTestDisplay4); + mTestDisplay4 = null; + } + } + + // When active discovery is on, pretend there's a route that we can't quite + // reach that blinks in and out of existence. + if (mode == DISCOVERY_MODE_ACTIVE) { + if (!mBlinking) { + mBlinking = true; + mHandler.post(mBlink); + } + } else { + mBlinking = false; + } + } + + @Override + public void onConnect(final RemoteDisplay display) { + Log.d(TAG, "onConnect: display.getId()=" + display.getId()); + + if (display == mTestDisplay1 || display == mTestDisplay2) { + display.setStatus(RemoteDisplay.STATUS_CONNECTING); + updateDisplay(display); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if ((display == mTestDisplay1 || display == mTestDisplay2) + && display.getStatus() == RemoteDisplay.STATUS_CONNECTING) { + display.setStatus(RemoteDisplay.STATUS_CONNECTED); + updateDisplay(display); + } + } + }, 2000); + } else if (display == mTestDisplay6 || display == mTestDisplay7) { + // never finishes connecting + display.setStatus(RemoteDisplay.STATUS_CONNECTING); + updateDisplay(display); + } else if (display == mTestDisplay8) { + // flakes out while connecting + display.setStatus(RemoteDisplay.STATUS_CONNECTING); + updateDisplay(display); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if ((display == mTestDisplay8) + && display.getStatus() == RemoteDisplay.STATUS_CONNECTING) { + display.setStatus(RemoteDisplay.STATUS_AVAILABLE); + updateDisplay(display); + } + } + }, 2000); + } else if (display == mTestDisplay9) { + // flakes out when connected + display.setStatus(RemoteDisplay.STATUS_CONNECTING); + updateDisplay(display); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if ((display == mTestDisplay9) + && display.getStatus() == RemoteDisplay.STATUS_CONNECTING) { + display.setStatus(RemoteDisplay.STATUS_CONNECTED); + updateDisplay(display); + } + } + }, 2000); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if ((display == mTestDisplay9) + && display.getStatus() == RemoteDisplay.STATUS_CONNECTED) { + display.setStatus(RemoteDisplay.STATUS_AVAILABLE); + updateDisplay(display); + } + } + }, 5000); + } else if (display == mTestDisplay10) { + display.setStatus(RemoteDisplay.STATUS_CONNECTING); + updateDisplay(display); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (display == mTestDisplay10) { + if (display.getStatus() == RemoteDisplay.STATUS_CONNECTING) { + display.setStatus(RemoteDisplay.STATUS_CONNECTED); + updateDisplay(display); + mHandler.postDelayed(this, 7000); + } else if (display.getStatus() == RemoteDisplay.STATUS_CONNECTED) { + display.setStatus(RemoteDisplay.STATUS_CONNECTING); + updateDisplay(display); + mHandler.postDelayed(this, 2000); + } + } + } + }, 2000); + } + } + + @Override + public void onDisconnect(RemoteDisplay display) { + Log.d(TAG, "onDisconnect: display.getId()=" + display.getId()); + + if (display == mTestDisplay1 || display == mTestDisplay2 + || display == mTestDisplay6 || display == mTestDisplay8 + || display == mTestDisplay9 || display == mTestDisplay10) { + display.setStatus(RemoteDisplay.STATUS_AVAILABLE); + updateDisplay(display); + } + } + + @Override + public void onSetVolume(RemoteDisplay display, int volume) { + Log.d(TAG, "onSetVolume: display.getId()=" + display.getId() + + ", volume=" + volume); + + if (display == mTestDisplay1) { + display.setVolume(Math.max(0, Math.min(display.getVolumeMax(), volume))); + updateDisplay(display); + } + } + + @Override + public void onAdjustVolume(RemoteDisplay display, int delta) { + Log.d(TAG, "onAdjustVolume: display.getId()=" + display.getId() + + ", delta=" + delta); + + if (display == mTestDisplay1) { + display.setVolume(Math.max(0, Math.min(display.getVolumeMax(), + display .getVolume() + delta))); + updateDisplay(display); + } + } + + @Override + public void addDisplay(RemoteDisplay display) { + Log.d(TAG, "addDisplay: display=" + display); + super.addDisplay(display); + } + + @Override + public void removeDisplay(RemoteDisplay display) { + Log.d(TAG, "removeDisplay: display=" + display); + super.removeDisplay(display); + } + + @Override + public void updateDisplay(RemoteDisplay display) { + Log.d(TAG, "updateDisplay: display=" + display); + super.updateDisplay(display); + } + + private final Runnable mBlink = new Runnable() { + @Override + public void run() { + if (mTestDisplay7 == null) { + if (mBlinking) { + mTestDisplay7 = new RemoteDisplay("testDisplay7", + "Test Display 7 (blinky)"); + mTestDisplay7.setDescription("Comes and goes but can't connect"); + mTestDisplay7.setStatus(RemoteDisplay.STATUS_AVAILABLE); + addDisplay(mTestDisplay7); + mHandler.postDelayed(this, 7000); + } + } else { + removeDisplay(mTestDisplay7); + mTestDisplay7 = null; + if (mBlinking) { + mHandler.postDelayed(this, 4000); + } + } + } + }; + } +} diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java index e4c4214..df32ee1 100644 --- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java +++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java @@ -93,7 +93,7 @@ public class WindowManagerPermissionTests extends TestCase { } try { - mWm.addAppToken(0, null, 0, 0, 0, false, false, 0); + mWm.addAppToken(0, null, 0, 0, 0, false, false, 0, 0); fail("IWindowManager.addAppToken did not throw SecurityException as" + " expected"); } catch (SecurityException e) { diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk index 4e73568..ed497a5 100644 --- a/tools/layoutlib/Android.mk +++ b/tools/layoutlib/Android.mk @@ -31,6 +31,9 @@ built_framework_classes := $(call java-lib-files,framework-base) built_core_dep := $(call java-lib-deps,core) built_core_classes := $(call java-lib-files,core) +built_ext_dep := $(call java-lib-deps,ext) +built_ext_classes := $(call java-lib-files,ext) + built_layoutlib_create_jar := $(call intermediates-dir-for, \ JAVA_LIBRARIES,layoutlib_create,HOST)/javalib.jar @@ -47,6 +50,7 @@ include $(BUILD_SYSTEM)/base_rules.mk $(LOCAL_BUILT_MODULE): $(built_core_dep) \ $(built_framework_dep) \ + $(built_ext_dep) \ $(built_layoutlib_create_jar) $(hide) echo "host layoutlib_create: $@" $(hide) mkdir -p $(dir $@) @@ -55,7 +59,8 @@ $(LOCAL_BUILT_MODULE): $(built_core_dep) \ $(hide) java -jar $(built_layoutlib_create_jar) \ $@ \ $(built_core_classes) \ - $(built_framework_classes) + $(built_framework_classes) \ + $(built_ext_classes) $(hide) ls -l $(built_framework_classes) diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java index ec284ac..93284db 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java @@ -308,8 +308,9 @@ public final class Bitmap_Delegate { } @LayoutlibDelegate - /*package*/ static void nativeRecycle(int nativeBitmap) { + /*package*/ static boolean nativeRecycle(int nativeBitmap) { sManager.removeJavaReferenceFor(nativeBitmap); + return true; } @LayoutlibDelegate diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index fd153af..dd2cbc1 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -81,7 +81,7 @@ public class IWindowManagerImpl implements IWindowManager { @Override public void addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, int arg4, - boolean arg5, boolean arg6, int arg7) + boolean arg5, boolean arg6, int arg7, int arg8) throws RemoteException { // TODO Auto-generated method stub diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java index 17b0eb6..bcd08eb4 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java @@ -290,7 +290,7 @@ abstract class CustomBar extends LinearLayout { TypedValue out = new TypedValue(); if (ResourceHelper.parseFloatAttribute("textSize", textSize.getValue(), out, true /*requireUnit*/)) { - textView.setTextSize( + textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, out.getDimension(bridgeContext.getResources().getDisplayMetrics())); } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java index 23d08e3..60f5331 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java @@ -273,7 +273,6 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso mContext.getRenderResources().setLogger(null); } - mContext = null; } public static BridgeContext getCurrentContext() { diff --git a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java index 06ae804..ad4103b 100644 --- a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java +++ b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java @@ -46,7 +46,7 @@ public class ICU_Delegate { // --- Native methods accessing ICU's database. @LayoutlibDelegate - /*package*/ static String getBestDateTimePattern(String skeleton, String localeName) { + /*package*/ static String getBestDateTimePatternNative(String skeleton, String localeName) { return DateTimePatternGenerator.getInstance(new ULocale(localeName)) .getBestPattern(skeleton); } @@ -167,7 +167,7 @@ public class ICU_Delegate { } @LayoutlibDelegate - /*package*/ static boolean initLocaleDataImpl(String locale, LocaleData result) { + /*package*/ static boolean initLocaleDataNative(String locale, LocaleData result) { // Used by Calendar. result.firstDayOfWeek = Integer.valueOf(1); diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java index ba23048..ee501d2 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java @@ -113,6 +113,7 @@ public class Main { "android.pim.*", // for datepicker "android.os.*", // for android.os.Handler "android.database.ContentObserver", // for Digital clock + "com.android.i18n.phonenumbers.*", // for TextView with autolink attribute }, excludeClasses); aa.analyze(); diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 5a1928c..149bda6 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -117,7 +117,7 @@ interface IWifiManager void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable); - boolean requestBatchedScan(in BatchedScanSettings requested, IBinder binder); + boolean requestBatchedScan(in BatchedScanSettings requested, IBinder binder, in WorkSource ws); void stopBatchedScan(in BatchedScanSettings requested); diff --git a/wifi/java/android/net/wifi/SupplicantStateTracker.java b/wifi/java/android/net/wifi/SupplicantStateTracker.java index f6a621f..e76eb17 100644 --- a/wifi/java/android/net/wifi/SupplicantStateTracker.java +++ b/wifi/java/android/net/wifi/SupplicantStateTracker.java @@ -55,7 +55,7 @@ class SupplicantStateTracker extends StateMachine { private static final int MAX_RETRIES_ON_AUTHENTICATION_FAILURE = 2; /* Maximum retries on assoc rejection events */ - private static final int MAX_RETRIES_ON_ASSOCIATION_REJECT = 4; + private static final int MAX_RETRIES_ON_ASSOCIATION_REJECT = 16; /* Tracks if networks have been disabled during a connection */ private boolean mNetworksDisabledDuringConnect = false; diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 7fc8bef..3b47e63 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -799,7 +799,13 @@ public class WifiManager { */ public boolean requestBatchedScan(BatchedScanSettings requested) { try { - return mService.requestBatchedScan(requested, new Binder()); + return mService.requestBatchedScan(requested, new Binder(), null); + } catch (RemoteException e) { return false; } + } + /** @hide */ + public boolean requestBatchedScan(BatchedScanSettings requested, WorkSource workSource) { + try { + return mService.requestBatchedScan(requested, new Binder(), workSource); } catch (RemoteException e) { return false; } } diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java index 520668e..c2f278a 100644 --- a/wifi/java/android/net/wifi/WifiNative.java +++ b/wifi/java/android/net/wifi/WifiNative.java @@ -39,7 +39,6 @@ import java.util.Locale; public class WifiNative { private static final boolean DBG = false; - private static final boolean VDBG = false; private final String mTAG; private static final int DEFAULT_GROUP_OWNER_INTENT = 6; @@ -118,12 +117,12 @@ public class WifiNative { public boolean connectToSupplicant() { // No synchronization necessary .. it is implemented in WifiMonitor - if (VDBG) localLog(mInterfacePrefix + "connectToSupplicant"); + localLog(mInterfacePrefix + "connectToSupplicant"); return connectToSupplicantNative(); } public void closeSupplicantConnection() { - if (VDBG) localLog(mInterfacePrefix + "closeSupplicantConnection"); + localLog(mInterfacePrefix + "closeSupplicantConnection"); closeSupplicantConnectionNative(); } @@ -136,9 +135,9 @@ public class WifiNative { if (DBG) Log.d(mTAG, "doBoolean: " + command); synchronized (mLock) { int cmdId = getNewCmdIdLocked(); - if (VDBG) localLog(cmdId + "->" + mInterfacePrefix + command); + localLog(cmdId + "->" + mInterfacePrefix + command); boolean result = doBooleanCommandNative(mInterfacePrefix + command); - if (VDBG) localLog(cmdId + "<-" + result); + localLog(cmdId + "<-" + result); if (DBG) Log.d(mTAG, " returned " + result); return result; } @@ -148,9 +147,9 @@ public class WifiNative { if (DBG) Log.d(mTAG, "doInt: " + command); synchronized (mLock) { int cmdId = getNewCmdIdLocked(); - if (VDBG) localLog(cmdId + "->" + mInterfacePrefix + command); + localLog(cmdId + "->" + mInterfacePrefix + command); int result = doIntCommandNative(mInterfacePrefix + command); - if (VDBG) localLog(cmdId + "<-" + result); + localLog(cmdId + "<-" + result); if (DBG) Log.d(mTAG, " returned " + result); return result; } @@ -160,9 +159,9 @@ public class WifiNative { if (DBG) Log.d(mTAG, "doString: " + command); synchronized (mLock) { int cmdId = getNewCmdIdLocked(); - if (VDBG) localLog(cmdId + "->" + mInterfacePrefix + command); + localLog(cmdId + "->" + mInterfacePrefix + command); String result = doStringCommandNative(mInterfacePrefix + command); - if (VDBG) localLog(cmdId + "<-" + result); + localLog(cmdId + "<-" + result); if (DBG) Log.d(mTAG, " returned " + result); return result; } @@ -298,7 +297,9 @@ public class WifiNative { * the firmware can remember before it runs out of buffer space or -1 on error */ public String setBatchedScanSettings(BatchedScanSettings settings) { - if (settings == null) return doStringCommand("DRIVER WLS_BATCHING STOP"); + if (settings == null) { + return doStringCommand("DRIVER WLS_BATCHING STOP"); + } String cmd = "DRIVER WLS_BATCHING SET SCANFREQ=" + settings.scanIntervalSec; cmd += " MSCAN=" + settings.maxScansPerBatch; if (settings.maxApPerScan != BatchedScanSettings.UNSPECIFIED) { diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index 78da7e7..eedd66f 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -57,6 +57,7 @@ import android.net.wifi.p2p.WifiP2pManager; import android.net.wifi.p2p.WifiP2pService; import android.os.BatteryStats; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.os.INetworkManagementService; import android.os.Message; @@ -416,7 +417,8 @@ public class WifiStateMachine extends StateMachine { /* change the batch scan settings. * arg1 = responsible UID - * obj = the new settings + * arg2 = csph (channel scans per hour) + * obj = bundle with the new settings and the optional worksource */ public static final int CMD_SET_BATCHED_SCAN = BASE + 135; public static final int CMD_START_NEXT_BATCHED_SCAN = BASE + 136; @@ -628,6 +630,15 @@ public class WifiStateMachine extends StateMachine { private BatchedScanSettings mBatchedScanSettings = null; + /** + * Track the worksource/cost of the current settings and track what's been noted + * to the battery stats, so we can mark the end of the previous when changing. + */ + private WorkSource mBatchedScanWorkSource = null; + private int mBatchedScanCsph = 0; + private WorkSource mNotedBatchedScanWorkSource = null; + private int mNotedBatchedScanCsph = 0; + public WifiStateMachine(Context context, String wlanInterface) { super("WifiStateMachine"); @@ -841,8 +852,14 @@ public class WifiStateMachine extends StateMachine { /** * start or stop batched scanning using the given settings */ - public void setBatchedScanSettings(BatchedScanSettings settings, int callingUid) { - sendMessage(CMD_SET_BATCHED_SCAN, callingUid, 0, settings); + private static final String BATCHED_SETTING = "batched_settings"; + private static final String BATCHED_WORKSOURCE = "batched_worksource"; + public void setBatchedScanSettings(BatchedScanSettings settings, int callingUid, int csph, + WorkSource workSource) { + Bundle bundle = new Bundle(); + bundle.putParcelable(BATCHED_SETTING, settings); + bundle.putParcelable(BATCHED_WORKSOURCE, workSource); + sendMessage(CMD_SET_BATCHED_SCAN, callingUid, csph, bundle); } public List<BatchedScanResult> syncGetBatchedScanResultsList() { @@ -861,6 +878,8 @@ public class WifiStateMachine extends StateMachine { } private void startBatchedScan() { + if (mBatchedScanSettings == null) return; + if (mDhcpActive) { if (DBG) log("not starting Batched Scans due to DHCP"); return; @@ -872,10 +891,10 @@ public class WifiStateMachine extends StateMachine { mAlarmManager.cancel(mBatchedScanIntervalIntent); String scansExpected = mWifiNative.setBatchedScanSettings(mBatchedScanSettings); - try { mExpectedBatchedScans = Integer.parseInt(scansExpected); setNextBatchedAlarm(mExpectedBatchedScans); + if (mExpectedBatchedScans > 0) noteBatchedScanStart(); } catch (NumberFormatException e) { stopBatchedScan(); loge("Exception parsing WifiNative.setBatchedScanSettings response " + e); @@ -918,25 +937,31 @@ public class WifiStateMachine extends StateMachine { } // return true if new/different - private boolean recordBatchedScanSettings(BatchedScanSettings settings) { - if (DBG) log("set batched scan to " + settings); + private boolean recordBatchedScanSettings(int responsibleUid, int csph, Bundle bundle) { + BatchedScanSettings settings = bundle.getParcelable(BATCHED_SETTING); + WorkSource responsibleWorkSource = bundle.getParcelable(BATCHED_WORKSOURCE); + + if (DBG) { + log("set batched scan to " + settings + " for uid=" + responsibleUid + + ", worksource=" + responsibleWorkSource); + } if (settings != null) { - // TODO - noteBatchedScanStart(message.arg1); if (settings.equals(mBatchedScanSettings)) return false; } else { if (mBatchedScanSettings == null) return false; - // TODO - noteBatchedScanStop(message.arg1); } mBatchedScanSettings = settings; + if (responsibleWorkSource == null) responsibleWorkSource = new WorkSource(responsibleUid); + mBatchedScanWorkSource = responsibleWorkSource; + mBatchedScanCsph = csph; return true; } private void stopBatchedScan() { mAlarmManager.cancel(mBatchedScanIntervalIntent); - if (mBatchedScanSettings != null) { - retrieveBatchedScanData(); - mWifiNative.setBatchedScanSettings(null); - } + retrieveBatchedScanData(); + mWifiNative.setBatchedScanSettings(null); + noteBatchedScanStop(); } private void setNextBatchedAlarm(int scansExpected) { @@ -1164,6 +1189,44 @@ public class WifiStateMachine extends StateMachine { } } + private void noteBatchedScanStart() { + // note the end of a previous scan set + if (mNotedBatchedScanWorkSource != null && + (mNotedBatchedScanWorkSource.equals(mBatchedScanWorkSource) == false || + mNotedBatchedScanCsph != mBatchedScanCsph)) { + try { + mBatteryStats.noteWifiBatchedScanStoppedFromSource(mNotedBatchedScanWorkSource); + } catch (RemoteException e) { + log(e.toString()); + } finally { + mNotedBatchedScanWorkSource = null; + mNotedBatchedScanCsph = 0; + } + } + // note the start of the new + try { + mBatteryStats.noteWifiBatchedScanStartedFromSource(mBatchedScanWorkSource, + mBatchedScanCsph); + mNotedBatchedScanWorkSource = mBatchedScanWorkSource; + mNotedBatchedScanCsph = mBatchedScanCsph; + } catch (RemoteException e) { + log(e.toString()); + } + } + + private void noteBatchedScanStop() { + if (mNotedBatchedScanWorkSource != null) { + try { + mBatteryStats.noteWifiBatchedScanStoppedFromSource(mNotedBatchedScanWorkSource); + } catch (RemoteException e) { + log(e.toString()); + } finally { + mNotedBatchedScanWorkSource = null; + mNotedBatchedScanCsph = 0; + } + } + } + private void startScanNative(int type) { mWifiNative.scan(type); mScanResultIsPending = true; @@ -1868,6 +1931,9 @@ public class WifiStateMachine extends StateMachine { return; } + // note that all these splits and substrings keep references to the original + // huge string buffer while the amount we really want is generally pretty small + // so make copies instead (one example b/11087956 wasted 400k of heap here). synchronized(mScanResultCache) { mScanResults = new ArrayList<ScanResult>(); String[] lines = scanResults.split("\n"); @@ -2307,9 +2373,7 @@ public class WifiStateMachine extends StateMachine { mDhcpActive = false; - if (mBatchedScanSettings != null) { - startBatchedScan(); - } + startBatchedScan(); } private void handleSuccessfulIpConfiguration(DhcpResults dhcpResults) { @@ -2447,7 +2511,7 @@ public class WifiStateMachine extends StateMachine { } break; case CMD_SET_BATCHED_SCAN: - recordBatchedScanSettings((BatchedScanSettings)message.obj); + recordBatchedScanSettings(message.arg1, message.arg2, (Bundle)message.obj); break; case CMD_POLL_BATCHED_SCAN: handleBatchedScanPollRequest(); @@ -2960,9 +3024,7 @@ public class WifiStateMachine extends StateMachine { mDhcpActive = false; - if (mBatchedScanSettings != null) { - startBatchedScan(); - } + startBatchedScan(); if (mOperationalMode != CONNECT_MODE) { mWifiNative.disconnect(); @@ -3021,8 +3083,10 @@ public class WifiStateMachine extends StateMachine { startScanNative(WifiNative.SCAN_WITH_CONNECTION_SETUP); break; case CMD_SET_BATCHED_SCAN: - recordBatchedScanSettings((BatchedScanSettings)message.obj); - startBatchedScan(); + if (recordBatchedScanSettings(message.arg1, message.arg2, + (Bundle)message.obj)) { + startBatchedScan(); + } break; case CMD_SET_COUNTRY_CODE: String country = (String) message.obj; @@ -3160,9 +3224,7 @@ public class WifiStateMachine extends StateMachine { updateBatteryWorkSource(null); mScanResults = new ArrayList<ScanResult>(); - if (mBatchedScanSettings != null) { - stopBatchedScan(); - } + stopBatchedScan(); final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); |
