Merge "Animate drag shadow when the drag is cancelled"
diff --git a/Android.mk b/Android.mk
index 1eff5cc..7ca04a5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -344,8 +344,6 @@
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/IRemoteVolumeController.aidl \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 6e44d77..40908f1 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -236,6 +236,8 @@
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/target/common/obj/framework.aidl)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/DocumentsUI_intermediates)
+$(call add-clean-step, rm -f $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/media/java/android/media/IRemoteControlClient.*)
+$(call add-clean-step, rm -f $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/media/java/android/media/IRemoteControlDisplay.*)
# ******************************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 7fdb972..74634a9 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1915,6 +1915,19 @@
builder.build(); // callers expect this notification to be ready to use
}
+ /**
+ * @hide
+ */
+ public static void addFieldsFromContext(Context context, Notification notification) {
+ if (notification.extras.getParcelable(EXTRA_BUILDER_APPLICATION_INFO) == null) {
+ notification.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO,
+ context.getApplicationInfo());
+ }
+ if (!notification.extras.containsKey(EXTRA_ORIGINATING_USERID)) {
+ notification.extras.putInt(EXTRA_ORIGINATING_USERID, context.getUserId());
+ }
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@@ -2105,11 +2118,6 @@
private boolean mColorUtilInited = false;
/**
- * The user that built the notification originally.
- */
- private int mOriginatingUserId;
-
- /**
* Constructs a new Builder with the defaults:
*
@@ -2940,7 +2948,7 @@
// Note: This assumes that the current user can read the profile badge of the
// originating user.
return mContext.getPackageManager().getUserBadgeForDensity(
- new UserHandle(mOriginatingUserId), 0);
+ new UserHandle(mContext.getUserId()), 0);
}
private Bitmap getProfileBadge() {
@@ -3428,10 +3436,6 @@
mN.extras.putStringArray(EXTRA_PEOPLE,
mPersonList.toArray(new String[mPersonList.size()]));
}
- if (mN.topic == null) {
- mN.topic = new Topic(TOPIC_DEFAULT, mContext.getString(
- R.string.default_notification_topic_label));
- }
return mN;
}
@@ -3440,12 +3444,16 @@
ApplicationInfo applicationInfo = n.extras.getParcelable(
EXTRA_BUILDER_APPLICATION_INFO);
Context builderContext;
- try {
- builderContext = context.createApplicationContext(applicationInfo,
- Context.CONTEXT_RESTRICTED);
- } catch (NameNotFoundException e) {
- Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
- builderContext = context; // try with our context
+ if (applicationInfo != null) {
+ try {
+ builderContext = context.createApplicationContext(applicationInfo,
+ Context.CONTEXT_RESTRICTED);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "ApplicationInfo " + applicationInfo + " not found");
+ builderContext = context; // try with our context
+ }
+ } else {
+ builderContext = context; // try with given context
}
return new Builder(builderContext, n);
@@ -3494,9 +3502,7 @@
}
// lazy stuff from mContext; see comment in Builder(Context, Notification)
- mN.extras.putParcelable(EXTRA_BUILDER_APPLICATION_INFO, mContext.getApplicationInfo());
- mOriginatingUserId = mContext.getUserId();
- mN.extras.putInt(EXTRA_ORIGINATING_USERID, mOriginatingUserId);
+ Notification.addFieldsFromContext(mContext, mN);
buildUnstyled();
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 043c503..3eb3e0f 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -218,6 +218,8 @@
int[] idOut = new int[1];
INotificationManager service = getService();
String pkg = mContext.getPackageName();
+ // Fix the notification as best we can.
+ Notification.addFieldsFromContext(mContext, notification);
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
if (StrictMode.vmFileUriExposureEnabled()) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f1a55f9..1c65c94 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4558,4 +4558,37 @@
return false;
}
}
+
+ /**
+ * @hide
+ * Return if this user is a managed profile of another user. An admin can become the profile
+ * owner of a managed profile with {@link #ACTION_PROVISION_MANAGED_PROFILE} and of a managed
+ * user with {@link #ACTION_PROVISION_MANAGED_USER}.
+ * @param admin Which profile owner this request is associated with.
+ * @return if this user is a managed profile of another user.
+ */
+ public boolean isManagedProfile(@NonNull ComponentName admin) {
+ try {
+ return mService.isManagedProfile(admin);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed talking with device policy service", re);
+ return false;
+ }
+ }
+
+ /**
+ * @hide
+ * Return if this user is a system-only user. An admin can manage a device from a system only
+ * user by calling {@link #ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE}.
+ * @param admin Which device owner this request is associated with.
+ * @return if this user is a system-only user.
+ */
+ public boolean isSystemOnlyUser(@NonNull ComponentName admin) {
+ try {
+ return mService.isSystemOnlyUser(admin);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed talking with device policy service", re);
+ return false;
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index c6f8740..fc7c2b3 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -233,4 +233,6 @@
boolean isProvisioningAllowed(String action);
void setKeepUninstalledPackages(in ComponentName admin,in List<String> packageList);
List<String> getKeepUninstalledPackages(in ComponentName admin);
+ boolean isManagedProfile(in ComponentName admin);
+ boolean isSystemOnlyUser(in ComponentName admin);
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 885255f..4a3c59b 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -627,9 +627,9 @@
* {@link #CONFIG_MCC}, {@link #CONFIG_MNC},
* {@link #CONFIG_LOCALE}, {@link #CONFIG_TOUCHSCREEN},
* {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION},
- * {@link #CONFIG_ORIENTATION}, {@link #CONFIG_SCREEN_LAYOUT} and
- * {@link #CONFIG_LAYOUT_DIRECTION}. Set from the {@link android.R.attr#configChanges}
- * attribute.
+ * {@link #CONFIG_ORIENTATION}, {@link #CONFIG_SCREEN_LAYOUT},
+ * {@link #CONFIG_DENSITY}, and {@link #CONFIG_LAYOUT_DIRECTION}.
+ * Set from the {@link android.R.attr#configChanges} attribute.
*/
public int configChanges;
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index d9005b2..6586426 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -19,6 +19,7 @@
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.IOnAppsChangedListener;
+import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.graphics.Rect;
import android.os.Bundle;
@@ -31,7 +32,7 @@
interface ILauncherApps {
void addOnAppsChangedListener(in IOnAppsChangedListener listener);
void removeOnAppsChangedListener(in IOnAppsChangedListener listener);
- List<ResolveInfo> getLauncherActivities(String packageName, in UserHandle user);
+ ParceledListSlice getLauncherActivities(String packageName, in UserHandle user);
ResolveInfo resolveActivity(in Intent intent, in UserHandle user);
void startActivityAsUser(in ComponentName component, in Rect sourceBounds,
in Bundle opts, in UserHandle user);
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index 6827d7a..a5617b4 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -41,7 +41,6 @@
private ComponentName mComponentName;
private ResolveInfo mResolveInfo;
private UserHandle mUser;
- private long mFirstInstallTime;
/**
* Create a launchable activity object for a given ResolveInfo and user.
@@ -50,14 +49,12 @@
* @param info ResolveInfo from which to create the LauncherActivityInfo.
* @param user The UserHandle of the profile to which this activity belongs.
*/
- LauncherActivityInfo(Context context, ResolveInfo info, UserHandle user,
- long firstInstallTime) {
+ LauncherActivityInfo(Context context, ResolveInfo info, UserHandle user) {
this(context);
mResolveInfo = info;
mActivityInfo = info.activityInfo;
mComponentName = LauncherApps.getComponentName(info);
mUser = user;
- mFirstInstallTime = firstInstallTime;
}
LauncherActivityInfo(Context context) {
@@ -136,7 +133,7 @@
/**
* Returns the drawable for this activity, without any badging for the profile.
- * @param resource id of the drawable.
+ * @param iconRes id of the drawable.
* @param density The preferred density of the icon, zero for default density. Use
* density DPI values from {@link DisplayMetrics}.
* @see DisplayMetrics
@@ -179,7 +176,13 @@
* @return The time of installation of the package, in milliseconds.
*/
public long getFirstInstallTime() {
- return mFirstInstallTime;
+ try {
+ return mPm.getPackageInfo(mActivityInfo.packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES).firstInstallTime;
+ } catch (NameNotFoundException nnfe) {
+ // Sorry, can't find package
+ return 0;
+ }
}
/**
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index e9ec771..6e67af4 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -142,28 +142,18 @@
* @return List of launchable activities. Can be an empty list but will not be null.
*/
public List<LauncherActivityInfo> getActivityList(String packageName, UserHandle user) {
- List<ResolveInfo> activities = null;
+ ParceledListSlice<ResolveInfo> activities = null;
try {
activities = mService.getLauncherActivities(packageName, user);
} catch (RemoteException re) {
- throw new RuntimeException("Failed to call LauncherAppsService");
+ throw new RuntimeException("Failed to call LauncherAppsService", re);
}
if (activities == null) {
return Collections.EMPTY_LIST;
}
ArrayList<LauncherActivityInfo> lais = new ArrayList<LauncherActivityInfo>();
- final int count = activities.size();
- for (int i = 0; i < count; i++) {
- ResolveInfo ri = activities.get(i);
- long firstInstallTime = 0;
- try {
- firstInstallTime = mPm.getPackageInfo(ri.activityInfo.packageName,
- PackageManager.GET_UNINSTALLED_PACKAGES).firstInstallTime;
- } catch (NameNotFoundException nnfe) {
- // Sorry, can't find package
- }
- LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri, user,
- firstInstallTime);
+ for (ResolveInfo ri : activities.getList()) {
+ LauncherActivityInfo lai = new LauncherActivityInfo(mContext, ri, user);
if (DEBUG) {
Log.v(TAG, "Returning activity for profile " + user + " : "
+ lai.getComponentName());
@@ -189,19 +179,11 @@
try {
ResolveInfo ri = mService.resolveActivity(intent, user);
if (ri != null) {
- long firstInstallTime = 0;
- try {
- firstInstallTime = mPm.getPackageInfo(ri.activityInfo.packageName,
- PackageManager.GET_UNINSTALLED_PACKAGES).firstInstallTime;
- } catch (NameNotFoundException nnfe) {
- // Sorry, can't find package
- }
- LauncherActivityInfo info = new LauncherActivityInfo(mContext, ri, user,
- firstInstallTime);
+ LauncherActivityInfo info = new LauncherActivityInfo(mContext, ri, user);
return info;
}
} catch (RemoteException re) {
- throw new RuntimeException("Failed to call LauncherAppsService");
+ throw new RuntimeException("Failed to call LauncherAppsService", re);
}
return null;
}
@@ -256,7 +238,7 @@
try {
return mService.isPackageEnabled(packageName, user);
} catch (RemoteException re) {
- throw new RuntimeException("Failed to call LauncherAppsService");
+ throw new RuntimeException("Failed to call LauncherAppsService", re);
}
}
@@ -272,7 +254,7 @@
try {
return mService.isActivityEnabled(component, user);
} catch (RemoteException re) {
- throw new RuntimeException("Failed to call LauncherAppsService");
+ throw new RuntimeException("Failed to call LauncherAppsService", re);
}
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 42fef3b..0c28008 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1729,6 +1729,8 @@
* {@link #hasSystemFeature}: The device supports freeform window management.
* Windows have title bars and can be moved and resized.
*/
+ // If this feature is present, you also need to set
+ // com.android.internal.R.config_freeformWindowManagement to true in your configuration overlay.
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_FREEFORM_WINDOW_MANAGEMENT
= "android.software.freeform_window_management";
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7b5f5ab..64a046e 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -289,8 +289,12 @@
/**
* Create a screenshot of the applications currently displayed.
+ *
+ * @param frameScale the scale to apply to the frame, only used when width = -1 and
+ * height = -1
*/
- Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight);
+ Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth, int maxHeight,
+ float frameScale);
/**
* Called by the status bar to notify Views of changes to System UI visiblity.
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index f78d8d8..8e318a2 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -346,7 +346,7 @@
long startTime = SystemClock.uptimeMillis();
TypedArray ar = mResources.obtainTypedArray(
com.android.internal.R.array.preloaded_drawables);
- int N = preloadDrawables(runtime, ar);
+ int N = preloadDrawables(ar);
ar.recycle();
Log.i(TAG, "...preloaded " + N + " resources in "
+ (SystemClock.uptimeMillis()-startTime) + "ms.");
@@ -354,10 +354,21 @@
startTime = SystemClock.uptimeMillis();
ar = mResources.obtainTypedArray(
com.android.internal.R.array.preloaded_color_state_lists);
- N = preloadColorStateLists(runtime, ar);
+ N = preloadColorStateLists(ar);
ar.recycle();
Log.i(TAG, "...preloaded " + N + " resources in "
+ (SystemClock.uptimeMillis()-startTime) + "ms.");
+
+ if (mResources.getBoolean(
+ com.android.internal.R.bool.config_freeformWindowManagement)) {
+ startTime = SystemClock.uptimeMillis();
+ ar = mResources.obtainTypedArray(
+ com.android.internal.R.array.preloaded_freeform_multi_window_drawables);
+ N = preloadDrawables(ar);
+ ar.recycle();
+ Log.i(TAG, "...preloaded " + N + " resource in "
+ + (SystemClock.uptimeMillis() - startTime) + "ms.");
+ }
}
mResources.finishPreloading();
} catch (RuntimeException e) {
@@ -365,7 +376,7 @@
}
}
- private static int preloadColorStateLists(VMRuntime runtime, TypedArray ar) {
+ private static int preloadColorStateLists(TypedArray ar) {
int N = ar.length();
for (int i=0; i<N; i++) {
int id = ar.getResourceId(i, 0);
@@ -385,7 +396,7 @@
}
- private static int preloadDrawables(VMRuntime runtime, TypedArray ar) {
+ private static int preloadDrawables(TypedArray ar) {
int N = ar.length();
for (int i=0; i<N; i++) {
int id = ar.getResourceId(i, 0);
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index 33c41ef..a6a4564 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -364,6 +364,11 @@
<item>@color/search_url_text_material_light</item>
</array>
+ <array name="preloaded_freeform_multi_window_drawables">
+ <item>@drawable/decor_maximize_button_dark</item>
+ <item>@drawable/decor_maximize_button_light</item>
+ </array>
+
<!-- Used in LocalePicker -->
<string-array translatable="false" name="special_locale_codes">
<!-- http://b/17150708 - ensure that the list of languages says "Arabic"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index e376903..67933cd 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -793,6 +793,10 @@
physical screen size has changed such as switching to an external
display. -->
<flag name="smallestScreenSize" value="0x0800" />
+ <!-- The display density has changed. This might be caused by the user
+ specifying a different display scale, or it might be caused by a
+ different display being activated. -->
+ <flag name="density" value="0x1000" />
<!-- The layout direction has changed. For example going from LTR to RTL. -->
<flag name="layoutDirection" value="0x2000" />
<!-- The font scaling factor has changed, that is the user has
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 539baa5..d9e0472 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2377,4 +2377,11 @@
<!-- The BT name of the keyboard packaged with the device. If this is defined, SystemUI will
automatically try to pair with it when the device exits tablet mode. -->
<string translatable="false" name="config_packagedKeyboardName"></string>
+
+ <!-- The device supports freeform window management. Windows have title bars and can be moved
+ and resized. If you set this to true, you also need to add
+ PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT feature to your device specification.
+ The duplication is necessary, because this information is used before the features are
+ available to the system.-->
+ <bool name="config_freeformWindowManagement">false</bool>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 476b879..c66dd18 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -311,6 +311,7 @@
<java-symbol type="bool" name="config_wifi_enable_wifi_firmware_debugging" />
<java-symbol type="bool" name="config_supportMicNearUltrasound" />
<java-symbol type="bool" name="config_supportSpeakerNearUltrasound" />
+ <java-symbol type="bool" name="config_freeformWindowManagement" />
<java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_threshold" />
<java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_factor" />
<java-symbol type="integer" name="config_wifi_framework_5GHz_preference_penalty_threshold" />
@@ -1123,6 +1124,7 @@
<java-symbol type="array" name="networkAttributes" />
<java-symbol type="array" name="preloaded_color_state_lists" />
<java-symbol type="array" name="preloaded_drawables" />
+ <java-symbol type="array" name="preloaded_freeform_multi_window_drawables" />
<java-symbol type="array" name="sim_colors" />
<java-symbol type="array" name="special_locale_codes" />
<java-symbol type="array" name="special_locale_names" />
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 89cadea..ca85dfb 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -237,7 +237,7 @@
if (CC_LIKELY(mSwapHistory.size())) {
nsecs_t latestVsync = mRenderThread.timeLord().latestVsync();
const SwapHistory& lastSwap = mSwapHistory.back();
- int vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync);
+ nsecs_t vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync);
// The slight fudge-factor is to deal with cases where
// the vsync was estimated due to being slow handling the signal.
// See the logic in TimeLord#computeFrameTimeNanos or in
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 50df556..c658675 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2658,107 +2658,6 @@
rctlr.stopListeningToSessions();
}
- /**
- * @hide
- * Registers a remote control display that will be sent information by remote control clients.
- * Use this method if your IRemoteControlDisplay is not going to display artwork, otherwise
- * use {@link #registerRemoteControlDisplay(IRemoteControlDisplay, int, int)} to pass the
- * artwork size directly, or
- * {@link #remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay, int, int)} later if artwork
- * is not yet needed.
- * <p>Registration requires the {@link Manifest.permission#MEDIA_CONTENT_CONTROL} permission.
- * @param rcd the IRemoteControlDisplay
- */
- public void registerRemoteControlDisplay(IRemoteControlDisplay rcd) {
- // passing a negative value for art work width and height as they are unknown at this stage
- registerRemoteControlDisplay(rcd, /*w*/-1, /*h*/ -1);
- }
-
- /**
- * @hide
- * Registers a remote control display that will be sent information by remote control clients.
- * <p>Registration requires the {@link Manifest.permission#MEDIA_CONTENT_CONTROL} permission.
- * @param rcd
- * @param w the maximum width of the expected bitmap. Negative values indicate it is
- * useless to send artwork.
- * @param h the maximum height of the expected bitmap. Negative values indicate it is
- * useless to send artwork.
- */
- public void registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
- if (rcd == null) {
- return;
- }
- IAudioService service = getService();
- try {
- service.registerRemoteControlDisplay(rcd, w, h);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in registerRemoteControlDisplay " + e);
- }
- }
-
- /**
- * @hide
- * Unregisters a remote control display that was sent information by remote control clients.
- * @param rcd
- */
- public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
- if (rcd == null) {
- return;
- }
- IAudioService service = getService();
- try {
- service.unregisterRemoteControlDisplay(rcd);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in unregisterRemoteControlDisplay " + e);
- }
- }
-
- /**
- * @hide
- * Sets the artwork size a remote control display expects when receiving bitmaps.
- * @param rcd
- * @param w the maximum width of the expected bitmap. Negative values indicate it is
- * useless to send artwork.
- * @param h the maximum height of the expected bitmap. Negative values indicate it is
- * useless to send artwork.
- */
- public void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) {
- if (rcd == null) {
- return;
- }
- IAudioService service = getService();
- try {
- service.remoteControlDisplayUsesBitmapSize(rcd, w, h);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in remoteControlDisplayUsesBitmapSize " + e);
- }
- }
-
- /**
- * @hide
- * Controls whether a remote control display needs periodic checks of the RemoteControlClient
- * playback position to verify that the estimated position has not drifted from the actual
- * position. By default the check is not performed.
- * The IRemoteControlDisplay must have been previously registered for this to have any effect.
- * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled
- * or disabled. No effect is null.
- * @param wantsSync if true, RemoteControlClient instances which expose their playback position
- * to the framework will regularly compare the estimated playback position with the actual
- * position, and will update the IRemoteControlDisplay implementation whenever a drift is
- * detected.
- */
- public void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd,
- boolean wantsSync) {
- if (rcd == null) {
- return;
- }
- IAudioService service = getService();
- try {
- service.remoteControlDisplayWantsPlaybackPositionSync(rcd, wantsSync);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in remoteControlDisplayWantsPlaybackPositionSync " + e);
- }
- }
/**
* @hide
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 8aebe11..693a519 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -23,9 +23,6 @@
import android.media.AudioRoutesInfo;
import android.media.IAudioFocusDispatcher;
import android.media.IAudioRoutesObserver;
-import android.media.IRemoteControlClient;
-import android.media.IRemoteControlDisplay;
-import android.media.IRemoteVolumeObserver;
import android.media.IRingtonePlayer;
import android.media.IVolumeController;
import android.media.Rating;
@@ -47,8 +44,6 @@
void setStreamVolume(int streamType, int index, int flags, String callingPackage);
- oneway void setRemoteStreamVolume(int index);
-
boolean isStreamMute(int streamType);
void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb);
@@ -121,59 +116,6 @@
int getCurrentAudioFocus();
- /**
- * Register an IRemoteControlDisplay.
- * Success of registration is subject to a check on
- * the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission.
- * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient
- * at the top of the stack to update the new display with its information.
- * @param rcd the IRemoteControlDisplay to register. No effect if null.
- * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
- * display doesn't need to receive artwork.
- * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
- * display doesn't need to receive artwork.
- */
- boolean registerRemoteControlDisplay(in IRemoteControlDisplay rcd, int w, int h);
-
- /**
- * Like registerRemoteControlDisplay, but with success being subject to a check on
- * the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission, and if it fails,
- * success is subject to listenerComp being one of the ENABLED_NOTIFICATION_LISTENERS
- * components.
- */
- boolean registerRemoteController(in IRemoteControlDisplay rcd, int w, int h,
- in ComponentName listenerComp);
-
- /**
- * Unregister an IRemoteControlDisplay.
- * No effect if the IRemoteControlDisplay hasn't been successfully registered.
- * @param rcd the IRemoteControlDisplay to unregister. No effect if null.
- */
- oneway void unregisterRemoteControlDisplay(in IRemoteControlDisplay rcd);
- /**
- * Update the size of the artwork used by an IRemoteControlDisplay.
- * @param rcd the IRemoteControlDisplay with the new artwork size requirement
- * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
- * display doesn't need to receive artwork.
- * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
- * display doesn't need to receive artwork.
- */
- oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h);
- /**
- * Controls whether a remote control display needs periodic checks of the RemoteControlClient
- * playback position to verify that the estimated position has not drifted from the actual
- * position. By default the check is not performed.
- * The IRemoteControlDisplay must have been previously registered for this to have any effect.
- * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled
- * or disabled. Not null.
- * @param wantsSync if true, RemoteControlClient instances which expose their playback position
- * to the framework will regularly compare the estimated playback position with the actual
- * position, and will update the IRemoteControlDisplay implementation whenever a drift is
- * detected.
- */
- oneway void remoteControlDisplayWantsPlaybackPositionSync(in IRemoteControlDisplay rcd,
- boolean wantsSync);
-
void startBluetoothSco(IBinder cb, int targetSdkVersion);
void startBluetoothScoVirtualCall(IBinder cb);
void stopBluetoothSco(IBinder cb);
diff --git a/media/java/android/media/IRemoteControlClient.aidl b/media/java/android/media/IRemoteControlClient.aidl
deleted file mode 100644
index aa142d6..0000000
--- a/media/java/android/media/IRemoteControlClient.aidl
+++ /dev/null
@@ -1,62 +0,0 @@
-/* Copyright (C) 2011 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.graphics.Bitmap;
-import android.media.IRemoteControlDisplay;
-import android.media.Rating;
-
-/**
- * @hide
- * Interface registered by AudioManager to notify a source of remote control information
- * that information is requested to be displayed on the remote control (through
- * IRemoteControlDisplay).
- * {@see AudioManager#registerRemoteControlClient(RemoteControlClient)}.
- */
-oneway interface IRemoteControlClient
-{
- /**
- * Notifies a remote control client that information for the given generation ID is
- * requested. If the flags contains
- * {@link RemoteControlClient#FLAG_INFORMATION_REQUESTED_ALBUM_ART} then the width and height
- * parameters are valid.
- * @param generationId
- * @param infoFlags
- * FIXME: is infoFlags required? since the RCC pushes info, this might always be called
- * with RC_INFO_ALL
- */
- void onInformationRequested(int generationId, int infoFlags);
-
- /**
- * Notifies a remote control client that information for the given generation ID is
- * requested for the given IRemoteControlDisplay alone.
- * @param rcd the display to which current info should be sent
- */
- void informationRequestForDisplay(IRemoteControlDisplay rcd, int w, int h);
-
- /**
- * Sets the generation counter of the current client that is displayed on the remote control.
- */
- void setCurrentClientGenerationId(int clientGeneration);
-
- void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h);
- void unplugRemoteControlDisplay(IRemoteControlDisplay rcd);
- void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h);
- void setWantsSyncForDisplay(IRemoteControlDisplay rcd, boolean wantsSync);
- void enableRemoteControlDisplay(IRemoteControlDisplay rcd, boolean enabled);
- void seekTo(int clientGeneration, long timeMs);
- void updateMetadata(int clientGeneration, int key, in Rating value);
-}
\ No newline at end of file
diff --git a/media/java/android/media/IRemoteControlDisplay.aidl b/media/java/android/media/IRemoteControlDisplay.aidl
deleted file mode 100644
index 1609030..0000000
--- a/media/java/android/media/IRemoteControlDisplay.aidl
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2011 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.app.PendingIntent;
-import android.content.ComponentName;
-import android.graphics.Bitmap;
-import android.os.Bundle;
-
-/**
- * @hide
- * Interface registered through AudioManager of an object that displays information
- * received from a remote control client.
- * {@see AudioManager#registerRemoteControlDisplay(IRemoteControlDisplay)}.
- */
-oneway interface IRemoteControlDisplay
-{
- /**
- * Sets the generation counter of the current client that is displayed on the remote control.
- * @param clientGeneration the new RemoteControlClient generation
- * @param clientMediaIntent the PendingIntent associated with the client.
- * May be null, which implies there is no registered media button event receiver.
- * @param clearing true if the new client generation value maps to a remote control update
- * where the display should be cleared.
- */
- void setCurrentClientId(int clientGeneration, in PendingIntent clientMediaIntent,
- boolean clearing);
-
- /**
- * Sets whether the controls of this display are enabled
- * @param if false, the display shouldn't any commands
- */
- void setEnabled(boolean enabled);
-
- /**
- * Sets the playback information (state, position and speed) of a client.
- * @param generationId the current generation ID as known by this client
- * @param state the current playback state, one of the following values:
- * {@link RemoteControlClient#PLAYSTATE_STOPPED},
- * {@link RemoteControlClient#PLAYSTATE_PAUSED},
- * {@link RemoteControlClient#PLAYSTATE_PLAYING},
- * {@link RemoteControlClient#PLAYSTATE_FAST_FORWARDING},
- * {@link RemoteControlClient#PLAYSTATE_REWINDING},
- * {@link RemoteControlClient#PLAYSTATE_SKIPPING_FORWARDS},
- * {@link RemoteControlClient#PLAYSTATE_SKIPPING_BACKWARDS},
- * {@link RemoteControlClient#PLAYSTATE_BUFFERING},
- * {@link RemoteControlClient#PLAYSTATE_ERROR}.
- * @param stateChangeTimeMs the time at which the client reported the playback information
- * @param currentPosMs a 0 or positive value for the current media position expressed in ms
- * Strictly negative values imply that position is not known:
- * a value of {@link RemoteControlClient#PLAYBACK_POSITION_INVALID} is intended to express
- * that an application doesn't know the position (e.g. listening to a live stream of a radio)
- * or that the position information is not applicable (e.g. when state
- * is {@link RemoteControlClient#PLAYSTATE_BUFFERING} and nothing had played yet);
- * a value of {@link RemoteControlClient#PLAYBACK_POSITION_ALWAYS_UNKNOWN} implies that the
- * application uses {@link RemoteControlClient#setPlaybackState(int)} (legacy API) and will
- * never pass a playback position.
- * @param speed a value expressed as a ratio of 1x playback: 1.0f is normal playback,
- * 2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is
- * playing (e.g. when state is {@link RemoteControlClient#PLAYSTATE_ERROR}).
- */
- void setPlaybackState(int generationId, int state, long stateChangeTimeMs, long currentPosMs,
- float speed);
-
- /**
- * Sets the transport control flags and playback position capabilities of a client.
- * @param generationId the current generation ID as known by this client
- * @param transportControlFlags bitmask of the transport controls this client supports, see
- * {@link RemoteControlClient#setTransportControlFlags(int)}
- * @param posCapabilities a bit mask for playback position capabilities, see
- * {@link RemoteControlClient#MEDIA_POSITION_READABLE} and
- * {@link RemoteControlClient#MEDIA_POSITION_WRITABLE}
- */
- void setTransportControlInfo(int generationId, int transportControlFlags, int posCapabilities);
-
- void setMetadata(int generationId, in Bundle metadata);
-
- void setArtwork(int generationId, in Bitmap artwork);
-
- /**
- * To combine metadata text and artwork in one binder call
- */
- void setAllMetadata(int generationId, in Bundle metadata, in Bitmap artwork);
-}
diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java
index c9a86d8..6d32eff 100644
--- a/media/java/android/media/RemoteControlClient.java
+++ b/media/java/android/media/RemoteControlClient.java
@@ -349,16 +349,6 @@
*/
public RemoteControlClient(PendingIntent mediaButtonIntent) {
mRcMediaIntent = mediaButtonIntent;
-
- Looper looper;
- if ((looper = Looper.myLooper()) != null) {
- mEventHandler = new EventHandler(this, looper);
- } else if ((looper = Looper.getMainLooper()) != null) {
- mEventHandler = new EventHandler(this, looper);
- } else {
- mEventHandler = null;
- Log.e(TAG, "RemoteControlClient() couldn't find main application thread");
- }
}
/**
@@ -378,8 +368,6 @@
*/
public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) {
mRcMediaIntent = mediaButtonIntent;
-
- mEventHandler = new EventHandler(this, looper);
}
/**
@@ -707,39 +695,6 @@
}
}
- // TODO investigate if we still need position drift checking
- private void onPositionDriftCheck() {
- if (DEBUG) { Log.d(TAG, "onPositionDriftCheck()"); }
- synchronized(mCacheLock) {
- if ((mEventHandler == null) || (mPositionProvider == null) || !mNeedsPositionSync) {
- return;
- }
- if ((mPlaybackPositionMs < 0) || (mPlaybackSpeed == 0.0f)) {
- if (DEBUG) { Log.d(TAG, " no valid position or 0 speed, no check needed"); }
- return;
- }
- long estPos = mPlaybackPositionMs + (long)
- ((SystemClock.elapsedRealtime() - mPlaybackStateChangeTimeMs) / mPlaybackSpeed);
- long actPos = mPositionProvider.onGetPlaybackPosition();
- if (actPos >= 0) {
- if (Math.abs(estPos - actPos) > POSITION_DRIFT_MAX_MS) {
- // drift happened, report the new position
- if (DEBUG) { Log.w(TAG, " drift detected: actual=" +actPos +" est=" +estPos); }
- setPlaybackState(mPlaybackState, actPos, mPlaybackSpeed);
- } else {
- if (DEBUG) { Log.d(TAG, " no drift: actual=" + actPos +" est=" + estPos); }
- // no drift, schedule the next drift check
- mEventHandler.sendMessageDelayed(
- mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
- getCheckPeriodFromSpeed(mPlaybackSpeed));
- }
- } else {
- // invalid position (negative value), can't check for drift
- mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK);
- }
- }
- }
-
/**
* Sets the flags for the media transport control buttons that this client supports.
* @param transportControlFlags A combination of the following flags:
@@ -856,14 +811,6 @@
public void setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener l) {
synchronized(mCacheLock) {
mPositionProvider = l;
- if ((mPositionProvider != null) && (mEventHandler != null)
- && playbackPositionShouldMove(mPlaybackState)) {
- // playback position is already moving, but now we have a position provider,
- // so schedule a drift check right now
- mEventHandler.sendMessageDelayed(
- mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK),
- 0 /*check now*/);
- }
}
}
@@ -1001,26 +948,6 @@
}
};
- private EventHandler mEventHandler;
- private final static int MSG_POSITION_DRIFT_CHECK = 11;
-
- private class EventHandler extends Handler {
- public EventHandler(RemoteControlClient rcc, Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch(msg.what) {
- case MSG_POSITION_DRIFT_CHECK:
- onPositionDriftCheck();
- break;
- default:
- Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler");
- }
- }
- }
-
//===========================================================
// Message handlers
diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java
index d84cf30..90f2163 100644
--- a/media/java/android/media/RemoteController.java
+++ b/media/java/android/media/RemoteController.java
@@ -17,8 +17,6 @@
package android.media;
import android.app.ActivityManager;
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -32,7 +30,6 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -61,15 +58,10 @@
@Deprecated public final class RemoteController
{
private final static int MAX_BITMAP_DIMENSION = 512;
- private final static int TRANSPORT_UNKNOWN = 0;
private final static String TAG = "RemoteController";
private final static boolean DEBUG = false;
- private final static boolean USE_SESSIONS = true;
- private final static Object mGenLock = new Object();
private final static Object mInfoLock = new Object();
- private final RcDisplay mRcd;
private final Context mContext;
- private final AudioManager mAudioManager;
private final int mMaxBitmapDimension;
private MetadataEditor mMetadataEditor;
@@ -78,15 +70,9 @@
private MediaController.Callback mSessionCb = new MediaControllerCallback();
/**
- * Synchronized on mGenLock
- */
- private int mClientGenerationIdCurrent = 0;
-
- /**
* Synchronized on mInfoLock
*/
private boolean mIsRegistered = false;
- private PendingIntent mClientPendingIntentCurrent;
private OnClientUpdateListener mOnClientUpdateListener;
private PlaybackInfo mLastPlaybackInfo;
private int mArtworkWidth = -1;
@@ -136,8 +122,6 @@
}
mOnClientUpdateListener = updateListener;
mContext = context;
- mRcd = new RcDisplay(this);
- mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mSessionManager = (MediaSessionManager) context
.getSystemService(Context.MEDIA_SESSION_SERVICE);
mSessionListener = new TopTransportSessionListener();
@@ -207,22 +191,6 @@
public void onClientMetadataUpdate(MetadataEditor metadataEditor);
};
-
- /**
- * @hide
- */
- public String getRemoteControlClientPackageName() {
- if (USE_SESSIONS) {
- synchronized (mInfoLock) {
- return mCurrentSession != null ? mCurrentSession.getPackageName()
- : null;
- }
- } else {
- return mClientPendingIntentCurrent != null ?
- mClientPendingIntentCurrent.getCreatorPackage() : null;
- }
- }
-
/**
* Return the estimated playback position of the current media track or a negative value
* if not available.
@@ -240,38 +208,13 @@
* @see OnClientUpdateListener#onClientPlaybackStateUpdate(int, long, long, float)
*/
public long getEstimatedMediaPosition() {
- if (USE_SESSIONS) {
- synchronized (mInfoLock) {
- if (mCurrentSession != null) {
- PlaybackState state = mCurrentSession.getPlaybackState();
- if (state != null) {
- return state.getPosition();
- }
+ synchronized (mInfoLock) {
+ if (mCurrentSession != null) {
+ PlaybackState state = mCurrentSession.getPlaybackState();
+ if (state != null) {
+ return state.getPosition();
}
}
- } else {
- final PlaybackInfo lastPlaybackInfo;
- synchronized (mInfoLock) {
- lastPlaybackInfo = mLastPlaybackInfo;
- }
- if (lastPlaybackInfo != null) {
- if (!RemoteControlClient.playbackPositionShouldMove(lastPlaybackInfo.mState)) {
- return lastPlaybackInfo.mCurrentPosMs;
- }
-
- // Take the current position at the time of state change and
- // estimate.
- final long thenPos = lastPlaybackInfo.mCurrentPosMs;
- if (thenPos < 0) {
- return -1;
- }
-
- final long now = SystemClock.elapsedRealtime();
- final long then = lastPlaybackInfo.mStateChangeTimeMs;
- final long sinceThen = now - then;
- final long scaledSinceThen = (long) (sinceThen * lastPlaybackInfo.mSpeed);
- return thenPos + scaledSinceThen;
- }
}
return -1;
}
@@ -308,42 +251,12 @@
if (!KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
throw new IllegalArgumentException("not a media key event");
}
- if (USE_SESSIONS) {
- synchronized (mInfoLock) {
- if (mCurrentSession != null) {
- return mCurrentSession.dispatchMediaButtonEvent(keyEvent);
- }
- return false;
+ synchronized (mInfoLock) {
+ if (mCurrentSession != null) {
+ return mCurrentSession.dispatchMediaButtonEvent(keyEvent);
}
- } else {
- final PendingIntent pi;
- synchronized (mInfoLock) {
- if (!mIsRegistered) {
- Log.e(TAG,
- "Cannot use sendMediaKeyEvent() from an unregistered RemoteController");
- return false;
- }
- if (!mEnabled) {
- Log.e(TAG, "Cannot use sendMediaKeyEvent() from a disabled RemoteController");
- return false;
- }
- pi = mClientPendingIntentCurrent;
- }
- if (pi != null) {
- Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- try {
- pi.send(mContext, 0, intent);
- } catch (CanceledException e) {
- Log.e(TAG, "Error sending intent for media button down: ", e);
- return false;
- }
- } else {
- Log.i(TAG, "No-op when sending key click, no receiver right now");
- return false;
- }
+ return false;
}
- return true;
}
@@ -453,8 +366,7 @@
Log.e(TAG, "Cannot set synchronization mode on an unregistered RemoteController");
return false;
}
- mAudioManager.remoteControlDisplayWantsPlaybackPositionSync(mRcd,
- POSITION_SYNCHRONIZATION_CHECK == sync);
+ // deprecated, no-op
return true;
}
@@ -541,154 +453,6 @@
}
-
- //==================================================
- // Implementation of IRemoteControlDisplay interface
- private static class RcDisplay extends IRemoteControlDisplay.Stub {
- private final WeakReference<RemoteController> mController;
-
- RcDisplay(RemoteController rc) {
- mController = new WeakReference<RemoteController>(rc);
- }
-
- public void setCurrentClientId(int genId, PendingIntent clientMediaIntent,
- boolean clearing) {
- final RemoteController rc = mController.get();
- if (rc == null) {
- return;
- }
- boolean isNew = false;
- synchronized(mGenLock) {
- if (rc.mClientGenerationIdCurrent != genId) {
- rc.mClientGenerationIdCurrent = genId;
- isNew = true;
- }
- }
- if (clientMediaIntent != null) {
- sendMsg(rc.mEventHandler, MSG_NEW_PENDING_INTENT, SENDMSG_REPLACE,
- genId /*arg1*/, 0, clientMediaIntent /*obj*/, 0 /*delay*/);
- }
- if (isNew || clearing) {
- sendMsg(rc.mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE,
- genId /*arg1*/, clearing ? 1 : 0, null /*obj*/, 0 /*delay*/);
- }
- }
-
- public void setEnabled(boolean enabled) {
- final RemoteController rc = mController.get();
- if (rc == null) {
- return;
- }
- sendMsg(rc.mEventHandler, MSG_DISPLAY_ENABLE, SENDMSG_REPLACE,
- enabled ? 1 : 0 /*arg1*/, 0, null /*obj*/, 0 /*delay*/);
- }
-
- public void setPlaybackState(int genId, int state,
- long stateChangeTimeMs, long currentPosMs, float speed) {
- final RemoteController rc = mController.get();
- if (rc == null) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "> new playback state: genId="+genId
- + " state="+ state
- + " changeTime="+ stateChangeTimeMs
- + " pos=" + currentPosMs
- + "ms speed=" + speed);
- }
-
- synchronized(mGenLock) {
- if (rc.mClientGenerationIdCurrent != genId) {
- return;
- }
- }
- final PlaybackInfo playbackInfo =
- new PlaybackInfo(state, stateChangeTimeMs, currentPosMs, speed);
- sendMsg(rc.mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE,
- genId /*arg1*/, 0, playbackInfo /*obj*/, 0 /*delay*/);
-
- }
-
- public void setTransportControlInfo(int genId, int transportControlFlags,
- int posCapabilities) {
- final RemoteController rc = mController.get();
- if (rc == null) {
- return;
- }
- synchronized(mGenLock) {
- if (rc.mClientGenerationIdCurrent != genId) {
- return;
- }
- }
- sendMsg(rc.mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE,
- genId /*arg1*/, transportControlFlags /*arg2*/,
- null /*obj*/, 0 /*delay*/);
- }
-
- public void setMetadata(int genId, Bundle metadata) {
- final RemoteController rc = mController.get();
- if (rc == null) {
- return;
- }
- if (DEBUG) { Log.e(TAG, "setMetadata("+genId+")"); }
- if (metadata == null) {
- return;
- }
- synchronized(mGenLock) {
- if (rc.mClientGenerationIdCurrent != genId) {
- return;
- }
- }
- sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
- genId /*arg1*/, 0 /*arg2*/,
- metadata /*obj*/, 0 /*delay*/);
- }
-
- public void setArtwork(int genId, Bitmap artwork) {
- final RemoteController rc = mController.get();
- if (rc == null) {
- return;
- }
- if (DEBUG) { Log.v(TAG, "setArtwork("+genId+")"); }
- synchronized(mGenLock) {
- if (rc.mClientGenerationIdCurrent != genId) {
- return;
- }
- }
- Bundle metadata = new Bundle(1);
- metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK), artwork);
- sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
- genId /*arg1*/, 0 /*arg2*/,
- metadata /*obj*/, 0 /*delay*/);
- }
-
- public void setAllMetadata(int genId, Bundle metadata, Bitmap artwork) {
- final RemoteController rc = mController.get();
- if (rc == null) {
- return;
- }
- if (DEBUG) { Log.e(TAG, "setAllMetadata("+genId+")"); }
- if ((metadata == null) && (artwork == null)) {
- return;
- }
- synchronized(mGenLock) {
- if (rc.mClientGenerationIdCurrent != genId) {
- return;
- }
- }
- if (metadata == null) {
- metadata = new Bundle(1);
- }
- if (artwork != null) {
- metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK),
- artwork);
- }
- sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
- genId /*arg1*/, 0 /*arg2*/,
- metadata /*obj*/, 0 /*delay*/);
- }
- }
-
/**
* This receives updates when the current session changes. This is
* registered to receive the updates on the handler thread so it can call
@@ -734,14 +498,9 @@
//==================================================
// Event handling
private final EventHandler mEventHandler;
- private final static int MSG_NEW_PENDING_INTENT = 0;
- private final static int MSG_NEW_PLAYBACK_INFO = 1;
- private final static int MSG_NEW_TRANSPORT_INFO = 2;
- private final static int MSG_NEW_METADATA = 3; // msg always has non-null obj parameter
- private final static int MSG_CLIENT_CHANGE = 4;
- private final static int MSG_DISPLAY_ENABLE = 5;
- private final static int MSG_NEW_PLAYBACK_STATE = 6;
- private final static int MSG_NEW_MEDIA_METADATA = 7;
+ private final static int MSG_CLIENT_CHANGE = 0;
+ private final static int MSG_NEW_PLAYBACK_STATE = 1;
+ private final static int MSG_NEW_MEDIA_METADATA = 2;
private class EventHandler extends Handler {
@@ -752,26 +511,10 @@
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
- case MSG_NEW_PENDING_INTENT:
- onNewPendingIntent(msg.arg1, (PendingIntent) msg.obj);
- break;
- case MSG_NEW_PLAYBACK_INFO:
- onNewPlaybackInfo(msg.arg1, (PlaybackInfo) msg.obj);
- break;
- case MSG_NEW_TRANSPORT_INFO:
- onNewTransportInfo(msg.arg1, msg.arg2);
- break;
- case MSG_NEW_METADATA:
- onNewMetadata(msg.arg1, (Bundle)msg.obj);
- break;
case MSG_CLIENT_CHANGE:
- onClientChange(msg.arg1, msg.arg2 == 1);
- break;
- case MSG_DISPLAY_ENABLE:
- onDisplayEnable(msg.arg1 == 1);
+ onClientChange(msg.arg2 == 1);
break;
case MSG_NEW_PLAYBACK_STATE:
- // same as new playback info but using new apis
onNewPlaybackState((PlaybackState) msg.obj);
break;
case MSG_NEW_MEDIA_METADATA:
@@ -835,100 +578,7 @@
handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delayMs);
}
- ///////////// These calls are used by the old APIs with RCC and RCD //////////////////////
- private void onNewPendingIntent(int genId, PendingIntent pi) {
- synchronized(mGenLock) {
- if (mClientGenerationIdCurrent != genId) {
- return;
- }
- }
- synchronized(mInfoLock) {
- mClientPendingIntentCurrent = pi;
- }
- }
-
- private void onNewPlaybackInfo(int genId, PlaybackInfo pi) {
- synchronized(mGenLock) {
- if (mClientGenerationIdCurrent != genId) {
- return;
- }
- }
- final OnClientUpdateListener l;
- synchronized(mInfoLock) {
- l = this.mOnClientUpdateListener;
- mLastPlaybackInfo = pi;
- }
- if (l != null) {
- if (pi.mCurrentPosMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
- l.onClientPlaybackStateUpdate(pi.mState);
- } else {
- l.onClientPlaybackStateUpdate(pi.mState, pi.mStateChangeTimeMs, pi.mCurrentPosMs,
- pi.mSpeed);
- }
- }
- }
-
- private void onNewTransportInfo(int genId, int transportControlFlags) {
- synchronized(mGenLock) {
- if (mClientGenerationIdCurrent != genId) {
- return;
- }
- }
- final OnClientUpdateListener l;
- synchronized(mInfoLock) {
- l = mOnClientUpdateListener;
- }
- if (l != null) {
- l.onClientTransportControlUpdate(transportControlFlags);
- }
- }
-
- /**
- * @param genId
- * @param metadata guaranteed to be always non-null
- */
- private void onNewMetadata(int genId, Bundle metadata) {
- synchronized(mGenLock) {
- if (mClientGenerationIdCurrent != genId) {
- return;
- }
- }
- final OnClientUpdateListener l;
- final MetadataEditor metadataEditor;
- // prepare the received Bundle to be used inside a MetadataEditor
- final long editableKeys = metadata.getLong(
- String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK), 0);
- if (editableKeys != 0) {
- metadata.remove(String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK));
- }
- synchronized(mInfoLock) {
- l = mOnClientUpdateListener;
- if ((mMetadataEditor != null) && (mMetadataEditor.mEditorMetadata != null)) {
- if (mMetadataEditor.mEditorMetadata != metadata) {
- // existing metadata, merge existing and new
- mMetadataEditor.mEditorMetadata.putAll(metadata);
- }
-
- mMetadataEditor.putBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK,
- (Bitmap)metadata.getParcelable(
- String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK)));
- mMetadataEditor.cleanupBitmapFromBundle(MediaMetadataEditor.BITMAP_KEY_ARTWORK);
- } else {
- mMetadataEditor = new MetadataEditor(metadata, editableKeys);
- }
- metadataEditor = mMetadataEditor;
- }
- if (l != null) {
- l.onClientMetadataUpdate(metadataEditor);
- }
- }
-
- private void onClientChange(int genId, boolean clearing) {
- synchronized(mGenLock) {
- if (mClientGenerationIdCurrent != genId) {
- return;
- }
- }
+ private void onClientChange(boolean clearing) {
final OnClientUpdateListener l;
synchronized(mInfoLock) {
l = mOnClientUpdateListener;
@@ -939,39 +589,6 @@
}
}
- private void onDisplayEnable(boolean enabled) {
- final OnClientUpdateListener l;
- synchronized(mInfoLock) {
- mEnabled = enabled;
- l = this.mOnClientUpdateListener;
- }
- if (!enabled) {
- // when disabling, reset all info sent to the user
- final int genId;
- synchronized (mGenLock) {
- genId = mClientGenerationIdCurrent;
- }
- // send "stopped" state, happened "now", playback position is 0, speed 0.0f
- final PlaybackInfo pi = new PlaybackInfo(RemoteControlClient.PLAYSTATE_STOPPED,
- SystemClock.elapsedRealtime() /*stateChangeTimeMs*/,
- 0 /*currentPosMs*/, 0.0f /*speed*/);
- sendMsg(mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE,
- genId /*arg1*/, 0 /*arg2, ignored*/, pi /*obj*/, 0 /*delay*/);
- // send "blank" transport control info: no controls are supported
- sendMsg(mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE,
- genId /*arg1*/, 0 /*arg2, no flags*/,
- null /*obj, ignored*/, 0 /*delay*/);
- // send dummy metadata with empty string for title and artist, duration of 0
- Bundle metadata = new Bundle(3);
- metadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_TITLE), "");
- metadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ARTIST), "");
- metadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DURATION), 0);
- sendMsg(mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE,
- genId /*arg1*/, 0 /*arg2, ignored*/, metadata /*obj*/, 0 /*delay*/);
- }
- }
-
- ///////////// These calls are used by the new APIs with Sessions //////////////////////
private void updateController(MediaController controller) {
if (DEBUG) {
Log.d(TAG, "Updating controller to " + controller + " previous controller is "
@@ -983,7 +600,7 @@
mCurrentSession.unregisterCallback(mSessionCb);
mCurrentSession = null;
sendMsg(mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE,
- 0 /* genId */, 1 /* clearing */, null /* obj */, 0 /* delay */);
+ 0 /* arg1 ignored */, 1 /* clearing */, null /* obj */, 0 /* delay */);
}
} else if (mCurrentSession == null
|| !controller.getSessionToken()
@@ -992,17 +609,17 @@
mCurrentSession.unregisterCallback(mSessionCb);
}
sendMsg(mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE,
- 0 /* genId */, 0 /* clearing */, null /* obj */, 0 /* delay */);
+ 0 /* arg1 ignored */, 0 /* clearing */, null /* obj */, 0 /* delay */);
mCurrentSession = controller;
mCurrentSession.registerCallback(mSessionCb, mEventHandler);
PlaybackState state = controller.getPlaybackState();
sendMsg(mEventHandler, MSG_NEW_PLAYBACK_STATE, SENDMSG_REPLACE,
- 0 /* genId */, 0, state /* obj */, 0 /* delay */);
+ 0 /* arg1 ignored */, 0 /* arg2 ignored */, state /* obj */, 0 /* delay */);
MediaMetadata metadata = controller.getMetadata();
sendMsg(mEventHandler, MSG_NEW_MEDIA_METADATA, SENDMSG_REPLACE,
- 0 /* arg1 */, 0 /* arg2 */, metadata /* obj */, 0 /* delay */);
+ 0 /* arg1 ignored */, 0 /* arg2 ignored*/, metadata /* obj */, 0 /*delay*/);
}
// else same controller, no need to update
}
@@ -1069,38 +686,6 @@
/**
* @hide
- * Used by AudioManager to mark this instance as registered.
- * @param registered
- */
- void setIsRegistered(boolean registered) {
- synchronized (mInfoLock) {
- mIsRegistered = registered;
- }
- }
-
- /**
- * @hide
- * Used by AudioManager to access binder to be registered/unregistered inside MediaFocusControl
- * @return
- */
- RcDisplay getRcDisplay() {
- return mRcd;
- }
-
- /**
- * @hide
- * Used by AudioManager to read the current artwork dimension
- * @return array containing width (index 0) and height (index 1) of currently set artwork size
- */
- int[] getArtworkSize() {
- synchronized (mInfoLock) {
- int[] size = { mArtworkWidth, mArtworkHeight };
- return size;
- }
- }
-
- /**
- * @hide
* Used by AudioManager to access user listener receiving the client update notifications
* @return
*/
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 368f9f7..f2920e5 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -12,12 +12,23 @@
-keep class com.android.systemui.statusbar.phone.PhoneStatusBar
-keep class com.android.systemui.statusbar.tv.TvStatusBar
--keep class com.android.systemui.recents.*
-keepclassmembers class ** {
public void onBusEvent(**);
public void onInterprocessBusEvent(**);
}
-keepclassmembers class ** extends **.EventBus$InterprocessEvent {
- public <init>(android.os.Bundle);
-}
\ No newline at end of file
+ public <init>(android.os.Bundle);
+}
+
+-keep class com.android.systemui.recents.views.TaskStackLayoutAlgorithm {
+ public float getFocusState();
+ public void setFocusState(float);
+}
+
+-keep class com.android.systemui.recents.views.TaskView {
+ public int getDim();
+ public void setDim(int);
+ public float getTaskProgress();
+ public void setTaskProgress(float);
+}
diff --git a/packages/SystemUI/res/layout/recents_dismiss_button.xml b/packages/SystemUI/res/layout/recents_dismiss_button.xml
deleted file mode 100644
index 6a2f782..0000000
--- a/packages/SystemUI/res/layout/recents_dismiss_button.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 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.
--->
-<!-- Extends Framelayout -->
-<com.android.systemui.statusbar.DismissView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/recents_dismiss_all_button_size"
- android:visibility="gone"
- android:clipChildren="false"
- android:clipToPadding="false">
- <com.android.systemui.statusbar.DismissViewButton
- android:id="@+id/dismiss_text"
- android:layout_width="@dimen/recents_dismiss_all_button_size"
- android:layout_height="@dimen/recents_dismiss_all_button_size"
- android:layout_gravity="end"
- android:alpha="1.0"
- android:background="@drawable/recents_button_bg"
- android:contentDescription="@string/recents_dismiss_all_message"/>
-</com.android.systemui.statusbar.DismissView>
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index f7e2344..43e7bac 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -28,4 +28,14 @@
<!-- We have only space for one notification on phone landscape layouts. -->
<integer name="keyguard_max_notification_count">1</integer>
+
+ <!-- Recents: The relative range of visible tasks from the current scroll position
+ while the stack is focused. -->
+ <item name="recents_layout_focused_range_min" format="float" type="integer">-3</item>
+ <item name="recents_layout_focused_range_max" format="float" type="integer">2</item>
+
+ <!-- Recents: The relative range of visible tasks from the current scroll position
+ while the stack is not focused. -->
+ <item name="recents_layout_unfocused_range_min" format="float" type="integer">-2</item>
+ <item name="recents_layout_unfocused_range_max" format="float" type="integer">1.5</item>
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 4f6d209..6795da4 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -32,4 +32,14 @@
<!-- Set to true to enable the user switcher on the keyguard. -->
<bool name="config_keyguardUserSwitcher">true</bool>
+
+ <!-- Recents: The relative range of visible tasks from the current scroll position
+ while the stack is focused. -->
+ <item name="recents_layout_focused_range_min" format="float" type="integer">-4</item>
+ <item name="recents_layout_focused_range_max" format="float" type="integer">3</item>
+
+ <!-- Recents: The relative range of visible tasks from the current scroll position
+ while the stack is not focused. -->
+ <item name="recents_layout_unfocused_range_min" format="float" type="integer">-2</item>
+ <item name="recents_layout_unfocused_range_max" format="float" type="integer">2.5</item>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 0c638a2..d8193ab 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -138,12 +138,6 @@
<!-- The duration in seconds to wait before the dismiss buttons are shown. -->
<integer name="recents_task_bar_dismiss_delay_seconds">1000</integer>
- <!-- The min animation duration for animating views that are currently visible. -->
- <integer name="recents_filter_animate_current_views_duration">250</integer>
-
- <!-- The min animation duration for animating views that are newly visible. -->
- <integer name="recents_filter_animate_new_views_duration">250</integer>
-
<!-- The duration of the window transition when coming to Recents from an app.
In order to defer the in-app animations until after the transition is complete,
we also need to use this value as the starting delay when animating the first
@@ -192,6 +186,16 @@
<!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. -->
<integer name="recents_svelte_level">0</integer>
+ <!-- Recents: The relative range of visible tasks from the current scroll position
+ while the stack is focused. -->
+ <item name="recents_layout_focused_range_min" format="float" type="integer">-4</item>
+ <item name="recents_layout_focused_range_max" format="float" type="integer">3</item>
+
+ <!-- Recents: The relative range of visible tasks from the current scroll position
+ while the stack is not focused. -->
+ <item name="recents_layout_unfocused_range_min" format="float" type="integer">-2</item>
+ <item name="recents_layout_unfocused_range_max" format="float" type="integer">2.5</item>
+
<!-- Whether to enable KeyguardService or not -->
<bool name="config_enableKeyguardService">true</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 73f63a9..c85ada8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -247,6 +247,12 @@
<!-- The amount to allow the stack to overscroll. -->
<dimen name="recents_stack_overscroll">24dp</dimen>
+ <!-- The size of the peek area at the top of the stack. -->
+ <dimen name="recents_layout_focused_peek_size">@dimen/recents_history_button_height</dimen>
+
+ <!-- The height of the history button. -->
+ <dimen name="recents_history_button_height">48dp</dimen>
+
<!-- Space reserved for the cards behind the top card in the top stack -->
<dimen name="top_stack_peek_amount">12dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 00b10a1..e0c0f6a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -698,8 +698,6 @@
<string name="recents_search_bar_label">search</string>
<!-- Recents: Launch error string. [CHAR LIMIT=NONE] -->
<string name="recents_launch_error_message">Could not start <xliff:g id="app" example="Calendar">%s</xliff:g>.</string>
- <!-- Recents: Dismiss all button. [CHAR LIMIT=NONE] -->
- <string name="recents_dismiss_all_message">Dismiss all applications</string>
<!-- Recents: MultiStack add stack split horizontal radio button. [CHAR LIMIT=NONE] -->
<string name="recents_multistack_add_stack_dialog_split_horizontal">Split Horizontal</string>
@@ -1159,10 +1157,20 @@
<!-- Option to use new paging layout in quick settings [CHAR LIMIT=60] -->
<string name="qs_paging" translatable="false">Use the new Quick Settings</string>
+ <!-- Toggles paging recents via the recents button -->
+ <string name="overview_page_on_toggle">Enable paging</string>
+ <!-- Description for the toggle for fast-toggling recents via the recents button -->
+ <string name="overview_page_on_toggle_desc">Enable paging via the Overview button</string>
+
<!-- Toggles fast-toggling recents via the recents button -->
<string name="overview_fast_toggle_via_button">Enable fast toggle</string>
<!-- Description for the toggle for fast-toggling recents via the recents button -->
- <string name="overview_fast_toggle_via_button_desc">Enable paging via the Overview button</string>
+ <string name="overview_fast_toggle_via_button_desc">Enable launch timeout while paging</string>
+
+ <!-- Toggles fullscreen screenshots -->
+ <string name="overview_fullscreen_thumbnails">Enable fullscreen screenshots</string>
+ <!-- Description for the toggle for fullscreen screenshots -->
+ <string name="overview_fullscreen_thumbnails_desc">Enable fullscreen screenshots in Overview</string>
<!-- Category in the System UI Tuner settings, where new/experimental
settings are -->
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 585f3c7..4d07d5f 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -88,10 +88,20 @@
android:title="@string/overview" >
<com.android.systemui.tuner.TunerSwitch
+ android:key="overview_page_on_toggle"
+ android:title="@string/overview_page_on_toggle"
+ android:summary="@string/overview_page_on_toggle_desc" />
+
+ <com.android.systemui.tuner.TunerSwitch
android:key="overview_fast_toggle"
android:title="@string/overview_fast_toggle_via_button"
android:summary="@string/overview_fast_toggle_via_button_desc" />
+ <com.android.systemui.tuner.TunerSwitch
+ android:key="overview_fullscreen_thumbnails"
+ android:title="@string/overview_fullscreen_thumbnails"
+ android:summary="@string/overview_fullscreen_thumbnails_desc" />
+
</PreferenceScreen>
<SwitchPreference
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 04def86..92978f2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -43,9 +43,9 @@
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEvent;
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
+import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
-import com.android.systemui.recents.events.activity.DebugFlagsChangedEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.activity.IterateRecentsEvent;
import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
@@ -53,9 +53,12 @@
import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.ResizeTaskEvent;
import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent;
+import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
+import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
import com.android.systemui.recents.events.ui.UserInteractionEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
@@ -79,40 +82,40 @@
/**
* The main Recents activity that is started from AlternateRecentsComponent.
*/
-public class RecentsActivity extends Activity implements RecentsView.RecentsViewCallbacks,
- ViewTreeObserver.OnPreDrawListener {
+public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreDrawListener {
private final static String TAG = "RecentsActivity";
private final static boolean DEBUG = false;
public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1;
- RecentsPackageMonitor mPackageMonitor;
- long mLastTabKeyEventTime;
- boolean mFinishedOnStartup;
+ private RecentsPackageMonitor mPackageMonitor;
+ private long mLastTabKeyEventTime;
+ private boolean mFinishedOnStartup;
+ private boolean mIgnoreAltTabRelease;
// Top level views
- RecentsView mRecentsView;
- SystemBarScrimViews mScrimViews;
- ViewStub mEmptyViewStub;
- View mEmptyView;
+ private RecentsView mRecentsView;
+ private SystemBarScrimViews mScrimViews;
+ private ViewStub mEmptyViewStub;
+ private View mEmptyView;
// Resize task debug
- RecentsResizeTaskDialog mResizeTaskDebugDialog;
+ private RecentsResizeTaskDialog mResizeTaskDebugDialog;
// Search AppWidget
- AppWidgetProviderInfo mSearchWidgetInfo;
- RecentsAppWidgetHost mAppWidgetHost;
- RecentsAppWidgetHostView mSearchWidgetHostView;
+ private AppWidgetProviderInfo mSearchWidgetInfo;
+ private RecentsAppWidgetHost mAppWidgetHost;
+ private RecentsAppWidgetHostView mSearchWidgetHostView;
// Runnables to finish the Recents activity
- FinishRecentsRunnable mFinishLaunchHomeRunnable;
+ private FinishRecentsRunnable mFinishLaunchHomeRunnable;
// The trigger to automatically launch the current task
- DozeTrigger mIterateTrigger = new DozeTrigger(500, new Runnable() {
+ private DozeTrigger mIterateTrigger = new DozeTrigger(500, new Runnable() {
@Override
public void run() {
- dismissRecentsToFocusedTask(false);
+ dismissRecentsToFocusedTask();
}
});
@@ -259,12 +262,9 @@
/**
* Dismisses recents if we are already visible and the intent is to toggle the recents view.
*/
- boolean dismissRecentsToFocusedTask(boolean checkFilteredStackState) {
+ boolean dismissRecentsToFocusedTask() {
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
- // If we currently have filtered stacks, then unfilter those first
- if (checkFilteredStackState &&
- mRecentsView.unfilterFilteredStacks()) return true;
// If we have a focused Task, launch that Task now
if (mRecentsView.launchFocusedTask()) return true;
}
@@ -274,12 +274,9 @@
/**
* Dismisses recents if we are already visible and the intent is to toggle the recents view.
*/
- boolean dismissRecentsToFocusedTaskOrHome(boolean checkFilteredStackState) {
+ boolean dismissRecentsToFocusedTaskOrHome() {
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
- // If we currently have filtered stacks, then unfilter those first
- if (checkFilteredStackState &&
- mRecentsView.unfilterFilteredStacks()) return true;
// If we have a focused Task, launch that Task now
if (mRecentsView.launchFocusedTask()) return true;
// If none of the other cases apply, then just go Home
@@ -348,7 +345,6 @@
// Set the Recents layout
setContentView(R.layout.recents);
mRecentsView = (RecentsView) findViewById(R.id.recents_view);
- mRecentsView.setCallbacks(this);
mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
@@ -406,13 +402,6 @@
mRecentsView.disableLayersForOneFrame();
}
- if (launchState.startHidden) {
- launchState.startHidden = false;
- mRecentsView.setStackViewVisibility(View.INVISIBLE);
- } else {
- mRecentsView.setStackViewVisibility(View.VISIBLE);
- }
-
// Notify that recents is now visible
SystemServicesProxy ssp = Recents.getSystemServices();
EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, true));
@@ -441,6 +430,9 @@
protected void onStop() {
super.onStop();
+ // Reset some states
+ mIgnoreAltTabRelease = false;
+
// Notify that recents is now hidden
SystemServicesProxy ssp = Recents.getSystemServices();
EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, ssp, false));
@@ -511,10 +503,6 @@
boolean hasRepKeyTimeElapsed = (SystemClock.elapsedRealtime() -
mLastTabKeyEventTime) > altTabKeyDelay;
if (event.getRepeatCount() <= 0 || hasRepKeyTimeElapsed) {
- // As we iterate to the next/previous task, cancel any current/lagging window
- // transition animations
- EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
-
// Focus the next task in the stack
final boolean backward = event.isShiftPressed();
if (backward) {
@@ -523,6 +511,11 @@
EventBus.getDefault().send(new FocusNextTaskViewEvent());
}
mLastTabKeyEventTime = SystemClock.elapsedRealtime();
+
+ // In the case of another ALT event, don't ignore the next release
+ if (event.isAltPressed()) {
+ mIgnoreAltTabRelease = false;
+ }
}
return true;
}
@@ -557,7 +550,7 @@
@Override
public void onBackPressed() {
// Dismiss Recents to the focused Task or Home
- dismissRecentsToFocusedTaskOrHome(true);
+ dismissRecentsToFocusedTaskOrHome();
}
/**** RecentsResizeTaskDialog ****/
@@ -569,23 +562,25 @@
return mResizeTaskDebugDialog;
}
- /**** RecentsView.RecentsViewCallbacks Implementation ****/
-
- @Override
- public void onAllTaskViewsDismissed() {
- mFinishLaunchHomeRunnable.run();
- }
-
/**** EventBus events ****/
public final void onBusEvent(ToggleRecentsEvent event) {
- dismissRecentsToFocusedTaskOrHome(true /* checkFilteredStackState */);
+ dismissRecentsToFocusedTaskOrHome();
}
public final void onBusEvent(IterateRecentsEvent event) {
// Focus the next task
EventBus.getDefault().send(new FocusNextTaskViewEvent());
- mIterateTrigger.poke();
+
+ // Start dozing after the recents button is clicked
+ RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+ if (debugFlags.isFastToggleRecentsEnabled()) {
+ if (!mIterateTrigger.isDozing()) {
+ mIterateTrigger.startDozing();
+ } else {
+ mIterateTrigger.poke();
+ }
+ }
}
public final void onBusEvent(UserInteractionEvent event) {
@@ -595,10 +590,12 @@
public final void onBusEvent(HideRecentsEvent event) {
if (event.triggeredFromAltTab) {
// If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app
- dismissRecentsToFocusedTaskOrHome(false /* checkFilteredStackState */);
+ if (!mIgnoreAltTabRelease) {
+ dismissRecentsToFocusedTaskOrHome();
+ }
} else if (event.triggeredFromHomeKey) {
// Otherwise, dismiss Recents to Home
- dismissRecentsToHome(true /* checkFilteredStackState */);
+ dismissRecentsToHome(true /* animated */);
} else {
// Do nothing
}
@@ -622,31 +619,22 @@
});
}
}
- ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
- @Override
- public void run() {
- // If we are not launching with alt-tab and fast-toggle is enabled, then start
- // the dozer now
- RecentsConfiguration config = Recents.getConfiguration();
- RecentsActivityLaunchState launchState = config.getLaunchState();
- RecentsDebugFlags flags = Recents.getDebugFlags();
- if (flags.isFastToggleRecentsEnabled() && !launchState.launchedWithAltTab) {
- mIterateTrigger.startDozing();
- }
- }
- });
mRecentsView.startEnterRecentsAnimation(ctx);
ctx.postAnimationTrigger.decrement();
}
public final void onBusEvent(EnterRecentsWindowLastAnimationFrameEvent event) {
- mRecentsView.setStackViewVisibility(View.VISIBLE);
+ EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(true));
mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
+ mRecentsView.invalidate();
}
public final void onBusEvent(ExitRecentsWindowFirstAnimationFrameEvent event) {
- mRecentsView.setStackViewVisibility(View.INVISIBLE);
+ if (mRecentsView.isLastTaskLaunchedFreeform()) {
+ EventBus.getDefault().send(new UpdateFreeformTaskViewVisibilityEvent(false));
+ }
mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
+ mRecentsView.invalidate();
}
public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) {
@@ -687,6 +675,14 @@
ssp.removeTask(event.task.key.id);
}
+ public final void onBusEvent(AllTaskViewsDismissedEvent event) {
+ // Just go straight home (no animation necessary because there are no more task views)
+ dismissRecentsToHome(false /* animated */);
+
+ // Keep track of all-deletions
+ MetricsLogger.count(this, "overview_task_all_dismissed", 1);
+ }
+
public final void onBusEvent(ResizeTaskEvent event) {
getResizeTaskDebugDialog().showResizeTaskDialog(event.task, mRecentsView);
}
@@ -723,6 +719,12 @@
finish();
}
+ public final void onBusEvent(StackViewScrolledEvent event) {
+ // Once the user has scrolled while holding alt-tab, then we should ignore the release of
+ // the key
+ mIgnoreAltTabRelease = true;
+ }
+
private void refreshSearchWidgetView() {
if (mSearchWidgetInfo != null) {
SystemServicesProxy ssp = Recents.getSystemServices();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index 7f7dbce..a1e5118 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -34,7 +34,7 @@
public boolean launchedFromSearchHome;
public boolean launchedReuseTaskStackViews;
public boolean launchedHasConfigurationChanged;
- public boolean startHidden;
+ public boolean launchedViaDragGesture;
public int launchedToTaskId;
public int launchedNumVisibleTasks;
public int launchedNumVisibleThumbnails;
@@ -75,7 +75,7 @@
*/
public int getInitialFocusTaskIndex(int numTasks) {
RecentsDebugFlags flags = Recents.getDebugFlags();
- if (flags.isFastToggleRecentsEnabled() && !launchedWithAltTab) {
+ if (flags.isPageOnToggleEnabled() && !launchedWithAltTab) {
// If we are fast toggling, then focus the next task depending on when you are on home
// or coming in from another app
if (launchedFromHome) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 8f952be..440ed6b1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -180,7 +180,8 @@
* Constrain the width of the landscape stack to the smallest width of the device.
*/
private int getInsetToSmallestWidth(int availableWidth) {
- if (availableWidth > smallestWidth) {
+ RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+ if (!debugFlags.isFullscreenThumbnailsEnabled() && (availableWidth > smallestWidth)) {
return (availableWidth - smallestWidth) / 2;
}
return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
index 6c74a4e..67d7115 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java
@@ -27,6 +27,8 @@
public class RecentsDebugFlags implements TunerService.Tunable {
private static final String KEY_FAST_TOGGLE = "overview_fast_toggle";
+ private static final String KEY_PAGE_ON_TOGGLE = "overview_page_on_toggle";
+ private static final String KEY_FULLSCREEN_THUMBNAILS = "overview_fullscreen_thumbnails";
public static class Static {
// Enables debug drawing for the transition thumbnail
@@ -47,8 +49,9 @@
public static final int SystemServicesProxyMockTaskCount = 100;
}
- private boolean mForceEnableFreeformWorkspace;
- private boolean mEnableFastToggleRecents;
+ private boolean mFastToggleRecents;
+ private boolean mPageOnToggle;
+ private boolean mUseFullscreenThumbnails;
/**
* We read the prefs once when we start the activity, then update them as the tuner changes
@@ -57,21 +60,44 @@
public RecentsDebugFlags(Context context) {
// Register all our flags, this will also call onTuningChanged() for each key, which will
// initialize the current state of each flag
- TunerService.get(context).addTunable(this, KEY_FAST_TOGGLE);
+ TunerService.get(context).addTunable(this, KEY_FAST_TOGGLE, KEY_PAGE_ON_TOGGLE,
+ KEY_FULLSCREEN_THUMBNAILS);
}
/**
* @return whether we are enabling fast toggling.
*/
public boolean isFastToggleRecentsEnabled() {
- return mEnableFastToggleRecents;
+ return mPageOnToggle && mFastToggleRecents;
+ }
+
+ /**
+ * @return whether the recents button toggles pages.
+ */
+ public boolean isPageOnToggleEnabled() {
+ return mPageOnToggle;
+ }
+
+ /**
+ * @return whether we should show fullscreen thumbnails
+ */
+ public boolean isFullscreenThumbnailsEnabled() {
+ return mUseFullscreenThumbnails;
}
@Override
public void onTuningChanged(String key, String newValue) {
switch (key) {
case KEY_FAST_TOGGLE:
- mEnableFastToggleRecents = (newValue != null) &&
+ mFastToggleRecents = (newValue != null) &&
+ (Integer.parseInt(newValue) != 0);
+ break;
+ case KEY_PAGE_ON_TOGGLE:
+ mPageOnToggle = (newValue != null) &&
+ (Integer.parseInt(newValue) != 0);
+ break;
+ case KEY_FULLSCREEN_THUMBNAILS:
+ mUseFullscreenThumbnails = (newValue != null) &&
(Integer.parseInt(newValue) != 0);
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 713eb09..e0ff4cc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -331,7 +331,7 @@
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
RecentsDebugFlags flags = Recents.getDebugFlags();
- if (flags.isFastToggleRecentsEnabled() && !launchState.launchedWithAltTab) {
+ if (flags.isPageOnToggleEnabled() && !launchState.launchedWithAltTab) {
// Notify recents to move onto the next task
EventBus.getDefault().post(new IterateRecentsEvent());
} else {
@@ -799,12 +799,13 @@
// Update the header bar if necessary
reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
- if (sInstanceLoadPlan == null) {
- // Create a new load plan if onPreloadRecents() was never triggered
+ // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
+ // should always preload the tasks now
+ if (mTriggeredFromAltTab ||sInstanceLoadPlan == null) {
+ // Create a new load plan if preloadRecents() was never triggered
sInstanceLoadPlan = loader.createLoadPlan(mContext);
}
-
- if (!sInstanceLoadPlan.hasTasks()) {
+ if (mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
}
TaskStack stack = sInstanceLoadPlan.getTaskStack();
@@ -880,8 +881,7 @@
launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
launchState.launchedHasConfigurationChanged = false;
- launchState.startHidden = topTask != null && topTask.stackId == FREEFORM_WORKSPACE_STACK_ID
- || mDraggingInRecents;
+ launchState.launchedViaDragGesture = mDraggingInRecents;
Intent intent = new Intent();
intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
index 9d96d8e..f9ccfc8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
@@ -18,7 +18,6 @@
import android.content.Context;
import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.misc.SystemServicesProxy;
/**
* This is sent when we want to start screen pinning.
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/AllTaskViewsDismissedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/AllTaskViewsDismissedEvent.java
new file mode 100644
index 0000000..cf74519
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/AllTaskViewsDismissedEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 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.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent whenever all the task views in a stack have been dismissed.
+ */
+public class AllTaskViewsDismissedEvent extends EventBus.Event {
+ // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java
new file mode 100644
index 0000000..cb5011a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/StackViewScrolledEvent.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 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.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent whenever a new scroll gesture happens on a stack view.
+ */
+public class StackViewScrolledEvent extends EventBus.Event {
+
+ public final int yMovement;
+
+ public StackViewScrolledEvent(int yMovement) {
+ this.yMovement = yMovement;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java
new file mode 100644
index 0000000..b42da9c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/UpdateFreeformTaskViewVisibilityEvent.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 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.systemui.recents.events.ui;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent to update the visibility of all visible freeform task views.
+ */
+public class UpdateFreeformTaskViewVisibilityEvent extends EventBus.Event {
+
+ public final boolean visible;
+
+ public UpdateFreeformTaskViewVisibilityEvent(boolean visible) {
+ this.visible = visible;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/FreePathInterpolator.java b/packages/SystemUI/src/com/android/systemui/recents/misc/FreePathInterpolator.java
new file mode 100644
index 0000000..720c952
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/FreePathInterpolator.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2015 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.systemui.recents.misc;
+
+import android.graphics.Path;
+import android.view.animation.BaseInterpolator;
+import android.view.animation.Interpolator;
+
+/**
+ * An interpolator that can traverse a Path. The x coordinate along the <code>Path</code>
+ * is the input value and the output is the y coordinate of the line at that point.
+ * This means that the Path must conform to a function <code>y = f(x)</code>.
+ *
+ * <p>The <code>Path</code> must not have gaps in the x direction and must not
+ * loop back on itself such that there can be two points sharing the same x coordinate.
+ * It is alright to have a disjoint line in the vertical direction:</p>
+ * <p><blockquote><pre>
+ * Path path = new Path();
+ * path.lineTo(0.25f, 0.25f);
+ * path.moveTo(0.25f, 0.5f);
+ * path.lineTo(1f, 1f);
+ * </pre></blockquote></p>
+ */
+public class FreePathInterpolator extends BaseInterpolator {
+
+ // This governs how accurate the approximation of the Path is.
+ private static final float PRECISION = 0.002f;
+
+ private float[] mX;
+ private float[] mY;
+ private float mArcLength;
+
+ /**
+ * Create an interpolator for an arbitrary <code>Path</code>.
+ *
+ * @param path The <code>Path</code> to use to make the line representing the interpolator.
+ */
+ public FreePathInterpolator(Path path) {
+ initPath(path);
+ }
+
+ private void initPath(Path path) {
+ float[] pointComponents = path.approximate(PRECISION);
+
+ int numPoints = pointComponents.length / 3;
+
+ mX = new float[numPoints];
+ mY = new float[numPoints];
+ mArcLength = 0;
+ float prevX = 0;
+ float prevY = 0;
+ float prevFraction = 0;
+ int componentIndex = 0;
+ for (int i = 0; i < numPoints; i++) {
+ float fraction = pointComponents[componentIndex++];
+ float x = pointComponents[componentIndex++];
+ float y = pointComponents[componentIndex++];
+ if (fraction == prevFraction && x != prevX) {
+ throw new IllegalArgumentException(
+ "The Path cannot have discontinuity in the X axis.");
+ }
+ if (x < prevX) {
+ throw new IllegalArgumentException("The Path cannot loop back on itself.");
+ }
+ mX[i] = x;
+ mY[i] = y;
+ mArcLength += Math.hypot(x - prevX, y - prevY);
+ prevX = x;
+ prevY = y;
+ prevFraction = fraction;
+ }
+ }
+
+ /**
+ * Using the line in the Path in this interpolator that can be described as
+ * <code>y = f(x)</code>, finds the y coordinate of the line given <code>t</code>
+ * as the x coordinate.
+ *
+ * @param t Treated as the x coordinate along the line.
+ * @return The y coordinate of the Path along the line where x = <code>t</code>.
+ * @see Interpolator#getInterpolation(float)
+ */
+ @Override
+ public float getInterpolation(float t) {
+ int startIndex = 0;
+ int endIndex = mX.length - 1;
+
+ // Return early if out of bounds
+ if (t <= 0) {
+ return mY[startIndex];
+ } else if (t >= 1) {
+ return mY[endIndex];
+ }
+
+ // Do a binary search for the correct x to interpolate between.
+ while (endIndex - startIndex > 1) {
+ int midIndex = (startIndex + endIndex) / 2;
+ if (t < mX[midIndex]) {
+ endIndex = midIndex;
+ } else {
+ startIndex = midIndex;
+ }
+ }
+
+ float xRange = mX[endIndex] - mX[startIndex];
+ if (xRange == 0) {
+ return mY[startIndex];
+ }
+
+ float tInRange = t - mX[startIndex];
+ float fraction = tInRange / xRange;
+
+ float startY = mY[startIndex];
+ float endY = mY[endIndex];
+ return startY + (fraction * (endY - startY));
+ }
+
+ /**
+ * Finds the x that provides the given <code>y = f(x)</code>.
+ *
+ * @param y a value from (0,1) that is in this path.
+ */
+ public float getX(float y) {
+ int startIndex = 0;
+ int endIndex = mY.length - 1;
+
+ // Return early if out of bounds
+ if (y <= 0) {
+ return mX[endIndex];
+ } else if (y >= 1) {
+ return mX[startIndex];
+ }
+
+ // Do a binary search for index that bounds the y
+ while (endIndex - startIndex > 1) {
+ int midIndex = (startIndex + endIndex) / 2;
+ if (y < mY[midIndex]) {
+ startIndex = midIndex;
+ } else {
+ endIndex = midIndex;
+ }
+ }
+
+ float yRange = mY[endIndex] - mY[startIndex];
+ if (yRange == 0) {
+ return mX[startIndex];
+ }
+
+ float tInRange = y - mY[startIndex];
+ float fraction = tInRange / yRange;
+
+ float startX = mX[startIndex];
+ float endX = mX[endIndex];
+ return startX + (fraction * (endX - startX));
+ }
+
+ /**
+ * Returns the arclength of the path we are interpolating.
+ */
+ public float getArcLength() {
+ return mArcLength;
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
deleted file mode 100644
index 515c3bd..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright (C) 2014 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.systemui.recents.misc;
-
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.util.Log;
-
-/**
- * Represents a 2d curve that is parameterized along the arc length of the curve (p), and allows the
- * conversions of x->p and p->x.
- */
-public class ParametricCurve {
-
- private static final boolean DEBUG = false;
- private static final String TAG = "ParametricCurve";
-
- private static final int PrecisionSteps = 250;
-
- /**
- * A 2d function, representing the curve.
- */
- public interface CurveFunction {
- float f(float x);
- float invF(float y);
- }
-
- /**
- * A function that returns a value given a parametric value.
- */
- public interface ParametricCurveFunction {
- float f(float p);
- }
-
- float[] xp;
- float[] px;
- float mLength;
-
- CurveFunction mFn;
- ParametricCurveFunction mScaleFn;
-
- public ParametricCurve(CurveFunction fn, ParametricCurveFunction scaleFn) {
- long t1;
- if (DEBUG) {
- t1 = SystemClock.currentThreadTimeMicro();
- Log.d(TAG, "initializeCurve");
- }
- mFn = fn;
- mScaleFn = scaleFn;
- xp = new float[PrecisionSteps + 1];
- px = new float[PrecisionSteps + 1];
-
- // Approximate f(x)
- float[] fx = new float[PrecisionSteps + 1];
- float step = 1f / PrecisionSteps;
- float x = 0;
- for (int xStep = 0; xStep <= PrecisionSteps; xStep++) {
- fx[xStep] = fn.f(x);
- x += step;
- }
- // Calculate the arc length for x:1->0
- float pLength = 0;
- float[] dx = new float[PrecisionSteps + 1];
- dx[0] = 0;
- for (int xStep = 1; xStep < PrecisionSteps; xStep++) {
- dx[xStep] = (float) Math.hypot(fx[xStep] - fx[xStep - 1], step);
- pLength += dx[xStep];
- }
- mLength = pLength;
- // Approximate p(x), a function of cumulative progress with x, normalized to 0..1
- float p = 0;
- px[0] = 0f;
- px[PrecisionSteps] = 1f;
- if (DEBUG) {
- Log.d(TAG, "p[0]=0");
- Log.d(TAG, "p[" + PrecisionSteps + "]=1");
- }
- for (int xStep = 1; xStep < PrecisionSteps; xStep++) {
- p += Math.abs(dx[xStep] / pLength);
- px[xStep] = p;
- if (DEBUG) {
- Log.d(TAG, "p[" + xStep + "]=" + p);
- }
- }
- // Given p(x), calculate the inverse function x(p). This assumes that x(p) is also a valid
- // function.
- int xStep = 0;
- p = 0;
- xp[0] = 0f;
- xp[PrecisionSteps] = 1f;
- if (DEBUG) {
- Log.d(TAG, "x[0]=0");
- Log.d(TAG, "x[" + PrecisionSteps + "]=1");
- }
- for (int pStep = 0; pStep < PrecisionSteps; pStep++) {
- // Walk forward in px and find the x where px <= p && p < px+1
- while (xStep < PrecisionSteps) {
- if (px[xStep] > p) break;
- xStep++;
- }
- // Now, px[xStep-1] <= p < px[xStep]
- if (xStep == 0) {
- xp[pStep] = 0;
- } else {
- // Find x such that proportionally, x is correct
- float fraction = (p - px[xStep - 1]) / (px[xStep] - px[xStep - 1]);
- x = (xStep - 1 + fraction) * step;
- xp[pStep] = x;
- }
- if (DEBUG) {
- Log.d(TAG, "x[" + pStep + "]=" + xp[pStep]);
- }
- p += step;
- }
- if (DEBUG) {
- Log.d(TAG, "\t1t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
- }
- }
-
- /**
- * Converts from the progress along the arc-length of the curve to a coordinate within the
- * bounds. Note, p=0 represents the top of the bounds, and p=1 represents the bottom.
- */
- public int pToX(float p, Rect bounds) {
- int top = bounds.top;
- int height = bounds.height();
-
- if (p <= 0f) return top;
- if (p >= 1f) return top + (int) (p * height);
-
- float pIndex = p * PrecisionSteps;
- int pFloorIndex = (int) Math.floor(pIndex);
- int pCeilIndex = (int) Math.ceil(pIndex);
- float x = xp[pFloorIndex];
- if (pFloorIndex < PrecisionSteps && (pCeilIndex != pFloorIndex)) {
- // Interpolate between the two precalculated positions
- x += (xp[pCeilIndex] - xp[pFloorIndex]) * (pIndex - pFloorIndex);
- }
- return top + (int) (x * height);
- }
-
- /**
- * Converts from the progress along the arc-length of the curve to a scale.
- */
- public float pToScale(float p) {
- return mScaleFn.f(p);
- }
-
- /**
- * Converts from a bounds coordinate to the progress along the arc-length of the curve.
- * Note, p=0 represents the top of the bounds, and p=1 represents the bottom.
- */
- public float xToP(int x, Rect bounds) {
- int top = bounds.top;
-
- float xf = (float) (x - top) / bounds.height();
- if (xf <= 0f) return 0f;
- if (xf >= 1f) return xf;
-
- float xIndex = xf * PrecisionSteps;
- int xFloorIndex = (int) Math.floor(xIndex);
- int xCeilIndex = (int) Math.ceil(xIndex);
- float p = px[xFloorIndex];
- if (xFloorIndex < PrecisionSteps && (xCeilIndex != xFloorIndex)) {
- // Interpolate between the two precalculated positions
- p += (px[xCeilIndex] - px[xFloorIndex]) * (xIndex - xFloorIndex);
- }
- return p;
- }
-
- /**
- * Computes the progress offset from the bottom of the curve (p=1) such that the given height
- * is visible when scaled at the that progress.
- */
- public float computePOffsetForScaledHeight(int height, Rect bounds) {
- int top = bounds.top;
- int bottom = bounds.bottom;
- height = Math.min(height, bottom - top);
-
- if (bounds.height() == 0) {
- return 0;
- }
-
- // Find the next p(x) such that (bottom-x) == scale(p(x))*height
- int minX = top;
- int maxX = bottom;
- long t1;
- if (DEBUG) {
- t1 = SystemClock.currentThreadTimeMicro();
- Log.d(TAG, "computePOffsetForScaledHeight: " + height);
- }
- while (minX <= maxX) {
- int midX = minX + ((maxX - minX) / 2);
- float pMidX = xToP(midX, bounds);
- float scaleMidX = mScaleFn.f(pMidX);
- int scaledHeight = (int) (scaleMidX * height);
- if ((bottom - midX) < scaledHeight) {
- maxX = midX - 1;
- } else if ((bottom - midX) > scaledHeight) {
- minX = midX + 1;
- } else {
- if (DEBUG) {
- Log.d(TAG, "\t1t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
- }
- return 1f - pMidX;
- }
- }
- if (DEBUG) {
- Log.d(TAG, "\t2t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
- }
- return 1f - xToP(maxX, bounds);
- }
-
- /**
- * Computes the progress offset from the bottom of the curve (p=1) that allows the given height,
- * unscaled at the progress, will be visible.
- */
- public float computePOffsetForHeight(int height, Rect bounds) {
- int top = bounds.top;
- int bottom = bounds.bottom;
- height = Math.min(height, bottom - top);
-
- if (bounds.height() == 0) {
- return 0;
- }
-
- // Find the next p(x) such that (bottom-x) == height
- int minX = top;
- int maxX = bottom;
- long t1;
- if (DEBUG) {
- t1 = SystemClock.currentThreadTimeMicro();
- Log.d(TAG, "computePOffsetForHeight: " + height);
- }
- while (minX <= maxX) {
- int midX = minX + ((maxX - minX) / 2);
- if ((bottom - midX) < height) {
- maxX = midX - 1;
- } else if ((bottom - midX) > height) {
- minX = midX + 1;
- } else {
- if (DEBUG) {
- Log.d(TAG, "\t1t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
- }
- return 1f - xToP(midX, bounds);
- }
- }
- if (DEBUG) {
- Log.d(TAG, "\t2t: " + (SystemClock.currentThreadTimeMicro() - t1) + "microsecs");
- }
- return 1f - xToP(maxX, bounds);
- }
-
- /**
- * Returns the length of this curve.
- */
- public float getArcLength() {
- return mLength;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 03d5cf1..5026c790 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -342,6 +342,13 @@
}
/**
+ * Returns whether the given stack id is the docked stack id.
+ */
+ public static boolean isDockedStack(int stackId) {
+ return stackId == DOCKED_STACK_ID;
+ }
+
+ /**
* Returns whether the given stack id is the freeform workspace stack id.
*/
public static boolean isFreeformStack(int stackId) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index f26dcde..0970252 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -59,7 +59,7 @@
class FilteredTaskList {
private static final String TAG = "FilteredTaskList";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
ArrayList<Task> mTasks = new ArrayList<>();
ArrayList<Task> mFilteredTasks = new ArrayList<>();
@@ -74,18 +74,17 @@
if (!prevFilteredTasks.equals(mFilteredTasks)) {
return true;
} else {
- // If the tasks are exactly the same pre/post filter, then just reset it
- mFilter = null;
return false;
}
}
- /** Resets this FilteredTaskList. */
+ /**
+ * Resets the task list, but does not remove the filter.
+ */
void reset() {
mTasks.clear();
mFilteredTasks.clear();
mTaskIndices.clear();
- mFilter = null;
}
/** Removes the task filter and returns the previous touch state */
@@ -200,16 +199,10 @@
/** Task stack callbacks */
public interface TaskStackCallbacks {
/* Notifies when a task has been added to the stack */
- public void onStackTaskAdded(TaskStack stack, Task t);
+ void onStackTaskAdded(TaskStack stack, Task t);
/* Notifies when a task has been removed from the stack */
- public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
+ void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
Task newFrontMostTask);
- /* Notifies when all task has been removed from the stack */
- public void onStackAllTasksRemoved(TaskStack stack, ArrayList<Task> removedTasks);
- /** Notifies when the stack was filtered */
- public void onStackFiltered(TaskStack newStack, ArrayList<Task> curTasks, Task t);
- /** Notifies when the stack was un-filtered */
- public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curTasks);
}
@@ -300,8 +293,18 @@
FilteredTaskList mTaskList = new FilteredTaskList();
TaskStackCallbacks mCb;
- ArrayList<TaskGrouping> mGroups = new ArrayList<TaskGrouping>();
- HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<Integer, TaskGrouping>();
+ ArrayList<TaskGrouping> mGroups = new ArrayList<>();
+ HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<>();
+
+ public TaskStack() {
+ // Ensure that we only show non-docked tasks
+ mTaskList.setFilter(new TaskFilter() {
+ @Override
+ public boolean acceptTask(Task t, int index) {
+ return !SystemServicesProxy.isDockedStack(t.key.stackId);
+ }
+ });
+ }
/** Sets the callbacks for this task stack. */
public void setCallbacks(TaskStackCallbacks cb) {
@@ -377,20 +380,6 @@
}
}
- /** Removes all tasks */
- public void removeAllTasks() {
- ArrayList<Task> taskList = new ArrayList<Task>(mTaskList.getTasks());
- int taskCount = taskList.size();
- for (int i = taskCount - 1; i >= 0; i--) {
- Task t = taskList.get(i);
- removeTaskImpl(t);
- }
- if (mCb != null) {
- // Notify that all tasks have been removed
- mCb.onStackAllTasksRemoved(this, taskList);
- }
- }
-
/** Sets a few tasks in one go */
public void setTasks(List<Task> tasks) {
ArrayList<Task> taskList = mTaskList.getTasks();
@@ -419,7 +408,7 @@
/** Gets the task keys */
public ArrayList<Task.TaskKey> getTaskKeys() {
- ArrayList<Task.TaskKey> taskKeys = new ArrayList<Task.TaskKey>();
+ ArrayList<Task.TaskKey> taskKeys = new ArrayList<>();
ArrayList<Task> tasks = mTaskList.getTasks();
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
@@ -486,41 +475,6 @@
return false;
}
- /******** Filtering ********/
-
- /** Filters the stack into tasks similar to the one specified */
- public void filterTasks(final Task t) {
- ArrayList<Task> oldStack = new ArrayList<Task>(mTaskList.getTasks());
-
- // Set the task list filter
- boolean filtered = mTaskList.setFilter(new TaskFilter() {
- @Override
- public boolean acceptTask(Task at, int i) {
- return t.key.getComponent().getPackageName().equals(
- at.key.getComponent().getPackageName());
- }
- });
- if (filtered && mCb != null) {
- mCb.onStackFiltered(this, oldStack, t);
- }
- }
-
- /** Unfilters the current stack */
- public void unfilterTasks() {
- ArrayList<Task> oldStack = new ArrayList<Task>(mTaskList.getTasks());
-
- // Unset the filter, then update the virtual scroll
- mTaskList.removeFilter();
- if (mCb != null) {
- mCb.onStackUnfiltered(this, oldStack);
- }
- }
-
- /** Returns whether tasks are currently filtered */
- public boolean hasFilteredTasks() {
- return mTaskList.hasFilter();
- }
-
/******** Grouping ********/
/** Adds a group to the set */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
index f3c66a5..ff3aef9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -17,7 +17,6 @@
package com.android.systemui.recents.views;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.ActivityManager.StackId;
import android.app.ActivityOptions;
import android.content.Context;
@@ -49,7 +48,6 @@
import java.util.ArrayList;
import java.util.List;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index d18389f..311ee65 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -23,24 +23,23 @@
import android.os.Handler;
import android.util.ArraySet;
import android.util.AttributeSet;
-import android.view.AppTransitionAnimationSpec;
import android.view.LayoutInflater;
import android.view.MotionEvent;
-import android.view.View;
import android.view.WindowInsets;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
+import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsAppWidgetHostView;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
+import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
@@ -64,20 +63,13 @@
private static final String TAG = "RecentsView";
private static final boolean DEBUG = false;
- private int mStackViewVisibility = View.VISIBLE;
-
- /** The RecentsView callbacks */
- public interface RecentsViewCallbacks {
- public void onAllTaskViewsDismissed();
- }
-
LayoutInflater mInflater;
Handler mHandler;
- ArrayList<TaskStack> mStacks;
TaskStackView mTaskStackView;
RecentsAppWidgetHostView mSearchBar;
- RecentsViewCallbacks mCb;
+ boolean mAwaitingFirstLayout = true;
+ boolean mLastTaskLaunchedWasFreeform;
RecentsTransitionHelper mTransitionHelper;
RecentsViewTouchHandler mTouchHandler;
@@ -116,11 +108,6 @@
mTouchHandler = new RecentsViewTouchHandler(this);
}
- /** Sets the callbacks */
- public void setCallbacks(RecentsViewCallbacks cb) {
- mCb = cb;
- }
-
/** Set/get the bsp root node */
public void setTaskStack(TaskStack stack) {
RecentsConfiguration config = Recents.getConfiguration();
@@ -142,12 +129,18 @@
mTaskStackView.setCallbacks(this);
addView(mTaskStackView);
}
- mTaskStackView.setVisibility(mStackViewVisibility);
// Trigger a new layout
requestLayout();
}
+ /**
+ * Returns whether the last task launched was in the freeform stack or not.
+ */
+ public boolean isLastTaskLaunchedFreeform() {
+ return mLastTaskLaunchedWasFreeform;
+ }
+
/** Gets the next task in the stack - or if the last - the top task */
public Task getNextTaskOrTopTask(Task taskToSearch) {
Task returnTask = null;
@@ -336,6 +329,17 @@
mDragView.layout(left, top, left + mDragView.getMeasuredWidth(),
top + mDragView.getMeasuredHeight());
}
+
+ if (mAwaitingFirstLayout) {
+ mAwaitingFirstLayout = false;
+
+ // If launched via dragging from the nav bar, then we should translate the whole view
+ // down offscreen
+ RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
+ if (launchState.launchedViaDragGesture) {
+ setTranslationY(getMeasuredHeight());
+ }
+ }
}
@Override
@@ -377,24 +381,6 @@
return super.verifyDrawable(who);
}
- /** Unfilters any filtered stacks */
- public boolean unfilterFilteredStacks() {
- if (mStacks != null) {
- // Check if there are any filtered stacks and unfilter them before we back out of Recents
- boolean stacksUnfiltered = false;
- int numStacks = mStacks.size();
- for (int i = 0; i < numStacks; i++) {
- TaskStack stack = mStacks.get(i);
- if (stack.hasFilteredTasks()) {
- stack.unfilterTasks();
- stacksUnfiltered = true;
- }
- }
- return stacksUnfiltered;
- }
- return false;
- }
-
public void disableLayersForOneFrame() {
if (mTaskStackView != null) {
mTaskStackView.disableLayersForOneFrame();
@@ -407,59 +393,11 @@
public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
final TaskStack stack, final Task task, final boolean lockToTask,
final Rect bounds, int destinationStack) {
+ mLastTaskLaunchedWasFreeform = SystemServicesProxy.isFreeformStack(task.key.stackId);
mTransitionHelper.launchTaskFromRecents(stack, task, stackView, tv, lockToTask, bounds,
destinationStack);
}
- @Override
- public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks) {
- /* TODO: Not currently enabled
- if (removedTasks != null) {
- int taskCount = removedTasks.size();
- for (int i = 0; i < taskCount; i++) {
- onTaskViewDismissed(removedTasks.get(i));
- }
- }
- */
-
- mCb.onAllTaskViewsDismissed();
-
- // Keep track of all-deletions
- MetricsLogger.count(getContext(), "overview_task_all_dismissed", 1);
- }
-
- @Override
- public void onTaskStackFilterTriggered() {
- // Hide the search bar
- if (mSearchBar != null) {
- int filterDuration = getResources().getInteger(
- R.integer.recents_filter_animate_current_views_duration);
- mSearchBar.animate()
- .alpha(0f)
- .setStartDelay(0)
- .setInterpolator(mFastOutSlowInInterpolator)
- .setDuration(filterDuration)
- .withLayer()
- .start();
- }
- }
-
- @Override
- public void onTaskStackUnfilterTriggered() {
- // Show the search bar
- if (mSearchBar != null) {
- int filterDuration = getResources().getInteger(
- R.integer.recents_filter_animate_new_views_duration);
- mSearchBar.animate()
- .alpha(1f)
- .setStartDelay(0)
- .setInterpolator(mFastOutSlowInInterpolator)
- .setDuration(filterDuration)
- .withLayer()
- .start();
- }
- }
-
/**** EventBus Events ****/
public final void onBusEvent(DragStartEvent event) {
@@ -545,7 +483,6 @@
}
public final void onBusEvent(DraggingInRecentsEvent event) {
- setStackViewVisibility(View.VISIBLE);
setTranslationY(event.distanceFromTop - mTaskStackView.getTaskViews().get(0).getY());
}
@@ -579,11 +516,11 @@
}
}
- public void setStackViewVisibility(int stackViewVisibility) {
- mStackViewVisibility = stackViewVisibility;
- if (mTaskStackView != null) {
- mTaskStackView.setVisibility(stackViewVisibility);
- invalidate();
+ public final void onBusEvent(RecentsVisibilityChangedEvent event) {
+ if (!event.visible) {
+ // Reset the view state
+ mAwaitingFirstLayout = true;
+ mLastTaskLaunchedWasFreeform = false;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index b3bd6ed..f8f0ea1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -16,14 +16,22 @@
package com.android.systemui.recents.views;
+import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Path;
import android.graphics.Rect;
+import android.util.FloatProperty;
import android.util.Log;
+import android.util.Property;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.misc.ParametricCurve;
+import com.android.systemui.recents.RecentsDebugFlags;
+import com.android.systemui.recents.misc.FreePathInterpolator;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
@@ -32,21 +40,96 @@
import java.util.ArrayList;
import java.util.HashMap;
+/**
+ * Used to describe a visible range that can be normalized to [0, 1].
+ */
+class Range {
+ final float relativeMin;
+ final float relativeMax;
+ float origin;
+ float min;
+ float max;
+
+ public Range(float relMin, float relMax) {
+ min = relativeMin = relMin;
+ max = relativeMax = relMax;
+ }
+
+ /**
+ * Offsets this range to a given absolute position.
+ */
+ public void offset(float x) {
+ this.origin = x;
+ min = x + relativeMin;
+ max = x + relativeMax;
+ }
+
+ /**
+ * Returns x normalized to the range 0 to 1 such that 0 = min, 0.5 = origin and 1 = max
+ *
+ * @param x is an absolute value in the same domain as origin
+ */
+ public float getNormalizedX(float x) {
+ if (x < origin) {
+ return 0.5f + 0.5f * (x - origin) / -relativeMin;
+ } else {
+ return 0.5f + 0.5f * (x - origin) / relativeMax;
+ }
+ }
+
+ /**
+ * Given a normalized {@param x} value in this range, projected onto the full range to get an
+ * absolute value about the given {@param origin}.
+ */
+ public float getAbsoluteX(float normX) {
+ if (normX < 0.5f) {
+ return (normX - 0.5f) / 0.5f * -relativeMin;
+ } else {
+ return (normX - 0.5f) / 0.5f * relativeMax;
+ }
+ }
+
+ /**
+ * Returns whether a value at an absolute x would be within range.
+ */
+ public boolean isInRange(float absX) {
+ return (absX >= Math.floor(min)) && (absX <= Math.ceil(max));
+ }
+}
/**
- * The layout logic for a TaskStackView.
+ * The layout logic for a TaskStackView. This layout can have two states focused and unfocused,
+ * and in the focused state, there is a task that is displayed more prominently in the stack.
*/
public class TaskStackLayoutAlgorithm {
private static final String TAG = "TaskStackViewLayoutAlgorithm";
private static final boolean DEBUG = false;
- // The min scale of the last task at the top of the curve
- private static final float STACK_PEEK_MIN_SCALE = 0.85f;
- // The scale of the last task
- private static final float SINGLE_TASK_SCALE = 0.95f;
- // The percentage of height of task to show between tasks
- private static final float VISIBLE_TASK_HEIGHT_BETWEEN_TASKS = 0.5f;
+ // The scale factor to apply to the user movement in the stack to unfocus it
+ private static final float UNFOCUS_MULTIPLIER = 0.8f;
+
+ // The various focus states
+ public static final float STATE_FOCUSED = 1f;
+ public static final float STATE_UNFOCUSED = 0f;
+
+ /**
+ * A Property wrapper around the <code>focusState</code> functionality handled by the
+ * {@link TaskStackLayoutAlgorithm#setFocusState(float)} and
+ * {@link TaskStackLayoutAlgorithm#getFocusState()} methods.
+ */
+ private static final Property<TaskStackLayoutAlgorithm, Float> FOCUS_STATE =
+ new FloatProperty<TaskStackLayoutAlgorithm>("focusState") {
+ @Override
+ public void setValue(TaskStackLayoutAlgorithm object, float value) {
+ object.setFocusState(value);
+ }
+
+ @Override
+ public Float get(TaskStackLayoutAlgorithm object) {
+ return object.getFocusState();
+ }
+ };
// A report of the visibility state of the stack
public class VisibilityReport {
@@ -61,6 +144,8 @@
}
Context mContext;
+ private TaskStackView mStackView;
+ private Interpolator mFastOutSlowInInterpolator;
// The task bounds (untransformed) for layout. This rect is anchored at mTaskRoot.
public Rect mTaskRect = new Rect();
@@ -74,10 +159,33 @@
private Rect mStackRect = new Rect();
// The current stack rect, can either by mFreeformStackRect or mStackRect depending on whether
// there is a freeform workspace
- public Rect mCurrentStackRect;
+ public Rect mCurrentStackRect = new Rect();
// This is the current system insets
public Rect mSystemInsets = new Rect();
+ // The visible ranges when the stack is focused and unfocused
+ private Range mUnfocusedRange;
+ private Range mFocusedRange;
+
+ // The offset from the top when scrolled to the top of the stack
+ private int mFocusedPeekHeight;
+
+ // The offset from the bottom of the stack to the bottom of the bounds
+ private int mStackBottomOffset;
+
+ // The paths defining the motion of the tasks when the stack is focused and unfocused
+ private Path mUnfocusedCurve;
+ private Path mFocusedCurve;
+ private FreePathInterpolator mUnfocusedCurveInterpolator;
+ private FreePathInterpolator mFocusedCurveInterpolator;
+
+ // The state of the stack focus (0..1), which controls the transition of the stack from the
+ // focused to non-focused state
+ private float mFocusState;
+
+ // The animator used to reset the focused state
+ private ObjectAnimator mFocusStateAnimator;
+
// The smallest scroll progress, at this value, the back most task will be visible
float mMinScrollP;
// The largest scroll progress, at this value, the front most task will be visible above the
@@ -88,78 +196,44 @@
// The task progress for the front-most task in the stack
float mFrontMostTaskP;
- // The relative progress to ensure that the height between affiliated tasks is respected
- float mWithinAffiliationPOffset;
- // The relative progress to ensure that the height between non-affiliated tasks is
- // respected
- float mBetweenAffiliationPOffset;
- // The relative progress to ensure that the task height is respected
- float mTaskHeightPOffset;
- // The relative progress to ensure that the half task height is respected
- float mTaskHalfHeightPOffset;
- // The front-most task bottom offset
- int mStackBottomOffset;
- // The relative progress to ensure that the offset from the bottom of the stack to the bottom
- // of the task is respected
- float mStackBottomPOffset;
-
// The last computed task counts
int mNumStackTasks;
int mNumFreeformTasks;
+
// The min/max z translations
int mMinTranslationZ;
int mMaxTranslationZ;
- // Optimization, allows for quick lookup of task -> progress
- HashMap<Task.TaskKey, Float> mTaskProgressMap = new HashMap<>();
+ // Optimization, allows for quick lookup of task -> index
+ private HashMap<Task.TaskKey, Integer> mTaskIndexMap = new HashMap<>();
// The freeform workspace layout
FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
- // Log function
- static ParametricCurve sCurve;
-
- public TaskStackLayoutAlgorithm(Context context) {
+ public TaskStackLayoutAlgorithm(Context context, TaskStackView stackView) {
Resources res = context.getResources();
+ mStackView = stackView;
+
+ mFocusedRange = new Range(res.getFloat(R.integer.recents_layout_focused_range_min),
+ res.getFloat(R.integer.recents_layout_focused_range_max));
+ mUnfocusedRange = new Range(res.getFloat(R.integer.recents_layout_unfocused_range_min),
+ res.getFloat(R.integer.recents_layout_unfocused_range_max));
+ mFocusState = getDefaultFocusState();
+ mFocusedPeekHeight = res.getDimensionPixelSize(R.dimen.recents_layout_focused_peek_size);
+
mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min);
mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max);
mContext = context;
mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm();
- if (sCurve == null) {
- sCurve = new ParametricCurve(new ParametricCurve.CurveFunction() {
- // The large the XScale, the longer the flat area of the curve
- private static final float XScale = 1.75f;
- private static final float LogBase = 3000;
+ mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+ com.android.internal.R.interpolator.fast_out_slow_in);
+ }
- float reverse(float x) {
- return (-x * XScale) + 1;
- }
-
- @Override
- public float f(float x) {
- return 1f - (float) (Math.pow(LogBase, reverse(x))) / (LogBase);
- }
-
- @Override
- public float invF(float y) {
- return (float) (Math.log(1f - reverse(y)) / (-Math.log(LogBase) * XScale));
- }
- }, new ParametricCurve.ParametricCurveFunction() {
- @Override
- public float f(float p) {
- SystemServicesProxy ssp = Recents.getSystemServices();
- if (ssp.hasFreeformWorkspaceSupport()) {
- return 1f;
- }
-
- if (p < 0) return STACK_PEEK_MIN_SCALE;
- if (p > 1) return 1f;
- float scaleRange = (1f - STACK_PEEK_MIN_SCALE);
- float scale = STACK_PEEK_MIN_SCALE + (p * scaleRange);
- return scale;
- }
- });
- }
+ /**
+ * Resets this layout when the stack view is reset.
+ */
+ public void reset() {
+ setFocusState(getDefaultFocusState());
}
/**
@@ -173,16 +247,32 @@
}
/**
+ * Sets the focused state.
+ */
+ public void setFocusState(float focusState) {
+ mFocusState = focusState;
+ mStackView.requestSynchronizeStackViewsWithModel();
+ }
+
+ /**
+ * Gets the focused state.
+ */
+ public float getFocusState() {
+ return mFocusState;
+ }
+
+ /**
* Computes the stack and task rects. The given task stack bounds is the whole bounds not
* including the search bar.
*/
public void initialize(Rect taskStackBounds) {
SystemServicesProxy ssp = Recents.getSystemServices();
-
+ RecentsDebugFlags debugFlags = Recents.getDebugFlags();
RecentsConfiguration config = Recents.getConfiguration();
int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
int heightPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.recents_stack_top_padding);
+ Rect lastStackRect = new Rect(mCurrentStackRect);
// The freeform height is the visible height (not including system insets) - padding above
// freeform and below stack - gap between the freeform and stack
@@ -200,43 +290,34 @@
taskStackBounds.top + heightPadding,
taskStackBounds.right - widthPadding,
taskStackBounds.bottom);
+
// Anchor the task rect to the top-center of the non-freeform stack rect
- int size = mStackRect.width();
+ float aspect = (float) (taskStackBounds.width() - mSystemInsets.left - mSystemInsets.right)
+ / (taskStackBounds.height() - mSystemInsets.bottom);
+ int width = mStackRect.width();
+ int height = debugFlags.isFullscreenThumbnailsEnabled() ? (int) (width / aspect) : width;
mTaskRect.set(mStackRect.left, mStackRect.top,
- mStackRect.left + size, mStackRect.top + size);
+ mStackRect.left + width, mStackRect.top + height);
mCurrentStackRect = ssp.hasFreeformWorkspaceSupport() ? mFreeformStackRect : mStackRect;
- // Compute the progress offsets
- int withinAffiliationOffset = mContext.getResources().getDimensionPixelSize(
- R.dimen.recents_task_bar_height);
- int betweenAffiliationOffset = (int) (VISIBLE_TASK_HEIGHT_BETWEEN_TASKS * mTaskRect.height());
- mWithinAffiliationPOffset = sCurve.computePOffsetForScaledHeight(withinAffiliationOffset,
- mCurrentStackRect);
- mBetweenAffiliationPOffset = sCurve.computePOffsetForScaledHeight(betweenAffiliationOffset,
- mCurrentStackRect);
- mTaskHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height(),
- mCurrentStackRect);
- mTaskHalfHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height() / 2,
- mCurrentStackRect);
- mStackBottomPOffset = sCurve.computePOffsetForHeight(mStackBottomOffset, mCurrentStackRect);
+ // Short circuit here if the stack rects haven't changed so we don't do all the work below
+ if (lastStackRect.equals(mCurrentStackRect)) {
+ return;
+ }
+
+ // Reinitialize the focused and unfocused curves
+ mUnfocusedCurve = constructUnfocusedCurve();
+ mUnfocusedCurveInterpolator = new FreePathInterpolator(mUnfocusedCurve);
+ mFocusedCurve = constructFocusedCurve();
+ mFocusedCurveInterpolator = new FreePathInterpolator(mFocusedCurve);
if (DEBUG) {
Log.d(TAG, "initialize");
- Log.d(TAG, "\tarclength: " + sCurve.getArcLength());
Log.d(TAG, "\tmFreeformRect: " + mFreeformRect);
Log.d(TAG, "\tmFreeformStackRect: " + mFreeformStackRect);
Log.d(TAG, "\tmStackRect: " + mStackRect);
Log.d(TAG, "\tmTaskRect: " + mTaskRect);
Log.d(TAG, "\tmSystemInsets: " + mSystemInsets);
-
- Log.d(TAG, "\tpWithinAffiliateOffset: " + mWithinAffiliationPOffset);
- Log.d(TAG, "\tpBetweenAffiliateOffset: " + mBetweenAffiliationPOffset);
- Log.d(TAG, "\tmTaskHeightPOffset: " + mTaskHeightPOffset);
- Log.d(TAG, "\tmTaskHalfHeightPOffset: " + mTaskHalfHeightPOffset);
- Log.d(TAG, "\tmStackBottomPOffset: " + mStackBottomPOffset);
-
- Log.d(TAG, "\ty at p=0: " + sCurve.pToX(0f, mCurrentStackRect));
- Log.d(TAG, "\ty at p=1: " + sCurve.pToX(1f, mCurrentStackRect));
}
}
@@ -248,7 +329,7 @@
SystemServicesProxy ssp = Recents.getSystemServices();
// Clear the progress map
- mTaskProgressMap.clear();
+ mTaskIndexMap.clear();
// Return early if we have no tasks
ArrayList<Task> tasks = stack.getTasks();
@@ -273,41 +354,28 @@
mNumStackTasks = stackTasks.size();
mNumFreeformTasks = freeformTasks.size();
- float pAtBackMostTaskTop = 0;
- float pAtFrontMostTaskTop = pAtBackMostTaskTop;
- if (!stackTasks.isEmpty()) {
- // Update the for each task from back to front.
- int taskCount = stackTasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = stackTasks.get(i);
- mTaskProgressMap.put(task.key, pAtFrontMostTaskTop);
+ // Put each of the tasks in the progress map at a fixed index (does not need to actually
+ // map to a scroll position, just by index)
+ int taskCount = stackTasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = stackTasks.get(i);
+ mTaskIndexMap.put(task.key, i);
+ }
- if (DEBUG) {
- Log.d(TAG, "Update: " + task.activityLabel + " p: " + pAtFrontMostTaskTop);
- }
-
- if (i < (taskCount - 1)) {
- // Increment the peek height
- float pPeek = task.group == null || task.group.isFrontMostTask(task) ?
- mBetweenAffiliationPOffset : mWithinAffiliationPOffset;
- pAtFrontMostTaskTop += pPeek;
- }
- }
-
- mFrontMostTaskP = pAtFrontMostTaskTop;
- if (mNumStackTasks > 1) {
- // Set the stack end scroll progress to the point at which the bottom of the front-most
- // task is aligned to the bottom of the stack
- mMaxScrollP = alignToStackBottom(pAtFrontMostTaskTop,
- mStackBottomPOffset + (ssp.hasFreeformWorkspaceSupport() ?
- mTaskHalfHeightPOffset : mTaskHeightPOffset));
- // Basically align the back-most task such that the last two tasks would be visible
- mMinScrollP = alignToStackBottom(pAtBackMostTaskTop,
- mStackBottomPOffset + (ssp.hasFreeformWorkspaceSupport() ?
- mTaskHalfHeightPOffset : mTaskHeightPOffset));
- } else {
- // When there is a single item, then just make all the stack progresses the same
+ // Calculate the min/max scroll
+ if (getDefaultFocusState() > 0f) {
+ mMinScrollP = 0;
+ mMaxScrollP = Math.max(mMinScrollP, mNumStackTasks - 1);
+ } else {
+ if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
mMinScrollP = mMaxScrollP = 0;
+ } else {
+ float bottomOffsetPct = (float) (mStackBottomOffset + mTaskRect.height()) /
+ mCurrentStackRect.height();
+ float normX = mUnfocusedCurveInterpolator.getX(bottomOffsetPct);
+ mMinScrollP = 0;
+ mMaxScrollP = Math.max(mMinScrollP,
+ (mNumStackTasks - 1) - mUnfocusedRange.getAbsoluteX(normX));
}
}
@@ -315,7 +383,20 @@
mFreeformLayoutAlgorithm.update(freeformTasks, this);
mInitialScrollP = mMaxScrollP;
} else {
- mInitialScrollP = Math.max(mMinScrollP, mMaxScrollP - mTaskHalfHeightPOffset);
+ if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
+ mInitialScrollP = mMinScrollP;
+ } else if (getDefaultFocusState() > 0f) {
+ RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
+ if (launchState.launchedFromHome) {
+ mInitialScrollP = Math.max(mMinScrollP, mNumStackTasks - 1);
+ } else {
+ mInitialScrollP = Math.max(mMinScrollP, mNumStackTasks - 2);
+ }
+ } else {
+ float offsetPct = (float) (mTaskRect.height() / 2) / mCurrentStackRect.height();
+ float normX = mUnfocusedCurveInterpolator.getX(offsetPct);
+ mInitialScrollP = (mNumStackTasks - 1) - mUnfocusedRange.getAbsoluteX(normX);
+ }
}
if (DEBUG) {
@@ -327,8 +408,45 @@
}
/**
- * Computes the maximum number of visible tasks and thumbnails. Requires that
- * update() is called first.
+ * Updates this stack when a scroll happens.
+ */
+ public void updateFocusStateOnScroll(int yMovement) {
+ Utilities.cancelAnimationWithoutCallbacks(mFocusStateAnimator);
+ if (mFocusState > STATE_UNFOCUSED) {
+ float delta = (float) yMovement / (UNFOCUS_MULTIPLIER * mCurrentStackRect.height());
+ mFocusState -= Math.min(mFocusState, Math.abs(delta));
+ }
+ }
+
+ /**
+ * Aniamtes the focused state back to its orginal state.
+ */
+ public void animateFocusState(float newState) {
+ Utilities.cancelAnimationWithoutCallbacks(mFocusStateAnimator);
+ if (Float.compare(newState, getFocusState()) != 0) {
+ mFocusStateAnimator = ObjectAnimator.ofFloat(this, FOCUS_STATE, getFocusState(),
+ newState);
+ mFocusStateAnimator.setDuration(200);
+ mFocusStateAnimator.setInterpolator(mFastOutSlowInInterpolator);
+ mFocusStateAnimator.start();
+ }
+ }
+
+ /**
+ * Returns the default focus state.
+ */
+ public float getDefaultFocusState() {
+ RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
+ RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+ if (debugFlags.isPageOnToggleEnabled() || launchState.launchedWithAltTab) {
+ return 1f;
+ }
+ return 0f;
+ }
+
+ /**
+ * Computes the maximum number of visible tasks and thumbnails when the scroll is at the initial
+ * stack scroll. Requires that update() is called first.
*/
public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
// Ensure minimum visibility count
@@ -344,29 +462,32 @@
// Otherwise, walk backwards in the stack and count the number of tasks and visible
// thumbnails and add that to the total freeform task count
- int taskHeight = mTaskRect.height();
+ TaskViewTransform tmpTransform = new TaskViewTransform();
+ Range currentRange = getDefaultFocusState() > 0f ? mFocusedRange : mUnfocusedRange;
+ currentRange.offset(mInitialScrollP);
int taskBarHeight = mContext.getResources().getDimensionPixelSize(
R.dimen.recents_task_bar_height);
int numVisibleTasks = Math.max(mNumFreeformTasks, 1);
int numVisibleThumbnails = Math.max(mNumFreeformTasks, 1);
- Task firstNonFreeformTask = tasks.get(tasks.size() - mNumFreeformTasks - 1);
- float progress = mTaskProgressMap.get(firstNonFreeformTask.key) - mInitialScrollP;
- int prevScreenY = sCurve.pToX(progress, mCurrentStackRect);
- for (int i = tasks.size() - 2; i >= 0; i--) {
+ float prevScreenY = Integer.MAX_VALUE;
+ for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
+
+ // Skip freeform
if (task.isFreeformTask()) {
continue;
}
- progress = mTaskProgressMap.get(task.key) - mInitialScrollP;
- if (progress < 0) {
- break;
+ // Skip invisible
+ float taskProgress = getStackScrollForTask(task);
+ if (!currentRange.isInRange(taskProgress)) {
+ continue;
}
+
boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task);
if (isFrontMostTaskInGroup) {
- float scaleAtP = sCurve.pToScale(progress);
- int scaleYOffsetAtP = (int) (((1f - scaleAtP) * taskHeight) / 2);
- int screenY = sCurve.pToX(progress, mCurrentStackRect) + scaleYOffsetAtP;
+ getStackTransform(taskProgress, mInitialScrollP, tmpTransform, null);
+ float screenY = tmpTransform.rect.top;
boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
if (hasVisibleThumbnail) {
numVisibleThumbnails++;
@@ -374,12 +495,12 @@
prevScreenY = screenY;
} else {
// Once we hit the next front most task that does not have a visible thumbnail,
- // w alk through remaining visible set
+ // walk through remaining visible set
for (int j = i; j >= 0; j--) {
numVisibleTasks++;
- progress = mTaskProgressMap.get(tasks.get(j).key) - mInitialScrollP;
- if (progress < 0) {
- break;
+ taskProgress = getStackScrollForTask(tasks.get(j));
+ if (!currentRange.isInRange(taskProgress)) {
+ continue;
}
}
break;
@@ -397,86 +518,82 @@
* is what the view is measured and laid out with.
*/
public TaskViewTransform getStackTransform(Task task, float stackScroll,
- TaskViewTransform transformOut, TaskViewTransform prevTransform) {
+ TaskViewTransform transformOut, TaskViewTransform frontTransform) {
if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
return transformOut;
} else {
// Return early if we have an invalid index
- if (task == null || !mTaskProgressMap.containsKey(task.key)) {
+ if (task == null || !mTaskIndexMap.containsKey(task.key)) {
transformOut.reset();
return transformOut;
}
- return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut,
- prevTransform);
+ return getStackTransform(mTaskIndexMap.get(task.key), stackScroll, transformOut,
+ frontTransform);
}
}
/** Update/get the transform */
public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
- TaskViewTransform transformOut, TaskViewTransform prevTransform) {
+ TaskViewTransform transformOut, TaskViewTransform frontTransform) {
SystemServicesProxy ssp = Recents.getSystemServices();
- if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
- // Center the task in the stack, changing the scale will not follow the curve, but just
- // modulate some values directly
- float pTaskRelative = mMinScrollP - stackScroll;
- float scale = ssp.hasFreeformWorkspaceSupport() ? 1f : SINGLE_TASK_SCALE;
- int topOffset = (mCurrentStackRect.top - mTaskRect.top) +
- (mCurrentStackRect.height() - mTaskRect.height()) / 2;
- transformOut.scale = scale;
- transformOut.translationX = (mStackRect.width() - mTaskRect.width()) / 2;
- transformOut.translationY = (int) (topOffset + (pTaskRelative * mCurrentStackRect.height()));
- transformOut.translationZ = mMaxTranslationZ;
- transformOut.rect.set(mTaskRect);
- transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
- Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
- transformOut.visible = true;
- transformOut.p = pTaskRelative;
- return transformOut;
+ // Compute the focused and unfocused offset
+ mUnfocusedRange.offset(stackScroll);
+ float p = mUnfocusedRange.getNormalizedX(taskProgress);
+ float yp = mUnfocusedCurveInterpolator.getInterpolation(p);
+ float unfocusedP = p;
+ int unFocusedY = (int) (Math.max(0f, (1f - yp)) * mCurrentStackRect.height());
+ boolean unfocusedVisible = mUnfocusedRange.isInRange(taskProgress);
+ int focusedY = 0;
+ boolean focusedVisible = true;
+ if (mFocusState > 0f) {
+ mFocusedRange.offset(stackScroll);
+ p = mFocusedRange.getNormalizedX(taskProgress);
+ yp = mFocusedCurveInterpolator.getInterpolation(p);
+ focusedY = (int) (Math.max(0f, (1f - yp)) * mCurrentStackRect.height());
+ focusedVisible = mFocusedRange.isInRange(taskProgress);
+ }
- } else {
- float pTaskRelative = taskProgress - stackScroll;
- float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
- if (DEBUG) {
- Log.d(TAG, "getStackTransform (normal): " + taskProgress + ", " + stackScroll);
- }
-
- // If the task top is outside of the bounds below the screen, then immediately reset it
- if (pTaskRelative > 1f) {
- transformOut.reset();
- transformOut.rect.set(mTaskRect);
- return transformOut;
- }
- // The check for the top is trickier, since we want to show the next task if it is at
- // all visible, even if p < 0.
- if (pTaskRelative < 0f) {
- if (prevTransform != null && Float.compare(prevTransform.p, 0f) <= 0) {
- transformOut.reset();
- transformOut.rect.set(mTaskRect);
- return transformOut;
- }
- }
- float scale = sCurve.pToScale(pBounded);
- int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
- transformOut.scale = scale;
- transformOut.translationX = (mStackRect.width() - mTaskRect.width()) / 2;
- transformOut.translationY = (mCurrentStackRect.top - mTaskRect.top) +
- (sCurve.pToX(pBounded, mCurrentStackRect) - mCurrentStackRect.top) -
- scaleYOffset;
- transformOut.translationZ = Math.max(mMinTranslationZ,
- mMinTranslationZ + (pBounded * (mMaxTranslationZ - mMinTranslationZ)));
- transformOut.rect.set(mTaskRect);
- transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
- Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
- transformOut.visible = true;
- transformOut.p = pTaskRelative;
- if (DEBUG) {
- Log.d(TAG, "\t" + transformOut);
- }
-
+ // Skip if the task is not visible
+ if (!unfocusedVisible && !focusedVisible) {
+ transformOut.reset();
return transformOut;
}
+
+ int y;
+ float z;
+ float relP;
+ if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
+ // When there is exactly one task, then decouple the task from the stack and just move
+ // in screen space
+ p = (mMinScrollP - stackScroll) / mNumStackTasks;
+ int centerYOffset = (mCurrentStackRect.top - mTaskRect.top) +
+ (mCurrentStackRect.height() - mTaskRect.height()) / 2;
+ y = (int) (centerYOffset + (p * mCurrentStackRect.height()));
+ z = mMaxTranslationZ;
+ relP = p;
+
+ } else {
+ // Otherwise, update the task to the stack layout
+ y = unFocusedY + (int) (mFocusState * (focusedY - unFocusedY));
+ y += (mCurrentStackRect.top - mTaskRect.top);
+ z = Math.max(mMinTranslationZ, Math.min(mMaxTranslationZ,
+ mMinTranslationZ + (p * (mMaxTranslationZ - mMinTranslationZ))));
+ relP = unfocusedP;
+ }
+
+ // Fill out the transform
+ transformOut.scale = 1f;
+ transformOut.translationX = (mCurrentStackRect.width() - mTaskRect.width()) / 2;
+ transformOut.translationY = y;
+ transformOut.translationZ = z;
+ transformOut.rect.set(mTaskRect);
+ transformOut.rect.offset(transformOut.translationX, transformOut.translationY);
+ Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
+ transformOut.visible = true;
+ transformOut.p = relP;
+ return transformOut;
}
/**
@@ -491,8 +608,8 @@
* stack.
*/
float getStackScrollForTask(Task t) {
- if (!mTaskProgressMap.containsKey(t.key)) return 0f;
- return mTaskProgressMap.get(t.key);
+ if (!mTaskIndexMap.containsKey(t.key)) return 0f;
+ return mTaskIndexMap.get(t.key);
}
/**
@@ -501,7 +618,8 @@
* screen along the arc-length proportionally (1/arclength).
*/
public float getDeltaPForY(int downY, int y) {
- float deltaP = (float) (y - downY) / mCurrentStackRect.height() * (1f / sCurve.getArcLength());
+ float deltaP = (float) (y - downY) / mCurrentStackRect.height() *
+ mUnfocusedCurveInterpolator.getArcLength();
return -deltaP;
}
@@ -510,18 +628,61 @@
* of the curve, map back to the screen y.
*/
public int getYForDeltaP(float downScrollP, float p) {
- int y = (int) ((p - downScrollP) * mCurrentStackRect.height() * sCurve.getArcLength());
+ int y = (int) ((p - downScrollP) * mCurrentStackRect.height() *
+ (1f / mUnfocusedCurveInterpolator.getArcLength()));
return -y;
}
- private float alignToStackTop(float p) {
- // At scroll progress == p, then p is at the top of the stack
+ /**
+ * Creates a new path for the focused curve.
+ */
+ private Path constructFocusedCurve() {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ int taskBarHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.recents_task_bar_height);
+
+ // Initialize the focused curve. This curve is a piecewise curve composed of several
+ // quadradic beziers that goes from (0,1) through (0.5, peek height offset),
+ // (0.667, next task offset), (0.833, bottom task offset), and (1,0).
+ float peekHeightPct = 0f;
+ if (!ssp.hasFreeformWorkspaceSupport()) {
+ peekHeightPct = (float) mFocusedPeekHeight / mCurrentStackRect.height();
+ }
+ Path p = new Path();
+ p.moveTo(0f, 1f);
+ p.lineTo(0.5f, 1f - peekHeightPct);
+ p.lineTo(0.66666667f, (float) (taskBarHeight * 3) / mCurrentStackRect.height());
+ p.lineTo(0.83333333f, (float) (taskBarHeight / 2) / mCurrentStackRect.height());
+ p.lineTo(1f, 0f);
return p;
}
- private float alignToStackBottom(float p, float pOffsetFromBottom) {
- // At scroll progress == p, then p is at the top of the stack
- // At scroll progress == p + 1, then p is at the bottom of the stack
- return p - (1 - pOffsetFromBottom);
+ /**
+ * Creates a new path for the unfocused curve.
+ */
+ private Path constructUnfocusedCurve() {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+
+ // Initialize the unfocused curve. This curve is a piecewise curve composed of two quadradic
+ // beziers that goes from (0,1) through (0.5, peek height offset) and ends at (1,0). This
+ // ensures that we match the range, at which 0.5 represents the stack scroll at the current
+ // task progress. Because the height offset can change depending on a resource, we compute
+ // the control point of the second bezier such that between it and a first known point,
+ // there is a tangent at (0.5, peek height offset).
+ float cpoint1X = 0.4f;
+ float cpoint1Y = 1f;
+ float peekHeightPct = 0f;
+ if (!ssp.hasFreeformWorkspaceSupport()) {
+ peekHeightPct = (float) mFocusedPeekHeight / mCurrentStackRect.height();
+ }
+ float slope = ((1f - peekHeightPct) - cpoint1Y) / (0.5f - cpoint1X);
+ float b = 1f - slope * cpoint1X;
+ float cpoint2X = 0.75f;
+ float cpoint2Y = slope * cpoint2X + b;
+ Path p = new Path();
+ p.moveTo(0f, 1f);
+ p.cubicTo(0f, 1f, cpoint1X, 1f, 0.5f, 1f - peekHeightPct);
+ p.cubicTo(0.5f, 1f - peekHeightPct, cpoint2X, cpoint2Y, 1f, 0f);
+ return p;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 7250d6a..67710bf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -38,10 +38,17 @@
import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
+import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
+import com.android.systemui.recents.events.activity.IterateRecentsEvent;
import com.android.systemui.recents.events.activity.PackagesChangedEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
+import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
+import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
+import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
import com.android.systemui.recents.events.ui.UserInteractionEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
@@ -60,7 +67,6 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
@@ -80,14 +86,10 @@
interface TaskStackViewCallbacks {
public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
boolean lockToTask, Rect bounds, int destinationStack);
- public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks);
- public void onTaskStackFilterTriggered();
- public void onTaskStackUnfilterTriggered();
}
TaskStack mStack;
TaskStackLayoutAlgorithm mLayoutAlgorithm;
- TaskStackViewFilterAlgorithm mFilterAlgorithm;
TaskStackViewScroller mStackScroller;
TaskStackViewTouchHandler mTouchHandler;
TaskStackViewCallbacks mCb;
@@ -101,6 +103,7 @@
boolean mStackViewsDirty = true;
boolean mStackViewsClipDirty = true;
boolean mAwaitingFirstLayout = true;
+ boolean mEnterAnimationComplete = false;
boolean mStartEnterAnimationRequestedAfterLayout;
ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
@@ -151,8 +154,7 @@
setStack(stack);
mViewPool = new ViewPool<>(context, this);
mInflater = LayoutInflater.from(context);
- mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context);
- mFilterAlgorithm = new TaskStackViewFilterAlgorithm(this, mViewPool);
+ mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, this);
mStackScroller = new TaskStackViewScroller(context, mLayoutAlgorithm);
mStackScroller.setCallbacks(this);
mTouchHandler = new TaskStackViewTouchHandler(context, this, mStackScroller);
@@ -290,11 +292,14 @@
mStackViewsDirty = true;
mStackViewsClipDirty = true;
mAwaitingFirstLayout = true;
+ mEnterAnimationComplete = false;
if (mUIDozeTrigger != null) {
mUIDozeTrigger.stopDozing();
mUIDozeTrigger.resetTrigger();
}
mStackScroller.reset();
+ mLayoutAlgorithm.reset();
+ requestLayout();
}
/** Requests that the views be synchronized with the model */
@@ -353,7 +358,7 @@
}
// Update the stack transforms
- TaskViewTransform prevTransform = null;
+ TaskViewTransform frontTransform = null;
for (int i = taskCount - 1; i >= 0; i--) {
Task task = tasks.get(i);
if (task.isFreeformTask()) {
@@ -361,7 +366,7 @@
}
TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll,
- taskTransforms.get(i), prevTransform);
+ taskTransforms.get(i), frontTransform);
if (DEBUG) {
Log.d(TAG, "updateStackTransform: " + i + ", " + transform.visible);
}
@@ -386,7 +391,7 @@
transform.translationY = Math.min(transform.translationY,
mLayoutAlgorithm.mCurrentStackRect.bottom);
}
- prevTransform = transform;
+ frontTransform = transform;
}
if (visibleRangeOut != null) {
visibleRangeOut[0] = frontMostVisibleIndex;
@@ -605,15 +610,19 @@
/**
* Sets the focused task to the provided (bounded taskIndex).
+ *
+ * @return whether or not the stack will scroll as a part of this focus change
*/
- private void setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated) {
- setFocusedTask(taskIndex, scrollToTask, animated, true);
+ private boolean setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated) {
+ return setFocusedTask(taskIndex, scrollToTask, animated, true);
}
/**
* Sets the focused task to the provided (bounded taskIndex).
+ *
+ * @return whether or not the stack will scroll as a part of this focus change
*/
- private void setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated,
+ private boolean setFocusedTask(int taskIndex, boolean scrollToTask, final boolean animated,
final boolean requestViewFocus) {
// Find the next task to focus
int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
@@ -629,6 +638,7 @@
}
}
+ boolean willScroll = false;
mFocusedTaskIndex = newFocusedTaskIndex;
if (mFocusedTaskIndex != -1) {
Runnable focusTaskRunnable = new Runnable() {
@@ -643,19 +653,39 @@
if (scrollToTask) {
// TODO: Center the newly focused task view, only if not freeform
- float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask) - 0.5f;
+ RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+ float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask);
+ if (!debugFlags.isFullscreenThumbnailsEnabled()) {
+ newScroll -= 0.5f;
+ }
newScroll = mStackScroller.getBoundedStackScroll(newScroll);
- mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll,
- focusTaskRunnable);
+ if (Float.compare(newScroll, mStackScroller.getStackScroll()) != 0) {
+ mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll,
+ focusTaskRunnable);
+ willScroll = true;
+
+ // Cancel any running enter animations at this point when we scroll as well
+ if (!mEnterAnimationComplete) {
+ final List<TaskView> taskViews = getTaskViews();
+ for (TaskView tv : taskViews) {
+ tv.cancelEnterRecentsAnimation();
+ }
+ }
+ } else {
+ focusTaskRunnable.run();
+ }
+ mLayoutAlgorithm.animateFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED);
} else {
focusTaskRunnable.run();
}
}
+ return willScroll;
}
/**
* Sets the focused task relative to the currently focused task.
*
+ * @param forward whether to go to the next task in the stack (along the curve) or the previous
* @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and
* if the currently focused task is not a stack task, will set the focus
* to the first visible stack task
@@ -663,6 +693,23 @@
* focus.
*/
public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated) {
+ setRelativeFocusedTask(forward, stackTasksOnly, animated, false);
+ }
+
+ /**
+ * Sets the focused task relative to the currently focused task.
+ *
+ * @param forward whether to go to the next task in the stack (along the curve) or the previous
+ * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and
+ * if the currently focused task is not a stack task, will set the focus
+ * to the first visible stack task
+ * @param animated determines whether to actually draw the highlight along with the change in
+ * focus.
+ * @param cancelWindowAnimations if set, will attempt to cancel window animations if a scroll
+ * happens
+ */
+ public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated,
+ boolean cancelWindowAnimations) {
int newIndex = -1;
if (mFocusedTaskIndex != -1) {
if (stackTasksOnly) {
@@ -686,8 +733,10 @@
}
}
} else {
- // No restrictions, lets just move to the new task
- newIndex = mFocusedTaskIndex + (forward ? -1 : 1);
+ // No restrictions, lets just move to the new task (looping forward/backwards if
+ // necessary)
+ int taskCount = mStack.getTaskCount();
+ newIndex = (mFocusedTaskIndex + (forward ? -1 : 1) + taskCount) % taskCount;
}
} else {
// We don't have a focused task, so focus the first visible task view
@@ -697,7 +746,12 @@
}
}
if (newIndex != -1) {
- setFocusedTask(newIndex, true, animated);
+ boolean willScroll = setFocusedTask(newIndex, true, animated);
+ if (willScroll && cancelWindowAnimations) {
+ // As we iterate to the next/previous task, cancel any current/lagging window
+ // transition animations
+ EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
+ }
}
}
@@ -933,9 +987,15 @@
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
- boolean occludesLaunchTarget = (launchTargetTask != null) &&
- launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
- tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget,
+ boolean hideTask = false;
+ boolean occludesLaunchTarget = false;
+ if (launchTargetTask != null) {
+ occludesLaunchTarget = launchTargetTask.group.isTaskAboveTask(task,
+ launchTargetTask);
+ hideTask = SystemServicesProxy.isFreeformStack(launchTargetTask.key.stackId) &&
+ SystemServicesProxy.isFreeformStack(task.key.stackId);
+ }
+ tv.prepareEnterRecentsAnimation(task.isLaunchTarget, hideTask, occludesLaunchTarget,
offscreenY);
}
@@ -1030,20 +1090,6 @@
}
}
- /** Requests this task stack to start it's dismiss-all animation. */
- public void startDismissAllAnimation(final Runnable postAnimationRunnable) {
- // Clear the focused task
- resetFocusedTask();
- List<TaskView> taskViews = getTaskViews();
- int taskViewCount = taskViews.size();
- int count = 0;
- for (int i = taskViewCount - 1; i >= 0; i--) {
- TaskView tv = taskViews.get(i);
- tv.startDeleteTaskAnimation(i > 0 ? null : postAnimationRunnable, count * 50);
- count++;
- }
- }
-
/** Animates a task view in this stack as it launches. */
public void startLaunchTaskAnimation(TaskView tv, Runnable r, boolean lockToTask) {
Task launchTargetTask = tv.getTask();
@@ -1071,8 +1117,11 @@
mLayersDisabled = false;
// Draw the freeform workspace background
- if (mFreeformWorkspaceBackground.getAlpha() > 0) {
- mFreeformWorkspaceBackground.draw(canvas);
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (ssp.hasFreeformWorkspaceSupport()) {
+ if (mFreeformWorkspaceBackground.getAlpha() > 0) {
+ mFreeformWorkspaceBackground.draw(canvas);
+ }
}
super.dispatchDraw(canvas);
@@ -1116,8 +1165,8 @@
mViewPool.returnViewToPool(tv);
}
- // Get the stack scroll of the task to anchor to (since we are removing something, the front
- // most task will be our anchor task)
+ // Get the stack scroll of the task to anchor to (since we are removing something, the
+ // front most task will be our anchor task)
Task anchorTask = null;
float prevAnchorTaskScroll = 0;
boolean pullStackForward = stack.getTaskCount() > 0;
@@ -1134,11 +1183,16 @@
// to ensure that the new front most task is now fully visible
mStackScroller.setStackScroll(mLayoutAlgorithm.mMaxScrollP);
} else if (pullStackForward) {
- // Otherwise, offset the scroll by half the movement of the anchor task to allow the
- // tasks behind the removed task to move forward, and the tasks in front to move back
+ // Otherwise, offset the scroll by the movement of the anchor task
float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
- mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
- - prevAnchorTaskScroll) / 2);
+ float newStackScroll = mStackScroller.getStackScroll() +
+ (anchorTaskScroll - prevAnchorTaskScroll);
+ if (mLayoutAlgorithm.getFocusState() != TaskStackLayoutAlgorithm.STATE_FOCUSED) {
+ // If we are focused, we don't want the front task to move, but otherwise, we
+ // allow the back task to move up, and the front task to move back
+ newStackScroll /= 2;
+ }
+ mStackScroller.setStackScroll(newStackScroll);
mStackScroller.boundScroll();
}
@@ -1169,99 +1223,15 @@
}
}
- // If there are no remaining tasks, then either unfilter the current stack, or just close
- // the activity if there are no filtered stacks
+ // If there are no remaining tasks, then just close recents
if (mStack.getTaskCount() == 0) {
- boolean shouldFinishActivity = true;
- if (mStack.hasFilteredTasks()) {
- mStack.unfilterTasks();
- shouldFinishActivity = (mStack.getTaskCount() == 0);
- }
+ boolean shouldFinishActivity = (mStack.getTaskCount() == 0);
if (shouldFinishActivity) {
- mCb.onAllTaskViewsDismissed(null);
+ EventBus.getDefault().send(new AllTaskViewsDismissedEvent());
}
}
}
- @Override
- public void onStackAllTasksRemoved(TaskStack stack, final ArrayList<Task> removedTasks) {
- // Announce for accessibility
- String msg = getContext().getString(R.string.accessibility_recents_all_items_dismissed);
- announceForAccessibility(msg);
-
- startDismissAllAnimation(new Runnable() {
- @Override
- public void run() {
- // Notify that all tasks have been removed
- mCb.onAllTaskViewsDismissed(removedTasks);
- }
- });
- }
-
- @Override
- public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
- Task filteredTask) {
- /*
- // Stash the scroll and filtered task for us to restore to when we unfilter
- mStashedScroll = getStackScroll();
-
- // Calculate the current task transforms
- ArrayList<TaskViewTransform> curTaskTransforms =
- getStackTransforms(curTasks, getStackScroll(), null, true);
-
- // Update the task offsets
- mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks());
-
- // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
- updateLayout(false);
- float overlapHeight = mLayoutAlgorithm.getTaskOverlapHeight();
- setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
- boundScrollRaw();
-
- // Compute the transforms of the items in the new stack after setting the new scroll
- final ArrayList<Task> tasks = mStack.getTasks();
- final ArrayList<TaskViewTransform> taskTransforms =
- getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);
-
- // Animate
- mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
-
- // Notify any callbacks
- mCb.onTaskStackFilterTriggered();
- */
- }
-
- @Override
- public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) {
- /*
- // Calculate the current task transforms
- final ArrayList<TaskViewTransform> curTaskTransforms =
- getStackTransforms(curTasks, getStackScroll(), null, true);
-
- // Update the task offsets
- mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks());
-
- // Restore the stashed scroll
- updateLayout(false);
- setStackScrollRaw(mStashedScroll);
- boundScrollRaw();
-
- // Compute the transforms of the items in the new stack after restoring the stashed scroll
- final ArrayList<Task> tasks = mStack.getTasks();
- final ArrayList<TaskViewTransform> taskTransforms =
- getStackTransforms(tasks, getStackScroll(), null, true);
-
- // Animate
- mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
-
- // Clear the saved vars
- mStashedScroll = 0;
-
- // Notify any callbacks
- mCb.onTaskStackUnfilterTriggered();
- */
- }
-
/**** ViewPoolConsumer Implementation ****/
@Override
@@ -1502,6 +1472,30 @@
requestSynchronizeStackViewsWithModel(175);
}
+ public final void onBusEvent(StackViewScrolledEvent event) {
+ mLayoutAlgorithm.updateFocusStateOnScroll(event.yMovement);
+ }
+
+ public final void onBusEvent(IterateRecentsEvent event) {
+ mLayoutAlgorithm.animateFocusState(mLayoutAlgorithm.getDefaultFocusState());
+ }
+
+ public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
+ mEnterAnimationComplete = true;
+ }
+
+ public final void onBusEvent(UpdateFreeformTaskViewVisibilityEvent event) {
+ List<TaskView> taskViews = getTaskViews();
+ int taskViewCount = taskViews.size();
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
+ Task task = tv.getTask();
+ if (SystemServicesProxy.isFreeformStack(task.key.stackId)) {
+ tv.setVisibility(event.visible ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+ }
+
/**
* Removes the task from the stack, and updates the focus to the next task in the stack if the
* removed TaskView was focused.
@@ -1521,16 +1515,5 @@
// Remove the task from the stack
mStack.removeTask(task);
-
- if (taskWasFocused || ssp.isTouchExplorationEnabled()) {
- // If the dismissed task was focused or if we are in touch exploration mode, then focus
- // the next task
- RecentsConfiguration config = Recents.getConfiguration();
- RecentsActivityLaunchState launchState = config.getLaunchState();
- boolean isFreeformTask = taskIndex > 0 ?
- mStack.getTasks().get(taskIndex - 1).isFreeformTask() : false;
- setFocusedTask(taskIndex - 1, !isFreeformTask /* scrollToTask */,
- launchState.launchedWithAltTab);
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
deleted file mode 100644
index 45f573d..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewFilterAlgorithm.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2014 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.systemui.recents.views;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.model.Task;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/* The layout logic for a TaskStackView */
-public class TaskStackViewFilterAlgorithm {
-
- TaskStackView mStackView;
- ViewPool<TaskView, Task> mViewPool;
-
- public TaskStackViewFilterAlgorithm(TaskStackView stackView, ViewPool<TaskView, Task> viewPool) {
- mStackView = stackView;
- mViewPool = viewPool;
- }
-
- /** Orchestrates the animations of the current child views and any new views. */
- void startFilteringAnimation(ArrayList<Task> curTasks,
- ArrayList<TaskViewTransform> curTaskTransforms,
- final ArrayList<Task> tasks,
- final ArrayList<TaskViewTransform> taskTransforms) {
- // Calculate the transforms to animate out all the existing views if they are not in the
- // new visible range (or to their final positions in the stack if they are)
- final ArrayList<TaskView> childrenToRemove = new ArrayList<TaskView>();
- final HashMap<TaskView, TaskViewTransform> childViewTransforms =
- new HashMap<TaskView, TaskViewTransform>();
- int duration = getExitTransformsForFilterAnimation(curTasks, curTaskTransforms, tasks,
- taskTransforms, childViewTransforms, childrenToRemove);
-
- // If all the current views are in the visible range of the new stack, then don't wait for
- // views to animate out and animate all the new views into their place
- final boolean unifyNewViewAnimation = childrenToRemove.isEmpty();
- if (unifyNewViewAnimation) {
- int inDuration = getEnterTransformsForFilterAnimation(tasks, taskTransforms,
- childViewTransforms);
- duration = Math.max(duration, inDuration);
- }
-
- // Animate all the views to their final transforms
- for (final TaskView tv : childViewTransforms.keySet()) {
- TaskViewTransform t = childViewTransforms.get(tv);
- tv.animate().cancel();
- tv.animate()
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- childViewTransforms.remove(tv);
- if (childViewTransforms.isEmpty()) {
- // Return all the removed children to the view pool
- for (TaskView tv : childrenToRemove) {
- mViewPool.returnViewToPool(tv);
- }
-
- if (!unifyNewViewAnimation) {
- // For views that are not already visible, animate them in
- childViewTransforms.clear();
- int duration = getEnterTransformsForFilterAnimation(tasks,
- taskTransforms, childViewTransforms);
- for (final TaskView tv : childViewTransforms.keySet()) {
- TaskViewTransform t = childViewTransforms.get(tv);
- tv.updateViewPropertiesToTaskTransform(t, duration);
- }
- }
- }
- }
- });
- tv.updateViewPropertiesToTaskTransform(t, duration);
- }
- }
-
- /**
- * Creates the animations for all the children views that need to be animated in when we are
- * un/filtering a stack, and returns the duration for these animations.
- */
- int getEnterTransformsForFilterAnimation(ArrayList<Task> tasks,
- ArrayList<TaskViewTransform> taskTransforms,
- HashMap<TaskView, TaskViewTransform> childViewTransformsOut) {
- int offset = 0;
- int movement = 0;
- int taskCount = tasks.size();
- for (int i = taskCount - 1; i >= 0; i--) {
- Task task = tasks.get(i);
- TaskViewTransform toTransform = taskTransforms.get(i);
- if (toTransform.visible) {
- TaskView tv = mStackView.getChildViewForTask(task);
- if (tv == null) {
- // For views that are not already visible, animate them in
- tv = mViewPool.pickUpViewFromPool(task, task);
-
- // Compose a new transform to fade and slide the new task in
- TaskViewTransform fromTransform = new TaskViewTransform(toTransform);
- tv.prepareTaskTransformForFilterTaskHidden(fromTransform);
- tv.updateViewPropertiesToTaskTransform(fromTransform, 0);
-
- toTransform.startDelay = offset * 25;
- childViewTransformsOut.put(tv, toTransform);
-
- // Use the movement of the new views to calculate the duration of the animation
- movement = Math.max(movement,
- Math.abs(toTransform.translationY - fromTransform.translationY));
- offset++;
- }
- }
- }
- return mStackView.getResources().getInteger(
- R.integer.recents_filter_animate_new_views_duration);
- }
-
- /**
- * Creates the animations for all the children views that need to be removed or to move views
- * to their un/filtered position when we are un/filtering a stack, and returns the duration
- * for these animations.
- */
- int getExitTransformsForFilterAnimation(ArrayList<Task> curTasks,
- ArrayList<TaskViewTransform> curTaskTransforms,
- ArrayList<Task> tasks, ArrayList<TaskViewTransform> taskTransforms,
- HashMap<TaskView, TaskViewTransform> childViewTransformsOut,
- ArrayList<TaskView> childrenToRemoveOut) {
- // Animate all of the existing views out of view (if they are not in the visible range in
- // the new stack) or to their final positions in the new stack
- int offset = 0;
- int movement = 0;
- List<TaskView> taskViews = mStackView.getTaskViews();
- int taskViewCount = taskViews.size();
- for (int i = 0; i < taskViewCount; i++) {
- TaskView tv = taskViews.get(i);
- Task task = tv.getTask();
- int taskIndex = tasks.indexOf(task);
- TaskViewTransform toTransform;
-
- // If the view is no longer visible, then we should just animate it out
- boolean willBeInvisible = taskIndex < 0 || !taskTransforms.get(taskIndex).visible;
- if (willBeInvisible) {
- if (taskIndex < 0) {
- toTransform = curTaskTransforms.get(curTasks.indexOf(task));
- } else {
- toTransform = new TaskViewTransform(taskTransforms.get(taskIndex));
- }
- tv.prepareTaskTransformForFilterTaskVisible(toTransform);
- childrenToRemoveOut.add(tv);
- } else {
- toTransform = taskTransforms.get(taskIndex);
- // Use the movement of the visible views to calculate the duration of the animation
- movement = Math.max(movement, Math.abs(toTransform.translationY -
- (int) tv.getTranslationY()));
- }
-
- toTransform.startDelay = offset * 25;
- childViewTransformsOut.put(tv, toTransform);
- offset++;
- }
- return mStackView.getResources().getInteger(
- R.integer.recents_filter_animate_current_views_duration);
- }
-
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 3a2ed0f..62b640e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -166,12 +166,6 @@
mScrollAnimator.setDuration(mContext.getResources().getInteger(
R.integer.recents_animate_task_stack_scroll_duration));
mScrollAnimator.setInterpolator(mLinearOutSlowInInterpolator);
- mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- setStackScroll((Float) animation.getAnimatedValue());
- }
- });
mScrollAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 81c89a1..907ed2f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -31,10 +31,10 @@
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
+import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -59,6 +59,7 @@
boolean mIsScrolling;
float mDownScrollP;
int mDownX, mDownY;
+ int mLastY;
int mActivePointerId = INACTIVE_POINTER_ID;
int mOverscrollSize;
TaskView mActiveTaskView = null;
@@ -150,11 +151,6 @@
if (mSv.getTaskViews().size() == 0) {
return false;
}
- // Short circuit while we are alt-tabbing
- RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
- if (launchState.launchedWithAltTab) {
- return false;
- }
final TaskStackLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
int action = ev.getAction();
@@ -163,6 +159,7 @@
// Save the touch down info
mDownX = (int) ev.getX();
mDownY = (int) ev.getY();
+ mLastY = mDownY;
mDownScrollP = mScroller.getStackScroll();
mActivePointerId = ev.getPointerId(0);
mActiveTaskView = findViewAtPoint(mDownX, mDownY);
@@ -181,6 +178,7 @@
final int index = ev.getActionIndex();
mDownX = (int) ev.getX();
mDownY = (int) ev.getY();
+ mLastY = mDownY;
mDownScrollP = mScroller.getStackScroll();
mActivePointerId = ev.getPointerId(index);
mVelocityTracker.addMovement(ev);
@@ -209,8 +207,10 @@
if (DEBUG) {
Log.d(TAG, "scroll: " + curScrollP);
}
+ EventBus.getDefault().send(new StackViewScrolledEvent(y - mLastY));
}
+ mLastY = y;
mVelocityTracker.addMovement(ev);
break;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 2c8f316..523f84f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents.views;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -29,7 +30,6 @@
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
-import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
@@ -40,12 +40,10 @@
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import com.android.systemui.R;
-import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
@@ -155,7 +153,7 @@
}
/** Gets the task */
- Task getTask() {
+ public Task getTask() {
return mTask;
}
@@ -200,7 +198,7 @@
// Measure the content
mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
+ MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY));
// Measure the bar view, and action button
mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
@@ -211,7 +209,7 @@
// Measure the thumbnail to be square
mThumbnailView.measure(
MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY));
+ MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY));
mThumbnailView.updateClipToTaskBar(mHeaderView);
setMeasuredDimension(width, height);
invalidateOutline();
@@ -253,6 +251,7 @@
mActionButtonView.setAlpha(1f);
mActionButtonView.setTranslationZ(mActionButtonTranslationZ);
}
+ setVisibility(View.VISIBLE);
}
/**
@@ -277,12 +276,14 @@
/** Prepares this task view for the enter-recents animations. This is called earlier in the
* first layout because the actual animation into recents may take a long time. */
- void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask,
- boolean occludesLaunchTarget, int offscreenY) {
+ void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, boolean hideTask,
+ boolean occludesLaunchTarget, int offscreenY) {
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
int initialDim = getDim();
- if (launchState.launchedHasConfigurationChanged) {
+ if (hideTask) {
+ setVisibility(View.INVISIBLE);
+ } else if (launchState.launchedHasConfigurationChanged) {
// Just load the views as-is
} else if (launchState.launchedFromAppWithThumbnail) {
if (isTaskViewLaunchTargetTask) {
@@ -340,15 +341,21 @@
animate().alpha(1f)
.translationY(transform.translationY)
.setUpdateListener(null)
- .setInterpolator(mFastOutSlowInInterpolator)
- .setDuration(taskViewEnterFromHomeDuration)
- .withEndAction(new Runnable() {
+ .setListener(new AnimatorListenerAdapter() {
+ private boolean hasEnded;
+
+ // We use the animation listener instead of withEndAction() to
+ // ensure that onAnimationEnd() is called when the animator is
+ // cancelled
@Override
- public void run() {
- // Decrement the post animation trigger
+ public void onAnimationEnd(Animator animation) {
+ if (hasEnded) return;
ctx.postAnimationTrigger.decrement();
+ hasEnded = true;
}
})
+ .setInterpolator(mFastOutSlowInInterpolator)
+ .setDuration(taskViewEnterFromHomeDuration)
.start();
ctx.postAnimationTrigger.increment();
}
@@ -368,21 +375,30 @@
.translationY(transform.translationY)
.setStartDelay(delay)
.setUpdateListener(ctx.updateListener)
+ .setListener(new AnimatorListenerAdapter() {
+ private boolean hasEnded;
+
+ // We use the animation listener instead of withEndAction() to ensure that
+ // onAnimationEnd() is called when the animator is cancelled
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (hasEnded) return;
+ ctx.postAnimationTrigger.decrement();
+ hasEnded = true;
+ }
+ })
.setInterpolator(mQuintOutInterpolator)
.setDuration(taskViewEnterFromHomeDuration +
frontIndex * taskViewEnterFromHomeStaggerDelay)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- // Decrement the post animation trigger
- ctx.postAnimationTrigger.decrement();
- }
- })
.start();
ctx.postAnimationTrigger.increment();
}
}
+ public void cancelEnterRecentsAnimation() {
+ animate().cancel();
+ }
+
public void fadeInActionButton(int duration) {
// Hide the action button
mActionButtonView.setAlpha(0f);
@@ -409,11 +425,6 @@
ctx.postAnimationTrigger.increment();
}
- /** Animates this task view away when dismissing all tasks. */
- void startDismissAllAnimation() {
- dismissTask();
- }
-
/** Animates this task view as it exits recents */
void startLaunchTaskAnimation(final Runnable postAnimRunnable, boolean isLaunchingTask,
boolean occludesLaunchTarget, boolean lockToTask) {
@@ -619,23 +630,6 @@
setDim(getDimFromTaskProgress());
}
-
- @Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
-
- RecentsDebugFlags flags = Recents.getDebugFlags();
- if (flags.isFastToggleRecentsEnabled() && mIsFocused) {
- Paint tmpPaint = new Paint();
- Rect tmpRect = new Rect();
- tmpRect.set(0, 0, getWidth(), getHeight());
- tmpPaint.setColor(0xFFFF0000);
- tmpPaint.setStrokeWidth(35);
- tmpPaint.setStyle(Paint.Style.STROKE);
- canvas.drawRect(tmpRect, tmpPaint);
- }
- }
-
/**** View focus state ****/
/**
@@ -666,10 +660,6 @@
clearAccessibilityFocus();
}
}
- RecentsDebugFlags flags = Recents.getDebugFlags();
- if (flags.isFastToggleRecentsEnabled()) {
- invalidate();
- }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 649199e..76c6691 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -16,12 +16,7 @@
package com.android.systemui.recents.views;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
@@ -69,16 +64,13 @@
TextView mActivityDescription;
// Header drawables
- boolean mCurrentPrimaryColorIsDark;
- int mCurrentPrimaryColor;
- int mBackgroundColor;
int mCornerRadius;
int mHighlightHeight;
Drawable mLightDismissDrawable;
Drawable mDarkDismissDrawable;
RippleDrawable mBackground;
GradientDrawable mBackgroundColorDrawable;
- AnimatorSet mFocusAnimator;
+ ObjectAnimator mFocusAnimator;
String mDismissContentDescription;
// Static highlight that we draw at the top of each view
@@ -223,15 +215,12 @@
((ColorDrawable) getBackground()).getColor() : 0;
if (existingBgColor != t.colorPrimary) {
mBackgroundColorDrawable.setColor(t.colorPrimary);
- mBackgroundColor = t.colorPrimary;
}
int taskBarViewLightTextColor = getResources().getColor(
R.color.recents_task_bar_light_text_color);
int taskBarViewDarkTextColor = getResources().getColor(
R.color.recents_task_bar_dark_text_color);
- mCurrentPrimaryColor = t.colorPrimary;
- mCurrentPrimaryColorIsDark = t.useLightOnPrimaryColor;
mActivityDescription.setTextColor(t.useLightOnPrimaryColor ?
taskBarViewLightTextColor : taskBarViewDarkTextColor);
mDismissButton.setImageDrawable(t.useLightOnPrimaryColor ?
@@ -258,7 +247,6 @@
// Stop any focus animations
Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
- mBackground.jumpToCurrentState();
}
/** Updates the resize task bar button. */
@@ -376,84 +364,22 @@
isRunning = mFocusAnimator.isRunning();
}
Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
- mBackground.jumpToCurrentState();
if (focused) {
// If we are not animating the visible state, just return
if (!animateFocusedState) return;
- int currentColor = mBackgroundColor;
- int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
- int[][] states = new int[][] {
- new int[] {},
- new int[] { android.R.attr.state_enabled },
- new int[] { android.R.attr.state_pressed }
- };
- int[] newStates = new int[]{
- 0,
- android.R.attr.state_enabled,
- android.R.attr.state_pressed
- };
- int[] colors = new int[] {
- currentColor,
- secondaryColor,
- secondaryColor
- };
- mBackground.setColor(new ColorStateList(states, colors));
- mBackground.setState(newStates);
- // Pulse the background color
- int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
- ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
- currentColor, lightPrimaryColor);
- backgroundColor.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- mBackground.setState(new int[]{});
- }
- });
- backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int color = (int) animation.getAnimatedValue();
- mBackgroundColorDrawable.setColor(color);
- mBackgroundColor = color;
- }
- });
- backgroundColor.setRepeatCount(ValueAnimator.INFINITE);
- backgroundColor.setRepeatMode(ValueAnimator.REVERSE);
- // Pulse the translation
- ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 15f);
- translation.setRepeatCount(ValueAnimator.INFINITE);
- translation.setRepeatMode(ValueAnimator.REVERSE);
-
- mFocusAnimator = new AnimatorSet();
- mFocusAnimator.playTogether(backgroundColor, translation);
- mFocusAnimator.setStartDelay(150);
- mFocusAnimator.setDuration(750);
+ // Bump up the translation
+ mFocusAnimator = ObjectAnimator.ofFloat(this, "translationZ", 8f);
+ mFocusAnimator.setDuration(200);
mFocusAnimator.start();
} else {
if (isRunning) {
- // Restore the background color
- int currentColor = mBackgroundColor;
- ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
- currentColor, mCurrentPrimaryColor);
- backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int color = (int) animation.getAnimatedValue();
- mBackgroundColorDrawable.setColor(color);
- mBackgroundColor = color;
- }
- });
// Restore the translation
- ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 0f);
-
- mFocusAnimator = new AnimatorSet();
- mFocusAnimator.playTogether(backgroundColor, translation);
+ mFocusAnimator = ObjectAnimator.ofFloat(this, "translationZ", 0f);
mFocusAnimator.setDuration(150);
mFocusAnimator.start();
} else {
- mBackground.setState(new int[] {});
setTranslationZ(0f);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 67bca3e..723989a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -2092,7 +2092,7 @@
Notification n = notification.getNotification();
mNotificationData.updateRanking(ranking);
- boolean applyInPlace = !entry.cacheContentViews(mContext, notification.getNotification());
+ boolean applyInPlace = entry.cacheContentViews(mContext, notification.getNotification());
boolean shouldInterrupt = shouldInterrupt(entry, notification);
boolean alertAgain = alertAgain(entry, n);
if (DEBUG) {
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index c5ce2fd..f6af942 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -2251,7 +2251,7 @@
public boolean matches(PendingIntent pi, IAlarmListener rec) {
return (operation != null)
? operation.equals(pi)
- : listener.asBinder().equals(rec.asBinder());
+ : rec != null && listener.asBinder().equals(rec.asBinder());
}
public boolean matches(String packageName) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ac89c85..94a908c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1274,6 +1274,7 @@
boolean mAlwaysFinishActivities = false;
boolean mForceResizableActivities;
boolean mSupportsFreeformWindowManagement;
+ boolean mTakeFullscreenScreenshots;
IActivityController mController = null;
String mProfileApp = null;
ProcessRecord mProfileProc = null;
@@ -11881,9 +11882,13 @@
mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT);
final String debugApp = Settings.Global.getString(resolver, DEBUG_APP);
+ final String fsScreenshots = Settings.Secure.getString(resolver,
+ "overview_fullscreen_thumbnails");
final boolean waitForDebugger = Settings.Global.getInt(resolver, WAIT_FOR_DEBUGGER, 0) != 0;
final boolean alwaysFinishActivities =
Settings.Global.getInt(resolver, ALWAYS_FINISH_ACTIVITIES, 0) != 0;
+ final boolean takeFullscreenScreenshots = fsScreenshots != null &&
+ Integer.parseInt(fsScreenshots) != 0;
final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0;
final int defaultForceResizable = Build.IS_DEBUGGABLE ? 1 : 0;
final boolean forceResizable = Settings.Global.getInt(
@@ -11904,6 +11909,7 @@
mAlwaysFinishActivities = alwaysFinishActivities;
mForceResizableActivities = forceResizable;
mSupportsFreeformWindowManagement = freeformWindowManagement || forceResizable;
+ mTakeFullscreenScreenshots = takeFullscreenScreenshots;
// This happens before any activities are started, so we can
// change mConfiguration in-place.
updateConfigurationLocked(configuration, null, true);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index e28d198..6c68f88 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -54,6 +54,7 @@
import android.app.ResultInfo;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
@@ -72,6 +73,7 @@
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
+import android.provider.Settings;
import android.service.voice.IVoiceInteractionSession;
import android.util.EventLog;
import android.util.Slog;
@@ -836,8 +838,8 @@
}
}
- public final Bitmap screenshotActivities(ActivityRecord who) {
- if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "screenshotActivities: " + who);
+ public final Bitmap screenshotActivitiesLocked(ActivityRecord who) {
+ if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "screenshotActivitiesLocked: " + who);
if (who.noDisplay) {
if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "\tNo display");
return null;
@@ -852,10 +854,21 @@
int w = mService.mThumbnailWidth;
int h = mService.mThumbnailHeight;
+ float scale = 1f;
if (w > 0) {
if (DEBUG_SCREENSHOTS) Slog.d(TAG_SCREENSHOTS, "\tTaking screenshot");
+
+ // When this flag is set, we currently take the fullscreen screenshot of the activity
+ // but scaled inversely by the density. This gives us a "good-enough" fullscreen
+ // thumbnail to use within SystemUI without using enormous amounts of memory on high
+ // density devices.
+ if (mService.mTakeFullscreenScreenshots) {
+ Context context = mService.mContext;
+ w = h = -1;
+ scale = (1f / Math.max(1f, context.getResources().getDisplayMetrics().density));
+ }
return mWindowManager.screenshotApplications(who.appToken, Display.DEFAULT_DISPLAY,
- w, h);
+ w, h, scale);
}
Slog.e(TAG, "Invalid thumbnail dimensions: " + w + "x" + h);
return null;
@@ -914,7 +927,7 @@
final ActivityRecord next = mStackSupervisor.topRunningActivityLocked();
if (mService.mHasRecents
&& (next == null || next.noDisplay || next.task != prev.task || uiSleeping)) {
- prev.updateThumbnailLocked(screenshotActivities(prev), null);
+ prev.updateThumbnailLocked(screenshotActivitiesLocked(prev), null);
}
stopFullyDrawnTraceIfNeeded();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 722fc32..507d76d 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3691,7 +3691,7 @@
void handleLaunchTaskBehindCompleteLocked(ActivityRecord r) {
r.mLaunchTaskBehind = false;
final TaskRecord task = r.task;
- task.setLastThumbnailLocked(task.stack.screenshotActivities(r));
+ task.setLastThumbnailLocked(task.stack.screenshotActivitiesLocked(r));
mRecentTasks.addLocked(task);
mService.notifyTaskStackChangedLocked();
mWindowManager.setAppVisibility(r.appToken, false);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 02cf87a..755db6f 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -834,7 +834,7 @@
if (stack != null) {
final ActivityRecord resumedActivity = stack.mResumedActivity;
if (resumedActivity != null && resumedActivity.task == this) {
- final Bitmap thumbnail = stack.screenshotActivities(resumedActivity);
+ final Bitmap thumbnail = stack.screenshotActivitiesLocked(resumedActivity);
setLastThumbnailLocked(thumbnail);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 75886aa..4f2f486 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -62,7 +62,6 @@
import android.media.IAudioFocusDispatcher;
import android.media.IAudioRoutesObserver;
import android.media.IAudioService;
-import android.media.IRemoteControlDisplay;
import android.media.IRingtonePlayer;
import android.media.IVolumeController;
import android.media.MediaPlayer;
@@ -667,8 +666,7 @@
mSettingsObserver = new SettingsObserver();
createStreamStates();
- mMediaFocusControl = new MediaFocusControl(mAudioHandler.getLooper(),
- mContext, mVolumeController, this);
+ mMediaFocusControl = new MediaFocusControl(mContext);
readAndSetLowRamDevice();
@@ -5235,36 +5233,6 @@
}
}
- //==========================================================================================
- // RemoteControlDisplay / RemoteControlClient / Remote info
- //==========================================================================================
- public boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h,
- ComponentName listenerComp) {
- return mMediaFocusControl.registerRemoteController(rcd, w, h, listenerComp);
- }
-
- public boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
- return mMediaFocusControl.registerRemoteControlDisplay(rcd, w, h);
- }
-
- public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
- mMediaFocusControl.unregisterRemoteControlDisplay(rcd);
- }
-
- public void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) {
- mMediaFocusControl.remoteControlDisplayUsesBitmapSize(rcd, w, h);
- }
-
- public void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd,
- boolean wantsSync) {
- mMediaFocusControl.remoteControlDisplayWantsPlaybackPositionSync(rcd, wantsSync);
- }
-
- @Override
- public void setRemoteStreamVolume(int index) {
- enforceVolumeController("set the remote stream volume");
- mMediaFocusControl.setRemoteStreamVolume(index);
- }
//==========================================================================================
// Audio Focus
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index f72b598..d44d89d 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -16,52 +16,18 @@
package com.android.server.audio;
-import android.app.Activity;
-import android.app.ActivityManager;
import android.app.AppOpsManager;
-import android.app.KeyguardManager;
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.app.PendingIntent.OnFinished;
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.database.ContentObserver;
import android.media.AudioAttributes;
import android.media.AudioFocusInfo;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.IAudioFocusDispatcher;
-import android.media.IRemoteControlClient;
-import android.media.IRemoteControlDisplay;
-import android.media.IRemoteVolumeObserver;
-import android.media.RemoteControlClient;
import android.media.audiopolicy.IAudioPolicyCallback;
-import android.net.Uri;
import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.IDeviceIdleController;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.speech.RecognizerIntent;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
import android.util.Log;
-import android.util.Slog;
-import android.view.KeyEvent;
-
-import com.android.server.audio.PlayerRecord.RemotePlaybackState;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -74,329 +40,22 @@
* @hide
*
*/
-public class MediaFocusControl implements OnFinished {
+public class MediaFocusControl {
private static final String TAG = "MediaFocusControl";
- /** Debug remote control client/display feature */
- protected static final boolean DEBUG_RC = false;
- /** Debug volumes */
- protected static final boolean DEBUG_VOL = false;
-
- /** Used to alter media button redirection when the phone is ringing. */
- private boolean mIsRinging = false;
-
- private final PowerManager.WakeLock mMediaEventWakeLock;
- private final MediaEventHandler mEventHandler;
private final Context mContext;
- private final ContentResolver mContentResolver;
- private final AudioService.VolumeController mVolumeController;
private final AppOpsManager mAppOps;
- private final KeyguardManager mKeyguardManager;
- private final AudioService mAudioService;
- private final NotificationListenerObserver mNotifListenerObserver;
- protected MediaFocusControl(Looper looper, Context cntxt,
- AudioService.VolumeController volumeCtrl, AudioService as) {
- mEventHandler = new MediaEventHandler(looper);
+ protected MediaFocusControl(Context cntxt) {
mContext = cntxt;
- mContentResolver = mContext.getContentResolver();
- mVolumeController = volumeCtrl;
- mAudioService = as;
-
- PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
- mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
- int maxMusicLevel = as.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
- mMainRemote = new RemotePlaybackState(-1, maxMusicLevel, maxMusicLevel);
-
- // Register for phone state monitoring
- TelephonyManager tmgr = (TelephonyManager)
- mContext.getSystemService(Context.TELEPHONY_SERVICE);
- tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
-
mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
- mKeyguardManager =
- (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- mNotifListenerObserver = new NotificationListenerObserver();
-
- mHasRemotePlayback = false;
- mMainRemoteIsActive = false;
-
- PlayerRecord.setMediaFocusControl(this);
-
- postReevaluateRemote();
}
protected void dump(PrintWriter pw) {
pw.println("\nMediaFocusControl dump time: "
+ DateFormat.getTimeInstance().format(new Date()));
dumpFocusStack(pw);
- dumpRCStack(pw);
- dumpRCCStack(pw);
- dumpRCDList(pw);
- }
-
- //==========================================================================================
- // Management of RemoteControlDisplay registration permissions
- //==========================================================================================
- private final static Uri ENABLED_NOTIFICATION_LISTENERS_URI =
- Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
-
- private class NotificationListenerObserver extends ContentObserver {
-
- NotificationListenerObserver() {
- super(mEventHandler);
- mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS), false, this);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- if (!ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri) || selfChange) {
- return;
- }
- if (DEBUG_RC) { Log.d(TAG, "NotificationListenerObserver.onChange()"); }
- postReevaluateRemoteControlDisplays();
- }
- }
-
- private final static int RCD_REG_FAILURE = 0;
- private final static int RCD_REG_SUCCESS_PERMISSION = 1;
- private final static int RCD_REG_SUCCESS_ENABLED_NOTIF = 2;
-
- /**
- * Checks a caller's authorization to register an IRemoteControlDisplay.
- * Authorization is granted if one of the following is true:
- * <ul>
- * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL permission</li>
- * <li>the caller's listener is one of the enabled notification listeners</li>
- * </ul>
- * @return RCD_REG_FAILURE if it's not safe to proceed with the IRemoteControlDisplay
- * registration.
- */
- private int checkRcdRegistrationAuthorization(ComponentName listenerComp) {
- // MEDIA_CONTENT_CONTROL permission check
- if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.MEDIA_CONTENT_CONTROL)) {
- if (DEBUG_RC) { Log.d(TAG, "ok to register Rcd: has MEDIA_CONTENT_CONTROL permission");}
- return RCD_REG_SUCCESS_PERMISSION;
- }
-
- // ENABLED_NOTIFICATION_LISTENERS settings check
- if (listenerComp != null) {
- // this call is coming from an app, can't use its identity to read secure settings
- final long ident = Binder.clearCallingIdentity();
- try {
- final int currentUser = ActivityManager.getCurrentUser();
- final String enabledNotifListeners = Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- currentUser);
- if (enabledNotifListeners != null) {
- final String[] components = enabledNotifListeners.split(":");
- for (int i=0; i<components.length; i++) {
- final ComponentName component =
- ComponentName.unflattenFromString(components[i]);
- if (component != null) {
- if (listenerComp.equals(component)) {
- if (DEBUG_RC) { Log.d(TAG, "ok to register RCC: " + component +
- " is authorized notification listener"); }
- return RCD_REG_SUCCESS_ENABLED_NOTIF;
- }
- }
- }
- }
- if (DEBUG_RC) { Log.d(TAG, "not ok to register RCD, " + listenerComp +
- " is not in list of ENABLED_NOTIFICATION_LISTENERS"); }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- return RCD_REG_FAILURE;
- }
-
- protected boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h,
- ComponentName listenerComp) {
- int reg = checkRcdRegistrationAuthorization(listenerComp);
- if (reg != RCD_REG_FAILURE) {
- registerRemoteControlDisplay_int(rcd, w, h, listenerComp);
- return true;
- } else {
- Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() +
- ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL +
- " or be an enabled NotificationListenerService for registerRemoteController");
- return false;
- }
- }
-
- protected boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
- int reg = checkRcdRegistrationAuthorization(null);
- if (reg != RCD_REG_FAILURE) {
- registerRemoteControlDisplay_int(rcd, w, h, null);
- return true;
- } else {
- Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() +
- ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL +
- " to register IRemoteControlDisplay");
- return false;
- }
- }
-
- private void postReevaluateRemoteControlDisplays() {
- sendMsg(mEventHandler, MSG_REEVALUATE_RCD, SENDMSG_QUEUE, 0, 0, null, 0);
- }
-
- private void onReevaluateRemoteControlDisplays() {
- if (DEBUG_RC) { Log.d(TAG, "onReevaluateRemoteControlDisplays()"); }
- // read which components are enabled notification listeners
- final int currentUser = ActivityManager.getCurrentUser();
- final String enabledNotifListeners = Settings.Secure.getStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- currentUser);
- if (DEBUG_RC) { Log.d(TAG, " > enabled list: " + enabledNotifListeners); }
- synchronized(mAudioFocusLock) {
- synchronized(mPRStack) {
- // check whether the "enable" status of each RCD with a notification listener
- // has changed
- final String[] enabledComponents;
- if (enabledNotifListeners == null) {
- enabledComponents = null;
- } else {
- enabledComponents = enabledNotifListeners.split(":");
- }
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- while (displayIterator.hasNext()) {
- final DisplayInfoForServer di =
- displayIterator.next();
- if (di.mClientNotifListComp != null) {
- boolean wasEnabled = di.mEnabled;
- di.mEnabled = isComponentInStringArray(di.mClientNotifListComp,
- enabledComponents);
- if (wasEnabled != di.mEnabled){
- try {
- // tell the RCD whether it's enabled
- di.mRcDisplay.setEnabled(di.mEnabled);
- // tell the RCCs about the change for this RCD
- enableRemoteControlDisplayForClient_syncRcStack(
- di.mRcDisplay, di.mEnabled);
- // when enabling, refresh the information on the display
- if (di.mEnabled) {
- sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE,
- di.mArtworkExpectedWidth /*arg1*/,
- di.mArtworkExpectedHeight/*arg2*/,
- di.mRcDisplay /*obj*/, 0/*delay*/);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error en/disabling RCD: ", e);
- }
- }
- }
- }
- }
- }
- }
-
- /**
- * @param comp a non-null ComponentName
- * @param enabledArray may be null
- * @return
- */
- private boolean isComponentInStringArray(ComponentName comp, String[] enabledArray) {
- if (enabledArray == null || enabledArray.length == 0) {
- if (DEBUG_RC) { Log.d(TAG, " > " + comp + " is NOT enabled"); }
- return false;
- }
- final String compString = comp.flattenToString();
- for (int i=0; i<enabledArray.length; i++) {
- if (compString.equals(enabledArray[i])) {
- if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is enabled"); }
- return true;
- }
- }
- if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is NOT enabled"); }
- return false;
- }
-
- //==========================================================================================
- // Internal event handling
- //==========================================================================================
-
- // event handler messages
- private static final int MSG_RCDISPLAY_CLEAR = 1;
- private static final int MSG_RCDISPLAY_UPDATE = 2;
- private static final int MSG_REEVALUATE_REMOTE = 3;
- private static final int MSG_RCC_NEW_PLAYBACK_INFO = 4;
- private static final int MSG_RCC_NEW_VOLUME_OBS = 5;
- private static final int MSG_RCC_NEW_PLAYBACK_STATE = 6;
- private static final int MSG_RCC_SEEK_REQUEST = 7;
- private static final int MSG_RCC_UPDATE_METADATA = 8;
- private static final int MSG_RCDISPLAY_INIT_INFO = 9;
- private static final int MSG_REEVALUATE_RCD = 10;
- private static final int MSG_UNREGISTER_MEDIABUTTONINTENT = 11;
-
- // sendMsg() flags
- /** If the msg is already queued, replace it with this one. */
- private static final int SENDMSG_REPLACE = 0;
- /** If the msg is already queued, ignore this one and leave the old. */
- private static final int SENDMSG_NOOP = 1;
- /** If the msg is already queued, queue this one and leave the old. */
- private static final int SENDMSG_QUEUE = 2;
-
- private static void sendMsg(Handler handler, int msg,
- int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {
-
- if (existingMsgPolicy == SENDMSG_REPLACE) {
- handler.removeMessages(msg);
- } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
- return;
- }
-
- handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay);
- }
-
- private class MediaEventHandler extends Handler {
- MediaEventHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch(msg.what) {
- case MSG_RCDISPLAY_CLEAR:
- onRcDisplayClear();
- break;
-
- case MSG_RCDISPLAY_UPDATE:
- // msg.obj is guaranteed to be non null
- onRcDisplayUpdate( (PlayerRecord) msg.obj, msg.arg1);
- break;
-
- case MSG_REEVALUATE_REMOTE:
- onReevaluateRemote();
- break;
-
- case MSG_RCC_NEW_VOLUME_OBS:
- onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */,
- (IRemoteVolumeObserver)msg.obj /* rvo */);
- break;
-
- case MSG_RCDISPLAY_INIT_INFO:
- // msg.obj is guaranteed to be non null
- onRcDisplayInitInfo((IRemoteControlDisplay)msg.obj /*newRcd*/,
- msg.arg1/*w*/, msg.arg2/*h*/);
- break;
-
- case MSG_REEVALUATE_RCD:
- onReevaluateRemoteControlDisplays();
- break;
-
- case MSG_UNREGISTER_MEDIABUTTONINTENT:
- unregisterMediaButtonIntent( (PendingIntent) msg.obj );
- break;
- }
- }
}
@@ -406,25 +65,6 @@
private final static Object mAudioFocusLock = new Object();
- private final static Object mRingingLock = new Object();
-
- private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
- @Override
- public void onCallStateChanged(int state, String incomingNumber) {
- if (state == TelephonyManager.CALL_STATE_RINGING) {
- //Log.v(TAG, " CALL_STATE_RINGING");
- synchronized(mRingingLock) {
- mIsRinging = true;
- }
- } else if ((state == TelephonyManager.CALL_STATE_OFFHOOK)
- || (state == TelephonyManager.CALL_STATE_IDLE)) {
- synchronized(mRingingLock) {
- mIsRinging = false;
- }
- }
- }
- };
-
/**
* Discard the current audio focus owner.
* Notify top of audio focus stack that it lost focus (regardless of possibility to reassign
@@ -865,1374 +505,4 @@
}
}
-
- //==========================================================================================
- // RemoteControl
- //==========================================================================================
- /**
- * No-op if the key code for keyEvent is not a valid media key
- * (see {@link #isValidMediaKeyEvent(KeyEvent)})
- * @param keyEvent the key event to send
- */
- protected void dispatchMediaKeyEvent(KeyEvent keyEvent) {
- filterMediaKeyEvent(keyEvent, false /*needWakeLock*/);
- }
-
- /**
- * No-op if the key code for keyEvent is not a valid media key
- * (see {@link #isValidMediaKeyEvent(KeyEvent)})
- * @param keyEvent the key event to send
- */
- protected void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) {
- filterMediaKeyEvent(keyEvent, true /*needWakeLock*/);
- }
-
- private void filterMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
- // sanity check on the incoming key event
- if (!isValidMediaKeyEvent(keyEvent)) {
- Log.e(TAG, "not dispatching invalid media key event " + keyEvent);
- return;
- }
- // event filtering for telephony
- synchronized(mRingingLock) {
- synchronized(mPRStack) {
- if ((mMediaReceiverForCalls != null) &&
- (mIsRinging || (mAudioService.getMode() == AudioSystem.MODE_IN_CALL))) {
- dispatchMediaKeyEventForCalls(keyEvent, needWakeLock);
- return;
- }
- }
- }
- // event filtering based on voice-based interactions
- if (isValidVoiceInputKeyCode(keyEvent.getKeyCode())) {
- filterVoiceInputKeyEvent(keyEvent, needWakeLock);
- } else {
- dispatchMediaKeyEvent(keyEvent, needWakeLock);
- }
- }
-
- /**
- * Handles the dispatching of the media button events to the telephony package.
- * Precondition: mMediaReceiverForCalls != null
- * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
- * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
- * is dispatched.
- */
- private void dispatchMediaKeyEventForCalls(KeyEvent keyEvent, boolean needWakeLock) {
- Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
- keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- keyIntent.setPackage(mMediaReceiverForCalls.getPackageName());
- if (needWakeLock) {
- mMediaEventWakeLock.acquire();
- keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
- null, mKeyEventDone, mEventHandler, Activity.RESULT_OK, null, null);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- /**
- * Handles the dispatching of the media button events to one of the registered listeners,
- * or if there was none, broadcast an ACTION_MEDIA_BUTTON intent to the rest of the system.
- * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
- * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
- * is dispatched.
- */
- private void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
- if (needWakeLock) {
- mMediaEventWakeLock.acquire();
- }
- Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
- keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
- synchronized(mPRStack) {
- if (!mPRStack.empty()) {
- // send the intent that was registered by the client
- try {
- mPRStack.peek().getMediaButtonIntent().send(mContext,
- needWakeLock ? WAKELOCK_RELEASE_ON_FINISHED : 0 /*code*/,
- keyIntent, this, mEventHandler);
- } catch (CanceledException e) {
- Log.e(TAG, "Error sending pending intent " + mPRStack.peek());
- e.printStackTrace();
- }
- } else {
- // legacy behavior when nobody registered their media button event receiver
- // through AudioManager
- if (needWakeLock) {
- keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
- }
- final long ident = Binder.clearCallingIdentity();
- try {
- mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
- null, mKeyEventDone,
- mEventHandler, Activity.RESULT_OK, null, null);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
- }
-
- /**
- * The different actions performed in response to a voice button key event.
- */
- private final static int VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS = 1;
- private final static int VOICEBUTTON_ACTION_START_VOICE_INPUT = 2;
- private final static int VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS = 3;
-
- private final Object mVoiceEventLock = new Object();
- private boolean mVoiceButtonDown;
- private boolean mVoiceButtonHandled;
-
- /**
- * Filter key events that may be used for voice-based interactions
- * @param keyEvent a non-null KeyEvent whose key code is that of one of the supported
- * media buttons that can be used to trigger voice-based interactions.
- * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
- * is dispatched.
- */
- private void filterVoiceInputKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
- if (DEBUG_RC) {
- Log.v(TAG, "voice input key event: " + keyEvent + ", needWakeLock=" + needWakeLock);
- }
-
- int voiceButtonAction = VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS;
- int keyAction = keyEvent.getAction();
- synchronized (mVoiceEventLock) {
- if (keyAction == KeyEvent.ACTION_DOWN) {
- if (keyEvent.getRepeatCount() == 0) {
- // initial down
- mVoiceButtonDown = true;
- mVoiceButtonHandled = false;
- } else if (mVoiceButtonDown && !mVoiceButtonHandled
- && (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
- // long-press, start voice-based interactions
- mVoiceButtonHandled = true;
- voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT;
- }
- } else if (keyAction == KeyEvent.ACTION_UP) {
- if (mVoiceButtonDown) {
- // voice button up
- mVoiceButtonDown = false;
- if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
- voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS;
- }
- }
- }
- }//synchronized (mVoiceEventLock)
-
- // take action after media button event filtering for voice-based interactions
- switch (voiceButtonAction) {
- case VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS:
- if (DEBUG_RC) Log.v(TAG, " ignore key event");
- break;
- case VOICEBUTTON_ACTION_START_VOICE_INPUT:
- if (DEBUG_RC) Log.v(TAG, " start voice-based interactions");
- // then start the voice-based interactions
- startVoiceBasedInteractions(needWakeLock);
- break;
- case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS:
- if (DEBUG_RC) Log.v(TAG, " send simulated key event, wakelock=" + needWakeLock);
- sendSimulatedMediaButtonEvent(keyEvent, needWakeLock);
- break;
- }
- }
-
- private void sendSimulatedMediaButtonEvent(KeyEvent originalKeyEvent, boolean needWakeLock) {
- // send DOWN event
- KeyEvent keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_DOWN);
- dispatchMediaKeyEvent(keyEvent, needWakeLock);
- // send UP event
- keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_UP);
- dispatchMediaKeyEvent(keyEvent, needWakeLock);
-
- }
-
- private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) {
- if (keyEvent == null) {
- return false;
- }
- return KeyEvent.isMediaKey(keyEvent.getKeyCode());
- }
-
- /**
- * Checks whether the given key code is one that can trigger the launch of voice-based
- * interactions.
- * @param keyCode the key code associated with the key event
- * @return true if the key is one of the supported voice-based interaction triggers
- */
- private static boolean isValidVoiceInputKeyCode(int keyCode) {
- if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) {
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Tell the system to start voice-based interactions / voice commands
- */
- private void startVoiceBasedInteractions(boolean needWakeLock) {
- Intent voiceIntent = null;
- // select which type of search to launch:
- // - screen on and device unlocked: action is ACTION_WEB_SEARCH
- // - device locked or screen off: action is ACTION_VOICE_SEARCH_HANDS_FREE
- // with EXTRA_SECURE set to true if the device is securely locked
- PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
- boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
- if (!isLocked && pm.isScreenOn()) {
- voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
- Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
- } else {
- IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
- ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
- if (dic != null) {
- try {
- dic.exitIdle("voice-search");
- } catch (RemoteException e) {
- }
- }
- voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
- voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
- isLocked && mKeyguardManager.isKeyguardSecure());
- Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
- }
- // start the search activity
- if (needWakeLock) {
- mMediaEventWakeLock.acquire();
- }
- final long identity = Binder.clearCallingIdentity();
- try {
- if (voiceIntent != null) {
- voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- mContext.startActivityAsUser(voiceIntent, UserHandle.CURRENT);
- }
- } catch (ActivityNotFoundException e) {
- Log.w(TAG, "No activity for search: " + e);
- } finally {
- Binder.restoreCallingIdentity(identity);
- if (needWakeLock) {
- mMediaEventWakeLock.release();
- }
- }
- }
-
- private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; //magic number
-
- // only set when wakelock was acquired, no need to check value when received
- private static final String EXTRA_WAKELOCK_ACQUIRED =
- "android.media.AudioService.WAKELOCK_ACQUIRED";
-
- public void onSendFinished(PendingIntent pendingIntent, Intent intent,
- int resultCode, String resultData, Bundle resultExtras) {
- if (resultCode == WAKELOCK_RELEASE_ON_FINISHED) {
- mMediaEventWakeLock.release();
- }
- }
-
- BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- if (intent == null) {
- return;
- }
- Bundle extras = intent.getExtras();
- if (extras == null) {
- return;
- }
- if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)) {
- mMediaEventWakeLock.release();
- }
- }
- };
-
- /**
- * Synchronization on mCurrentRcLock always inside a block synchronized on mPRStack
- */
- private final Object mCurrentRcLock = new Object();
- /**
- * The one remote control client which will receive a request for display information.
- * This object may be null.
- * Access protected by mCurrentRcLock.
- */
- private IRemoteControlClient mCurrentRcClient = null;
- /**
- * The PendingIntent associated with mCurrentRcClient. Its value is irrelevant
- * if mCurrentRcClient is null
- */
- private PendingIntent mCurrentRcClientIntent = null;
-
- private final static int RC_INFO_NONE = 0;
- private final static int RC_INFO_ALL =
- RemoteControlClient.FLAG_INFORMATION_REQUEST_ALBUM_ART |
- RemoteControlClient.FLAG_INFORMATION_REQUEST_KEY_MEDIA |
- RemoteControlClient.FLAG_INFORMATION_REQUEST_METADATA |
- RemoteControlClient.FLAG_INFORMATION_REQUEST_PLAYSTATE;
-
- /**
- * A monotonically increasing generation counter for mCurrentRcClient.
- * Only accessed with a lock on mCurrentRcLock.
- * No value wrap-around issues as we only act on equal values.
- */
- private int mCurrentRcClientGen = 0;
-
-
- /**
- * Internal cache for the playback information of the RemoteControlClient whose volume gets to
- * be controlled by the volume keys ("main"), so we don't have to iterate over the RC stack
- * every time we need this info.
- */
- private RemotePlaybackState mMainRemote;
- /**
- * Indicates whether the "main" RemoteControlClient is considered active.
- * Use synchronized on mMainRemote.
- */
- private boolean mMainRemoteIsActive;
- /**
- * Indicates whether there is remote playback going on. True even if there is no "active"
- * remote playback (mMainRemoteIsActive is false), but a RemoteControlClient has declared it
- * handles remote playback.
- * Use synchronized on mMainRemote.
- */
- private boolean mHasRemotePlayback;
-
- /**
- * The stack of remote control event receivers.
- * All read and write operations on mPRStack are synchronized.
- */
- private final Stack<PlayerRecord> mPRStack = new Stack<PlayerRecord>();
-
- /**
- * The component the telephony package can register so telephony calls have priority to
- * handle media button events
- */
- private ComponentName mMediaReceiverForCalls = null;
-
- /**
- * Helper function:
- * Display in the log the current entries in the remote control focus stack
- */
- private void dumpRCStack(PrintWriter pw) {
- pw.println("\nRemote Control stack entries (last is top of stack):");
- synchronized(mPRStack) {
- Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while(stackIterator.hasNext()) {
- stackIterator.next().dump(pw, true);
- }
- }
- }
-
- /**
- * Helper function:
- * Display in the log the current entries in the remote control stack, focusing
- * on RemoteControlClient data
- */
- private void dumpRCCStack(PrintWriter pw) {
- pw.println("\nRemote Control Client stack entries (last is top of stack):");
- synchronized(mPRStack) {
- Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while(stackIterator.hasNext()) {
- stackIterator.next().dump(pw, false);
- }
- synchronized(mCurrentRcLock) {
- pw.println("\nCurrent remote control generation ID = " + mCurrentRcClientGen);
- }
- }
- synchronized (mMainRemote) {
- pw.println("\nRemote Volume State:");
- pw.println(" has remote: " + mHasRemotePlayback);
- pw.println(" is remote active: " + mMainRemoteIsActive);
- pw.println(" rccId: " + mMainRemote.mRccId);
- pw.println(" volume handling: "
- + ((mMainRemote.mVolumeHandling == RemoteControlClient.PLAYBACK_VOLUME_FIXED) ?
- "PLAYBACK_VOLUME_FIXED(0)" : "PLAYBACK_VOLUME_VARIABLE(1)"));
- pw.println(" volume: " + mMainRemote.mVolume);
- pw.println(" volume steps: " + mMainRemote.mVolumeMax);
- }
- }
-
- /**
- * Helper function:
- * Display in the log the current entries in the list of remote control displays
- */
- private void dumpRCDList(PrintWriter pw) {
- pw.println("\nRemote Control Display list entries:");
- synchronized(mPRStack) {
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- while (displayIterator.hasNext()) {
- final DisplayInfoForServer di = displayIterator.next();
- pw.println(" IRCD: " + di.mRcDisplay +
- " -- w:" + di.mArtworkExpectedWidth +
- " -- h:" + di.mArtworkExpectedHeight +
- " -- wantsPosSync:" + di.mWantsPositionSync +
- " -- " + (di.mEnabled ? "enabled" : "disabled"));
- }
- }
- }
-
- /**
- * Helper function:
- * Push the new media button receiver "near" the top of the PlayerRecord stack.
- * "Near the top" is defined as:
- * - at the top if the current PlayerRecord at the top is not playing
- * - below the entries at the top of the stack that correspond to the playing PlayerRecord
- * otherwise
- * Called synchronized on mPRStack
- * precondition: mediaIntent != null
- * @return true if the top of mPRStack was changed, false otherwise
- */
- private boolean pushMediaButtonReceiver_syncPrs(PendingIntent mediaIntent,
- ComponentName target, IBinder token) {
- if (mPRStack.empty()) {
- mPRStack.push(new PlayerRecord(mediaIntent, target, token));
- return true;
- } else if (mPRStack.peek().hasMatchingMediaButtonIntent(mediaIntent)) {
- // already at top of stack
- return false;
- }
- if (mAppOps.noteOp(AppOpsManager.OP_TAKE_MEDIA_BUTTONS, Binder.getCallingUid(),
- mediaIntent.getCreatorPackage()) != AppOpsManager.MODE_ALLOWED) {
- return false;
- }
- PlayerRecord oldTopPrse = mPRStack.lastElement(); // top of the stack before any changes
- boolean topChanged = false;
- PlayerRecord prse = null;
- int lastPlayingIndex = mPRStack.size();
- int inStackIndex = -1;
- try {
- // go through the stack from the top to figure out who's playing, and the position
- // of this media button receiver (note that it may not be in the stack)
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- prse = mPRStack.elementAt(index);
- if (prse.isPlaybackActive()) {
- lastPlayingIndex = index;
- }
- if (prse.hasMatchingMediaButtonIntent(mediaIntent)) {
- inStackIndex = index;
- }
- }
-
- if (inStackIndex == -1) {
- // is not in stack
- prse = new PlayerRecord(mediaIntent, target, token);
- // it's new so it's not playing (no RemoteControlClient to give a playstate),
- // therefore it goes after the ones with active playback
- mPRStack.add(lastPlayingIndex, prse);
- } else {
- // is in the stack
- if (mPRStack.size() > 1) { // no need to remove and add if stack contains only 1
- prse = mPRStack.elementAt(inStackIndex);
- // remove it from its old location in the stack
- mPRStack.removeElementAt(inStackIndex);
- if (prse.isPlaybackActive()) {
- // and put it at the top
- mPRStack.push(prse);
- } else {
- // and put it after the ones with active playback
- if (inStackIndex > lastPlayingIndex) {
- mPRStack.add(lastPlayingIndex, prse);
- } else {
- mPRStack.add(lastPlayingIndex - 1, prse);
- }
- }
- }
- }
-
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification or bad index
- Log.e(TAG, "Wrong index (inStack=" + inStackIndex + " lastPlaying=" + lastPlayingIndex
- + " size=" + mPRStack.size()
- + " accessing media button stack", e);
- }
-
- return (topChanged);
- }
-
- /**
- * Helper function:
- * Remove the remote control receiver from the RC focus stack.
- * Called synchronized on mPRStack
- * precondition: pi != null
- */
- private void removeMediaButtonReceiver_syncPrs(PendingIntent pi) {
- try {
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- final PlayerRecord prse = mPRStack.elementAt(index);
- if (prse.hasMatchingMediaButtonIntent(pi)) {
- prse.destroy();
- // ok to remove element while traversing the stack since we're leaving the loop
- mPRStack.removeElementAt(index);
- break;
- }
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
- }
- }
-
- /**
- * Helper function:
- * Called synchronized on mPRStack
- */
- private boolean isCurrentRcController(PendingIntent pi) {
- if (!mPRStack.empty() && mPRStack.peek().hasMatchingMediaButtonIntent(pi)) {
- return true;
- }
- return false;
- }
-
- //==========================================================================================
- // Remote control display / client
- //==========================================================================================
- /**
- * Update the remote control displays with the new "focused" client generation
- */
- private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration,
- PendingIntent newMediaIntent, boolean clearing) {
- synchronized(mPRStack) {
- if (mRcDisplays.size() > 0) {
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- while (displayIterator.hasNext()) {
- final DisplayInfoForServer di = displayIterator.next();
- try {
- di.mRcDisplay.setCurrentClientId(
- newClientGeneration, newMediaIntent, clearing);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc()",e);
- di.release();
- displayIterator.remove();
- }
- }
- }
- }
- }
-
- /**
- * Update the remote control clients with the new "focused" client generation
- */
- private void setNewRcClientGenerationOnClients_syncRcsCurrc(int newClientGeneration) {
- // (using an iterator on the stack so we can safely remove an entry if needed,
- // traversal order doesn't matter here as we update all entries)
- Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while(stackIterator.hasNext()) {
- PlayerRecord se = stackIterator.next();
- if ((se != null) && (se.getRcc() != null)) {
- try {
- se.getRcc().setCurrentClientGenerationId(newClientGeneration);
- } catch (RemoteException e) {
- Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()",e);
- stackIterator.remove();
- se.unlinkToRcClientDeath();
- }
- }
- }
- }
-
- /**
- * Update the displays and clients with the new "focused" client generation and name
- * @param newClientGeneration the new generation value matching a client update
- * @param newMediaIntent the media button event receiver associated with the client.
- * May be null, which implies there is no registered media button event receiver.
- * @param clearing true if the new client generation value maps to a remote control update
- * where the display should be cleared.
- */
- private void setNewRcClient_syncRcsCurrc(int newClientGeneration,
- PendingIntent newMediaIntent, boolean clearing) {
- // send the new valid client generation ID to all displays
- setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newMediaIntent, clearing);
- // send the new valid client generation ID to all clients
- setNewRcClientGenerationOnClients_syncRcsCurrc(newClientGeneration);
- }
-
- /**
- * Called when processing MSG_RCDISPLAY_CLEAR event
- */
- private void onRcDisplayClear() {
- if (DEBUG_RC) Log.i(TAG, "Clear remote control display");
-
- synchronized(mPRStack) {
- synchronized(mCurrentRcLock) {
- mCurrentRcClientGen++;
- // synchronously update the displays and clients with the new client generation
- setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
- null /*newMediaIntent*/, true /*clearing*/);
- }
- }
- }
-
- /**
- * Called when processing MSG_RCDISPLAY_UPDATE event
- */
- private void onRcDisplayUpdate(PlayerRecord prse, int flags /* USED ?*/) {
- synchronized(mPRStack) {
- synchronized(mCurrentRcLock) {
- if ((mCurrentRcClient != null) && (mCurrentRcClient.equals(prse.getRcc()))) {
- if (DEBUG_RC) Log.i(TAG, "Display/update remote control ");
-
- mCurrentRcClientGen++;
- // synchronously update the displays and clients with
- // the new client generation
- setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
- prse.getMediaButtonIntent() /*newMediaIntent*/,
- false /*clearing*/);
-
- // tell the current client that it needs to send info
- try {
- //TODO change name to informationRequestForAllDisplays()
- mCurrentRcClient.onInformationRequested(mCurrentRcClientGen, flags);
- } catch (RemoteException e) {
- Log.e(TAG, "Current valid remote client is dead: "+e);
- mCurrentRcClient = null;
- }
- } else {
- // the remote control display owner has changed between the
- // the message to update the display was sent, and the time it
- // gets to be processed (now)
- }
- }
- }
- }
-
- /**
- * Called when processing MSG_RCDISPLAY_INIT_INFO event
- * Causes the current RemoteControlClient to send its info (metadata, playstate...) to
- * a single RemoteControlDisplay, NOT all of them, as with MSG_RCDISPLAY_UPDATE.
- */
- private void onRcDisplayInitInfo(IRemoteControlDisplay newRcd, int w, int h) {
- synchronized(mPRStack) {
- synchronized(mCurrentRcLock) {
- if (mCurrentRcClient != null) {
- if (DEBUG_RC) { Log.i(TAG, "Init RCD with current info"); }
- try {
- // synchronously update the new RCD with the current client generation
- // and matching PendingIntent
- newRcd.setCurrentClientId(mCurrentRcClientGen, mCurrentRcClientIntent,
- false);
-
- // tell the current RCC that it needs to send info, but only to the new RCD
- try {
- mCurrentRcClient.informationRequestForDisplay(newRcd, w, h);
- } catch (RemoteException e) {
- Log.e(TAG, "Current valid remote client is dead: ", e);
- mCurrentRcClient = null;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Dead display in onRcDisplayInitInfo()", e);
- }
- }
- }
- }
- }
-
- /**
- * Helper function:
- * Called synchronized on mPRStack
- */
- private void clearRemoteControlDisplay_syncPrs() {
- synchronized(mCurrentRcLock) {
- mCurrentRcClient = null;
- }
- // will cause onRcDisplayClear() to be called in AudioService's handler thread
- mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_CLEAR) );
- }
-
- /**
- * Helper function for code readability: only to be called from
- * checkUpdateRemoteControlDisplay_syncPrs() which checks the preconditions for
- * this method.
- * Preconditions:
- * - called synchronized on mPRStack
- * - mPRStack.isEmpty() is false
- */
- private void updateRemoteControlDisplay_syncPrs(int infoChangedFlags) {
- PlayerRecord prse = mPRStack.peek();
- int infoFlagsAboutToBeUsed = infoChangedFlags;
- // this is where we enforce opt-in for information display on the remote controls
- // with the new AudioManager.registerRemoteControlClient() API
- if (prse.getRcc() == null) {
- //Log.w(TAG, "Can't update remote control display with null remote control client");
- clearRemoteControlDisplay_syncPrs();
- return;
- }
- synchronized(mCurrentRcLock) {
- if (!prse.getRcc().equals(mCurrentRcClient)) {
- // new RC client, assume every type of information shall be queried
- infoFlagsAboutToBeUsed = RC_INFO_ALL;
- }
- mCurrentRcClient = prse.getRcc();
- mCurrentRcClientIntent = prse.getMediaButtonIntent();
- }
- // will cause onRcDisplayUpdate() to be called in AudioService's handler thread
- mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_UPDATE,
- infoFlagsAboutToBeUsed /* arg1 */, 0, prse /* obj, != null */) );
- }
-
- /**
- * Helper function:
- * Called synchronized on mPRStack
- * Check whether the remote control display should be updated, triggers the update if required
- * @param infoChangedFlags the flags corresponding to the remote control client information
- * that has changed, if applicable (checking for the update conditions might trigger a
- * clear, rather than an update event).
- */
- private void checkUpdateRemoteControlDisplay_syncPrs(int infoChangedFlags) {
- // determine whether the remote control display should be refreshed
- // if the player record stack is empty, there is nothing to display, so clear the RC display
- if (mPRStack.isEmpty()) {
- clearRemoteControlDisplay_syncPrs();
- return;
- }
-
- // this is where more rules for refresh go
-
- // refresh conditions were verified: update the remote controls
- // ok to call: synchronized on mPRStack, mPRStack is not empty
- updateRemoteControlDisplay_syncPrs(infoChangedFlags);
- }
-
- /**
- * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c)
- * precondition: mediaIntent != null
- */
- protected void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver,
- IBinder token) {
- Log.i(TAG, " Remote Control registerMediaButtonIntent() for " + mediaIntent);
-
- synchronized(mPRStack) {
- if (pushMediaButtonReceiver_syncPrs(mediaIntent, eventReceiver, token)) {
- // new RC client, assume every type of information shall be queried
- checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
- }
- }
- }
-
- /**
- * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent)
- * precondition: mediaIntent != null, eventReceiver != null
- */
- protected void unregisterMediaButtonIntent(PendingIntent mediaIntent)
- {
- Log.i(TAG, " Remote Control unregisterMediaButtonIntent() for " + mediaIntent);
-
- synchronized(mPRStack) {
- boolean topOfStackWillChange = isCurrentRcController(mediaIntent);
- removeMediaButtonReceiver_syncPrs(mediaIntent);
- if (topOfStackWillChange) {
- // current RC client will change, assume every type of info needs to be queried
- checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
- }
- }
- }
-
- protected void unregisterMediaButtonIntentAsync(final PendingIntent mediaIntent) {
- mEventHandler.sendMessage(
- mEventHandler.obtainMessage(MSG_UNREGISTER_MEDIABUTTONINTENT, 0, 0,
- mediaIntent));
- }
-
- /**
- * see AudioManager.registerMediaButtonEventReceiverForCalls(ComponentName c)
- * precondition: c != null
- */
- protected void registerMediaButtonEventReceiverForCalls(ComponentName c) {
- if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE")
- != PackageManager.PERMISSION_GRANTED) {
- Log.e(TAG, "Invalid permissions to register media button receiver for calls");
- return;
- }
- synchronized(mPRStack) {
- mMediaReceiverForCalls = c;
- }
- }
-
- /**
- * see AudioManager.unregisterMediaButtonEventReceiverForCalls()
- */
- protected void unregisterMediaButtonEventReceiverForCalls() {
- if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE")
- != PackageManager.PERMISSION_GRANTED) {
- Log.e(TAG, "Invalid permissions to unregister media button receiver for calls");
- return;
- }
- synchronized(mPRStack) {
- mMediaReceiverForCalls = null;
- }
- }
-
- /**
- * see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...)
- * @return the unique ID of the PlayerRecord associated with the RemoteControlClient
- * Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient
- * without modifying the RC stack, but while still causing the display to refresh (will
- * become blank as a result of this)
- */
- protected int registerRemoteControlClient(PendingIntent mediaIntent,
- IRemoteControlClient rcClient, String callingPackageName) {
- if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient);
- int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
- synchronized(mPRStack) {
- // store the new display information
- try {
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- final PlayerRecord prse = mPRStack.elementAt(index);
- if(prse.hasMatchingMediaButtonIntent(mediaIntent)) {
- prse.resetControllerInfoForRcc(rcClient, callingPackageName,
- Binder.getCallingUid());
-
- if (rcClient == null) {
- break;
- }
-
- rccId = prse.getRccId();
-
- // there is a new (non-null) client:
- // give the new client the displays (if any)
- if (mRcDisplays.size() > 0) {
- plugRemoteControlDisplaysIntoClient_syncPrs(prse.getRcc());
- }
- break;
- }
- }//for
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
- }
-
- // if the eventReceiver is at the top of the stack
- // then check for potential refresh of the remote controls
- if (isCurrentRcController(mediaIntent)) {
- checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
- }
- }//synchronized(mPRStack)
- return rccId;
- }
-
- /**
- * see AudioManager.unregisterRemoteControlClient(PendingIntent pi, ...)
- * rcClient is guaranteed non-null
- */
- protected void unregisterRemoteControlClient(PendingIntent mediaIntent,
- IRemoteControlClient rcClient) {
- if (DEBUG_RC) Log.i(TAG, "Unregister remote control client rcClient="+rcClient);
- synchronized(mPRStack) {
- boolean topRccChange = false;
- try {
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- final PlayerRecord prse = mPRStack.elementAt(index);
- if ((prse.hasMatchingMediaButtonIntent(mediaIntent))
- && rcClient.equals(prse.getRcc())) {
- // we found the IRemoteControlClient to unregister
- prse.resetControllerInfoForNoRcc();
- topRccChange = (index == mPRStack.size()-1);
- // there can only be one matching RCC in the RC stack, we're done
- break;
- }
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
- }
- if (topRccChange) {
- // no more RCC for the RCD, check for potential refresh of the remote controls
- checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
- }
- }
- }
-
-
- /**
- * A class to encapsulate all the information about a remote control display.
- * After instanciation, init() must always be called before the object is added in the list
- * of displays.
- * Before being removed from the list of displays, release() must always be called (otherwise
- * it will leak death handlers).
- */
- private class DisplayInfoForServer implements IBinder.DeathRecipient {
- /** may never be null */
- private final IRemoteControlDisplay mRcDisplay;
- private final IBinder mRcDisplayBinder;
- private int mArtworkExpectedWidth = -1;
- private int mArtworkExpectedHeight = -1;
- private boolean mWantsPositionSync = false;
- private ComponentName mClientNotifListComp;
- private boolean mEnabled = true;
-
- public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) {
- if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + h);
- mRcDisplay = rcd;
- mRcDisplayBinder = rcd.asBinder();
- mArtworkExpectedWidth = w;
- mArtworkExpectedHeight = h;
- }
-
- public boolean init() {
- try {
- mRcDisplayBinder.linkToDeath(this, 0);
- } catch (RemoteException e) {
- // remote control display is DOA, disqualify it
- Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + mRcDisplayBinder);
- return false;
- }
- return true;
- }
-
- public void release() {
- try {
- mRcDisplayBinder.unlinkToDeath(this, 0);
- } catch (java.util.NoSuchElementException e) {
- // not much we can do here, the display should have been unregistered anyway
- Log.e(TAG, "Error in DisplaInfoForServer.relase()", e);
- }
- }
-
- public void binderDied() {
- synchronized(mPRStack) {
- Log.w(TAG, "RemoteControl: display " + mRcDisplay + " died");
- // remove the display from the list
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- while (displayIterator.hasNext()) {
- final DisplayInfoForServer di = displayIterator.next();
- if (di.mRcDisplay == mRcDisplay) {
- if (DEBUG_RC) Log.w(TAG, " RCD removed from list");
- displayIterator.remove();
- return;
- }
- }
- }
- }
- }
-
- /**
- * The remote control displays.
- * Access synchronized on mPRStack
- */
- private ArrayList<DisplayInfoForServer> mRcDisplays = new ArrayList<DisplayInfoForServer>(1);
-
- /**
- * Plug each registered display into the specified client
- * @param rcc, guaranteed non null
- */
- private void plugRemoteControlDisplaysIntoClient_syncPrs(IRemoteControlClient rcc) {
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- while (displayIterator.hasNext()) {
- final DisplayInfoForServer di = displayIterator.next();
- try {
- rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth,
- di.mArtworkExpectedHeight);
- if (di.mWantsPositionSync) {
- rcc.setWantsSyncForDisplay(di.mRcDisplay, true);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e);
- }
- }
- }
-
- private void enableRemoteControlDisplayForClient_syncRcStack(IRemoteControlDisplay rcd,
- boolean enabled) {
- // let all the remote control clients know whether the given display is enabled
- // (so the remote control stack traversal order doesn't matter).
- final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while(stackIterator.hasNext()) {
- PlayerRecord prse = stackIterator.next();
- if(prse.getRcc() != null) {
- try {
- prse.getRcc().enableRemoteControlDisplay(rcd, enabled);
- } catch (RemoteException e) {
- Log.e(TAG, "Error connecting RCD to client: ", e);
- }
- }
- }
- }
-
- /**
- * Is the remote control display interface already registered
- * @param rcd
- * @return true if the IRemoteControlDisplay is already in the list of displays
- */
- private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) {
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- while (displayIterator.hasNext()) {
- final DisplayInfoForServer di = displayIterator.next();
- if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Register an IRemoteControlDisplay.
- * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient
- * at the top of the stack to update the new display with its information.
- * @see android.media.IAudioService#registerRemoteControlDisplay(android.media.IRemoteControlDisplay, int, int)
- * @param rcd the IRemoteControlDisplay to register. No effect if null.
- * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
- * display doesn't need to receive artwork.
- * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
- * display doesn't need to receive artwork.
- * @param listenerComp the component for the listener interface, may be null if it's not needed
- * to verify it belongs to one of the enabled notification listeners
- */
- private void registerRemoteControlDisplay_int(IRemoteControlDisplay rcd, int w, int h,
- ComponentName listenerComp) {
- if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")");
- synchronized(mAudioFocusLock) {
- synchronized(mPRStack) {
- if ((rcd == null) || rcDisplayIsPluggedIn_syncRcStack(rcd)) {
- return;
- }
- DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h);
- di.mEnabled = true;
- di.mClientNotifListComp = listenerComp;
- if (!di.init()) {
- if (DEBUG_RC) Log.e(TAG, " error registering RCD");
- return;
- }
- // add RCD to list of displays
- mRcDisplays.add(di);
-
- // let all the remote control clients know there is a new display (so the remote
- // control stack traversal order doesn't matter).
- Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while(stackIterator.hasNext()) {
- PlayerRecord prse = stackIterator.next();
- if(prse.getRcc() != null) {
- try {
- prse.getRcc().plugRemoteControlDisplay(rcd, w, h);
- } catch (RemoteException e) {
- Log.e(TAG, "Error connecting RCD to client: ", e);
- }
- }
- }
-
- // we have a new display, of which all the clients are now aware: have it be
- // initialized wih the current gen ID and the current client info, do not
- // reset the information for the other (existing) displays
- sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE,
- w /*arg1*/, h /*arg2*/,
- rcd /*obj*/, 0/*delay*/);
- }
- }
- }
-
- /**
- * Unregister an IRemoteControlDisplay.
- * No effect if the IRemoteControlDisplay hasn't been successfully registered.
- * @see android.media.IAudioService#unregisterRemoteControlDisplay(android.media.IRemoteControlDisplay)
- * @param rcd the IRemoteControlDisplay to unregister. No effect if null.
- */
- protected void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
- if (DEBUG_RC) Log.d(TAG, "<<< unregisterRemoteControlDisplay("+rcd+")");
- synchronized(mPRStack) {
- if (rcd == null) {
- return;
- }
-
- boolean displayWasPluggedIn = false;
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- while (displayIterator.hasNext() && !displayWasPluggedIn) {
- final DisplayInfoForServer di = displayIterator.next();
- if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
- displayWasPluggedIn = true;
- di.release();
- displayIterator.remove();
- }
- }
-
- if (displayWasPluggedIn) {
- // disconnect this remote control display from all the clients, so the remote
- // control stack traversal order doesn't matter
- final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while(stackIterator.hasNext()) {
- final PlayerRecord prse = stackIterator.next();
- if(prse.getRcc() != null) {
- try {
- prse.getRcc().unplugRemoteControlDisplay(rcd);
- } catch (RemoteException e) {
- Log.e(TAG, "Error disconnecting remote control display to client: ", e);
- }
- }
- }
- } else {
- if (DEBUG_RC) Log.w(TAG, " trying to unregister unregistered RCD");
- }
- }
- }
-
- /**
- * Update the size of the artwork used by an IRemoteControlDisplay.
- * @see android.media.IAudioService#remoteControlDisplayUsesBitmapSize(android.media.IRemoteControlDisplay, int, int)
- * @param rcd the IRemoteControlDisplay with the new artwork size requirement
- * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
- * display doesn't need to receive artwork.
- * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
- * display doesn't need to receive artwork.
- */
- protected void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) {
- synchronized(mPRStack) {
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- boolean artworkSizeUpdate = false;
- while (displayIterator.hasNext() && !artworkSizeUpdate) {
- final DisplayInfoForServer di = displayIterator.next();
- if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
- if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
- di.mArtworkExpectedWidth = w;
- di.mArtworkExpectedHeight = h;
- artworkSizeUpdate = true;
- }
- }
- }
- if (artworkSizeUpdate) {
- // RCD is currently plugged in and its artwork size has changed, notify all RCCs,
- // stack traversal order doesn't matter
- final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while(stackIterator.hasNext()) {
- final PlayerRecord prse = stackIterator.next();
- if(prse.getRcc() != null) {
- try {
- prse.getRcc().setBitmapSizeForDisplay(rcd, w, h);
- } catch (RemoteException e) {
- Log.e(TAG, "Error setting bitmap size for RCD on RCC: ", e);
- }
- }
- }
- }
- }
- }
-
- /**
- * Controls whether a remote control display needs periodic checks of the RemoteControlClient
- * playback position to verify that the estimated position has not drifted from the actual
- * position. By default the check is not performed.
- * The IRemoteControlDisplay must have been previously registered for this to have any effect.
- * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled
- * or disabled. Not null.
- * @param wantsSync if true, RemoteControlClient instances which expose their playback position
- * to the framework will regularly compare the estimated playback position with the actual
- * position, and will update the IRemoteControlDisplay implementation whenever a drift is
- * detected.
- */
- protected void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd,
- boolean wantsSync) {
- synchronized(mPRStack) {
- boolean rcdRegistered = false;
- // store the information about this display
- // (display stack traversal order doesn't matter).
- final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
- while (displayIterator.hasNext()) {
- final DisplayInfoForServer di = displayIterator.next();
- if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
- di.mWantsPositionSync = wantsSync;
- rcdRegistered = true;
- break;
- }
- }
- if (!rcdRegistered) {
- return;
- }
- // notify all current RemoteControlClients
- // (stack traversal order doesn't matter as we notify all RCCs)
- final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
- while (stackIterator.hasNext()) {
- final PlayerRecord prse = stackIterator.next();
- if (prse.getRcc() != null) {
- try {
- prse.getRcc().setWantsSyncForDisplay(rcd, wantsSync);
- } catch (RemoteException e) {
- Log.e(TAG, "Error setting position sync flag for RCD on RCC: ", e);
- }
- }
- }
- }
- }
-
- // handler for MSG_RCC_NEW_VOLUME_OBS
- private void onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) {
- synchronized(mPRStack) {
- // The stack traversal order doesn't matter because there is only one stack entry
- // with this RCC ID, but the matching ID is more likely at the top of the stack, so
- // start iterating from the top.
- try {
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- final PlayerRecord prse = mPRStack.elementAt(index);
- if (prse.getRccId() == rccId) {
- prse.mRemoteVolumeObs = rvo;
- break;
- }
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
- }
- }
- }
-
- /**
- * Checks if a remote client is active on the supplied stream type. Update the remote stream
- * volume state if found and playing
- * @param streamType
- * @return false if no remote playing is currently playing
- */
- protected boolean checkUpdateRemoteStateIfActive(int streamType) {
- synchronized(mPRStack) {
- // iterating from top of stack as active playback is more likely on entries at the top
- try {
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- final PlayerRecord prse = mPRStack.elementAt(index);
- if ((prse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE)
- && isPlaystateActive(prse.mPlaybackState.mState)
- && (prse.mPlaybackStream == streamType)) {
- if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType
- + ", vol =" + prse.mPlaybackVolume);
- synchronized (mMainRemote) {
- mMainRemote.mRccId = prse.getRccId();
- mMainRemote.mVolume = prse.mPlaybackVolume;
- mMainRemote.mVolumeMax = prse.mPlaybackVolumeMax;
- mMainRemote.mVolumeHandling = prse.mPlaybackVolumeHandling;
- mMainRemoteIsActive = true;
- }
- return true;
- }
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
- }
- }
- synchronized (mMainRemote) {
- mMainRemoteIsActive = false;
- }
- return false;
- }
-
- /**
- * Returns true if the given playback state is considered "active", i.e. it describes a state
- * where playback is happening, or about to
- * @param playState the playback state to evaluate
- * @return true if active, false otherwise (inactive or unknown)
- */
- protected static boolean isPlaystateActive(int playState) {
- switch (playState) {
- case RemoteControlClient.PLAYSTATE_PLAYING:
- case RemoteControlClient.PLAYSTATE_BUFFERING:
- case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
- case RemoteControlClient.PLAYSTATE_REWINDING:
- case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
- case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
- return true;
- default:
- return false;
- }
- }
-
- private void sendVolumeUpdateToRemote(int rccId, int direction) {
- if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); }
- if (direction == 0) {
- // only handling discrete events
- return;
- }
- IRemoteVolumeObserver rvo = null;
- synchronized (mPRStack) {
- // The stack traversal order doesn't matter because there is only one stack entry
- // with this RCC ID, but the matching ID is more likely at the top of the stack, so
- // start iterating from the top.
- try {
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- final PlayerRecord prse = mPRStack.elementAt(index);
- //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
- if (prse.getRccId() == rccId) {
- rvo = prse.mRemoteVolumeObs;
- break;
- }
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
- }
- }
- if (rvo != null) {
- try {
- rvo.dispatchRemoteVolumeUpdate(direction, -1);
- } catch (RemoteException e) {
- Log.e(TAG, "Error dispatching relative volume update", e);
- }
- }
- }
-
- protected int getRemoteStreamMaxVolume() {
- synchronized (mMainRemote) {
- if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
- return 0;
- }
- return mMainRemote.mVolumeMax;
- }
- }
-
- protected int getRemoteStreamVolume() {
- synchronized (mMainRemote) {
- if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
- return 0;
- }
- return mMainRemote.mVolume;
- }
- }
-
- protected void setRemoteStreamVolume(int vol) {
- if (DEBUG_VOL) { Log.d(TAG, "setRemoteStreamVolume(vol="+vol+")"); }
- int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
- synchronized (mMainRemote) {
- if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
- return;
- }
- rccId = mMainRemote.mRccId;
- }
- IRemoteVolumeObserver rvo = null;
- synchronized (mPRStack) {
- // The stack traversal order doesn't matter because there is only one stack entry
- // with this RCC ID, but the matching ID is more likely at the top of the stack, so
- // start iterating from the top.
- try {
- for (int index = mPRStack.size()-1; index >= 0; index--) {
- final PlayerRecord prse = mPRStack.elementAt(index);
- //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
- if (prse.getRccId() == rccId) {
- rvo = prse.mRemoteVolumeObs;
- break;
- }
- }
- } catch (ArrayIndexOutOfBoundsException e) {
- // not expected to happen, indicates improper concurrent modification
- Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
- }
- }
- if (rvo != null) {
- try {
- rvo.dispatchRemoteVolumeUpdate(0, vol);
- } catch (RemoteException e) {
- Log.e(TAG, "Error dispatching absolute volume update", e);
- }
- }
- }
-
- /**
- * Call to make AudioService reevaluate whether it's in a mode where remote players should
- * have their volume controlled. In this implementation this is only to reset whether
- * VolumePanel should display remote volumes
- */
- protected void postReevaluateRemote() {
- sendMsg(mEventHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0);
- }
-
- private void onReevaluateRemote() {
- // TODO This was used to notify VolumePanel if there was remote playback
- // in the stack. This is now in MediaSessionService. More code should be
- // removed.
- }
-
}
diff --git a/services/core/java/com/android/server/audio/PlayerRecord.java b/services/core/java/com/android/server/audio/PlayerRecord.java
deleted file mode 100644
index e98f12e..0000000
--- a/services/core/java/com/android/server/audio/PlayerRecord.java
+++ /dev/null
@@ -1,360 +0,0 @@
-/*
- * Copyright (C) 2014 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.audio;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.media.AudioManager;
-import android.media.IRemoteControlClient;
-import android.media.IRemoteVolumeObserver;
-import android.media.RemoteControlClient;
-import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.io.PrintWriter;
-
-/**
- * @hide
- * Class to handle all the information about a media player, encapsulating information
- * about its use RemoteControlClient, playback type and volume... The lifecycle of each
- * instance is managed by android.media.MediaFocusControl, from its addition to the player stack
- * stack to its release.
- */
-class PlayerRecord implements DeathRecipient {
-
- // on purpose not using this classe's name, as it will only be used from MediaFocusControl
- private static final String TAG = "MediaFocusControl";
- private static final boolean DEBUG = false;
-
- /**
- * A global counter for RemoteControlClient identifiers
- */
- private static int sLastRccId = 0;
-
- public static MediaFocusControl sController;
-
- /**
- * The target for the ACTION_MEDIA_BUTTON events.
- * Always non null. //FIXME verify
- */
- final private PendingIntent mMediaIntent;
- /**
- * The registered media button event receiver.
- */
- final private ComponentName mReceiverComponent;
-
- private int mRccId = -1;
-
- /**
- * A non-null token implies this record tracks a "live" player whose death is being monitored.
- */
- private IBinder mToken;
- private String mCallingPackageName;
- private int mCallingUid;
- /**
- * Provides access to the information to display on the remote control.
- * May be null (when a media button event receiver is registered,
- * but no remote control client has been registered) */
- private IRemoteControlClient mRcClient;
- private RcClientDeathHandler mRcClientDeathHandler;
- /**
- * Information only used for non-local playback
- */
- //FIXME private?
- public int mPlaybackType;
- public int mPlaybackVolume;
- public int mPlaybackVolumeMax;
- public int mPlaybackVolumeHandling;
- public int mPlaybackStream;
- public RccPlaybackState mPlaybackState;
- public IRemoteVolumeObserver mRemoteVolumeObs;
-
-
- protected static class RccPlaybackState {
- public int mState;
- public long mPositionMs;
- public float mSpeed;
-
- public RccPlaybackState(int state, long positionMs, float speed) {
- mState = state;
- mPositionMs = positionMs;
- mSpeed = speed;
- }
-
- public void reset() {
- mState = RemoteControlClient.PLAYSTATE_STOPPED;
- mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID;
- mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X;
- }
-
- @Override
- public String toString() {
- return stateToString() + ", " + posToString() + ", " + mSpeed + "X";
- }
-
- private String posToString() {
- if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) {
- return "PLAYBACK_POSITION_INVALID";
- } else if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) {
- return "PLAYBACK_POSITION_ALWAYS_UNKNOWN";
- } else {
- return (String.valueOf(mPositionMs) + "ms");
- }
- }
-
- private String stateToString() {
- switch (mState) {
- case RemoteControlClient.PLAYSTATE_NONE:
- return "PLAYSTATE_NONE";
- case RemoteControlClient.PLAYSTATE_STOPPED:
- return "PLAYSTATE_STOPPED";
- case RemoteControlClient.PLAYSTATE_PAUSED:
- return "PLAYSTATE_PAUSED";
- case RemoteControlClient.PLAYSTATE_PLAYING:
- return "PLAYSTATE_PLAYING";
- case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
- return "PLAYSTATE_FAST_FORWARDING";
- case RemoteControlClient.PLAYSTATE_REWINDING:
- return "PLAYSTATE_REWINDING";
- case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
- return "PLAYSTATE_SKIPPING_FORWARDS";
- case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
- return "PLAYSTATE_SKIPPING_BACKWARDS";
- case RemoteControlClient.PLAYSTATE_BUFFERING:
- return "PLAYSTATE_BUFFERING";
- case RemoteControlClient.PLAYSTATE_ERROR:
- return "PLAYSTATE_ERROR";
- default:
- return "[invalid playstate]";
- }
- }
- }
-
-
- /**
- * Inner class to monitor remote control client deaths, and remove the client for the
- * remote control stack if necessary.
- */
- private class RcClientDeathHandler implements IBinder.DeathRecipient {
- final private IBinder mCb; // To be notified of client's death
- //FIXME needed?
- final private PendingIntent mMediaIntent;
-
- RcClientDeathHandler(IBinder cb, PendingIntent pi) {
- mCb = cb;
- mMediaIntent = pi;
- }
-
- public void binderDied() {
- Log.w(TAG, " RemoteControlClient died");
- // remote control client died, make sure the displays don't use it anymore
- // by setting its remote control client to null
- sController.registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/);
- // the dead client was maybe handling remote playback, the controller should reevaluate
- sController.postReevaluateRemote();
- }
-
- public IBinder getBinder() {
- return mCb;
- }
- }
-
-
- protected static class RemotePlaybackState {
- int mRccId;
- int mVolume;
- int mVolumeMax;
- int mVolumeHandling;
-
- protected RemotePlaybackState(int id, int vol, int volMax) {
- mRccId = id;
- mVolume = vol;
- mVolumeMax = volMax;
- mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
- }
- }
-
-
- void dump(PrintWriter pw, boolean registrationInfo) {
- if (registrationInfo) {
- pw.println(" pi: " + mMediaIntent +
- " -- pack: " + mCallingPackageName +
- " -- ercvr: " + mReceiverComponent +
- " -- client: " + mRcClient +
- " -- uid: " + mCallingUid +
- " -- type: " + mPlaybackType +
- " state: " + mPlaybackState);
- } else {
- // emphasis on state
- pw.println(" uid: " + mCallingUid +
- " -- id: " + mRccId +
- " -- type: " + mPlaybackType +
- " -- state: " + mPlaybackState +
- " -- vol handling: " + mPlaybackVolumeHandling +
- " -- vol: " + mPlaybackVolume +
- " -- volMax: " + mPlaybackVolumeMax +
- " -- volObs: " + mRemoteVolumeObs);
- }
- }
-
-
- static protected void setMediaFocusControl(MediaFocusControl mfc) {
- sController = mfc;
- }
-
- /** precondition: mediaIntent != null */
- protected PlayerRecord(PendingIntent mediaIntent, ComponentName eventReceiver, IBinder token)
- {
- mMediaIntent = mediaIntent;
- mReceiverComponent = eventReceiver;
- mToken = token;
- mCallingUid = -1;
- mRcClient = null;
- mRccId = ++sLastRccId;
- mPlaybackState = new RccPlaybackState(
- RemoteControlClient.PLAYSTATE_STOPPED,
- RemoteControlClient.PLAYBACK_POSITION_INVALID,
- RemoteControlClient.PLAYBACK_SPEED_1X);
-
- resetPlaybackInfo();
- if (mToken != null) {
- try {
- mToken.linkToDeath(this, 0);
- } catch (RemoteException e) {
- sController.unregisterMediaButtonIntentAsync(mMediaIntent);
- }
- }
- }
-
- //---------------------------------------------
- // Accessors
- protected int getRccId() {
- return mRccId;
- }
-
- protected IRemoteControlClient getRcc() {
- return mRcClient;
- }
-
- protected ComponentName getMediaButtonReceiver() {
- return mReceiverComponent;
- }
-
- protected PendingIntent getMediaButtonIntent() {
- return mMediaIntent;
- }
-
- protected boolean hasMatchingMediaButtonIntent(PendingIntent pi) {
- if (mToken != null) {
- return mMediaIntent.equals(pi);
- } else {
- if (mReceiverComponent != null) {
- return mReceiverComponent.equals(pi.getIntent().getComponent());
- } else {
- return false;
- }
- }
- }
-
- protected boolean isPlaybackActive() {
- return MediaFocusControl.isPlaystateActive(mPlaybackState.mState);
- }
-
- //---------------------------------------------
- // Modify the records stored in the instance
- protected void resetControllerInfoForRcc(IRemoteControlClient rcClient,
- String callingPackageName, int uid) {
- // already had a remote control client?
- if (mRcClientDeathHandler != null) {
- // stop monitoring the old client's death
- unlinkToRcClientDeath();
- }
- // save the new remote control client
- mRcClient = rcClient;
- mCallingPackageName = callingPackageName;
- mCallingUid = uid;
- if (rcClient == null) {
- // here mcse.mRcClientDeathHandler is null;
- resetPlaybackInfo();
- } else {
- IBinder b = mRcClient.asBinder();
- RcClientDeathHandler rcdh =
- new RcClientDeathHandler(b, mMediaIntent);
- try {
- b.linkToDeath(rcdh, 0);
- } catch (RemoteException e) {
- // remote control client is DOA, disqualify it
- Log.w(TAG, "registerRemoteControlClient() has a dead client " + b);
- mRcClient = null;
- }
- mRcClientDeathHandler = rcdh;
- }
- }
-
- protected void resetControllerInfoForNoRcc() {
- // stop monitoring the RCC death
- unlinkToRcClientDeath();
- // reset the RCC-related fields
- mRcClient = null;
- mCallingPackageName = null;
- }
-
- public void resetPlaybackInfo() {
- mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL;
- mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
- mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME;
- mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING;
- mPlaybackStream = AudioManager.STREAM_MUSIC;
- mPlaybackState.reset();
- mRemoteVolumeObs = null;
- }
-
- //---------------------------------------------
- public void unlinkToRcClientDeath() {
- if ((mRcClientDeathHandler != null) && (mRcClientDeathHandler.mCb != null)) {
- try {
- mRcClientDeathHandler.mCb.unlinkToDeath(mRcClientDeathHandler, 0);
- mRcClientDeathHandler = null;
- } catch (java.util.NoSuchElementException e) {
- // not much we can do here
- Log.e(TAG, "Error in unlinkToRcClientDeath()", e);
- }
- }
- }
-
- // FIXME rename to "release"? (as in FocusRequester class)
- public void destroy() {
- unlinkToRcClientDeath();
- if (mToken != null) {
- mToken.unlinkToDeath(this, 0);
- mToken = null;
- }
- }
-
- @Override
- public void binderDied() {
- sController.unregisterMediaButtonIntentAsync(mMediaIntent);
- }
-
- @Override
- protected void finalize() throws Throwable {
- destroy(); // unlink exception handled inside method
- super.finalize();
- }
-}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 4d33248..543cd89 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -226,9 +226,7 @@
r = new Record();
r.pkg = pkg;
r.uid = uid;
- r.topics.put(Notification.TOPIC_DEFAULT,
- new Topic(new Notification.Topic(Notification.TOPIC_DEFAULT,
- mContext.getString(R.string.default_notification_topic_label))));
+ r.topics.put(Notification.TOPIC_DEFAULT, new Topic(createDefaultTopic()));
mRecords.put(key, r);
}
return r;
@@ -406,6 +404,9 @@
}
private Topic getOrCreateTopic(Record r, Notification.Topic topic) {
+ if (topic == null) {
+ topic = createDefaultTopic();
+ }
Topic t = r.topics.get(topic.getId());
if (t != null) {
return t;
@@ -416,6 +417,11 @@
}
}
+ private Notification.Topic createDefaultTopic() {
+ return new Notification.Topic(Notification.TOPIC_DEFAULT,
+ mContext.getString(R.string.default_notification_topic_label));
+ }
+
public void dump(PrintWriter pw, String prefix, NotificationManagerService.DumpFilter filter) {
if (filter == null) {
final int N = mSignalExtractors.length;
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 4582828..0796811 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -26,6 +26,7 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageInfo;
+import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.graphics.Rect;
@@ -187,11 +188,11 @@
}
@Override
- public List<ResolveInfo> getLauncherActivities(String packageName, UserHandle user)
+ public ParceledListSlice<ResolveInfo> getLauncherActivities(String packageName, UserHandle user)
throws RemoteException {
ensureInUserProfiles(user, "Cannot retrieve activities for unrelated profile " + user);
if (!isUserEnabled(user)) {
- return new ArrayList<ResolveInfo>();
+ return null;
}
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
@@ -201,7 +202,7 @@
try {
List<ResolveInfo> apps = mPm.queryIntentActivitiesAsUser(mainIntent, 0 /* flags */,
user.getIdentifier());
- return apps;
+ return new ParceledListSlice<>(apps);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 203c4b6..c7fccc3 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5717,7 +5717,7 @@
@Override
public void run() {
Bitmap bm = screenshotApplicationsInner(null, Display.DEFAULT_DISPLAY, -1, -1,
- true);
+ true, 1f);
try {
receiver.send(bm);
} catch (RemoteException e) {
@@ -5736,18 +5736,20 @@
* @param displayId the Display to take a screenshot of.
* @param width the width of the target bitmap
* @param height the height of the target bitmap
+ * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1
*/
@Override
- public Bitmap screenshotApplications(IBinder appToken, int displayId, int width, int height) {
+ public Bitmap screenshotApplications(IBinder appToken, int displayId, int width, int height,
+ float frameScale) {
if (!checkCallingPermission(Manifest.permission.READ_FRAME_BUFFER,
"screenshotApplications()")) {
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
}
- return screenshotApplicationsInner(appToken, displayId, width, height, false);
+ return screenshotApplicationsInner(appToken, displayId, width, height, false, frameScale);
}
Bitmap screenshotApplicationsInner(IBinder appToken, int displayId, int width, int height,
- boolean includeFullDisplay) {
+ boolean includeFullDisplay, float frameScale) {
final DisplayContent displayContent;
synchronized(mWindowMap) {
displayContent = getDisplayContentLocked(displayId);
@@ -5927,10 +5929,10 @@
}
if (width < 0) {
- width = frame.width();
+ width = (int) (frame.width() * frameScale);
}
if (height < 0) {
- height = frame.height();
+ height = (int) (frame.height() * frameScale);
}
// Tell surface flinger what part of the image to crop. Take the top
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 4b855ed..8beee73 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -6841,4 +6841,30 @@
final int targetSdkVersion = ai == null ? 0 : ai.targetSdkVersion;
return targetSdkVersion;
}
+
+ @Override
+ public boolean isManagedProfile(ComponentName admin) {
+ synchronized (this) {
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ }
+ final int callingUserId = mInjector.userHandleGetCallingUserId();
+ final UserInfo user;
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ user = mUserManager.getUserInfo(callingUserId);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
+ return user != null && user.isManagedProfile();
+ }
+
+ @Override
+ public boolean isSystemOnlyUser(ComponentName admin) {
+ synchronized (this) {
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ }
+ final int callingUserId = mInjector.userHandleGetCallingUserId();
+ return UserManager.isSplitSystemUser() && callingUserId == UserHandle.USER_SYSTEM;
+ }
+
}
diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h
index 7ea26b3..ab4d284 100644
--- a/tools/aapt2/Diagnostics.h
+++ b/tools/aapt2/Diagnostics.h
@@ -18,7 +18,6 @@
#define AAPT_DIAGNOSTICS_H
#include "Source.h"
-
#include "util/StringPiece.h"
#include "util/Util.h"
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 02fe59c..c2ddb5c 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -711,6 +711,46 @@
}
}
+ Maybe<int32_t> maybeMin, maybeMax;
+
+ if (Maybe<StringPiece16> maybeMinStr = xml::findAttribute(parser, u"min")) {
+ StringPiece16 minStr = util::trimWhitespace(maybeMinStr.value());
+ if (!minStr.empty()) {
+ android::Res_value value;
+ if (android::ResTable::stringToInt(minStr.data(), minStr.size(), &value)) {
+ maybeMin = static_cast<int32_t>(value.data);
+ }
+ }
+
+ if (!maybeMin) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "invalid 'min' value '" << minStr << "'");
+ return false;
+ }
+ }
+
+ if (Maybe<StringPiece16> maybeMaxStr = xml::findAttribute(parser, u"max")) {
+ StringPiece16 maxStr = util::trimWhitespace(maybeMaxStr.value());
+ if (!maxStr.empty()) {
+ android::Res_value value;
+ if (android::ResTable::stringToInt(maxStr.data(), maxStr.size(), &value)) {
+ maybeMax = static_cast<int32_t>(value.data);
+ }
+ }
+
+ if (!maybeMax) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "invalid 'max' value '" << maxStr << "'");
+ return false;
+ }
+ }
+
+ if ((maybeMin || maybeMax) && (typeMask & android::ResTable_map::TYPE_INTEGER) == 0) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "'min' and 'max' can only be used when format='integer'");
+ return false;
+ }
+
struct SymbolComparator {
bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
return a.symbol.name.value() < b.symbol.name.value();
@@ -794,6 +834,13 @@
std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
+ if (maybeMin) {
+ attr->minInt = maybeMin.value();
+ }
+
+ if (maybeMax) {
+ attr->maxInt = maybeMax.value();
+ }
outResource->value = std::move(attr);
return true;
}
@@ -872,6 +919,7 @@
}
transformReferenceFromNamespace(parser, u"", &maybeKey.value());
+ maybeKey.value().setSource(source);
std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
if (!value) {
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index ab16424..6e0812b 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -138,6 +138,22 @@
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
}
+TEST_F(ResourceParserTest, ParseAttrWithMinMax) {
+ std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>";
+ ASSERT_TRUE(testParse(input));
+
+ Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->typeMask);
+ EXPECT_EQ(10, attr->minInt);
+ EXPECT_EQ(23, attr->maxInt);
+}
+
+TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) {
+ std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>";
+ ASSERT_FALSE(testParse(input));
+}
+
TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) {
std::string input = "<declare-styleable name=\"Styleable\">\n"
" <attr name=\"foo\" />\n"
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 5550f19..04c375f 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -15,9 +15,9 @@
*/
#include "Resource.h"
+#include "ResourceUtils.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
-
#include "util/Util.h"
#include "flatten/ResourceTypeExtensions.h"
@@ -216,7 +216,7 @@
*out << "(null)";
break;
case android::Res_value::TYPE_INT_DEC:
- *out << "(integer) " << value.data;
+ *out << "(integer) " << static_cast<int32_t>(value.data);
break;
case android::Res_value::TYPE_INT_HEX:
*out << "(integer) " << std::hex << value.data << std::dec;
@@ -237,7 +237,10 @@
}
}
-Attribute::Attribute(bool w, uint32_t t) : weak(w), typeMask(t) {
+Attribute::Attribute(bool w, uint32_t t) :
+ weak(w), typeMask(t),
+ minInt(std::numeric_limits<int32_t>::min()),
+ maxInt(std::numeric_limits<int32_t>::max()) {
}
bool Attribute::isWeak() const {
@@ -361,6 +364,81 @@
}
}
+static void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr,
+ const Item* value) {
+ *msg << "expected";
+ if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+ *msg << " boolean";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_COLOR) {
+ *msg << " color";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) {
+ *msg << " dimension";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_ENUM) {
+ *msg << " enum";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) {
+ *msg << " flags";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) {
+ *msg << " float";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) {
+ *msg << " fraction";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) {
+ *msg << " integer";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) {
+ *msg << " reference";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_STRING) {
+ *msg << " string";
+ }
+
+ *msg << " but got " << *value;
+}
+
+bool Attribute::matches(const Item* item, DiagMessage* outMsg) const {
+ android::Res_value val = {};
+ item->flatten(&val);
+
+ // Always allow references.
+ const uint32_t mask = typeMask | android::ResTable_map::TYPE_REFERENCE;
+ if (!(mask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
+ if (outMsg) {
+ buildAttributeMismatchMessage(outMsg, this, item);
+ }
+ return false;
+
+ } else if (ResourceUtils::androidTypeToAttributeTypeMask(val.dataType) &
+ android::ResTable_map::TYPE_INTEGER) {
+ if (static_cast<int32_t>(util::deviceToHost32(val.data)) < minInt) {
+ if (outMsg) {
+ *outMsg << *item << " is less than minimum integer " << minInt;
+ }
+ return false;
+ } else if (static_cast<int32_t>(util::deviceToHost32(val.data)) > maxInt) {
+ if (outMsg) {
+ *outMsg << *item << " is greater than maximum integer " << maxInt;
+ }
+ return false;
+ }
+ }
+ return true;
+}
+
Style* Style::clone(StringPool* newPool) const {
Style* style = new Style();
style->parent = parent;
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 7ae346a..a038282 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -17,9 +17,10 @@
#ifndef AAPT_RESOURCE_VALUES_H
#define AAPT_RESOURCE_VALUES_H
-#include "util/Maybe.h"
+#include "Diagnostics.h"
#include "Resource.h"
#include "StringPool.h"
+#include "util/Maybe.h"
#include <array>
#include <androidfw/ResourceTypes.h>
@@ -233,8 +234,8 @@
bool weak;
uint32_t typeMask;
- uint32_t minInt;
- uint32_t maxInt;
+ int32_t minInt;
+ int32_t maxInt;
std::vector<Symbol> symbols;
Attribute(bool w, uint32_t t = 0u);
@@ -243,6 +244,7 @@
Attribute* clone(StringPool* newPool) const override;
void printMask(std::ostream* out) const;
void print(std::ostream* out) const override;
+ bool matches(const Item* item, DiagMessage* outMsg) const;
};
struct Style : public BaseValue<Style> {
diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h
index 8af203c..319528e 100644
--- a/tools/aapt2/Source.h
+++ b/tools/aapt2/Source.h
@@ -58,6 +58,10 @@
return out;
}
+inline bool operator==(const Source& lhs, const Source& rhs) {
+ return lhs.path == rhs.path && lhs.line == rhs.line;
+}
+
inline bool operator<(const Source& lhs, const Source& rhs) {
int cmp = lhs.path.compare(rhs.path);
if (cmp < 0) return true;
diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
index acf5bb5..02bff2c 100644
--- a/tools/aapt2/flatten/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -67,6 +67,28 @@
};
/**
+ * New types for a ResTable_map.
+ */
+struct ExtendedResTableMapTypes {
+ enum {
+ /**
+ * Type that contains the source path of the next item in the map.
+ */
+ ATTR_SOURCE_PATH = Res_MAKEINTERNAL(0xffff),
+
+ /**
+ * Type that contains the source line of the next item in the map.
+ */
+ ATTR_SOURCE_LINE = Res_MAKEINTERNAL(0xfffe),
+
+ /**
+ * Type that contains the comment of the next item in the map.
+ */
+ ATTR_COMMENT = Res_MAKEINTERNAL(0xfffd)
+ };
+};
+
+/**
* Followed by exportedSymbolCount ExportedSymbol structs, followed by the string pool.
*/
struct FileExport_header {
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 636e977..f42e6b7 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -108,15 +108,19 @@
SymbolWriter* mSymbols;
FlatEntry* mEntry;
BigBuffer* mBuffer;
+ StringPool* mSourcePool;
+ StringPool* mCommentPool;
bool mUseExtendedChunks;
+
size_t mEntryCount = 0;
Maybe<uint32_t> mParentIdent;
Maybe<ResourceNameRef> mParentName;
MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer,
+ StringPool* sourcePool, StringPool* commentPool,
bool useExtendedChunks) :
- mSymbols(symbols), mEntry(entry), mBuffer(buffer),
- mUseExtendedChunks(useExtendedChunks) {
+ mSymbols(symbols), mEntry(entry), mBuffer(buffer), mSourcePool(sourcePool),
+ mCommentPool(commentPool), mUseExtendedChunks(useExtendedChunks) {
}
void flattenKey(Reference* key, ResTable_map* outEntry) {
@@ -158,6 +162,32 @@
mEntryCount++;
}
+ void flattenMetaData(Value* value) {
+ if (!mUseExtendedChunks) {
+ return;
+ }
+
+ Reference key(ResourceId{ ExtendedResTableMapTypes::ATTR_SOURCE_PATH });
+ StringPool::Ref sourcePathRef = mSourcePool->makeRef(
+ util::utf8ToUtf16(value->getSource().path));
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC,
+ static_cast<uint32_t>(sourcePathRef.getIndex()));
+ flattenEntry(&key, &val);
+
+ if (value->getSource().line) {
+ key.id = ResourceId(ExtendedResTableMapTypes::ATTR_SOURCE_LINE);
+ val.value.data = static_cast<uint32_t>(value->getSource().line.value());
+ flattenEntry(&key, &val);
+ }
+
+ if (!value->getComment().empty()) {
+ key.id = ResourceId(ExtendedResTableMapTypes::ATTR_COMMENT);
+ StringPool::Ref commentRef = mCommentPool->makeRef(value->getComment());
+ val.value.data = static_cast<uint32_t>(commentRef.getIndex());
+ flattenEntry(&key, &val);
+ }
+ }
+
void visit(Attribute* attr) override {
{
Reference key(ResourceId{ ResTable_map::ATTR_TYPE });
@@ -165,6 +195,18 @@
flattenEntry(&key, &val);
}
+ if (attr->minInt != std::numeric_limits<int32_t>::min()) {
+ Reference key(ResourceId{ ResTable_map::ATTR_MIN });
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->minInt));
+ flattenEntry(&key, &val);
+ }
+
+ if (attr->maxInt != std::numeric_limits<int32_t>::max()) {
+ Reference key(ResourceId{ ResTable_map::ATTR_MAX });
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->maxInt));
+ flattenEntry(&key, &val);
+ }
+
for (Attribute::Symbol& s : attr->symbols) {
BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
flattenEntry(&s.symbol, &val);
@@ -199,6 +241,7 @@
for (Style::Entry& entry : style->entries) {
flattenEntry(&entry.key, entry.value.get());
+ flattenMetaData(&entry.key);
}
}
@@ -206,6 +249,7 @@
for (auto& attrRef : styleable->entries) {
BinaryPrimitive val(Res_value{});
flattenEntry(&attrRef, &val);
+ flattenMetaData(&attrRef);
}
}
@@ -215,6 +259,7 @@
flattenValue(item.get(), outEntry);
outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
mEntryCount++;
+ flattenMetaData(item.get());
}
}
@@ -258,6 +303,7 @@
Reference key(q);
flattenEntry(&key, plural->values[i].get());
+ flattenMetaData(plural->values[i].get());
}
}
};
@@ -377,7 +423,8 @@
} else {
const size_t beforeEntry = buffer->size();
ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer);
- MapFlattenVisitor visitor(mSymbols, entry, buffer, mOptions.useExtendedChunks);
+ MapFlattenVisitor visitor(mSymbols, entry, buffer, mSourcePool, mSourcePool,
+ mOptions.useExtendedChunks);
entry->value->accept(&visitor);
outEntry->count = util::hostToDevice32(visitor.mEntryCount);
if (visitor.mParentName) {
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
index 4ffb980..7030603 100644
--- a/tools/aapt2/flatten/TableFlattener_test.cpp
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -262,4 +262,55 @@
EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@com.app.test:color/green"));
}
+TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
+ Attribute attr(false);
+ attr.typeMask = android::ResTable_map::TYPE_INTEGER;
+ attr.minInt = 10;
+ attr.maxInt = 23;
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addValue(u"@android:attr/foo", ResourceId(0x01010000),
+ util::make_unique<Attribute>(attr))
+ .build();
+
+ ResourceTable result;
+ ASSERT_TRUE(flatten(table.get(), &result));
+
+ Attribute* actualAttr = test::getValue<Attribute>(&result, u"@android:attr/foo");
+ ASSERT_NE(nullptr, actualAttr);
+ EXPECT_EQ(attr.isWeak(), actualAttr->isWeak());
+ EXPECT_EQ(attr.typeMask, actualAttr->typeMask);
+ EXPECT_EQ(attr.minInt, actualAttr->minInt);
+ EXPECT_EQ(attr.maxInt, actualAttr->maxInt);
+}
+
+TEST_F(TableFlattenerTest, FlattenSourceAndCommentsForChildrenOfCompoundValues) {
+ Style style;
+ Reference key(test::parseNameOrDie(u"@android:attr/foo"));
+ key.id = ResourceId(0x01010000);
+ key.setSource(Source("test").withLine(2));
+ key.setComment(StringPiece16(u"comment"));
+ style.entries.push_back(Style::Entry{ key, util::make_unique<Id>() });
+
+ test::ResourceTableBuilder builder = test::ResourceTableBuilder();
+ std::unique_ptr<ResourceTable> table = builder
+ .setPackageId(u"android", 0x01)
+ .addValue(u"@android:attr/foo", ResourceId(0x01010000),
+ test::AttributeBuilder().build())
+ .addValue(u"@android:style/foo", ResourceId(0x01020000),
+ std::unique_ptr<Style>(style.clone(builder.getStringPool())))
+ .build();
+
+ ResourceTable result;
+ ASSERT_TRUE(flatten(table.get(), &result));
+
+ Style* actualStyle = test::getValue<Style>(&result, u"@android:style/foo");
+ ASSERT_NE(nullptr, actualStyle);
+ ASSERT_EQ(1u, actualStyle->entries.size());
+
+ Reference* actualKey = &actualStyle->entries[0].key;
+ EXPECT_EQ(key.getSource(), actualKey->getSource());
+ EXPECT_EQ(key.getComment(), actualKey->getComment());
+}
+
} // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 4b82537..2743539 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -78,52 +78,6 @@
return value;
}
- void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr,
- const Item* value) {
- *msg << "expected";
- if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) {
- *msg << " boolean";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_COLOR) {
- *msg << " color";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) {
- *msg << " dimension";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_ENUM) {
- *msg << " enum";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) {
- *msg << " flags";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) {
- *msg << " float";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) {
- *msg << " fraction";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) {
- *msg << " integer";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) {
- *msg << " reference";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_STRING) {
- *msg << " string";
- }
-
- *msg << " but got " << *value;
- }
-
public:
using ValueVisitor::visit;
@@ -175,32 +129,25 @@
entry.value->accept(this);
// Now verify that the type of this item is compatible with the attribute it
- // is defined for.
- android::Res_value val = {};
- entry.value->flatten(&val);
-
- // Always allow references.
- const uint32_t typeMask = symbol->attribute->typeMask |
- android::ResTable_map::TYPE_REFERENCE;
-
- if (!(typeMask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
+ // is defined for. We pass `nullptr` as the DiagMessage so that this check is
+ // fast and we avoid creating a DiagMessage when the match is successful.
+ if (!symbol->attribute->matches(entry.value.get(), nullptr)) {
// The actual type of this item is incompatible with the attribute.
- DiagMessage msg(style->getSource());
- buildAttributeMismatchMessage(&msg, symbol->attribute.get(), entry.value.get());
+ DiagMessage msg(entry.key.getSource());
+
+ // Call the matches method again, this time with a DiagMessage so we fill
+ // in the actual error message.
+ symbol->attribute->matches(entry.value.get(), &msg);
mContext->getDiagnostics()->error(msg);
mError = true;
}
+
} else {
- DiagMessage msg(style->getSource());
+ DiagMessage msg(entry.key.getSource());
msg << "style attribute '";
- if (entry.key.name) {
- msg << entry.key.name.value().package << ":" << entry.key.name.value().entry;
- } else {
- msg << entry.key.id.value();
- }
+ ReferenceLinker::writeResourceName(&msg, entry.key, transformedReference);
msg << "' " << errStr;
mContext->getDiagnostics()->error(msg);
- mContext->getDiagnostics()->note(DiagMessage(style->getSource()) << entry.key);
mError = true;
}
}
@@ -249,16 +196,12 @@
CallSite* callSite, std::string* outError) {
const ISymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols);
if (!symbol) {
- std::stringstream errStr;
- errStr << "not found";
- if (outError) *outError = errStr.str();
+ if (outError) *outError = "not found";
return nullptr;
}
if (!isSymbolVisible(*symbol, reference, *callSite)) {
- std::stringstream errStr;
- errStr << "is private";
- if (outError) *outError = errStr.str();
+ if (outError) *outError = "is private";
return nullptr;
}
return symbol;
@@ -275,9 +218,7 @@
}
if (!symbol->attribute) {
- std::stringstream errStr;
- errStr << "is not an attribute";
- if (outError) *outError = errStr.str();
+ if (outError) *outError = "is not an attribute";
return nullptr;
}
return symbol;
@@ -294,14 +235,26 @@
}
if (!symbol->attribute) {
- std::stringstream errStr;
- errStr << "is not an attribute";
- if (outError) *outError = errStr.str();
+ if (outError) *outError = "is not an attribute";
return {};
}
return xml::AaptAttribute{ symbol->id, *symbol->attribute };
}
+void ReferenceLinker::writeResourceName(DiagMessage* outMsg, const Reference& orig,
+ const Reference& transformed) {
+ assert(outMsg);
+
+ if (orig.name) {
+ *outMsg << orig.name.value();
+ if (transformed.name.value() != orig.name.value()) {
+ *outMsg << " (aka " << transformed.name.value() << ")";
+ }
+ } else {
+ *outMsg << orig.id.value();
+ }
+}
+
bool ReferenceLinker::linkReference(Reference* reference, IAaptContext* context,
ISymbolTable* symbols, xml::IPackageDeclStack* decls,
CallSite* callSite) {
@@ -322,15 +275,7 @@
DiagMessage errorMsg(reference->getSource());
errorMsg << "resource ";
- if (reference->name) {
- errorMsg << reference->name.value();
- if (transformedReference.name.value() != reference->name.value()) {
- errorMsg << " (aka " << transformedReference.name.value() << ")";
- }
- } else {
- errorMsg << reference->id.value();
- }
-
+ writeResourceName(&errorMsg, *reference, transformedReference);
errorMsg << " " << errStr;
context->getDiagnostics()->error(errorMsg);
return false;
diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h
index 6f11d58..a0eb00c 100644
--- a/tools/aapt2/link/ReferenceLinker.h
+++ b/tools/aapt2/link/ReferenceLinker.h
@@ -80,6 +80,13 @@
std::string* outError);
/**
+ * Writes the resource name to the DiagMessage, using the "orig_name (aka <transformed_name>)"
+ * syntax.
+ */
+ static void writeResourceName(DiagMessage* outMsg, const Reference& orig,
+ const Reference& transformed);
+
+ /**
* Transforms the package name of the reference to the fully qualified package name using
* the xml::IPackageDeclStack, then mangles and looks up the symbol. If the symbol is visible
* to the reference at the callsite, the reference is updated with an ID.
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index d04181d..6ad2f9c 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -61,7 +61,7 @@
if (iter != sr.entry->values.end() && iter->config == kDefaultConfig) {
// This resource has an Attribute.
if (Attribute* attr = valueCast<Attribute>(iter->value.get())) {
- symbol->attribute = std::unique_ptr<Attribute>(attr->clone(nullptr));
+ symbol->attribute = util::make_unique<Attribute>(*attr);
} else {
return {};
}
@@ -77,7 +77,6 @@
return symbol.get();
}
-
static std::shared_ptr<ISymbolTable::Symbol> lookupAttributeInTable(const android::ResTable& table,
ResourceId id) {
// Try as a bag.
@@ -103,29 +102,40 @@
if (s->attribute) {
for (size_t i = 0; i < (size_t) count; i++) {
- if (!Res_INTERNALID(entry[i].map.name.ident)) {
- android::ResTable::resource_name entryName;
- if (!table.getResourceName(entry[i].map.name.ident, false, &entryName)) {
- table.unlockBag(entry);
- return nullptr;
+ const android::ResTable_map& mapEntry = entry[i].map;
+ if (Res_INTERNALID(mapEntry.name.ident)) {
+ switch (mapEntry.name.ident) {
+ case android::ResTable_map::ATTR_MIN:
+ s->attribute->minInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
+ case android::ResTable_map::ATTR_MAX:
+ s->attribute->maxInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
}
-
- const ResourceType* parsedType = parseResourceType(
- StringPiece16(entryName.type, entryName.typeLen));
- if (!parsedType) {
- table.unlockBag(entry);
- return nullptr;
- }
-
- Attribute::Symbol symbol;
- symbol.symbol.name = ResourceNameRef(
- StringPiece16(entryName.package, entryName.packageLen),
- *parsedType,
- StringPiece16(entryName.name, entryName.nameLen)).toResourceName();
- symbol.symbol.id = ResourceId(entry[i].map.name.ident);
- symbol.value = entry[i].map.value.data;
- s->attribute->symbols.push_back(std::move(symbol));
+ continue;
}
+
+ android::ResTable::resource_name entryName;
+ if (!table.getResourceName(mapEntry.name.ident, false, &entryName)) {
+ table.unlockBag(entry);
+ return nullptr;
+ }
+
+ const ResourceType* parsedType = parseResourceType(
+ StringPiece16(entryName.type, entryName.typeLen));
+ if (!parsedType) {
+ table.unlockBag(entry);
+ return nullptr;
+ }
+
+ Attribute::Symbol symbol;
+ symbol.symbol.name = ResourceName(
+ StringPiece16(entryName.package, entryName.packageLen),
+ *parsedType,
+ StringPiece16(entryName.name, entryName.nameLen));
+ symbol.symbol.id = ResourceId(mapEntry.name.ident);
+ symbol.value = mapEntry.value.data;
+ s->attribute->symbols.push_back(std::move(symbol));
}
}
table.unlockBag(entry);
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 9ca694a..f8e3d03 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -36,6 +36,10 @@
public:
ResourceTableBuilder() = default;
+ StringPool* getStringPool() {
+ return &mTable->stringPool;
+ }
+
ResourceTableBuilder& setPackageId(const StringPiece16& packageName, uint8_t id) {
ResourceTablePackage* package = mTable->createPackage(packageName, id);
assert(package);
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 49625b5..5cc7aa7 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -575,11 +575,10 @@
Source source = mSource;
if (sourceBlock) {
- size_t len;
- const char* str = mSourcePool.string8At(util::deviceToHost32(sourceBlock->path.index),
- &len);
- if (str) {
- source.path.assign(str, len);
+ StringPiece path = util::getString8(mSourcePool,
+ util::deviceToHost32(sourceBlock->path.index));
+ if (!path.empty()) {
+ source.path = path.toString();
}
source.line = util::deviceToHost32(sourceBlock->line);
}
@@ -728,6 +727,16 @@
}
for (const ResTable_map& mapEntry : map) {
+ if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
+ if (style->entries.empty()) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "out-of-sequence meta data in style");
+ return {};
+ }
+ collectMetaData(mapEntry, &style->entries.back().key);
+ continue;
+ }
+
style->entries.emplace_back();
Style::Entry& styleEntry = style->entries.back();
@@ -771,12 +780,20 @@
attr->typeMask = util::deviceToHost32(typeMaskIter->value.data);
}
- if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
- for (const ResTable_map& mapEntry : map) {
- if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
- continue;
+ for (const ResTable_map& mapEntry : map) {
+ if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
+ switch (util::deviceToHost32(mapEntry.name.ident)) {
+ case ResTable_map::ATTR_MIN:
+ attr->minInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
+ case ResTable_map::ATTR_MAX:
+ attr->maxInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
}
+ continue;
+ }
+ if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
Attribute::Symbol symbol;
symbol.value = util::deviceToHost32(mapEntry.value.data);
if (util::deviceToHost32(mapEntry.name.ident) == 0) {
@@ -799,15 +816,57 @@
}
}
- // TODO(adamlesinski): Find min, max, i80n, etc attributes.
+ // TODO(adamlesinski): Find i80n, attributes.
return attr;
}
+static bool isMetaDataEntry(const ResTable_map& mapEntry) {
+ switch (util::deviceToHost32(mapEntry.name.ident)) {
+ case ExtendedResTableMapTypes::ATTR_SOURCE_PATH:
+ case ExtendedResTableMapTypes::ATTR_SOURCE_LINE:
+ case ExtendedResTableMapTypes::ATTR_COMMENT:
+ return true;
+ }
+ return false;
+}
+
+bool BinaryResourceParser::collectMetaData(const ResTable_map& mapEntry, Value* value) {
+ switch (util::deviceToHost32(mapEntry.name.ident)) {
+ case ExtendedResTableMapTypes::ATTR_SOURCE_PATH:
+ value->setSource(Source(util::getString8(mSourcePool,
+ util::deviceToHost32(mapEntry.value.data))));
+ return true;
+ break;
+
+ case ExtendedResTableMapTypes::ATTR_SOURCE_LINE:
+ value->setSource(value->getSource().withLine(util::deviceToHost32(mapEntry.value.data)));
+ return true;
+ break;
+
+ case ExtendedResTableMapTypes::ATTR_COMMENT:
+ value->setComment(util::getString(mSourcePool, util::deviceToHost32(mapEntry.value.data)));
+ return true;
+ break;
+ }
+ return false;
+}
+
std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name,
const ConfigDescription& config,
const ResTable_map_entry* map) {
std::unique_ptr<Array> array = util::make_unique<Array>();
+ Source source;
for (const ResTable_map& mapEntry : map) {
+ if (isMetaDataEntry(mapEntry)) {
+ if (array->items.empty()) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "out-of-sequence meta data in array");
+ return {};
+ }
+ collectMetaData(mapEntry, array->items.back().get());
+ continue;
+ }
+
array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
}
return array;
@@ -818,6 +877,16 @@
const ResTable_map_entry* map) {
std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
for (const ResTable_map& mapEntry : map) {
+ if (isMetaDataEntry(mapEntry)) {
+ if (styleable->entries.empty()) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "out-of-sequence meta data in styleable");
+ return {};
+ }
+ collectMetaData(mapEntry, &styleable->entries.back());
+ continue;
+ }
+
if (util::deviceToHost32(mapEntry.name.ident) == 0) {
// The map entry's key (attribute) is not set. This must be
// a symbol reference, so resolve it.
@@ -841,29 +910,42 @@
const ConfigDescription& config,
const ResTable_map_entry* map) {
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+ Item* lastEntry = nullptr;
for (const ResTable_map& mapEntry : map) {
+ if (isMetaDataEntry(mapEntry)) {
+ if (!lastEntry) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "out-of-sequence meta data in plural");
+ return {};
+ }
+ collectMetaData(mapEntry, lastEntry);
+ continue;
+ }
+
std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
if (!item) {
return {};
}
+ lastEntry = item.get();
+
switch (util::deviceToHost32(mapEntry.name.ident)) {
- case android::ResTable_map::ATTR_ZERO:
+ case ResTable_map::ATTR_ZERO:
plural->values[Plural::Zero] = std::move(item);
break;
- case android::ResTable_map::ATTR_ONE:
+ case ResTable_map::ATTR_ONE:
plural->values[Plural::One] = std::move(item);
break;
- case android::ResTable_map::ATTR_TWO:
+ case ResTable_map::ATTR_TWO:
plural->values[Plural::Two] = std::move(item);
break;
- case android::ResTable_map::ATTR_FEW:
+ case ResTable_map::ATTR_FEW:
plural->values[Plural::Few] = std::move(item);
break;
- case android::ResTable_map::ATTR_MANY:
+ case ResTable_map::ATTR_MANY:
plural->values[Plural::Many] = std::move(item);
break;
- case android::ResTable_map::ATTR_OTHER:
+ case ResTable_map::ATTR_OTHER:
plural->values[Plural::Other] = std::move(item);
break;
}
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
index 73fcf52..0745a59 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -91,6 +91,13 @@
const ConfigDescription& config,
const android::ResTable_map_entry* map);
+ /**
+ * If the mapEntry is a special type that denotes meta data (source, comment), then it is
+ * read and added to the Value.
+ * Returns true if the mapEntry was meta data.
+ */
+ bool collectMetaData(const android::ResTable_map& mapEntry, Value* value);
+
IAaptContext* mContext;
ResourceTable* mTable;
diff --git a/tools/aapt2/util/Maybe.h b/tools/aapt2/util/Maybe.h
index 1f7d5ce..aa409ea 100644
--- a/tools/aapt2/util/Maybe.h
+++ b/tools/aapt2/util/Maybe.h
@@ -275,6 +275,29 @@
return Maybe<T>();
}
+/**
+ * Define the == operator between Maybe<T> and Maybe<U> if the operator T == U is defined.
+ * Otherwise this won't be defined and the compiler will yell at the callsite instead of inside
+ * Maybe.h.
+ */
+template <typename T, typename U>
+auto operator==(const Maybe<T>& a, const Maybe<U>& b)
+-> decltype(std::declval<T> == std::declval<U>) {
+ if (a && b) {
+ return a.value() == b.value();
+ }
+ return false;
+}
+
+/**
+ * Same as operator== but negated.
+ */
+template <typename T, typename U>
+auto operator!=(const Maybe<T>& a, const Maybe<U>& b)
+-> decltype(std::declval<T> == std::declval<U>) {
+ return !(a == b);
+}
+
} // namespace aapt
#endif // AAPT_MAYBE_H
diff --git a/tools/aapt2/util/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp
index d2c33ca..9cca40e 100644
--- a/tools/aapt2/util/Maybe_test.cpp
+++ b/tools/aapt2/util/Maybe_test.cpp
@@ -119,4 +119,14 @@
}
}
+TEST(MaybeTest, Equality) {
+ Maybe<int> a = 1;
+ Maybe<int> b = 1;
+ Maybe<int> c;
+
+ EXPECT_EQ(a, b);
+ EXPECT_EQ(b, a);
+ EXPECT_NE(a, c);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index a898619..0dacbd7 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -158,6 +158,15 @@
return StringPiece16();
}
+inline StringPiece getString8(const android::ResStringPool& pool, size_t idx) {
+ size_t len;
+ const char* str = pool.string8At(idx, &len);
+ if (str != nullptr) {
+ return StringPiece(str, len);
+ }
+ return StringPiece();
+}
+
/**
* Checks that the Java string format contains no non-positional arguments (arguments without
* explicitly specifying an index) when there are more than one argument. This is an error
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 3c260a8..6951ede 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -298,7 +298,7 @@
@Override
public Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth,
- int maxHeight) throws RemoteException {
+ int maxHeight, float frameScale) throws RemoteException {
// TODO Auto-generated method stub
return null;
}