am 1448d3cd: Merge "Pass a new samplesdir param to doclava as the starting point for generating samples browsing files. Removes older project-based configuration." into klp-docs
* commit '1448d3cd81a685c68b0102e8303c0db4e6e5668f':
Pass a new samplesdir param to doclava as the starting point for generating samples browsing files. Removes older project-based configuration.
diff --git a/Android.mk b/Android.mk
index 5a585e5..24091f1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -251,10 +251,14 @@
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 @@
" 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 @@
}
private void doWipe() {
+ String transport = nextArg();
+ if (transport == null) {
+ showUsage();
+ return;
+ }
+
String pkg = nextArg();
if (pkg == null) {
showUsage();
@@ -194,8 +200,8 @@
}
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 @@
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 @@
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 @@
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 @@
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 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 @@
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 @@
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 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 @@
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);
+ 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);
+ }
}
- mRouteTypes = types;
- if (types != 0) {
- mRouter.addCallback(types, mCallback);
- }
- 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;
+ if (mButton != null) {
+ return mButton.showDialogInternal();
}
-
- 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);
- }
+ return false;
}
@Override
@@ -136,36 +143,43 @@
@Override
public boolean isVisible() {
- return mRouter.getRouteCount() > 1;
+ return mRouter.isRouteAvailable(mRouteTypes,
+ MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE);
}
- private static class RouterCallback extends MediaRouter.SimpleCallback {
- private WeakReference<MediaRouteActionProvider> mAp;
+ private void refreshRoute() {
+ refreshVisibility();
+ }
- RouterCallback(MediaRouteActionProvider ap) {
- mAp = new WeakReference<MediaRouteActionProvider>(ap);
+ private static class MediaRouterCallback extends MediaRouter.SimpleCallback {
+ private final WeakReference<MediaRouteActionProvider> mProviderWeak;
+
+ 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) {
- router.removeCallback(this);
- return;
- }
+ refreshRoute(router);
+ }
- ap.refreshVisibility();
+ @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);
+ }
}
}
}
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.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.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 @@
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 @@
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,19 +97,87 @@
setRouteTypes(routeTypes);
}
- private void setRemoteIndicatorDrawable(Drawable d) {
- if (mRemoteIndicator != null) {
- mRemoteIndicator.setCallback(null);
- unscheduleDrawable(mRemoteIndicator);
+ /**
+ * 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();
}
- mRemoteIndicator = d;
- if (d != null) {
- d.setCallback(this);
- d.setState(getDrawableState());
- d.setVisible(getVisibility() == VISIBLE, false);
+ }
+
+ public void setExtendedSettingsClickListener(OnClickListener listener) {
+ mExtendedSettingsClickListener = listener;
+ }
+
+ /**
+ * 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;
}
- refreshDrawableState();
+ 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;
+ }
+ context = ((ContextWrapper)context).getBaseContext();
+ }
+ 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
@@ -120,29 +187,7 @@
if (!handled) {
playSoundEffect(SoundEffectConstants.CLICK);
}
-
- 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);
- }
- }
- }
- } else {
- showDialog();
- }
-
- return handled;
- }
-
- void setCheatSheetEnabled(boolean enable) {
- mCheatSheetEnabled = enable;
+ return showDialogInternal() || handled;
}
@Override
@@ -183,85 +228,9 @@
}
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 @@
}
}
+ 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 @@
@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 @@
@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() {
- if (mRouteTypes != 0) {
- mRouter.removeCallback(mRouterCallback);
- }
mAttachedToWindow = false;
+ if (mRouteTypes != 0) {
+ mRouter.removeCallback(mCallback);
+ }
+
super.onDetachedFromWindow();
}
@@ -386,93 +377,71 @@
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);
- }
- }
+ 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();
- /**
- * 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;
- }
-
- mDialogFragment = new MediaRouteChooserDialogFragment();
- mDialogFragment.setExtendedSettingsClickListener(mExtendedSettingsClickListener);
- mDialogFragment.setLauncherListener(new MediaRouteChooserDialogFragment.LauncherListener() {
- @Override
- public void onDetached(MediaRouteChooserDialogFragment detachedFragment) {
- mDialogFragment = null;
+ boolean needsRefresh = false;
+ if (mRemoteActive != isRemote) {
+ mRemoteActive = isRemote;
+ needsRefresh = true;
}
- });
- mDialogFragment.setRouteTypes(mRouteTypes);
- mDialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG);
+ if (mIsConnecting != isConnecting) {
+ mIsConnecting = isConnecting;
+ needsRefresh = true;
+ }
+
+ if (needsRefresh) {
+ refreshDrawableState();
+ }
+
+ setEnabled(mRouter.isRouteAvailable(mRouteTypes,
+ MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE));
+ }
}
- 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();
- }
- if (!(context instanceof Activity)) {
- throw new IllegalStateException("The MediaRouteButton's Context is not an Activity.");
- }
-
- return (Activity) context;
- }
-
- private class MediaRouteCallback extends MediaRouter.SimpleCallback {
- @Override
- public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
- updateRemoteIndicator();
- }
-
- @Override
- public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
- updateRemoteIndicator();
- }
-
- @Override
- public void onRouteChanged(MediaRouter router, RouteInfo info) {
- updateRemoteIndicator();
- }
-
+ private final class MediaRouterCallback extends MediaRouter.SimpleCallback {
@Override
public void onRouteAdded(MediaRouter router, RouteInfo info) {
- updateRouteCount();
+ refreshRoute();
}
@Override
public void onRouteRemoved(MediaRouter router, RouteInfo info) {
- updateRouteCount();
+ refreshRoute();
+ }
+
+ @Override
+ public void onRouteChanged(MediaRouter router, RouteInfo info) {
+ refreshRoute();
+ }
+
+ @Override
+ public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
+ refreshRoute();
+ }
+
+ @Override
+ 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 @@
| 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 @@
@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 @@
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,67 +398,6 @@
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;
- }
-
- 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;
- }
- }
- }
- }
-
- // 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);
- }
-
private int enforceWritePermission(String callingPkg, Uri uri) throws SecurityException {
enforceWritePermissionInner(uri);
if (mWriteOp != AppOpsManager.OP_NONE) {
@@ -466,67 +405,130 @@
}
return AppOpsManager.MODE_ALLOWED;
}
+ }
- private void enforceWritePermissionInner(Uri uri) throws SecurityException {
- final Context context = getContext();
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- String missingPerm = null;
+ /** {@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 (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;
+ }
}
- if (mExported) {
- final String componentPerm = getWritePermission();
- 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);
- // 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;
- }
+ 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 (allowDefaultWrite) return;
}
- // 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);
+ // 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);
+ }
+
+ /** {@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 (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;
+ }
+ }
+ }
+ }
+
+ // 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;
+ }
+
+ 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 @@
}
/** @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 @@
* {@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 @@
|| (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 @@
/**
* <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 @@
}
/**
+ * 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 @@
/**
* <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 @@
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 @@
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 @@
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 @@
stopRepeating();
try {
- mRemoteDevice.waitUntilIdle();
+ waitUntilIdle();
// TODO: mRemoteDevice.beginConfigure
// Delete all streams first (to free up HW resources)
@@ -279,6 +279,10 @@
checkIfCameraClosed();
int requestId;
+ if (repeating) {
+ stopRepeating();
+ }
+
try {
requestId = mRemoteDevice.submitRequest(request, repeating);
} catch (CameraRuntimeException e) {
@@ -293,7 +297,7 @@
}
if (repeating) {
- mRepeatingRequestIdStack.add(requestId);
+ mRepeatingRequestId = requestId;
}
if (mIdle) {
@@ -327,8 +331,13 @@
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 @@
synchronized (mLock) {
checkIfCameraClosed();
- if (!mRepeatingRequestIdStack.isEmpty()) {
+ if (mRepeatingRequestId != REQUEST_ID_NONE) {
throw new IllegalStateException("Active repeating request ongoing");
}
@@ -359,6 +368,10 @@
// impossible
return;
}
+
+ mRepeatingRequestId = REQUEST_ID_NONE;
+ mRepeatingRequestIdDeletedList.clear();
+ mCaptureListenerMap.clear();
}
}
@@ -564,6 +577,9 @@
}
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 @@
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 @@
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 @@
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..093e0e9 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -297,12 +297,31 @@
}
/**
- * Initiates a fresh scan of availble Wifi displays.
+ * Starts scanning for available Wifi displays.
* The results are sent as a {@link #ACTION_WIFI_DISPLAY_STATUS_CHANGED} broadcast.
+ * <p>
+ * Calls to this method nest and must be matched by an equal number of calls to
+ * {@link #stopWifiDisplayScan()}.
+ * </p><p>
+ * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY}.
+ * </p>
+ *
* @hide
*/
- public void scanWifiDisplays() {
- mGlobal.scanWifiDisplays();
+ public void startWifiDisplayScan() {
+ mGlobal.startWifiDisplayScan();
+ }
+
+ /**
+ * Stops scanning for available Wifi displays.
+ * <p>
+ * Requires {@link android.Manifest.permission#CONFIGURE_WIFI_DISPLAY}.
+ * </p>
+ *
+ * @hide
+ */
+ public void stopWifiDisplayScan() {
+ mGlobal.stopWifiDisplayScan();
}
/**
@@ -312,8 +331,7 @@
* 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/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 936a086..3417430 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -72,6 +72,8 @@
private final SparseArray<DisplayInfo> mDisplayInfoCache = new SparseArray<DisplayInfo>();
private int[] mDisplayIdCache;
+ private int mWifiDisplayScanNestCount;
+
private DisplayManagerGlobal(IDisplayManager dm) {
mDm = dm;
}
@@ -267,11 +269,32 @@
}
}
- public void scanWifiDisplays() {
- try {
- mDm.scanWifiDisplays();
- } catch (RemoteException ex) {
- Log.e(TAG, "Failed to scan for Wifi displays.", ex);
+ public void startWifiDisplayScan() {
+ synchronized (mLock) {
+ if (mWifiDisplayScanNestCount++ == 0) {
+ registerCallbackIfNeededLocked();
+ try {
+ mDm.startWifiDisplayScan();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to scan for Wifi displays.", ex);
+ }
+ }
+ }
+ }
+
+ public void stopWifiDisplayScan() {
+ synchronized (mLock) {
+ if (--mWifiDisplayScanNestCount == 0) {
+ try {
+ mDm.stopWifiDisplayScan();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to scan for Wifi displays.", ex);
+ }
+ } else if (mWifiDisplayScanNestCount < 0) {
+ Log.wtf(TAG, "Wifi display scan nest count became negative: "
+ + mWifiDisplayScanNestCount);
+ mWifiDisplayScanNestCount = 0;
+ }
}
}
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 6b2c887..68eb13f 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -29,11 +29,14 @@
void registerCallback(in IDisplayManagerCallback callback);
- // No permissions required.
- void scanWifiDisplays();
+ // Requires CONFIGURE_WIFI_DISPLAY permission.
+ // The process must have previously registered a callback.
+ void startWifiDisplayScan();
- // Requires CONFIGURE_WIFI_DISPLAY permission to connect to an unknown device.
- // No permissions required to connect to a known device.
+ // Requires CONFIGURE_WIFI_DISPLAY permission.
+ void stopWifiDisplayScan();
+
+ // Requires CONFIGURE_WIFI_DISPLAY permission.
void connectWifiDisplay(String address);
// No permissions required.
@@ -45,6 +48,12 @@
// Requires CONFIGURE_WIFI_DISPLAY permission.
void forgetWifiDisplay(String address);
+ // Requires CONFIGURE_WIFI_DISPLAY permission.
+ void pauseWifiDisplay();
+
+ // Requires CONFIGURE_WIFI_DISPLAY permission.
+ void resumeWifiDisplay();
+
// No permissions required.
WifiDisplayStatus getWifiDisplayStatus();
@@ -55,10 +64,4 @@
// No permissions required but must be same Uid as the creator.
void releaseVirtualDisplay(in IBinder token);
-
- // Requires CONFIGURE_WIFI_DISPLAY permission.
- void pauseWifiDisplay();
-
- // Requires CONFIGURE_WIFI_DISPLAY permission.
- void resumeWifiDisplay();
}
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 @@
} 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 @@
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 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 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 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 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 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 @@
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 @@
* @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/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 @@
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 @@
* 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 @@
* 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 @@
}
@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 @@
}
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 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 @@
@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 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 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 @@
@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 @@
* 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 @@
* {@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 @@
* {@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 @@
* @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 @@
* @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 @@
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 @@
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.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 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.
+ 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();
+ }
+
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 @@
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 @@
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.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 @@
*/
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 @@
* 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 @@
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 @@
|| 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
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 @@
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 @@
// 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 @@
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 @@
mUserPaddingLeftInitial = left;
mUserPaddingRightInitial = right;
+ mLeftPaddingDefined = true;
+ mRightPaddingDefined = true;
+
internalSetPadding(left, top, right, bottom);
}
@@ -15517,6 +15554,8 @@
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 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 @@
}
}
+ 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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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.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 @@
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 @@
* 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 @@
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 final String ARGUMENT_ROUTE_TYPES = "routeTypes";
- 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 View.OnClickListener mExtendedSettingsClickListener;
- 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;
-
+ /**
+ * 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();
+ MediaRouteChooserDialog dialog = (MediaRouteChooserDialog)getDialog();
+ if (dialog != null) {
+ dialog.setRouteTypes(types);
}
}
}
- class MediaRouterCallback extends MediaRouter.Callback {
- @Override
- public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
- mAdapter.update();
- updateVolume();
- }
+ public void setExtendedSettingsClickListener(View.OnClickListener listener) {
+ if (listener != mExtendedSettingsClickListener) {
+ mExtendedSettingsClickListener = listener;
- @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();
- }
- }
- }
-
- 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 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 @@
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 @@
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 @@
}
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 @@
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 @@
}
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 @@
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 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 @@
}
}
- 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 int mRunState = STATE_NOTHING;
long mRunStartTime;
+ boolean mStarted;
+ boolean mRestarting;
int mStartedCount;
public int mStartedState = STATE_NOTHING;
long mStartedStartTime;
@@ -2902,10 +2954,9 @@
// 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 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 @@
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 @@
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 @@
/** {@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 @@
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 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 @@
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 @@
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 @@
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 @@
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 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 @@
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 @@
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 @@
}
@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 @@
}
@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 @@
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 @@
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 @@
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 @@
} 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 @@
} 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 @@
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 @@
} 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 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
if (mLogoNavItem != null) {
mLogoNavItem.setTitle(title);
}
- mUpGoerFive.setContentDescription(buildHomeContentDescription());
+ updateHomeAccessibility(mUpGoerFive.isEnabled());
}
public CharSequence getSubtitle() {
@@ -544,7 +544,7 @@
(!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 @@
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 @@
}
// 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 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 @@
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_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;
+ }
+
+ ~PageRecord() {
+ mPicture->unref();
+ }
+
+ 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;
+ }
+
+ void finishPage() {
+ assert(mCurrentPage != NULL);
+ mCurrentPage->mPicture->endRecording();
+ mCurrentPage = NULL;
+ }
+
+ void write(SkWStream* stream) {
+ SkDocument* document = SkDocument::CreatePDF(stream);
+ for (unsigned i = 0; i < mPages.size(); i++) {
+ PageRecord* page = mPages[i];
+
+ 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 void nativeFinalize(JNIEnv* env, jobject thiz, jint documentPtr) {
- delete reinterpret_cast<SkPDFDocument*>(documentPtr);
-}
-
-static jint nativeCreatePage(JNIEnv* env, jobject thiz, jint pageWidth, jint pageHeight,
+static jint nativeStartPage(JNIEnv* env, jobject thiz, jint documentPtr,
+ jint pageWidth, jint pageHeight,
jint contentLeft, jint contentTop, jint contentRight, jint contentBottom) {
-
- SkMatrix transformation;
- transformation.setTranslate(contentLeft, contentTop);
-
- SkISize skPageSize = SkISize::Make(pageWidth, pageHeight);
- SkISize skContentSize = SkISize::Make(contentRight - contentLeft, contentBottom - contentTop);
-
- SkPDFDevice* skPdfDevice = new SkPDFDevice(skPageSize, skContentSize, transformation);
- return reinterpret_cast<jint>(new SkCanvas(skPdfDevice));
+ 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
deleted file mode 100644
index 89abf2c..0000000
--- a/core/res/res/drawable-hdpi/ic_media_group_collapse.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_media_group_expand.png b/core/res/res/drawable-hdpi/ic_media_group_expand.png
deleted file mode 100644
index d9470b2..0000000
--- a/core/res/res/drawable-hdpi/ic_media_group_expand.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_notification_cast_0.png b/core/res/res/drawable-hdpi/ic_notification_cast_0.png
new file mode 100644
index 0000000..a35f281
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_notification_cast_0.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_notification_cast_1.png b/core/res/res/drawable-hdpi/ic_notification_cast_1.png
new file mode 100644
index 0000000..9f6e2ad
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_notification_cast_1.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_notification_cast_2.png b/core/res/res/drawable-hdpi/ic_notification_cast_2.png
new file mode 100644
index 0000000..737137a
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_notification_cast_2.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_notification_cast_on.png b/core/res/res/drawable-hdpi/ic_notification_cast_on.png
new file mode 100644
index 0000000..ff2753a
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_notification_cast_on.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_notify_wifidisplay.png b/core/res/res/drawable-hdpi/ic_notify_wifidisplay.png
deleted file mode 100644
index 35f27df..0000000
--- a/core/res/res/drawable-hdpi/ic_notify_wifidisplay.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-hdpi/toast_frame.9.png b/core/res/res/drawable-hdpi/toast_frame.9.png
index ca65994..a804a8a 100644
--- a/core/res/res/drawable-hdpi/toast_frame.9.png
+++ b/core/res/res/drawable-hdpi/toast_frame.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/toast_frame_holo.9.png b/core/res/res/drawable-hdpi/toast_frame_holo.9.png
deleted file mode 100644
index a804a8a..0000000
--- a/core/res/res/drawable-hdpi/toast_frame_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-ldpi/toast_frame.9.png b/core/res/res/drawable-ldpi/toast_frame.9.png
index 3b344ff..e64dc75 100644
--- a/core/res/res/drawable-ldpi/toast_frame.9.png
+++ b/core/res/res/drawable-ldpi/toast_frame.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_group_collapse.png b/core/res/res/drawable-mdpi/ic_media_group_collapse.png
deleted file mode 100644
index 34454ac..0000000
--- a/core/res/res/drawable-mdpi/ic_media_group_collapse.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_media_group_expand.png b/core/res/res/drawable-mdpi/ic_media_group_expand.png
deleted file mode 100644
index 8ce5a44..0000000
--- a/core/res/res/drawable-mdpi/ic_media_group_expand.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_notification_cast_0.png b/core/res/res/drawable-mdpi/ic_notification_cast_0.png
new file mode 100644
index 0000000..d9cedbd
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_notification_cast_0.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_notification_cast_1.png b/core/res/res/drawable-mdpi/ic_notification_cast_1.png
new file mode 100644
index 0000000..414c67f
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_notification_cast_1.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_notification_cast_2.png b/core/res/res/drawable-mdpi/ic_notification_cast_2.png
new file mode 100644
index 0000000..280a888
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_notification_cast_2.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_notification_cast_on.png b/core/res/res/drawable-mdpi/ic_notification_cast_on.png
new file mode 100644
index 0000000..ab5f1d7
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_notification_cast_on.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_notify_wifidisplay.png b/core/res/res/drawable-mdpi/ic_notify_wifidisplay.png
deleted file mode 100644
index f9c8678..0000000
--- a/core/res/res/drawable-mdpi/ic_notify_wifidisplay.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/toast_frame.9.png b/core/res/res/drawable-mdpi/toast_frame.9.png
index 9e93fe7..778e4e6 100644
--- a/core/res/res/drawable-mdpi/toast_frame.9.png
+++ b/core/res/res/drawable-mdpi/toast_frame.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/toast_frame_holo.9.png b/core/res/res/drawable-mdpi/toast_frame_holo.9.png
deleted file mode 100644
index 778e4e6..0000000
--- a/core/res/res/drawable-mdpi/toast_frame_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_group_collapse.png b/core/res/res/drawable-xhdpi/ic_media_group_collapse.png
deleted file mode 100644
index 2fb7428..0000000
--- a/core/res/res/drawable-xhdpi/ic_media_group_collapse.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_media_group_expand.png b/core/res/res/drawable-xhdpi/ic_media_group_expand.png
deleted file mode 100644
index 5755b9d..0000000
--- a/core/res/res/drawable-xhdpi/ic_media_group_expand.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_notification_cast_0.png b/core/res/res/drawable-xhdpi/ic_notification_cast_0.png
new file mode 100644
index 0000000..5fb23a0
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_notification_cast_0.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_notification_cast_1.png b/core/res/res/drawable-xhdpi/ic_notification_cast_1.png
new file mode 100644
index 0000000..f01d17d
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_notification_cast_1.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_notification_cast_2.png b/core/res/res/drawable-xhdpi/ic_notification_cast_2.png
new file mode 100644
index 0000000..4f4ba7f
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_notification_cast_2.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_notification_cast_on.png b/core/res/res/drawable-xhdpi/ic_notification_cast_on.png
new file mode 100644
index 0000000..38f15dd
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_notification_cast_on.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_notify_wifidisplay.png b/core/res/res/drawable-xhdpi/ic_notify_wifidisplay.png
deleted file mode 100644
index 4cc0ee8..0000000
--- a/core/res/res/drawable-xhdpi/ic_notify_wifidisplay.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/toast_frame.9.png b/core/res/res/drawable-xhdpi/toast_frame.9.png
index 1f63420..77e69c7 100644
--- a/core/res/res/drawable-xhdpi/toast_frame.9.png
+++ b/core/res/res/drawable-xhdpi/toast_frame.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/toast_frame_holo.9.png b/core/res/res/drawable-xhdpi/toast_frame_holo.9.png
deleted file mode 100644
index 77e69c7..0000000
--- a/core/res/res/drawable-xhdpi/toast_frame_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_notification_cast_0.png b/core/res/res/drawable-xxhdpi/ic_notification_cast_0.png
new file mode 100644
index 0000000..f5b16ed
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_notification_cast_0.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_notification_cast_1.png b/core/res/res/drawable-xxhdpi/ic_notification_cast_1.png
new file mode 100644
index 0000000..22efeec
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_notification_cast_1.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_notification_cast_2.png b/core/res/res/drawable-xxhdpi/ic_notification_cast_2.png
new file mode 100644
index 0000000..e24cd97
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_notification_cast_2.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_notification_cast_on.png b/core/res/res/drawable-xxhdpi/ic_notification_cast_on.png
new file mode 100644
index 0000000..da1a627
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_notification_cast_on.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_notify_wifidisplay.png b/core/res/res/drawable-xxhdpi/ic_notify_wifidisplay.png
deleted file mode 100644
index fea4774..0000000
--- a/core/res/res/drawable-xxhdpi/ic_notify_wifidisplay.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/toast_frame.9.png b/core/res/res/drawable-xxhdpi/toast_frame.9.png
index 882b9c6..edecb63 100644
--- a/core/res/res/drawable-xxhdpi/toast_frame.9.png
+++ b/core/res/res/drawable-xxhdpi/toast_frame.9.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/toast_frame_holo.9.png b/core/res/res/drawable-xxhdpi/toast_frame_holo.9.png
deleted file mode 100644
index edecb63..0000000
--- a/core/res/res/drawable-xxhdpi/toast_frame_holo.9.png
+++ /dev/null
Binary files differ
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/layout/media_route_list_item_top_header.xml b/core/res/res/layout/media_route_list_item_top_header.xml
deleted file mode 100644
index 0c49b24..0000000
--- a/core/res/res/layout/media_route_list_item_top_header.xml
+++ /dev/null
@@ -1,29 +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.
--->
-
-<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"
-/>
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..9025400a 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 @@
<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 @@
<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.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 @@
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 @@
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 @@
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 @@
* @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 @@
$(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 @@
$(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 @@
$(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 @@
$(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
index a9d8bbd..355d522 100644
--- a/data/sounds/notifications/ogg/Tethys_48k.ogg
+++ b/data/sounds/notifications/ogg/Tethys_48k.ogg
Binary files differ
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 @@
* @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.PixelFormat;
import android.graphics.Rect;
import android.os.SystemClock;
+import android.util.LayoutDirection;
import android.util.SparseArray;
/**
@@ -59,6 +60,8 @@
private long mExitAnimationEnd;
private Drawable mLastDrawable;
+ private Insets mInsets = Insets.NONE;
+
// overrides from Drawable
@Override
@@ -78,18 +81,31 @@
| 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 @@
*/
@Override
public Insets getOpticalInsets() {
- return (mCurrDrawable == null) ? Insets.NONE : mCurrDrawable.getOpticalInsets();
+ return mInsets;
}
@Override
@@ -334,6 +350,7 @@
mCurrDrawable = d;
mCurIndex = idx;
if (d != null) {
+ mInsets = d.getOpticalInsets();
d.mutate();
if (mDrawableContainerState.mEnterFadeDuration > 0) {
mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
@@ -348,9 +365,12 @@
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 @@
} 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 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Paint;
import android.graphics.Rect;
import dalvik.system.CloseGuard;
@@ -69,6 +70,12 @@
*/
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 @@
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 @@
}
mPages.add(page.getInfo());
mCurrentPage = null;
- nativeAppendPage(mNativeDocument, page.mCanvas.mNativeCanvas);
+ nativeFinishPage(mNativeDocument);
page.finish();
}
@@ -204,7 +211,7 @@
private void dispose() {
if (mNativeDocument != 0) {
- nativeFinalize(mNativeDocument);
+ nativeClose(mNativeDocument);
mCloseGuard.close();
mNativeDocument = 0;
}
@@ -230,14 +237,14 @@
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 @@
* 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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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..de20227 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.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,14 @@
*/
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;
-
+ 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,8 +82,16 @@
RouteInfo mSelectedRoute;
- WifiDisplayStatus mLastKnownWifiDisplayStatus;
+ final boolean mCanConfigureWifiDisplays;
boolean mActivelyScanningWifiDisplays;
+ String mPreviousActiveWifiDisplayAddress;
+
+ int mDiscoveryRequestRouteTypes;
+ boolean mDiscoverRequestActiveScan;
+
+ int mCurrentUserId = -1;
+ IMediaRouterClient mClient;
+ MediaRouterClientState mClientState;
final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
@Override
@@ -90,17 +104,8 @@
}
};
- final Runnable mScanWifiDisplays = new Runnable() {
- @Override
- public void run() {
- if (mActivelyScanningWifiDisplays) {
- mDisplayService.scanWifiDisplays();
- mHandler.postDelayed(this, WIFI_DISPLAY_SCAN_INTERVAL);
- }
- }
- };
-
Static(Context appContext) {
+ mAppContext = appContext;
mResources = Resources.getSystem();
mHandler = new Handler(appContext.getMainLooper());
@@ -109,10 +114,20 @@
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 +135,7 @@
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 +160,13 @@
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 +214,7 @@
dispatchRouteChanged(sStatic.mBluetoothA2dpRoute);
}
} else if (sStatic.mBluetoothA2dpRoute != null) {
- removeRoute(sStatic.mBluetoothA2dpRoute);
+ removeRouteStatic(sStatic.mBluetoothA2dpRoute);
sStatic.mBluetoothA2dpRoute = null;
}
}
@@ -205,38 +222,79 @@
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)) {
- if (!mActivelyScanningWifiDisplays) {
- mActivelyScanningWifiDisplays = true;
- mHandler.post(mScanWifiDisplays);
- }
- } else {
- if (mActivelyScanningWifiDisplays) {
- mActivelyScanningWifiDisplays = false;
- mHandler.removeCallbacks(mScanWifiDisplays);
- }
- }
- }
-
- private boolean hasActiveScanCallbackOfType(int type) {
+ 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) != 0
- && (cbi.type & type) != 0) {
- return true;
+ 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;
+ }
}
}
- return false;
+ 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.
+ // TODO: All of this should be managed by the media router service.
+ if (mCanConfigureWifiDisplays) {
+ if (mSelectedRoute != null
+ && mSelectedRoute.matchesTypes(ROUTE_TYPE_REMOTE_DISPLAY)) {
+ // Don't scan while already connected to a remote display since
+ // it may interfere with the ongoing transmission.
+ activeScanWifiDisplay = false;
+ }
+ if (activeScanWifiDisplay) {
+ if (!mActivelyScanningWifiDisplays) {
+ mActivelyScanningWifiDisplays = true;
+ mDisplayService.startWifiDisplayScan();
+ }
+ } else {
+ if (mActivelyScanningWifiDisplays) {
+ mActivelyScanningWifiDisplays = false;
+ mDisplayService.stopWifiDisplayScan();
+ }
+ }
+ }
+
+ // Tell the media router service all about it.
+ if (routeTypes != mDiscoveryRequestRouteTypes
+ || activeScan != mDiscoverRequestActiveScan) {
+ mDiscoveryRequestRouteTypes = routeTypes;
+ mDiscoverRequestActiveScan = activeScan;
+ publishClientDiscoveryRequest();
+ }
}
@Override
@@ -259,18 +317,268 @@
}
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();
+ }
+ }
+ });
+ }
+ }
}
static Static sStatic;
@@ -285,7 +593,7 @@
* <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 +610,13 @@
* @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 +626,10 @@
* 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 +653,40 @@
* 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 +698,9 @@
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 +737,11 @@
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 +765,38 @@
}
/**
+ * 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 +839,7 @@
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 +850,8 @@
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 +881,20 @@
* @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,19 +917,44 @@
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);
}
+
+ // The behavior of active scans may depend on the currently selected route.
+ sStatic.updateDiscoveryRequest();
+ }
+
+ 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);
+ }
}
/**
@@ -612,7 +1019,7 @@
* @see #addUserRoute(UserRouteInfo)
*/
public void removeUserRoute(UserRouteInfo info) {
- removeRoute(info);
+ removeRouteStatic(info);
}
/**
@@ -626,7 +1033,7 @@
// 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 @@
* @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 @@
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.
- 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) {
- // 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 @@
*
* @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 @@
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 @@
}
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 @@
}
}
- static void updateWifiDisplayStatus(WifiDisplayStatus newStatus) {
- final WifiDisplayStatus oldStatus = sStatic.mLastKnownWifiDisplayStatus;
-
- // TODO Naive implementation. Make this smarter later.
- boolean wantScan = false;
- boolean blockScan = false;
- WifiDisplay[] oldDisplays = oldStatus != null ?
- oldStatus.getDisplays() : WifiDisplay.EMPTY_ARRAY;
- WifiDisplay[] newDisplays;
+ static void updateWifiDisplayStatus(WifiDisplayStatus status) {
+ WifiDisplay[] displays;
WifiDisplay activeDisplay;
+ if (status.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) {
+ displays = status.getDisplays();
+ activeDisplay = status.getActiveDisplay();
- if (newStatus.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON) {
- newDisplays = newStatus.getDisplays();
- activeDisplay = newStatus.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;
}
+ String activeDisplayAddress = activeDisplay != null ?
+ activeDisplay.getDeviceAddress() : 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);
+ String address = d.getDeviceAddress();
+ boolean disconnected = !address.equals(activeDisplayAddress)
+ && address.equals(sStatic.mPreviousActiveWifiDisplayAddress);
+ updateWifiDisplayRoute(route, d, status, disconnected);
}
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;
- }
- }
- }
- 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));
+ selectRouteStatic(route.getSupportedTypes(), route, false);
}
}
}
- if (wantScan && !blockScan) {
- sStatic.mDisplayService.scanWifiDisplays();
+ // 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);
+ }
+ }
}
- sStatic.mLastKnownWifiDisplayStatus = newStatus;
+ // Remember the current active wifi display address so that we can infer disconnections.
+ // TODO: This hack will go away once all of this is moved into the media router service.
+ sStatic.mPreviousActiveWifiDisplayAddress = activeDisplayAddress;
+ }
+
+ 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 @@
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,23 +1391,23 @@
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;
}
private static void updateWifiDisplayRoute(
- RouteInfo route, WifiDisplay display, WifiDisplayStatus wfdStatus) {
+ RouteInfo route, WifiDisplay display, WifiDisplayStatus wfdStatus,
+ boolean disconnected) {
boolean changed = false;
final String newName = display.getFriendlyDisplayName();
if (!route.getName().equals(newName)) {
@@ -996,24 +1419,23 @@
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 || disconnected) && 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 +1452,6 @@
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 +1472,18 @@
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 +1491,20 @@
/** @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 +1512,13 @@
* 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 +1587,71 @@
* 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;
- 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;
- }
- mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null;
- return true;
+ 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) {
+ // 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:
+ case STATUS_SCANNING:
+ statusCode = STATUS_CONNECTING;
+ break;
+ }
+ }
+ 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 +1661,11 @@
return mSupportedTypes;
}
+ /** @hide */
+ public boolean matchesTypes(int types) {
+ return (mSupportedTypes & types) != 0;
+ }
+
/**
* @return The group that this route belongs to.
*/
@@ -1317,9 +1764,7 @@
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 +1783,7 @@
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 +1845,55 @@
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 +1910,22 @@
* @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 +1939,7 @@
}
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 +1968,7 @@
", status=" + getStatus() +
", category=" + getCategory() +
", supportedTypes=" + supportedTypes +
- ", presentationDisplay=" + mPresentationDisplay + "}";
+ ", presentationDisplay=" + mPresentationDisplay + " }";
}
}
@@ -1716,6 +2224,7 @@
mVolumeHandling = PLAYBACK_VOLUME_FIXED;
}
+ @Override
CharSequence getName(Resources res) {
if (mUpdateName) updateName();
return super.getName(res);
@@ -1916,7 +2425,7 @@
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 +2580,7 @@
return mIsSystem;
}
+ @Override
public String toString() {
return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
" groupable=" + mGroupable + " }";
@@ -2091,8 +2601,12 @@
}
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/media/lib/com.android.media.remotedisplay.xml b/media/lib/com.android.media.remotedisplay.xml
new file mode 100644
index 0000000..77a91d2
--- /dev/null
+++ b/media/lib/com.android.media.remotedisplay.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<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 55d73f23..1cbc221 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
@@ -51,7 +51,7 @@
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 @@
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 @@
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 @@
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 @@
private FixedSizeFrameLayout mPreview;
private View mFullscreenPreview;
private View mFakeNavBar;
+ private boolean mUseFastTransition;
private final Runnable mTransitionToCameraRunnable = new Runnable() {
@Override
@@ -243,11 +243,12 @@
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 @@
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 @@
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 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 @@
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..2ee21acd 100644
--- a/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java
+++ b/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java
@@ -39,7 +39,7 @@
*
* @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 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 @@
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 @@
}
}
- Runnable mSwitchPageRunnable = new Runnable() {
+ private final Runnable mSwitchPageRunnable = new Runnable() {
@Override
public void run() {
showAppropriateWidgetPage();
@@ -1438,7 +1438,7 @@
mAppWidgetToShow = ss.appWidgetToShow;
setInsets(ss.insets);
if (DEBUG) Log.d(TAG, "onRestoreInstanceState, transport=" + mTransportState);
- post(mSwitchPageRunnable);
+ mSwitchPageRunnable.run();
}
@Override
@@ -1472,10 +1472,21 @@
}
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 @@
*
* @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 @@
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 @@
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 @@
}
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 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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
+ " 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 @@
}
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));
+ Patterns.update(mContext);
- mDateView.setFormat24Hour(dateFormat);
- mDateView.setFormat12Hour(dateFormat);
+ mDateView.setFormat24Hour(Patterns.dateView);
+ mDateView.setFormat12Hour(Patterns.dateView);
- // 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);
+ mClockView.setFormat12Hour(Patterns.clockView12);
+ mClockView.setFormat24Hour(Patterns.clockView24);
refreshAlarmStatus();
}
@@ -149,4 +136,35 @@
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 @@
*/
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 @@
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 @@
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 @@
}
};
- 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 @@
}
if (keyCode != -1) {
sendMediaButtonClick(keyCode);
+ delayResetToMetadata(); // if the scrub bar is showing, keep showing it.
}
}
};
@@ -175,25 +189,67 @@
}
};
+ // 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 @@
= new KeyguardUpdateMonitorCallback() {
public void onScreenTurnedOff(int why) {
setEnableMarquee(false);
- };
+ }
public void onScreenTurnedOn() {
setEnableMarquee(true);
- };
+ }
};
public KeyguardTransportControlView(Context context, AttributeSet attrs) {
@@ -245,17 +301,11 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
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.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 @@
* 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 @@
*/
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 @@
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 @@
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 @@
// 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 @@
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 @@
* 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 void keyguardDonePending() {
mKeyguardDonePending = true;
}
+
+ @Override
+ public void keyguardGone() {
+ mKeyguardDisplayManager.hide();
+ }
};
private void userActivity() {
@@ -483,6 +498,8 @@
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 @@
? 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 @@
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 @@
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 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 void onPageSwitching(View newPage, int newPageIndex) {
if (mKeyguardWidgetPager != null && mChallengeLayout instanceof SlidingChallengeLayout) {
boolean isCameraPage = newPage instanceof CameraWidgetFrame;
+ if (isCameraPage) {
+ CameraWidgetFrame camera = (CameraWidgetFrame) newPage;
+ camera.setUseFastTransition(mKeyguardWidgetPager.isWarping());
+ }
SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout;
scl.setChallengeInteractive(!isCameraPage);
- if (isCameraPage) {
- scl.fadeOutChallenge();
- }
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 @@
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 @@
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 @@
@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 @@
// 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 @@
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 @@
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 @@
if (!mIsPageMoving) {
mIsPageMoving = true;
if (isWarping()) {
- onPageBeginWarp();
+ dispatchOnPageBeginWarp();
if (mPageSwapIndex != -1) {
swapPages(mPageSwapIndex, mPageWarpIndex);
}
@@ -500,6 +502,22 @@
}
}
+ 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 @@
if (mPageSwapIndex != -1) {
swapPages(mPageSwapIndex, mPageWarpIndex);
}
- onPageEndWarp();
+ dispatchOnPageEndWarp();
resetPageWarp();
}
onPageEndMoving();
@@ -1919,11 +1937,13 @@
}
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 @@
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 @@
@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 @@
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 @@
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 @@
}
}
+ @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 @@
private PrinterId mTrackedPrinter;
+ private boolean mPrintersUpdatedBefore;
+
public FusedPrintersProvider(Context context) {
super(context);
mPersistenceManager = new PersistenceManager(context);
@@ -88,13 +90,14 @@
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 @@
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 @@
.getSystemService(Context.PRINT_SERVICE);
mDiscoverySession = printManager.createPrinterDiscoverySession();
mPersistenceManager.readPrinterHistory();
+ } else if (mPersistenceManager.isHistoryChanged()) {
+ mPersistenceManager.readPrinterHistory();
}
if (mPersistenceManager.isReadHistoryCompleted()
&& !mDiscoverySession.isPrinterDiscoveryStarted()) {
@@ -176,7 +184,7 @@
+ mDiscoverySession.getPrinters().size()
+ " " + FusedPrintersProvider.this.hashCode());
}
- updatePrinters(mDiscoverySession.getPrinters());
+ updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters);
}
});
final int favoriteCount = mFavoritePrinters.size();
@@ -187,15 +195,19 @@
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 @@
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 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 @@
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 @@
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 @@
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 @@
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 void cancel() {
+ if (isWorking()) {
+ mRemotePrintAdapter.cancel();
+ }
mControllerState = CONTROLLER_STATE_CANCELLED;
}
@@ -934,6 +937,7 @@
mPrintJobId, mCurrentPrinter);
if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) {
+ mCapabilitiesTimeout.post();
updateUi();
return;
}
@@ -1525,9 +1529,13 @@
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 @@
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 @@
Slog.i(LOG_TAG, "[REMOVE] " + printJob.getId().flattenToString());
}
removePrintJobFileLocked(printJob.getId());
+ persistState = true;
}
}
- mPersistanceManager.writeStateLocked();
+ if (persistState) {
+ mPersistanceManager.writeStateLocked();
+ }
}
}
@@ -544,7 +548,7 @@
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 @@
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 @@
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.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.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 @@
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 @@
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 @@
}
});
+ registerForContextMenu(mListView);
+
return content;
}
@@ -185,6 +195,62 @@
}
@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 @@
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 @@
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
new file mode 100644
index 0000000..5bbfa4f
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/bg_protect.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png
index 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
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_available.png
new file mode 100644
index 0000000..1c3518a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_available.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connected.png
new file mode 100644
index 0000000..9dbc65e
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connected.png
Binary files differ
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
new file mode 100644
index 0000000..ddb002d
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_0.png
Binary files differ
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
new file mode 100644
index 0000000..43b7ef2
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_1.png
Binary files differ
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
new file mode 100644
index 0000000..1d8b7ee
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png
index 7220968..e3b3eeb 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png
deleted file mode 100644
index 02d7fda..0000000
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png
+++ /dev/null
Binary files differ
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
deleted file mode 100644
index 263f07c..0000000
--- a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display_connected.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png
index 8f4cb64..c6f03c4 100644
--- a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png
+++ b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/search_light.png b/packages/SystemUI/res/drawable-hdpi/search_light.png
index 116b1f0..3c0dc4e 100644
--- a/packages/SystemUI/res/drawable-hdpi/search_light.png
+++ b/packages/SystemUI/res/drawable-hdpi/search_light.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/search_light_land.png b/packages/SystemUI/res/drawable-hdpi/search_light_land.png
new file mode 100644
index 0000000..731f19b5
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/search_light_land.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-land-hdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-hdpi/bg_protect.9.png
new file mode 100644
index 0000000..1a58144
--- /dev/null
+++ b/packages/SystemUI/res/drawable-land-hdpi/bg_protect.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-land-mdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-mdpi/bg_protect.9.png
new file mode 100644
index 0000000..a12519e
--- /dev/null
+++ b/packages/SystemUI/res/drawable-land-mdpi/bg_protect.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-land-xhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-xhdpi/bg_protect.9.png
new file mode 100644
index 0000000..ce41454
--- /dev/null
+++ b/packages/SystemUI/res/drawable-land-xhdpi/bg_protect.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-land-xxhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-xxhdpi/bg_protect.9.png
new file mode 100644
index 0000000..b0b4561
--- /dev/null
+++ b/packages/SystemUI/res/drawable-land-xxhdpi/bg_protect.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-mdpi/bg_protect.9.png
new file mode 100644
index 0000000..2856e09
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/bg_protect.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png
index 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
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_available.png
new file mode 100644
index 0000000..11b2134
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_available.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connected.png
new file mode 100644
index 0000000..a858573
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connected.png
Binary files differ
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
new file mode 100644
index 0000000..04de5d7
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_0.png
Binary files differ
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
new file mode 100644
index 0000000..caea37e
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_1.png
Binary files differ
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
new file mode 100644
index 0000000..b66aa46
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png
index 8c2dc68..cc81794 100644
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png
+++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png
deleted file mode 100644
index 09ae409..0000000
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png
+++ /dev/null
Binary files differ
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
deleted file mode 100644
index 780cfc8..0000000
--- a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display_connected.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png
index 2142147..1c2d7aa 100644
--- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/search_light.png b/packages/SystemUI/res/drawable-mdpi/search_light.png
index 7a70984..8010ce7 100644
--- a/packages/SystemUI/res/drawable-mdpi/search_light.png
+++ b/packages/SystemUI/res/drawable-mdpi/search_light.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/search_light_land.png b/packages/SystemUI/res/drawable-mdpi/search_light_land.png
new file mode 100644
index 0000000..a4d82f0
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/search_light_land.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-xhdpi/bg_protect.9.png
new file mode 100644
index 0000000..72269f2
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/bg_protect.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png
index 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
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_available.png
new file mode 100644
index 0000000..10ebcd5
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_available.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connected.png
new file mode 100644
index 0000000..fef43b8
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connected.png
Binary files differ
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
new file mode 100644
index 0000000..05e3267
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_0.png
Binary files differ
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
new file mode 100644
index 0000000..ef42b27
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_1.png
Binary files differ
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
new file mode 100644
index 0000000..fc1c95e
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png
index bffbf55..65d15b5 100644
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png
deleted file mode 100644
index 48f90ac..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png
+++ /dev/null
Binary files differ
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
deleted file mode 100644
index 621c045..0000000
--- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display_connected.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png
index b0ea8e0..fbd4d6b 100644
--- a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/search_light.png b/packages/SystemUI/res/drawable-xhdpi/search_light.png
index e2aed09..6d46fdd 100644
--- a/packages/SystemUI/res/drawable-xhdpi/search_light.png
+++ b/packages/SystemUI/res/drawable-xhdpi/search_light.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/search_light_land.png b/packages/SystemUI/res/drawable-xhdpi/search_light_land.png
new file mode 100644
index 0000000..b62c74e
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/search_light_land.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-xxhdpi/bg_protect.9.png
new file mode 100644
index 0000000..efc9b04
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/bg_protect.9.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png
index 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
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_available.png
new file mode 100644
index 0000000..68b1b7c
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_available.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connected.png
new file mode 100644
index 0000000..8a8f890
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connected.png
Binary files differ
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
new file mode 100644
index 0000000..12d4a01
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_0.png
Binary files differ
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
new file mode 100644
index 0000000..3cb4421
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_1.png
Binary files differ
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
new file mode 100644
index 0000000..4620b3a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png
index ab841d2..1a5d26a 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png
deleted file mode 100644
index b07be828..0000000
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png
+++ /dev/null
Binary files differ
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
deleted file mode 100644
index f02d0ab..0000000
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display_connected.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png
index aac3428..86df881 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/search_light.png b/packages/SystemUI/res/drawable-xxhdpi/search_light.png
index e5ef85d..7742207 100644
--- a/packages/SystemUI/res/drawable-xxhdpi/search_light.png
+++ b/packages/SystemUI/res/drawable-xxhdpi/search_light.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png b/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png
new file mode 100644
index 0000000..f364577
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png
Binary files differ
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 @@
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 @@
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 @@
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 static class RescalingContainer extends FrameLayout {
- private static final int SYSTEM_UI_MODE_800 = 0x00000800;
private DessertCaseView mView;
private float mDarkness;
@@ -509,7 +508,7 @@
| 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 @@
}
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 @@
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.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 @@
protected boolean mUseHeadsUp = false;
protected IDreamManager mDreamManager;
- KeyguardManager mKeyguardManager;
PowerManager mPowerManager;
protected int mRowHeight;
@@ -221,7 +220,6 @@
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 @@
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 @@
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 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 @@
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 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 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 @@
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 @@
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 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 @@
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 @@
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 @@
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.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 @@
// 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 @@
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 @@
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 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 @@
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 @@
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 @@
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 @@
mVertical ? "true" : "false",
mShowMenu ? "true" : "false"));
- final View back = getBackButton();
- final View home = getHomeButton();
- final View recent = getRecentsButton();
- final View menu = getMenuButton();
+ 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(" 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())
- );
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.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 @@
}
}
+ 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 @@
@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 @@
}
private void repositionNavigationBar() {
- if (mNavigationBarView == null) return;
+ if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return;
prepareNavigationBarView();
@@ -1807,8 +1812,7 @@
return mGestureRec;
}
- @Override // CommandQueue
- public void setNavigationIconHints(int hints) {
+ private void setNavigationIconHints(int hints) {
if (hints == mNavigationIconHints) return;
mNavigationIconHints = hints;
@@ -2045,7 +2049,7 @@
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 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.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.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 @@
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 @@
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 @@
mLocationController = locationController;
setupQuickSettings();
- updateWifiDisplayStatus();
updateResources();
applyLocationEnabledStatus();
@@ -314,11 +309,19 @@
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 @@
});
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 @@
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 @@
@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..42201c5 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.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 @@
BrightnessStateChangeCallback,
RotationLockControllerCallback,
LocationSettingsChangeCallback {
-
// Sett InputMethoManagerService
private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
@@ -199,6 +199,30 @@
}
}
+ /** 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 @@
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 @@
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 @@
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 @@
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,58 @@
mBugreportCallback.refreshView(mBugreportTile, mBugreportState);
}
- // Wifi Display
- void addWifiDisplayTile(QuickSettingsTileView view, RefreshCallback cb) {
- mWifiDisplayTile = view;
- mWifiDisplayCallback = cb;
- }
- 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;
- } else {
- mWifiDisplayState.label = mContext.getString(
- R.string.quick_settings_wifi_display_no_connection_label);
- mWifiDisplayState.iconId = R.drawable.ic_qs_remote_display;
- }
- mWifiDisplayCallback.refreshView(mWifiDisplayTile, mWifiDisplayState);
+ // 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();
+ }
+
+ 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 {
+ connectedRoute = null;
+ connecting = false;
+ enabled = mMediaRouter.isRouteAvailable(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
+ MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE);
+ }
+
+ 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.Log;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewParent;
import android.widget.FrameLayout;
/**
@@ -31,14 +32,14 @@
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 @@
}
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 @@
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.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 @@
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 @@
@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 @@
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 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.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 @@
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 @@
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 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 @@
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 a53b25a5..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 @@
}
@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.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 @@
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 @@
}
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 DumbBitmapRegionDecoder implements SimpleBitmapRegionDecoder {
- //byte[] streamCopy;
Bitmap mBuffer;
+ Canvas mTempCanvas;
+ Paint mTempPaint;
private DumbBitmapRegionDecoder(Bitmap b) {
mBuffer = b;
}
@@ -115,9 +121,23 @@
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 @@
if (regionDecoder == null) {
is = regenerateInputStream();
regionDecoder = DumbBitmapRegionDecoder.newInstance(is);
+ Utils.closeSilently(is);
}
return regionDecoder;
} catch (FileNotFoundException e) {
@@ -280,8 +301,9 @@
}
@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 @@
} catch (IOException e) {
Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
return false;
+ } finally {
+ Utils.closeSilently(is);
}
}
}
@@ -316,6 +340,7 @@
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 @@
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 @@
}
} catch (IOException e) {
Log.w(LOGTAG, "Getting exif data failed", e);
+ } finally {
+ Utils.closeSilently(bis);
+ Utils.closeSilently(is);
}
return 0;
}
@@ -326,40 +329,15 @@
// 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 @@
// (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 @@
// 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 @@
}
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
});
+ return true;
}
+ return false;
}
public boolean checkHiddenLw() {
@@ -194,6 +199,9 @@
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 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 @@
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 @@
}
// 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 @@
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 @@
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.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 @@
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 @@
// 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 @@
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 @@
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 RestoreParams {
public IBackupTransport transport;
+ public String dirName;
public IRestoreObserver observer;
public long token;
public PackageInfo pkgInfo;
@@ -307,9 +319,10 @@
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 @@
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 @@
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 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 @@
// 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 @@
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 @@
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 @@
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 @@
}
// 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 @@
}
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);
+ }
}
- } catch (PackageManager.NameNotFoundException nnf) {
- // No such package? No binding.
- if (DEBUG) Slog.v(TAG, "Google transport not present");
+ 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());
+ }
+ }
}
// Now that we know about valid backup participants, parse any
@@ -1235,29 +1302,47 @@
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 (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
+ 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 @@
// 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 @@
}
}
} 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 @@
}
};
- // ----- 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 @@
agent = mConnectedAgent;
}
} catch (RemoteException e) {
- // can't happen
+ // can't happen - ActivityManager is local
}
}
return agent;
@@ -1821,17 +1917,13 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
}
// 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 @@
// 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 @@
+ " 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 @@
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 @@
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 @@
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.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.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.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 @@
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 @@
} 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 @@
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 @@
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 @@
}
}
+ // 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 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 @@
mCs.setEnableFailFastMobileData(DctConstants.ENABLED);
break;
}
- sleep(1);
+ sleep(POLLING_SLEEP_SEC);
}
}
@@ -4192,7 +4264,7 @@
}
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 @@
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 @@
// 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 @@
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 @@
// 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 @@
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 @@
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 @@
log("isMobileOk: connected ni=" +
mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
}
- sleep(1);
+ sleep(POLLING_SLEEP_SEC);
continue;
}
}
@@ -4435,7 +4528,7 @@
}
}
- 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.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 @@
|| 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 @@
}
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 @@
# ---------------------------
# 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 @@
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 @@
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 @@
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 @@
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 @@
}
}
+ 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 @@
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 @@
}
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 @@
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.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 @@
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 @@
} 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 @@
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 @@
} 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 @@
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 6957bac0..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.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 @@
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 @@
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 @@
}
// 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 @@
}
}
+ 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 @@
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 @@
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 @@
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 @@
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 @@
+ "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 @@
// 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 @@
}
/**
+ * 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 @@
// Keep track of the last up pointer data.
private long mLastReceivedUpPointerDownTime;
- private int mLastReceivedUpPointerId;
private float mLastReceivedUpPointerDownX;
private float mLastReceivedUpPointerDownY;
@@ -1690,7 +1722,6 @@
mReceivedPointersDown = 0;
mPrimaryPointerId = 0;
mLastReceivedUpPointerDownTime = 0;
- mLastReceivedUpPointerId = 0;
mLastReceivedUpPointerDownX = 0;
mLastReceivedUpPointerDownY = 0;
}
@@ -1823,7 +1854,6 @@
final int pointerId = event.getPointerId(pointerIndex);
final int pointerFlag = (1 << pointerId);
- mLastReceivedUpPointerId = 0;
mLastReceivedUpPointerDownTime = 0;
mLastReceivedUpPointerDownX = 0;
mLastReceivedUpPointerDownX = 0;
@@ -1848,7 +1878,6 @@
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 @@
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 @@
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 @@
}
}
+ @Override
public String getUserData(Account account, String key) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "getUserData: " + account
@@ -533,6 +535,7 @@
}
}
+ @Override
public AuthenticatorDescription[] getAuthenticatorTypes() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "getAuthenticatorTypes: "
@@ -763,6 +766,7 @@
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 @@
}
}
+ @Override
public void removeAccount(IAccountManagerResponse response, Account account) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "removeAccount: " + account
@@ -1049,6 +1054,7 @@
}
}
+ @Override
public String peekAuthToken(Account account, String authTokenType) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "peekAuthToken: " + account
@@ -1068,6 +1074,7 @@
}
}
+ @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 @@
}
}
+ @Override
public void setPassword(Account account, String password) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "setAuthToken: " + account
@@ -1135,6 +1143,7 @@
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 @@
}
}
+ @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 @@
}
}
+ @Override
public void getAuthTokenLabel(IAccountManagerResponse response, final String accountType,
final String authTokenType)
throws RemoteException {
@@ -1271,6 +1282,7 @@
}
}
+ @Override
public void getAuthToken(IAccountManagerResponse response, final Account account,
final String authTokenType, final boolean notifyOnAuthFailure,
final boolean expectActivityLaunch, Bundle loginOptionsIn) {
@@ -1284,8 +1296,22 @@
+ ", 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 @@
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 @@
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 @@
}
}
+ @Override
public void updateCredentials(IAccountManagerResponse response, final Account account,
final String authTokenType, final boolean expectActivityLaunch,
final Bundle loginOptions) {
@@ -1620,6 +1643,7 @@
}
}
+ @Override
public void editProperties(IAccountManagerResponse response, final String accountType,
final boolean expectActivityLaunch) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -1657,7 +1681,7 @@
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 @@
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 @@
unbind();
}
+ @Override
public void binderDied() {
mResponse = null;
close();
@@ -2112,6 +2138,7 @@
mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
}
+ @Override
public void onServiceConnected(ComponentName name, IBinder service) {
mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
try {
@@ -2122,6 +2149,7 @@
}
}
+ @Override
public void onServiceDisconnected(ComponentName name) {
mAuthenticator = null;
IAccountManagerResponse response = getResponseAndClose();
@@ -2217,7 +2245,14 @@
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 @@
}
}
+ @Override
public void onRequestContinued() {
mNumRequestContinued++;
}
+ @Override
public void onError(int errorCode, String errorMessage) {
mNumErrors++;
IAccountManagerResponse response = getResponseAndClose();
@@ -2731,6 +2768,7 @@
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..89bddc6 100644
--- a/services/java/com/android/server/am/ActiveServices.java
+++ b/services/java/com/android/server/am/ActiveServices.java
@@ -26,6 +26,7 @@
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 @@
// 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 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 @@
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 @@
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 @@
}
}
+ 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
} 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 @@
if (!mRestartingServices.contains(r)) {
r.createdFromFg = false;
mRestartingServices.add(r);
+ r.makeRestarting(mAm.mProcessStats.getMemFactorLocked(), now);
}
r.cancelNotification();
@@ -1170,16 +1215,44 @@
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 @@
// 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 @@
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 @@
} finally {
if (!created) {
app.services.remove(r);
+ r.app = null;
scheduleServiceRestartLocked(r, false);
}
}
@@ -1508,16 +1585,13 @@
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 @@
}
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 @@
+ 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 @@
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 @@
&& 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 @@
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 @@
r.tracker = null;
}
}
+ if (finishing) {
+ if (r.app != null && !r.app.persistent) {
+ r.app.services.remove(r);
+ }
+ r.app = null;
+ }
}
}
@@ -1839,6 +1930,9 @@
Slog.i(TAG, " Force stopping service " + service);
if (service.app != null) {
service.app.removed = true;
+ if (!service.app.persistent) {
+ service.app.services.remove(service);
+ }
}
service.app = null;
service.isolatedProc = null;
@@ -1905,8 +1999,7 @@
}
}
- 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 +2028,15 @@
}
}
- // 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.persistent) {
+ sr.app.services.remove(sr);
+ }
sr.app = null;
sr.isolatedProc = null;
sr.executeNesting = 0;
@@ -1965,8 +2053,33 @@
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 +2112,23 @@
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 d75fe5e1..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.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 @@
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 @@
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 @@
* 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 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
}
}
}
- 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
boolean dumpDalvik = false;
boolean oomOnly = false;
boolean isCompact = false;
+ boolean localOnly = false;
int opti = 0;
while (opti < args.length) {
@@ -11751,12 +11918,15 @@
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 @@
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 @@
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 @@
app.adjTarget = null;
app.empty = false;
app.cached = false;
- app.hasClientActivities = false;
final int activitiesSize = app.activities.size();
@@ -14089,7 +14266,6 @@
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 @@
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 @@
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 @@
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 @@
+ 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 @@
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 @@
}
}
+ 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 @@
// 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 @@
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 @@
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 @@
// 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..0e152611 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_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.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 @@
// 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 @@
mTaskHistory.remove(i);
mTaskHistory.add(task);
--index;
+ // Use same value for i.
+ } else {
+ ++i;
}
}
if (VALIDATE_TOKENS) {
@@ -997,8 +1000,8 @@
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 @@
} 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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
}
}
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 @@
}
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 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 @@
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 @@
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 @@
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 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.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 @@
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 @@
+ " 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 @@
*/
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..217a8d61 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -46,7 +46,7 @@
* 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 @@
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 @@
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 @@
}
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 @@
}
}
- 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 @@
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 @@
}
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 @@
}
}
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 @@
} 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 @@
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 @@
}
}
+ 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 @@
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,21 +1295,41 @@
}
/**
- * 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 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 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,
* so be careful what you do with them.
diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java
index 249c8b0..bcb677f 100644
--- a/services/java/com/android/server/display/DisplayManagerService.java
+++ b/services/java/com/android/server/display/DisplayManagerService.java
@@ -35,6 +35,7 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.text.TextUtils;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -173,6 +174,9 @@
// The Wifi display adapter, or null if not registered.
private WifiDisplayAdapter mWifiDisplayAdapter;
+ // The number of active wifi display scan requests.
+ private int mWifiDisplayScanRequestCount;
+
// The virtual display adapter, or null if not registered.
private VirtualDisplayAdapter mVirtualDisplayAdapter;
@@ -458,38 +462,94 @@
}
}
- private void onCallbackDied(int pid) {
+ private void onCallbackDied(CallbackRecord record) {
synchronized (mSyncRoot) {
- mCallbacks.remove(pid);
+ mCallbacks.remove(record.mPid);
+ stopWifiDisplayScanLocked(record);
}
}
@Override // Binder call
- public void scanWifiDisplays() {
+ public void startWifiDisplayScan() {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+ "Permission required to start wifi display scans");
+
+ final int callingPid = Binder.getCallingPid();
final long token = Binder.clearCallingIdentity();
try {
synchronized (mSyncRoot) {
- if (mWifiDisplayAdapter != null) {
- mWifiDisplayAdapter.requestScanLocked();
+ CallbackRecord record = mCallbacks.get(callingPid);
+ if (record == null) {
+ throw new IllegalStateException("The calling process has not "
+ + "registered an IDisplayManagerCallback.");
}
+ startWifiDisplayScanLocked(record);
}
} finally {
Binder.restoreCallingIdentity(token);
}
}
+ private void startWifiDisplayScanLocked(CallbackRecord record) {
+ if (!record.mWifiDisplayScanRequested) {
+ record.mWifiDisplayScanRequested = true;
+ if (mWifiDisplayScanRequestCount++ == 0) {
+ if (mWifiDisplayAdapter != null) {
+ mWifiDisplayAdapter.requestStartScanLocked();
+ }
+ }
+ }
+ }
+
+ @Override // Binder call
+ public void stopWifiDisplayScan() {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+ "Permission required to stop wifi display scans");
+
+ final int callingPid = Binder.getCallingPid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSyncRoot) {
+ CallbackRecord record = mCallbacks.get(callingPid);
+ if (record == null) {
+ throw new IllegalStateException("The calling process has not "
+ + "registered an IDisplayManagerCallback.");
+ }
+ stopWifiDisplayScanLocked(record);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void stopWifiDisplayScanLocked(CallbackRecord record) {
+ if (record.mWifiDisplayScanRequested) {
+ record.mWifiDisplayScanRequested = false;
+ if (--mWifiDisplayScanRequestCount == 0) {
+ if (mWifiDisplayAdapter != null) {
+ mWifiDisplayAdapter.requestStopScanLocked();
+ }
+ } else if (mWifiDisplayScanRequestCount < 0) {
+ Log.wtf(TAG, "mWifiDisplayScanRequestCount became negative: "
+ + mWifiDisplayScanRequestCount);
+ mWifiDisplayScanRequestCount = 0;
+ }
+ }
+ }
+
@Override // Binder call
public void connectWifiDisplay(String address) {
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 +559,8 @@
@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 +576,8 @@
@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 +593,11 @@
@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 +615,8 @@
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 +635,8 @@
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 +652,9 @@
@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 +668,6 @@
}
}
- 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) {
@@ -1112,6 +1163,7 @@
pw.println(" mDefaultViewport=" + mDefaultViewport);
pw.println(" mExternalTouchViewport=" + mExternalTouchViewport);
pw.println(" mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
+ pw.println(" mWifiDisplayScanRequestCount=" + mWifiDisplayScanRequestCount);
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.increaseIndent();
@@ -1139,6 +1191,15 @@
pw.println(" Display " + displayId + ":");
display.dumpLocked(ipw);
}
+
+ final int callbackCount = mCallbacks.size();
+ pw.println();
+ pw.println("Callbacks: size=" + callbackCount);
+ for (int i = 0; i < callbackCount; i++) {
+ CallbackRecord callback = mCallbacks.valueAt(i);
+ pw.println(" " + i + ": mPid=" + callback.mPid
+ + ", mWifiDisplayScanRequested=" + callback.mWifiDisplayScanRequested);
+ }
}
}
@@ -1239,9 +1300,11 @@
}
private final class CallbackRecord implements DeathRecipient {
- private final int mPid;
+ public final int mPid;
private final IDisplayManagerCallback mCallback;
+ public boolean mWifiDisplayScanRequested;
+
public CallbackRecord(int pid, IDisplayManagerCallback callback) {
mPid = pid;
mCallback = callback;
@@ -1252,7 +1315,7 @@
if (DEBUG) {
Slog.d(TAG, "Display listener for pid " + mPid + " died.");
}
- onCallbackDied(mPid);
+ onCallbackDied(this);
}
public void notifyDisplayEventAsync(int displayId, int event) {
diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java
index f7bbdf8..cd57941 100644
--- a/services/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/java/com/android/server/display/WifiDisplayAdapter.java
@@ -127,7 +127,7 @@
pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast);
pw.println("mPendingNotificationUpdate=" + mPendingNotificationUpdate);
pw.println("mSupportsProtectedBuffers=" + mSupportsProtectedBuffers);
-
+
// Try to dump the controller state.
if (mDisplayController == null) {
pw.println("mDisplayController=null");
@@ -157,34 +157,39 @@
});
}
- public void requestScanLocked() {
+ public void requestStartScanLocked() {
if (DEBUG) {
- Slog.d(TAG, "requestScanLocked");
+ Slog.d(TAG, "requestStartScanLocked");
}
getHandler().post(new Runnable() {
@Override
public void run() {
if (mDisplayController != null) {
- mDisplayController.requestScan();
+ mDisplayController.requestStartScan();
}
}
});
}
- public void requestConnectLocked(final String address, final boolean trusted) {
+ public void requestStopScanLocked() {
if (DEBUG) {
- Slog.d(TAG, "requestConnectLocked: address=" + address + ", trusted=" + trusted);
+ Slog.d(TAG, "requestStopScanLocked");
}
- 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;
+ getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ if (mDisplayController != null) {
+ mDisplayController.requestStopScan();
}
}
+ });
+ }
+
+ public void requestConnectLocked(final String address) {
+ if (DEBUG) {
+ Slog.d(TAG, "requestConnectLocked: address=" + address);
}
getHandler().post(new Runnable() {
@@ -197,15 +202,6 @@
});
}
- private boolean isRememberedDisplayLocked(String address) {
- for (WifiDisplay display : mRememberedDisplays) {
- if (display.getDeviceAddress().equals(address)) {
- return true;
- }
- }
- return false;
- }
-
public void requestPauseLocked() {
if (DEBUG) {
Slog.d(TAG, "requestPauseLocked");
@@ -400,8 +396,6 @@
mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height,
refreshRate, deviceFlags, address, surface);
sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED);
-
- scheduleUpdateNotificationLocked();
}
private void removeDisplayDeviceLocked() {
@@ -409,8 +403,6 @@
mDisplayDevice.destroyLocked();
sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED);
mDisplayDevice = null;
-
- scheduleUpdateNotificationLocked();
}
}
@@ -457,21 +449,24 @@
// 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 +488,38 @@
// 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);
}
}
@@ -545,20 +558,20 @@
}
@Override
- public void onScanFinished(WifiDisplay[] availableDisplays) {
+ public void onScanResults(WifiDisplay[] availableDisplays) {
synchronized (getSyncRoot()) {
availableDisplays = mPersistentDataStore.applyWifiDisplayAliases(
availableDisplays);
- // check if any of the available displays changed canConnect status
boolean changed = !Arrays.equals(mAvailableDisplays, availableDisplays);
+
+ // Check whether any of the available displays changed canConnect status.
for (int i = 0; !changed && i<availableDisplays.length; i++) {
changed = availableDisplays[i].canConnect()
!= mAvailableDisplays[i].canConnect();
}
- if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING || changed) {
- mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING;
+ if (changed) {
mAvailableDisplays = availableDisplays;
fixRememberedDisplayNamesFromAvailableDisplaysLocked();
updateDisplaysLocked();
@@ -568,6 +581,16 @@
}
@Override
+ public void onScanFinished() {
+ synchronized (getSyncRoot()) {
+ if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING) {
+ mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING;
+ scheduleStatusChangedBroadcastLocked();
+ }
+ }
+ }
+
+ @Override
public void onDisplayConnecting(WifiDisplay display) {
synchronized (getSyncRoot()) {
display = mPersistentDataStore.applyWifiDisplayAlias(display);
@@ -578,6 +601,7 @@
mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTING;
mActiveDisplay = display;
scheduleStatusChangedBroadcastLocked();
+ scheduleUpdateNotificationLocked();
}
}
}
@@ -590,6 +614,7 @@
mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
mActiveDisplay = null;
scheduleStatusChangedBroadcastLocked();
+ scheduleUpdateNotificationLocked();
}
}
}
@@ -607,6 +632,7 @@
mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTED;
mActiveDisplay = display;
scheduleStatusChangedBroadcastLocked();
+ scheduleUpdateNotificationLocked();
}
}
}
@@ -629,6 +655,7 @@
mActiveDisplay = display;
renameDisplayDeviceLocked(display.getFriendlyDisplayName());
scheduleStatusChangedBroadcastLocked();
+ scheduleUpdateNotificationLocked();
}
}
}
@@ -644,6 +671,7 @@
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..dbb59b2 100644
--- a/services/java/com/android/server/display/WifiDisplayController.java
+++ b/services/java/com/android/server/display/WifiDisplayController.java
@@ -27,7 +27,6 @@
import android.hardware.display.WifiDisplay;
import android.hardware.display.WifiDisplaySessionInfo;
import android.hardware.display.WifiDisplayStatus;
-import android.media.AudioManager;
import android.media.RemoteDisplay;
import android.net.NetworkInfo;
import android.net.Uri;
@@ -75,12 +74,19 @@
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 CONNECTION_TIMEOUT_SECONDS = 30;
+ 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;
- private static final int DISCOVER_PEERS_RETRY_DELAY_MILLIS = 500;
+ // We repeatedly issue calls to discover peers every so often for a few reasons.
+ // 1. The initial request may fail and need to retried.
+ // 2. Discovery will self-abort after any group is initiated, which may not necessarily
+ // be what we want to have happen.
+ // 3. Discovery will self-timeout after 2 minutes, whereas we want discovery to
+ // be occur for as long as a client is requesting it be.
+ // 4. We don't seem to get updated results for displays we've already found until
+ // we ask to discover again, particularly for the isSessionAvailable() property.
+ private static final int DISCOVER_PEERS_INTERVAL_MILLIS = 10000;
private static final int CONNECT_MAX_RETRIES = 3;
private static final int CONNECT_RETRY_DELAY_MILLIS = 500;
@@ -103,12 +109,12 @@
// True if Wifi display is enabled by the user.
private boolean mWifiDisplayOnSetting;
+ // True if a scan was requested independent of whether one is actually in progress.
+ private boolean mScanRequested;
+
// True if there is a call to discoverPeers in progress.
private boolean mDiscoverPeersInProgress;
- // Number of discover peers retries remaining.
- private int mDiscoverPeersRetriesLeft;
-
// The device to which we want to connect, or null if we want to be disconnected.
private WifiP2pDevice mDesiredDevice;
@@ -209,8 +215,8 @@
pw.println("mWfdEnabled=" + mWfdEnabled);
pw.println("mWfdEnabling=" + mWfdEnabling);
pw.println("mNetworkInfo=" + mNetworkInfo);
+ pw.println("mScanRequested=" + mScanRequested);
pw.println("mDiscoverPeersInProgress=" + mDiscoverPeersInProgress);
- pw.println("mDiscoverPeersRetriesLeft=" + mDiscoverPeersRetriesLeft);
pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice));
pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice));
pw.println("mDisconnectingDisplay=" + describeWifiP2pDevice(mDisconnectingDevice));
@@ -232,8 +238,18 @@
}
}
- public void requestScan() {
- discoverPeers();
+ public void requestStartScan() {
+ if (!mScanRequested) {
+ mScanRequested = true;
+ updateScanState();
+ }
+ }
+
+ public void requestStopScan() {
+ if (mScanRequested) {
+ mScanRequested = false;
+ updateScanState();
+ }
}
public void requestConnect(String address) {
@@ -282,6 +298,7 @@
mWfdEnabling = false;
mWfdEnabled = true;
reportFeatureState();
+ updateScanState();
}
}
@@ -318,6 +335,7 @@
mWfdEnabling = false;
mWfdEnabled = false;
reportFeatureState();
+ updateScanState();
disconnect();
}
}
@@ -340,12 +358,29 @@
WifiDisplayStatus.FEATURE_STATE_OFF;
}
- private void discoverPeers() {
- if (!mDiscoverPeersInProgress) {
- mDiscoverPeersInProgress = true;
- mDiscoverPeersRetriesLeft = DISCOVER_PEERS_MAX_RETRIES;
- handleScanStarted();
- tryDiscoverPeers();
+ private void updateScanState() {
+ if (mScanRequested && mWfdEnabled && mDesiredDevice == null) {
+ if (!mDiscoverPeersInProgress) {
+ Slog.i(TAG, "Starting Wifi display scan.");
+ mDiscoverPeersInProgress = true;
+ handleScanStarted();
+ tryDiscoverPeers();
+ }
+ } else {
+ if (mDiscoverPeersInProgress) {
+ // Cancel automatic retry right away.
+ mHandler.removeCallbacks(mDiscoverPeers);
+
+ // Defer actually stopping discovery if we have a connection attempt in progress.
+ // The wifi display connection attempt often fails if we are not in discovery
+ // mode. So we allow discovery to continue until we give up trying to connect.
+ if (mDesiredDevice == null || mDesiredDevice == mConnectedDevice) {
+ Slog.i(TAG, "Stopping Wifi display scan.");
+ mDiscoverPeersInProgress = false;
+ stopPeerDiscovery();
+ handleScanFinished();
+ }
+ }
}
}
@@ -357,8 +392,9 @@
Slog.d(TAG, "Discover peers succeeded. Requesting peers now.");
}
- mDiscoverPeersInProgress = false;
- requestPeers();
+ if (mDiscoverPeersInProgress) {
+ requestPeers();
+ }
}
@Override
@@ -367,30 +403,28 @@
Slog.d(TAG, "Discover peers failed with reason " + reason + ".");
}
- if (mDiscoverPeersInProgress) {
- if (reason == 0 && mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) {
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- if (mDiscoverPeersInProgress) {
- if (mDiscoverPeersRetriesLeft > 0 && mWfdEnabled) {
- mDiscoverPeersRetriesLeft -= 1;
- if (DEBUG) {
- Slog.d(TAG, "Retrying discovery. Retries left: "
- + mDiscoverPeersRetriesLeft);
- }
- tryDiscoverPeers();
- } else {
- handleScanFinished();
- mDiscoverPeersInProgress = false;
- }
- }
- }
- }, DISCOVER_PEERS_RETRY_DELAY_MILLIS);
- } else {
- handleScanFinished();
- mDiscoverPeersInProgress = false;
- }
+ // Ignore the error.
+ // We will retry automatically in a little bit.
+ }
+ });
+
+ // Retry discover peers periodically until stopped.
+ mHandler.postDelayed(mDiscoverPeers, DISCOVER_PEERS_INTERVAL_MILLIS);
+ }
+
+ private void stopPeerDiscovery() {
+ mWifiP2pManager.stopPeerDiscovery(mWifiP2pChannel, new ActionListener() {
+ @Override
+ public void onSuccess() {
+ if (DEBUG) {
+ Slog.d(TAG, "Stop peer discovery succeeded.");
+ }
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ if (DEBUG) {
+ Slog.d(TAG, "Stop peer discovery failed with reason " + reason + ".");
}
}
});
@@ -415,7 +449,9 @@
}
}
- handleScanFinished();
+ if (mDiscoverPeersInProgress) {
+ handleScanResults();
+ }
}
});
}
@@ -429,7 +465,7 @@
});
}
- private void handleScanFinished() {
+ private void handleScanResults() {
final int count = mAvailableWifiDisplayPeers.size();
final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count);
for (int i = 0; i < count; i++) {
@@ -441,7 +477,16 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- mListener.onScanFinished(displays);
+ mListener.onScanResults(displays);
+ }
+ });
+ }
+
+ private void handleScanFinished() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onScanFinished();
}
});
}
@@ -484,6 +529,12 @@
return;
}
+ if (!mWfdEnabled) {
+ Slog.i(TAG, "Ignoring request to connect to Wifi display because the "
+ +" feature is currently disabled: " + device.deviceName);
+ return;
+ }
+
mDesiredDevice = device;
mConnectionRetriesLeft = CONNECT_MAX_RETRIES;
updateConnection();
@@ -508,6 +559,10 @@
* connection is established (or not).
*/
private void updateConnection() {
+ // Step 0. Stop scans if necessary to prevent interference while connected.
+ // Resume scans later when no longer attempting to connect.
+ updateScanState();
+
// Step 1. Before we try to connect to a new device, tell the system we
// have disconnected from the old one.
if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
@@ -661,7 +716,7 @@
return; // wait for asynchronous callback
}
- // Step 6. Listen for incoming connections.
+ // Step 6. Listen for incoming RTSP connection.
if (mConnectedDevice != null && mRemoteDisplay == null) {
Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
if (addr == null) {
@@ -817,7 +872,11 @@
}
} else {
mConnectedDeviceGroupInfo = null;
- disconnect();
+
+ // Disconnect if we lost the network while connecting or connected to a display.
+ if (mConnectingDevice != null || mConnectedDevice != null) {
+ disconnect();
+ }
// After disconnection for a group, for some reason we have a tendency
// to get a peer change notification with an empty list of peers.
@@ -828,6 +887,13 @@
}
}
+ private final Runnable mDiscoverPeers = new Runnable() {
+ @Override
+ public void run() {
+ tryDiscoverPeers();
+ }
+ };
+
private final Runnable mConnectionTimeout = new Runnable() {
@Override
public void run() {
@@ -1033,7 +1099,8 @@
void onFeatureStateChanged(int featureState);
void onScanStarted();
- void onScanFinished(WifiDisplay[] availableDisplays);
+ void onScanResults(WifiDisplay[] availableDisplays);
+ void onScanFinished();
void onDisplayConnecting(WifiDisplay display);
void onDisplayConnectionFailed();
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
}
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), " ");
- }
- }
+ 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());
}
- if (removed != null) {
- for (int i=0; i<removed.size(); i++) {
- PreferredActivity pa = removed.get(i);
- pir.removeFilter(pa);
+ 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 @@
}
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 @@
}
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 @@
// 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 @@
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 @@
+ ", 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 @@
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 @@
}
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 @@
/**
* 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 @@
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 @@
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 @@
}
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 @@
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.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 @@
/** 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 @@
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 @@
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 @@
}
mTaskHistory.add(taskNdx, task);
+ EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, task.taskId, toTop ? 1 : 0, taskNdx);
}
void removeTask(Task task) {
@@ -277,6 +271,8 @@
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 @@
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 @@
/** 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 @@
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 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 @@
/** 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 @@
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 @@
int remove() {
mAnimationBackgroundSurface.destroySurface();
mDimLayer.destroySurface();
+ EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
return mStackBox.remove();
}
@@ -269,6 +271,8 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
displayContent.moveHomeStackBox(isHomeStackTask);
}
stack.moveTaskToTop(task);
- displayContent.moveStack(stack, true);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -4846,7 +4847,6 @@
weight);
if (stack != null) {
mStackIdToStack.put(stackId, stack);
- displayContent.moveStack(stack, true);
performLayoutAndPlaceSurfacesLocked();
return;
}
@@ -4878,6 +4878,7 @@
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 @@
// 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 @@
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 @@
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 @@
}
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 @@
mInnerFields.mScreenBrightness = -1;
mInnerFields.mButtonBrightness = -1;
mInnerFields.mUserActivityTimeout = -1;
- mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_UNKNOWN;
+ mInnerFields.mObscureApplicationContentOnSecondaryDisplays = false;
mTransactionSequence++;
@@ -8939,10 +8947,8 @@
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 @@
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 @@
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 @@
}
/**
+ * 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
new file mode 100755
index 0000000..66a1984
--- /dev/null
+++ b/tests/RemoteDisplayProvider/res/drawable-hdpi/ic_app.png
Binary files differ
diff --git a/tests/RemoteDisplayProvider/res/drawable-mdpi/ic_app.png b/tests/RemoteDisplayProvider/res/drawable-mdpi/ic_app.png
new file mode 100644
index 0000000..5ae7701
--- /dev/null
+++ b/tests/RemoteDisplayProvider/res/drawable-mdpi/ic_app.png
Binary files differ
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 @@
}
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_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 @@
$(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 @@
$(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 @@
}
@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 @@
@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 @@
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 @@
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 @@
// --- 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 @@
}
@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 @@
"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 @@
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 @@
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 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 @@
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 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 @@
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 @@
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 @@
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 @@
* 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.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 @@
/* 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 @@
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 @@
/**
* 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 @@
}
private void startBatchedScan() {
+ if (mBatchedScanSettings == null) return;
+
if (mDhcpActive) {
if (DBG) log("not starting Batched Scans due to DHCP");
return;
@@ -872,10 +891,10 @@
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 @@
}
// 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 @@
}
}
+ 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 @@
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 @@
mDhcpActive = false;
- if (mBatchedScanSettings != null) {
- startBatchedScan();
- }
+ startBatchedScan();
}
private void handleSuccessfulIpConfiguration(DhcpResults dhcpResults) {
@@ -2447,7 +2511,7 @@
}
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 @@
mDhcpActive = false;
- if (mBatchedScanSettings != null) {
- startBatchedScan();
- }
+ startBatchedScan();
if (mOperationalMode != CONNECT_MODE) {
mWifiNative.disconnect();
@@ -3021,8 +3083,10 @@
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 @@
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);