Merge "Revert "Fix broken build""
diff --git a/Android.mk b/Android.mk
index 71bba0f..9d2ca0d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -281,6 +281,7 @@
core/java/com/android/internal/app/IAppOpsService.aidl \
core/java/com/android/internal/app/IAssistScreenshotReceiver.aidl \
core/java/com/android/internal/app/IBatteryStats.aidl \
+ core/java/com/android/internal/app/IEphemeralResolver.aidl \
core/java/com/android/internal/app/IProcessStats.aidl \
core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl \
core/java/com/android/internal/app/IVoiceInteractionSessionShowCallback.aidl \
@@ -417,6 +418,8 @@
packages/services/PacProcessor/com/android/net/IProxyService.aidl \
packages/services/Proxy/com/android/net/IProxyCallback.aidl \
packages/services/Proxy/com/android/net/IProxyPortListener.aidl \
+ core/java/android/service/quicksettings/IQSService.aidl \
+ core/java/android/service/quicksettings/IQSTileService.aidl \
# FRAMEWORKS_BASE_JAVA_SRC_DIRS comes from build/core/pathmap.mk
LOCAL_AIDL_INCLUDES += $(FRAMEWORKS_BASE_JAVA_SRC_DIRS)
@@ -625,6 +628,7 @@
frameworks/base/core/java/android/bluetooth/le/ScanResult.aidl \
frameworks/base/core/java/android/bluetooth/BluetoothDevice.aidl \
frameworks/base/core/java/android/database/CursorWindow.aidl \
+ frameworks/base/core/java/android/service/quicksettings/Tile.aidl \
gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/framework.aidl
$(gen): PRIVATE_SRC_FILES := $(aidl_files)
diff --git a/api/current.txt b/api/current.txt
index 1a7139c..a5b7fa8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -30,6 +30,7 @@
field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
+ field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
@@ -2732,6 +2733,7 @@
method public abstract java.lang.String getAuthTokenLabel(java.lang.String);
method public final android.os.IBinder getIBinder();
method public abstract android.os.Bundle hasFeatures(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String[]) throws android.accounts.NetworkErrorException;
+ method public android.os.Bundle startAddAccountSession(android.accounts.AccountAuthenticatorResponse, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle) throws android.accounts.NetworkErrorException;
method public abstract android.os.Bundle updateCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
field public static final java.lang.String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry";
}
@@ -2796,6 +2798,7 @@
method public void setAuthToken(android.accounts.Account, java.lang.String, java.lang.String);
method public void setPassword(android.accounts.Account, java.lang.String);
method public void setUserData(android.accounts.Account, java.lang.String, java.lang.String);
+ method public android.accounts.AccountManagerFuture<android.os.Bundle> startAddAccountSession(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public android.accounts.AccountManagerFuture<android.os.Bundle> updateCredentials(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
field public static final java.lang.String ACTION_AUTHENTICATOR_INTENT = "android.accounts.AccountAuthenticator";
field public static final java.lang.String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
@@ -2812,6 +2815,8 @@
field public static final java.lang.String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse";
field public static final java.lang.String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse";
field public static final java.lang.String KEY_ACCOUNT_NAME = "authAccount";
+ field public static final java.lang.String KEY_ACCOUNT_SESSION_BUNDLE = "accountSessionBundle";
+ field public static final java.lang.String KEY_ACCOUNT_STATUS_TOKEN = "accountStatusToken";
field public static final java.lang.String KEY_ACCOUNT_TYPE = "accountType";
field public static final java.lang.String KEY_ANDROID_PACKAGE_NAME = "androidPackageName";
field public static final java.lang.String KEY_AUTHENTICATOR_TYPES = "authenticator_types";
@@ -3495,6 +3500,7 @@
method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
method public void openContextMenu(android.view.View);
method public void openOptionsMenu();
+ method public void overlayWithDecorCaption(boolean);
method public void overridePendingTransition(int, int);
method public void postponeEnterTransition();
method public void recreate();
@@ -3773,6 +3779,8 @@
}
public class ActivityOptions {
+ method public android.graphics.Rect getLaunchBounds();
+ method public boolean hasLaunchBounds();
method public static android.app.ActivityOptions makeBasic();
method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
@@ -3782,6 +3790,7 @@
method public static android.app.ActivityOptions makeTaskLaunchBehind();
method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
method public void requestUsageTimeReport(android.app.PendingIntent);
+ method public android.app.ActivityOptions setLaunchBounds(android.graphics.Rect);
method public android.os.Bundle toBundle();
method public void update(android.app.ActivityOptions);
field public static final java.lang.String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
@@ -29043,6 +29052,35 @@
}
+package android.service.quicksettings {
+
+ public final class Tile implements android.os.Parcelable {
+ method public int describeContents();
+ method public java.lang.CharSequence getContentDescription();
+ method public android.graphics.drawable.Icon getIcon();
+ method public java.lang.CharSequence getLabel();
+ method public void setContentDescription(java.lang.CharSequence);
+ method public void setIcon(android.graphics.drawable.Icon);
+ method public void setLabel(java.lang.CharSequence);
+ method public void updateTile();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.quicksettings.Tile> CREATOR;
+ }
+
+ public class TileService extends android.app.Service {
+ ctor public TileService();
+ method public final android.service.quicksettings.Tile getQsTile();
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public void onClick();
+ method public void onStartListening();
+ method public void onStopListening();
+ method public void onTileAdded();
+ method public void onTileRemoved();
+ field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
+ }
+
+}
+
package android.service.restrictions {
public abstract class RestrictionsReceiver extends android.content.BroadcastReceiver {
@@ -36238,7 +36276,6 @@
method public boolean canResolveTextDirection();
method public boolean canScrollHorizontally(int);
method public boolean canScrollVertically(int);
- method public final void cancelDrag();
method public void cancelLongPress();
method public final void cancelPendingInputEvents();
method public boolean checkInputConnectionProxy(android.view.View);
@@ -36256,6 +36293,7 @@
method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo();
method public void createContextMenu(android.view.ContextMenu);
method public void destroyDrawingCache();
+ method public final boolean didLayoutParamsChange();
method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
method public void dispatchConfigurationChanged(android.content.res.Configuration);
method public void dispatchDisplayHint(int);
@@ -36481,6 +36519,7 @@
method public boolean isOpaque();
method protected boolean isPaddingOffsetRequired();
method public boolean isPaddingRelative();
+ method public final boolean isPartialLayoutRequested();
method public boolean isPressed();
method public boolean isSaveEnabled();
method public boolean isSaveFromParentEnabled();
@@ -37090,6 +37129,7 @@
method protected void dispatchThawSelfOnly(android.util.SparseArray<android.os.Parcelable>);
method protected boolean drawChild(android.graphics.Canvas, android.view.View, long);
method public void endViewTransition(android.view.View);
+ method public int findDependentLayoutAxes(android.view.View, int);
method public android.view.View focusSearch(android.view.View, int);
method public void focusableViewAvailable(android.view.View);
method public boolean gatherTransparentRegion(android.graphics.Region);
@@ -37156,6 +37196,8 @@
method public void requestChildFocus(android.view.View, android.view.View);
method public boolean requestChildRectangleOnScreen(android.view.View, android.graphics.Rect, boolean);
method public void requestDisallowInterceptTouchEvent(boolean);
+ method public void requestLayoutForChild(android.view.View);
+ method public void requestPartialLayoutForChild(android.view.View);
method public boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
method public void requestTransparentRegion(android.view.View);
method public void scheduleLayoutAnimation();
@@ -37270,6 +37312,7 @@
method public abstract void childHasTransientStateChanged(android.view.View, boolean);
method public abstract void clearChildFocus(android.view.View);
method public abstract void createContextMenu(android.view.ContextMenu);
+ method public abstract int findDependentLayoutAxes(android.view.View, int);
method public abstract android.view.View focusSearch(android.view.View, int);
method public abstract void focusableViewAvailable(android.view.View);
method public abstract boolean getChildVisibleRect(android.view.View, android.graphics.Rect, android.graphics.Point);
@@ -37299,12 +37342,16 @@
method public abstract void requestDisallowInterceptTouchEvent(boolean);
method public abstract void requestFitSystemWindows();
method public abstract void requestLayout();
+ method public abstract void requestLayoutForChild(android.view.View);
method public abstract boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
method public abstract void requestTransparentRegion(android.view.View);
method public abstract boolean showContextMenuForChild(android.view.View);
method public abstract boolean showContextMenuForChild(android.view.View, float, float);
method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback);
method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback, int);
+ field public static final int FLAG_LAYOUT_AXIS_ANY = 3; // 0x3
+ field public static final int FLAG_LAYOUT_AXIS_HORIZONTAL = 1; // 0x1
+ field public static final int FLAG_LAYOUT_AXIS_VERTICAL = 2; // 0x2
}
public class ViewPropertyAnimator {
diff --git a/api/system-current.txt b/api/system-current.txt
index f2784410..ac13222 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -43,6 +43,7 @@
field public static final java.lang.String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
field public static final java.lang.String BIND_NOTIFICATION_LISTENER_SERVICE = "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
+ field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
@@ -2831,6 +2832,7 @@
method public abstract java.lang.String getAuthTokenLabel(java.lang.String);
method public final android.os.IBinder getIBinder();
method public abstract android.os.Bundle hasFeatures(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String[]) throws android.accounts.NetworkErrorException;
+ method public android.os.Bundle startAddAccountSession(android.accounts.AccountAuthenticatorResponse, java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle) throws android.accounts.NetworkErrorException;
method public abstract android.os.Bundle updateCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
field public static final java.lang.String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry";
}
@@ -2895,6 +2897,7 @@
method public void setAuthToken(android.accounts.Account, java.lang.String, java.lang.String);
method public void setPassword(android.accounts.Account, java.lang.String);
method public void setUserData(android.accounts.Account, java.lang.String, java.lang.String);
+ method public android.accounts.AccountManagerFuture<android.os.Bundle> startAddAccountSession(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
method public android.accounts.AccountManagerFuture<android.os.Bundle> updateCredentials(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
field public static final java.lang.String ACTION_AUTHENTICATOR_INTENT = "android.accounts.AccountAuthenticator";
field public static final java.lang.String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";
@@ -2911,6 +2914,8 @@
field public static final java.lang.String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse";
field public static final java.lang.String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse";
field public static final java.lang.String KEY_ACCOUNT_NAME = "authAccount";
+ field public static final java.lang.String KEY_ACCOUNT_SESSION_BUNDLE = "accountSessionBundle";
+ field public static final java.lang.String KEY_ACCOUNT_STATUS_TOKEN = "accountStatusToken";
field public static final java.lang.String KEY_ACCOUNT_TYPE = "accountType";
field public static final java.lang.String KEY_ANDROID_PACKAGE_NAME = "androidPackageName";
field public static final java.lang.String KEY_AUTHENTICATOR_TYPES = "authenticator_types";
@@ -3598,6 +3603,7 @@
method public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
method public void openContextMenu(android.view.View);
method public void openOptionsMenu();
+ method public void overlayWithDecorCaption(boolean);
method public void overridePendingTransition(int, int);
method public void postponeEnterTransition();
method public void recreate();
@@ -3882,6 +3888,8 @@
}
public class ActivityOptions {
+ method public android.graphics.Rect getLaunchBounds();
+ method public boolean hasLaunchBounds();
method public static android.app.ActivityOptions makeBasic();
method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
@@ -3891,6 +3899,7 @@
method public static android.app.ActivityOptions makeTaskLaunchBehind();
method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
method public void requestUsageTimeReport(android.app.PendingIntent);
+ method public android.app.ActivityOptions setLaunchBounds(android.graphics.Rect);
method public android.os.Bundle toBundle();
method public void update(android.app.ActivityOptions);
field public static final java.lang.String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
@@ -8515,6 +8524,7 @@
field public static final java.lang.String ACTION_INPUT_METHOD_CHANGED = "android.intent.action.INPUT_METHOD_CHANGED";
field public static final java.lang.String ACTION_INSERT = "android.intent.action.INSERT";
field public static final java.lang.String ACTION_INSERT_OR_EDIT = "android.intent.action.INSERT_OR_EDIT";
+ field public static final java.lang.String ACTION_INSTALL_EPHEMERAL_PACKAGE = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE";
field public static final java.lang.String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE";
field public static final java.lang.String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
field public static final java.lang.String ACTION_LOCALE_CHANGED = "android.intent.action.LOCALE_CHANGED";
@@ -8562,6 +8572,7 @@
field public static final java.lang.String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
field public static final java.lang.String ACTION_QUICK_CLOCK = "android.intent.action.QUICK_CLOCK";
field public static final java.lang.String ACTION_REBOOT = "android.intent.action.REBOOT";
+ field public static final java.lang.String ACTION_RESOLVE_EPHEMERAL_PACKAGE = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE";
field public static final java.lang.String ACTION_RUN = "android.intent.action.RUN";
field public static final java.lang.String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF";
field public static final java.lang.String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON";
@@ -31189,6 +31200,35 @@
}
+package android.service.quicksettings {
+
+ public final class Tile implements android.os.Parcelable {
+ method public int describeContents();
+ method public java.lang.CharSequence getContentDescription();
+ method public android.graphics.drawable.Icon getIcon();
+ method public java.lang.CharSequence getLabel();
+ method public void setContentDescription(java.lang.CharSequence);
+ method public void setIcon(android.graphics.drawable.Icon);
+ method public void setLabel(java.lang.CharSequence);
+ method public void updateTile();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.quicksettings.Tile> CREATOR;
+ }
+
+ public class TileService extends android.app.Service {
+ ctor public TileService();
+ method public final android.service.quicksettings.Tile getQsTile();
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public void onClick();
+ method public void onStartListening();
+ method public void onStopListening();
+ method public void onTileAdded();
+ method public void onTileRemoved();
+ field public static final java.lang.String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
+ }
+
+}
+
package android.service.restrictions {
public abstract class RestrictionsReceiver extends android.content.BroadcastReceiver {
@@ -38559,7 +38599,6 @@
method public boolean canResolveTextDirection();
method public boolean canScrollHorizontally(int);
method public boolean canScrollVertically(int);
- method public final void cancelDrag();
method public void cancelLongPress();
method public final void cancelPendingInputEvents();
method public boolean checkInputConnectionProxy(android.view.View);
@@ -38577,6 +38616,7 @@
method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo();
method public void createContextMenu(android.view.ContextMenu);
method public void destroyDrawingCache();
+ method public final boolean didLayoutParamsChange();
method public android.view.WindowInsets dispatchApplyWindowInsets(android.view.WindowInsets);
method public void dispatchConfigurationChanged(android.content.res.Configuration);
method public void dispatchDisplayHint(int);
@@ -38802,6 +38842,7 @@
method public boolean isOpaque();
method protected boolean isPaddingOffsetRequired();
method public boolean isPaddingRelative();
+ method public final boolean isPartialLayoutRequested();
method public boolean isPressed();
method public boolean isSaveEnabled();
method public boolean isSaveFromParentEnabled();
@@ -39411,6 +39452,7 @@
method protected void dispatchThawSelfOnly(android.util.SparseArray<android.os.Parcelable>);
method protected boolean drawChild(android.graphics.Canvas, android.view.View, long);
method public void endViewTransition(android.view.View);
+ method public int findDependentLayoutAxes(android.view.View, int);
method public android.view.View focusSearch(android.view.View, int);
method public void focusableViewAvailable(android.view.View);
method public boolean gatherTransparentRegion(android.graphics.Region);
@@ -39477,6 +39519,8 @@
method public void requestChildFocus(android.view.View, android.view.View);
method public boolean requestChildRectangleOnScreen(android.view.View, android.graphics.Rect, boolean);
method public void requestDisallowInterceptTouchEvent(boolean);
+ method public void requestLayoutForChild(android.view.View);
+ method public void requestPartialLayoutForChild(android.view.View);
method public boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
method public void requestTransparentRegion(android.view.View);
method public void scheduleLayoutAnimation();
@@ -39591,6 +39635,7 @@
method public abstract void childHasTransientStateChanged(android.view.View, boolean);
method public abstract void clearChildFocus(android.view.View);
method public abstract void createContextMenu(android.view.ContextMenu);
+ method public abstract int findDependentLayoutAxes(android.view.View, int);
method public abstract android.view.View focusSearch(android.view.View, int);
method public abstract void focusableViewAvailable(android.view.View);
method public abstract boolean getChildVisibleRect(android.view.View, android.graphics.Rect, android.graphics.Point);
@@ -39620,12 +39665,16 @@
method public abstract void requestDisallowInterceptTouchEvent(boolean);
method public abstract void requestFitSystemWindows();
method public abstract void requestLayout();
+ method public abstract void requestLayoutForChild(android.view.View);
method public abstract boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
method public abstract void requestTransparentRegion(android.view.View);
method public abstract boolean showContextMenuForChild(android.view.View);
method public abstract boolean showContextMenuForChild(android.view.View, float, float);
method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback);
method public abstract android.view.ActionMode startActionModeForChild(android.view.View, android.view.ActionMode.Callback, int);
+ field public static final int FLAG_LAYOUT_AXIS_ANY = 3; // 0x3
+ field public static final int FLAG_LAYOUT_AXIS_HORIZONTAL = 1; // 0x1
+ field public static final int FLAG_LAYOUT_AXIS_VERTICAL = 2; // 0x2
}
public class ViewPropertyAnimator {
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 62e0919a..daf01ec 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -65,6 +65,7 @@
import android.view.IWindowManager;
import com.android.internal.os.BaseCommand;
+import com.android.internal.util.HexDump;
import com.android.internal.util.Preconditions;
import java.io.BufferedReader;
@@ -152,6 +153,7 @@
" am to-app-uri [INTENT]\n" +
" am switch-user <USER_ID>\n" +
" am start-user <USER_ID>\n" +
+ " am unlock-user <USER_ID> [TOKEN_HEX]\n" +
" am stop-user [-w] <USER_ID>\n" +
" am stack start <DISPLAY_ID> <INTENT>\n" +
" am stack movetask <TASK_ID> <STACK_ID> [true|false]\n" +
@@ -411,6 +413,8 @@
runSwitchUser();
} else if (op.equals("start-user")) {
runStartUserInBackground();
+ } else if (op.equals("unlock-user")) {
+ runUnlockUser();
} else if (op.equals("stop-user")) {
runStopUser();
} else if (op.equals("stack")) {
@@ -1086,6 +1090,21 @@
}
}
+ private void runUnlockUser() throws Exception {
+ int userId = Integer.parseInt(nextArgRequired());
+ String tokenHex = nextArg();
+ byte[] token = null;
+ if (tokenHex != null) {
+ token = HexDump.hexStringToByteArray(tokenHex);
+ }
+ boolean success = mAm.unlockUser(userId, token);
+ if (success) {
+ System.out.println("Success: user unlocked");
+ } else {
+ System.err.println("Error: could not unlock user");
+ }
+ }
+
private static class StopUserCallback extends IStopUserCallback.Stub {
private boolean mFinished = false;
diff --git a/cmds/appops/Android.mk b/cmds/appops/Android.mk
index 1e15204..6801ce9 100644
--- a/cmds/appops/Android.mk
+++ b/cmds/appops/Android.mk
@@ -3,14 +3,8 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_MODULE := appops
-include $(BUILD_JAVA_LIBRARY)
-
-include $(CLEAR_VARS)
LOCAL_MODULE := appops
LOCAL_SRC_FILES := appops
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE_TAGS := optional
include $(BUILD_PREBUILT)
-
diff --git a/cmds/appops/appops b/cmds/appops/appops
index 407e551..25d2031 100755
--- a/cmds/appops/appops
+++ b/cmds/appops/appops
@@ -1,5 +1 @@
-# Script to start "appwidget" on the device, which has a very rudimentary shell.
-base=/system
-export CLASSPATH=$base/framework/appops.jar
-exec app_process $base/bin com.android.commands.appops.AppOpsCommand "$@"
-
+cmd appops $@
diff --git a/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java b/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java
deleted file mode 100644
index c9b9e58..0000000
--- a/cmds/appops/src/com/android/commands/appops/AppOpsCommand.java
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
-** Copyright 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.commands.appops;
-
-import android.app.ActivityManager;
-import android.app.ActivityThread;
-import android.app.AppOpsManager;
-import android.content.Context;
-import android.content.pm.IPackageManager;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-
-import android.util.TimeUtils;
-import com.android.internal.app.IAppOpsService;
-import com.android.internal.os.BaseCommand;
-
-import java.io.PrintStream;
-import java.util.List;
-
-/**
- * This class is a command line utility for manipulating AppOps permissions.
- */
-public class AppOpsCommand extends BaseCommand {
-
- public static void main(String[] args) {
- new AppOpsCommand().run(args);
- }
-
- @Override
- public void onShowUsage(PrintStream out) {
- out.println("usage: appops set [--user <USER_ID>] <PACKAGE> <OP> <MODE>\n"
- + " appops get [--user <USER_ID>] <PACKAGE> [<OP>]\n"
- + " appops reset [--user <USER_ID>] [<PACKAGE>]\n"
- + " <PACKAGE> an Android package name.\n"
- + " <OP> an AppOps operation.\n"
- + " <MODE> one of allow, ignore, deny, or default\n"
- + " <USER_ID> the user id under which the package is installed. If --user is not\n"
- + " specified, the current user is assumed.\n");
- }
-
- private static final String COMMAND_SET = "set";
- private static final String COMMAND_GET = "get";
- private static final String COMMAND_RESET = "reset";
-
- @Override
- public void onRun() throws Exception {
- String command = nextArgRequired();
- switch (command) {
- case COMMAND_SET:
- runSet();
- break;
-
- case COMMAND_GET:
- runGet();
- break;
-
- case COMMAND_RESET:
- runReset();
- break;
-
- default:
- System.err.println("Error: Unknown command: '" + command + "'.");
- break;
- }
- }
-
- private static final String ARGUMENT_USER = "--user";
-
- // Modes
- private static final String MODE_ALLOW = "allow";
- private static final String MODE_DENY = "deny";
- private static final String MODE_IGNORE = "ignore";
- private static final String MODE_DEFAULT = "default";
-
- private int strOpToOp(String op) {
- try {
- return AppOpsManager.strOpToOp(op);
- } catch (IllegalArgumentException e) {
- }
- try {
- return Integer.parseInt(op);
- } catch (NumberFormatException e) {
- }
- try {
- return AppOpsManager.strDebugOpToOp(op);
- } catch (IllegalArgumentException e) {
- System.err.println("Error: " + e.getMessage());
- return -1;
- }
- }
-
- private void runSet() throws Exception {
- String packageName = null;
- String op = null;
- String mode = null;
- int userId = UserHandle.USER_CURRENT;
- for (String argument; (argument = nextArg()) != null;) {
- if (ARGUMENT_USER.equals(argument)) {
- userId = Integer.parseInt(nextArgRequired());
- } else {
- if (packageName == null) {
- packageName = argument;
- } else if (op == null) {
- op = argument;
- } else if (mode == null) {
- mode = argument;
- } else {
- System.err.println("Error: Unsupported argument: " + argument);
- return;
- }
- }
- }
-
- if (packageName == null) {
- System.err.println("Error: Package name not specified.");
- return;
- } else if (op == null) {
- System.err.println("Error: Operation not specified.");
- return;
- } else if (mode == null) {
- System.err.println("Error: Mode not specified.");
- return;
- }
-
- final int opInt = strOpToOp(op);
- if (opInt < 0) {
- return;
- }
- final int modeInt;
- switch (mode) {
- case MODE_ALLOW:
- modeInt = AppOpsManager.MODE_ALLOWED;
- break;
- case MODE_DENY:
- modeInt = AppOpsManager.MODE_ERRORED;
- break;
- case MODE_IGNORE:
- modeInt = AppOpsManager.MODE_IGNORED;
- break;
- case MODE_DEFAULT:
- modeInt = AppOpsManager.MODE_DEFAULT;
- break;
- default:
- System.err.println("Error: Mode " + mode + " is not valid,");
- return;
- }
-
- // Parsing complete, let's execute the command.
-
- if (userId == UserHandle.USER_CURRENT) {
- userId = ActivityManager.getCurrentUser();
- }
-
- final IPackageManager pm = ActivityThread.getPackageManager();
- final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
- ServiceManager.getService(Context.APP_OPS_SERVICE));
- final int uid;
- if ("root".equals(packageName)) {
- uid = 0;
- } else {
- uid = pm.getPackageUid(packageName, userId);
- }
- if (uid < 0) {
- System.err.println("Error: No UID for " + packageName + " in user " + userId);
- return;
- }
- appOpsService.setMode(opInt, uid, packageName, modeInt);
- }
-
- private void runGet() throws Exception {
- String packageName = null;
- String op = null;
- int userId = UserHandle.USER_CURRENT;
- for (String argument; (argument = nextArg()) != null;) {
- if (ARGUMENT_USER.equals(argument)) {
- userId = Integer.parseInt(nextArgRequired());
- } else {
- if (packageName == null) {
- packageName = argument;
- } else if (op == null) {
- op = argument;
- } else {
- System.err.println("Error: Unsupported argument: " + argument);
- return;
- }
- }
- }
-
- if (packageName == null) {
- System.err.println("Error: Package name not specified.");
- return;
- }
-
- final int opInt = op != null ? strOpToOp(op) : 0;
-
- // Parsing complete, let's execute the command.
-
- if (userId == UserHandle.USER_CURRENT) {
- userId = ActivityManager.getCurrentUser();
- }
-
- final IPackageManager pm = ActivityThread.getPackageManager();
- final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
- ServiceManager.getService(Context.APP_OPS_SERVICE));
- final int uid;
- if ("root".equals(packageName)) {
- uid = 0;
- } else {
- uid = pm.getPackageUid(packageName, userId);
- }
- if (uid < 0) {
- System.err.println("Error: No UID for " + packageName + " in user " + userId);
- return;
- }
- List<AppOpsManager.PackageOps> ops = appOpsService.getOpsForPackage(uid, packageName,
- op != null ? new int[] {opInt} : null);
- if (ops == null || ops.size() <= 0) {
- System.out.println("No operations.");
- return;
- }
- final long now = System.currentTimeMillis();
- for (int i=0; i<ops.size(); i++) {
- List<AppOpsManager.OpEntry> entries = ops.get(i).getOps();
- for (int j=0; j<entries.size(); j++) {
- AppOpsManager.OpEntry ent = entries.get(j);
- System.out.print(AppOpsManager.opToName(ent.getOp()));
- System.out.print(": ");
- switch (ent.getMode()) {
- case AppOpsManager.MODE_ALLOWED:
- System.out.print("allow");
- break;
- case AppOpsManager.MODE_IGNORED:
- System.out.print("ignore");
- break;
- case AppOpsManager.MODE_ERRORED:
- System.out.print("deny");
- break;
- case AppOpsManager.MODE_DEFAULT:
- System.out.print("default");
- break;
- default:
- System.out.print("mode=");
- System.out.print(ent.getMode());
- break;
- }
- if (ent.getTime() != 0) {
- System.out.print("; time=");
- StringBuilder sb = new StringBuilder();
- TimeUtils.formatDuration(now - ent.getTime(), sb);
- System.out.print(sb);
- System.out.print(" ago");
- }
- if (ent.getRejectTime() != 0) {
- System.out.print("; rejectTime=");
- StringBuilder sb = new StringBuilder();
- TimeUtils.formatDuration(now - ent.getRejectTime(), sb);
- System.out.print(sb);
- System.out.print(" ago");
- }
- if (ent.getDuration() == -1) {
- System.out.print(" (running)");
- } else if (ent.getDuration() != 0) {
- System.out.print("; duration=");
- StringBuilder sb = new StringBuilder();
- TimeUtils.formatDuration(ent.getDuration(), sb);
- System.out.print(sb);
- }
- System.out.println();
- }
- }
- }
-
- private void runReset() throws Exception {
- String packageName = null;
- int userId = UserHandle.USER_CURRENT;
- for (String argument; (argument = nextArg()) != null;) {
- if (ARGUMENT_USER.equals(argument)) {
- String userStr = nextArgRequired();
- if ("all".equals(userStr)) {
- userId = UserHandle.USER_ALL;
- } else if ("current".equals(userStr)) {
- userId = UserHandle.USER_CURRENT;
- } else if ("owner".equals(userStr) || "system".equals(userStr)) {
- userId = UserHandle.USER_SYSTEM;
- } else {
- userId = Integer.parseInt(nextArgRequired());
- }
- } else {
- if (packageName == null) {
- packageName = argument;
- } else {
- System.err.println("Error: Unsupported argument: " + argument);
- return;
- }
- }
- }
-
- // Parsing complete, let's execute the command.
-
- if (userId == UserHandle.USER_CURRENT) {
- userId = ActivityManager.getCurrentUser();
- }
-
- final IAppOpsService appOpsService = IAppOpsService.Stub.asInterface(
- ServiceManager.getService(Context.APP_OPS_SERVICE));
- appOpsService.resetAllModes(userId, packageName);
- System.out.print("Reset all modes for: ");
- if (userId == UserHandle.USER_ALL) {
- System.out.print("all users");
- } else {
- System.out.print("user "); System.out.print(userId);
- }
- System.out.print(", ");
- if (packageName == null) {
- System.out.println("all packages");
- } else {
- System.out.print("package "); System.out.println(packageName);
- }
- }
-}
diff --git a/cmds/settings/src/com/android/commands/settings/SettingsCmd.java b/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
index a675769..726167e 100644
--- a/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
+++ b/cmds/settings/src/com/android/commands/settings/SettingsCmd.java
@@ -291,7 +291,7 @@
System.err.println(" settings [--user NUM] delete namespace key");
System.err.println(" settings [--user NUM] list namespace");
System.err.println("\n'namespace' is one of {system, secure, global}, case-insensitive");
- System.err.println("If '--user NUM' is not given, the operations are performed on the"
+ System.err.println("If '--user NUM' is not given, the operations are performed on the "
+ "system user.");
}
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index 9c401c7f..185ceb4 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -116,6 +116,25 @@
*/
public static final String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry";
+ /**
+ * Bundle key used for the {@link String} account type in session bundle.
+ * This is used in the default implementation of
+ * {@link #startAddAccountSession}. TODO: and startUpdateCredentialsSession.
+ */
+ private static final String KEY_AUTH_TOKEN_TYPE = "android.accounts.KEY_AUTH_TOKEN_TYPE";
+ /**
+ * Bundle key used for the {@link String} array of required features in
+ * session bundle. This is used in the default implementation of
+ * {@link #startAddAccountSession}. TODO: and startUpdateCredentialsSession.
+ */
+ private static final String KEY_REQUIRED_FEATURES = "android.accounts.AbstractAccountAuthenticator.KEY_REQUIRED_FEATURES";
+ /**
+ * Bundle key used for the {@link Bundle} options in session bundle. This is
+ * used in default implementation of {@link #startAddAccountSession}. TODO:
+ * and startUpdateCredentialsSession.
+ */
+ private static final String KEY_OPTIONS = "android.accounts.AbstractAccountAuthenticator.KEY_OPTIONS";
+
private final Context mContext;
public AbstractAccountAuthenticator(Context context) {
@@ -336,6 +355,36 @@
handleException(response, "addAccountFromCredentials", account.toString(), e);
}
}
+
+ @Override
+ public void startAddAccountSession(IAccountAuthenticatorResponse response,
+ String accountType, String authTokenType, String[] features, Bundle options)
+ throws RemoteException {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG,
+ "startAddAccountSession: accountType " + accountType
+ + ", authTokenType " + authTokenType
+ + ", features " + (features == null ? "[]" : Arrays.toString(features)));
+ }
+ checkBinderPermission();
+ try {
+ final Bundle result = AbstractAccountAuthenticator.this.startAddAccountSession(
+ new AccountAuthenticatorResponse(response), accountType, authTokenType,
+ features, options);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (result != null) {
+ result.keySet(); // force it to be unparcelled
+ }
+ Log.v(TAG, "startAddAccountSession: result "
+ + AccountManager.sanitizeResult(result));
+ }
+ if (result != null) {
+ response.onResult(result);
+ }
+ } catch (Exception e) {
+ handleException(response, "startAddAccountSession", accountType, e);
+ }
+ }
}
private void handleException(IAccountAuthenticatorResponse response, String method,
@@ -603,4 +652,52 @@
}).start();
return null;
}
+
+ /**
+ * Starts the add account session to authenticate user to an account of the
+ * specified accountType.
+ *
+ * @param response to send the result back to the AccountManager, will never
+ * be null
+ * @param accountType the type of account to authenticate with, will never
+ * be null
+ * @param authTokenType the type of auth token to retrieve after
+ * authenticating with the account, may be null
+ * @param requiredFeatures a String array of authenticator-specific features
+ * that the account authenticated with must support, may be null
+ * @param options a Bundle of authenticator-specific options, may be null
+ * @return a Bundle result or null if the result is to be returned via the
+ * response. The result will contain either:
+ * <ul>
+ * <li>{@link AccountManager#KEY_INTENT}, or
+ * <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for adding
+ * the account to device later, and if account is authenticated,
+ * optional {@link AccountManager#KEY_PASSWORD} and
+ * {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking the
+ * status of the account, or
+ * <li>{@link AccountManager#KEY_ERROR_CODE} and
+ * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error
+ * </ul>
+ * @throws NetworkErrorException if the authenticator could not honor the
+ * request due to a network error
+ */
+ public Bundle startAddAccountSession(final AccountAuthenticatorResponse response,
+ final String accountType, final String authTokenType, final String[] requiredFeatures,
+ final Bundle options)
+ throws NetworkErrorException {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ Bundle sessionBundle = new Bundle();
+ sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType);
+ sessionBundle.putStringArray(KEY_REQUIRED_FEATURES, requiredFeatures);
+ sessionBundle.putBundle(KEY_OPTIONS, options);
+ Bundle result = new Bundle();
+ result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
+ response.onResult(result);
+ }
+
+ }).start();
+ return null;
+ }
}
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 0a7568a..42e5e2a 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -240,6 +240,20 @@
*/
public static final String KEY_NOTIFY_ON_FAILURE = "notifyOnAuthFailure";
+ /**
+ * Bundle key used for a {@link Bundle} in result from
+ * {@link #startAddAccountSession} and friends which returns session data
+ * for installing an account later.
+ */
+ public static final String KEY_ACCOUNT_SESSION_BUNDLE = "accountSessionBundle";
+
+ /**
+ * Bundle key used for the {@link String} account status token in result
+ * from {@link #startAddAccountSession} and friends which returns
+ * information about a particular account.
+ */
+ public static final String KEY_ACCOUNT_STATUS_TOKEN = "accountStatusToken";
+
public static final String ACTION_AUTHENTICATOR_INTENT =
"android.accounts.AccountAuthenticator";
public static final String AUTHENTICATOR_META_DATA_NAME =
@@ -2590,4 +2604,84 @@
}
}
}
+
+ /**
+ * Asks the user to authenticate with an account of a specified type. The
+ * authenticator for this account type processes this request with the
+ * appropriate user interface. If the user does elect to authenticate with a
+ * new account, a bundle of session data for installing the account later is
+ * returned with optional account password and account status token.
+ * <p>
+ * This method may be called from any thread, but the returned
+ * {@link AccountManagerFuture} must not be used on the main thread.
+ * <p>
+ * <p>
+ * <b>NOTE:</b> The account will not be installed to the device by calling
+ * this api alone.
+ *
+ * @param accountType The type of account to add; must not be null
+ * @param authTokenType The type of auth token (see {@link #getAuthToken})
+ * this account will need to be able to generate, null for none
+ * @param requiredFeatures The features (see {@link #hasFeatures}) this
+ * account must have, null for none
+ * @param options Authenticator-specific options for the request, may be
+ * null or empty
+ * @param activity The {@link Activity} context to use for launching a new
+ * authenticator-defined sub-Activity to prompt the user to
+ * create an account; used only to call startActivity(); if null,
+ * the prompt will not be launched directly, but the necessary
+ * {@link Intent} will be returned to the caller instead
+ * @param callback Callback to invoke when the request completes, null for
+ * no callback
+ * @param handler {@link Handler} identifying the callback thread, null for
+ * the main thread
+ * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+ * these fields if activity was specified and user was authenticated
+ * with an account:
+ * <ul>
+ * <li>{@link #KEY_ACCOUNT_SESSION_BUNDLE} - encrypted Bundle for
+ * adding the the to the device later.
+ * <li>{@link #KEY_PASSWORD} - optional, the password or password
+ * hash of the account.
+ * <li>{@link #KEY_ACCOUNT_STATUS_TOKEN} - optional, token to check
+ * status of the account
+ * </ul>
+ * If no activity was specified, the returned Bundle contains only
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * actual account creation process. If authenticator doesn't support
+ * this method, the returned Bundle contains only
+ * {@link #KEY_ACCOUNT_SESSION_BUNDLE} with encrypted
+ * {@code options} needed to add account later. If an error
+ * occurred, {@link AccountManagerFuture#getResult()} throws:
+ * <ul>
+ * <li>{@link AuthenticatorException} if no authenticator was
+ * registered for this account type or the authenticator failed to
+ * respond
+ * <li>{@link OperationCanceledException} if the operation was
+ * canceled for any reason, including the user canceling the
+ * creation process or adding accounts (of this type) has been
+ * disabled by policy
+ * <li>{@link IOException} if the authenticator experienced an I/O
+ * problem creating a new account, usually because of network
+ * trouble
+ * </ul>
+ */
+ public AccountManagerFuture<Bundle> startAddAccountSession(final String accountType,
+ final String authTokenType, final String[] requiredFeatures, final Bundle options,
+ final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ final Bundle optionsIn = new Bundle();
+ if (options != null) {
+ optionsIn.putAll(options);
+ }
+ optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.startAddAccountSession(mResponse, accountType, authTokenType,
+ requiredFeatures, activity != null, optionsIn);
+ }
+ }.start();
+ }
}
diff --git a/core/java/android/accounts/IAccountAuthenticator.aidl b/core/java/android/accounts/IAccountAuthenticator.aidl
index 58612da..b326070 100644
--- a/core/java/android/accounts/IAccountAuthenticator.aidl
+++ b/core/java/android/accounts/IAccountAuthenticator.aidl
@@ -83,4 +83,11 @@
*/
void addAccountFromCredentials(in IAccountAuthenticatorResponse response, in Account account,
in Bundle accountCredentials);
+
+ /**
+ * Starts the add account session by prompting the user for account information
+ * and return a Bundle containing data to finish the session later.
+ */
+ void startAddAccountSession(in IAccountAuthenticatorResponse response, String accountType,
+ String authTokenType, in String[] requiredFeatures, in Bundle options);
}
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 0d95db1..5de311e 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -83,4 +83,9 @@
String getPreviousName(in Account account);
boolean renameSharedAccountAsUser(in Account accountToRename, String newName, int userId);
+ /* Add account in two steps. */
+ void startAddAccountSession(in IAccountManagerResponse response, String accountType,
+ String authTokenType, in String[] requiredFeatures, boolean expectActivityLaunch,
+ in Bundle options);
+
}
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index d8d2737..20d71a6 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -250,50 +250,19 @@
/**
* PathDataEvaluator is used to interpolate between two paths which are
* represented in the same format but different control points' values.
- * The path is represented as an array of PathDataNode here, which is
- * fundamentally an array of floating point numbers.
+ * The path is represented as verbs and points for each of the verbs.
*/
- private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathDataNode[]> {
- private PathParser.PathDataNode[] mNodeArray;
-
- /**
- * Create a PathParser.PathDataNode[] that does not reuse the animated value.
- * Care must be taken when using this option because on every evaluation
- * a new <code>PathParser.PathDataNode[]</code> will be allocated.
- */
- private PathDataEvaluator() {}
-
- /**
- * Create a PathDataEvaluator that reuses <code>nodeArray</code> for every evaluate() call.
- * Caution must be taken to ensure that the value returned from
- * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or
- * used across threads. The value will be modified on each <code>evaluate()</code> call.
- *
- * @param nodeArray The array to modify and return from <code>evaluate</code>.
- */
- public PathDataEvaluator(PathParser.PathDataNode[] nodeArray) {
- mNodeArray = nodeArray;
- }
+ private static class PathDataEvaluator implements TypeEvaluator<PathParser.PathData> {
+ private final PathParser.PathData mPathData = new PathParser.PathData();
@Override
- public PathParser.PathDataNode[] evaluate(float fraction,
- PathParser.PathDataNode[] startPathData,
- PathParser.PathDataNode[] endPathData) {
- if (!PathParser.canMorph(startPathData, endPathData)) {
+ public PathParser.PathData evaluate(float fraction, PathParser.PathData startPathData,
+ PathParser.PathData endPathData) {
+ if (!PathParser.interpolatePathData(mPathData, startPathData, endPathData, fraction)) {
throw new IllegalArgumentException("Can't interpolate between"
+ " two incompatible pathData");
}
-
- if (mNodeArray == null || !PathParser.canMorph(mNodeArray, startPathData)) {
- mNodeArray = PathParser.deepCopyNodes(startPathData);
- }
-
- for (int i = 0; i < startPathData.length; i++) {
- mNodeArray[i].interpolatePathDataNode(startPathData[i],
- endPathData[i], fraction);
- }
-
- return mNodeArray;
+ return mPathData;
}
}
@@ -323,13 +292,14 @@
if (valueType == VALUE_TYPE_PATH) {
String fromString = styledAttributes.getString(valueFromId);
String toString = styledAttributes.getString(valueToId);
- PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString);
- PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString);
+ PathParser.PathData nodesFrom = fromString == null
+ ? null : new PathParser.PathData(fromString);
+ PathParser.PathData nodesTo = toString == null
+ ? null : new PathParser.PathData(toString);
if (nodesFrom != null || nodesTo != null) {
if (nodesFrom != null) {
- TypeEvaluator evaluator =
- new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom));
+ TypeEvaluator evaluator = new PathDataEvaluator();
if (nodesTo != null) {
if (!PathParser.canMorph(nodesFrom, nodesTo)) {
throw new InflateException(" Can't morph from " + fromString + " to " +
@@ -342,8 +312,7 @@
(Object) nodesFrom);
}
} else if (nodesTo != null) {
- TypeEvaluator evaluator =
- new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo));
+ TypeEvaluator evaluator = new PathDataEvaluator();
returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
(Object) nodesTo);
}
@@ -484,23 +453,25 @@
TypeEvaluator evaluator = null;
String fromString = arrayAnimator.getString(R.styleable.Animator_valueFrom);
String toString = arrayAnimator.getString(R.styleable.Animator_valueTo);
- PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString);
- PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString);
+ PathParser.PathData pathDataFrom = fromString == null
+ ? null : new PathParser.PathData(fromString);
+ PathParser.PathData pathDataTo = toString == null
+ ? null : new PathParser.PathData(toString);
- if (nodesFrom != null) {
- if (nodesTo != null) {
- anim.setObjectValues(nodesFrom, nodesTo);
- if (!PathParser.canMorph(nodesFrom, nodesTo)) {
+ if (pathDataFrom != null) {
+ if (pathDataTo != null) {
+ anim.setObjectValues(pathDataFrom, pathDataTo);
+ if (!PathParser.canMorph(pathDataFrom, pathDataTo)) {
throw new InflateException(arrayAnimator.getPositionDescription()
+ " Can't morph from " + fromString + " to " + toString);
}
} else {
- anim.setObjectValues((Object)nodesFrom);
+ anim.setObjectValues((Object)pathDataFrom);
}
- evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom));
- } else if (nodesTo != null) {
- anim.setObjectValues((Object)nodesTo);
- evaluator = new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo));
+ evaluator = new PathDataEvaluator();
+ } else if (pathDataTo != null) {
+ anim.setObjectValues((Object)pathDataTo);
+ evaluator = new PathDataEvaluator();
}
if (DBG_ANIMATOR_INFLATER && evaluator != null) {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 472d97f..8bb0ff5 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6618,6 +6618,17 @@
}
/**
+ * Set whether the caption should displayed directly on the content rather than push it down.
+ *
+ * This affects only freeform windows since they display the caption and only the main
+ * window of the activity. The caption is used to drag the window around and also shows
+ * maximize and close action buttons.
+ */
+ public void overlayWithDecorCaption(boolean overlay) {
+ mWindow.setOverlayDecorCaption(overlay);
+ }
+
+ /**
* Interface for informing a translucent {@link Activity} once all visible activities below it
* have completed drawing. This is necessary only after an {@link Activity} has been made
* opaque using {@link Activity#convertFromTranslucent()} and before it has been drawn
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 00bba2d..f7aee75 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -528,6 +528,15 @@
return stackId == FULLSCREEN_WORKSPACE_STACK_ID
|| stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID;
}
+
+ /**
+ * Returns true if animation specs should be constructed for app transition that moves
+ * the task to the specified stack.
+ */
+ public static boolean useAnimationSpecForAppTransition(int stackId) {
+ return stackId == FREEFORM_WORKSPACE_STACK_ID
+ || stackId == FULLSCREEN_WORKSPACE_STACK_ID || stackId == DOCKED_STACK_ID;
+ }
}
/**
@@ -884,7 +893,7 @@
if (mIcon != null) {
return mIcon;
}
- return loadTaskDescriptionIcon(mIconFilename);
+ return loadTaskDescriptionIcon(mIconFilename, UserHandle.myUserId());
}
/** @hide */
@@ -898,11 +907,11 @@
}
/** @hide */
- public static Bitmap loadTaskDescriptionIcon(String iconFilename) {
+ public static Bitmap loadTaskDescriptionIcon(String iconFilename, int userId) {
if (iconFilename != null) {
try {
return ActivityManagerNative.getDefault().
- getTaskDescriptionIcon(iconFilename);
+ getTaskDescriptionIcon(iconFilename, userId);
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index e246e62..0b7b6fc 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1962,6 +1962,16 @@
return true;
}
+ case UNLOCK_USER_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int userId = data.readInt();
+ byte[] token = data.createByteArray();
+ boolean result = unlockUser(userId, token);
+ reply.writeNoException();
+ reply.writeInt(result ? 1 : 0);
+ return true;
+ }
+
case STOP_USER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int userid = data.readInt();
@@ -2490,7 +2500,8 @@
case GET_TASK_DESCRIPTION_ICON_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String filename = data.readString();
- Bitmap icon = getTaskDescriptionIcon(filename);
+ int userId = data.readInt();
+ Bitmap icon = getTaskDescriptionIcon(filename, userId);
reply.writeNoException();
if (icon == null) {
reply.writeInt(0);
@@ -5249,6 +5260,20 @@
return result;
}
+ public boolean unlockUser(int userId, byte[] token) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(userId);
+ data.writeByteArray(token);
+ mRemote.transact(IActivityManager.UNLOCK_USER_TRANSACTION, data, reply, 0);
+ reply.readException();
+ boolean result = reply.readInt() != 0;
+ reply.recycle();
+ data.recycle();
+ return result;
+ }
+
public int stopUser(int userid, IStopUserCallback callback) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -5998,11 +6023,12 @@
}
@Override
- public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException {
+ public Bitmap getTaskDescriptionIcon(String filename, int userId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeString(filename);
+ data.writeInt(userId);
mRemote.transact(GET_TASK_DESCRIPTION_ICON_TRANSACTION, data, reply, 0);
reply.readException();
final Bitmap icon = reply.readInt() == 0 ? null : Bitmap.CREATOR.createFromParcel(reply);
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 57900aa..cee1aa5 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -64,11 +64,13 @@
public static final String KEY_PACKAGE_NAME = "android:activity.packageName";
/**
- * The bounds that the activity should be started in. Set to null explicitly
- * for full screen. If the key is not found, previous bounds will be preserved.
+ * The bounds (window size) that the activity should be launched in. Set to null explicitly for
+ * full screen. If the key is not found, previous bounds will be preserved.
+ * NOTE: This value is ignored on devices that don't have
+ * {@link android.content.pm.PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT} enabled.
* @hide
*/
- public static final String KEY_BOUNDS = "android:activity.bounds";
+ public static final String KEY_LAUNCH_BOUNDS = "android:activity.launchBounds";
/**
* Type of animation that arguments specify.
@@ -193,8 +195,8 @@
public static final int ANIM_CLIP_REVEAL = 11;
private String mPackageName;
- private boolean mHasBounds;
- private Rect mBounds;
+ private boolean mHasLaunchBounds;
+ private Rect mLaunchBounds;
private int mAnimationType = ANIM_NONE;
private int mCustomEnterResId;
private int mCustomExitResId;
@@ -705,9 +707,9 @@
} catch (RuntimeException e) {
Slog.w(TAG, e);
}
- mHasBounds = opts.containsKey(KEY_BOUNDS);
- if (mHasBounds) {
- mBounds = opts.getParcelable(KEY_BOUNDS);
+ mHasLaunchBounds = opts.containsKey(KEY_LAUNCH_BOUNDS);
+ if (mHasLaunchBounds) {
+ mLaunchBounds = opts.getParcelable(KEY_LAUNCH_BOUNDS);
}
mAnimationType = opts.getInt(KEY_ANIM_TYPE);
switch (mAnimationType) {
@@ -766,10 +768,15 @@
}
}
- /** @hide */
- public ActivityOptions setBounds(Rect bounds) {
- mHasBounds = true;
- mBounds = bounds;
+ /**
+ * Sets the bounds (window size) that the activity should be launched in. Set to null explicitly
+ * for full screen.
+ * NOTE: This value is ignored on devices that don't have
+ * {@link android.content.pm.PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT} enabled.
+ */
+ public ActivityOptions setLaunchBounds(Rect launchBounds) {
+ mHasLaunchBounds = true;
+ mLaunchBounds = launchBounds;
return this;
}
@@ -778,14 +785,12 @@
return mPackageName;
}
- /** @hide */
- public boolean hasBounds() {
- return mHasBounds;
+ public boolean hasLaunchBounds() {
+ return mHasLaunchBounds;
}
- /** @hide */
- public Rect getBounds(){
- return mBounds;
+ public Rect getLaunchBounds(){
+ return mLaunchBounds;
}
/** @hide */
@@ -997,8 +1002,8 @@
if (mPackageName != null) {
b.putString(KEY_PACKAGE_NAME, mPackageName);
}
- if (mHasBounds) {
- b.putParcelable(KEY_BOUNDS, mBounds);
+ if (mHasLaunchBounds) {
+ b.putParcelable(KEY_LAUNCH_BOUNDS, mLaunchBounds);
}
b.putInt(KEY_ANIM_TYPE, mAnimationType);
if (mUsageTimeReport != null) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b3d6382..802880d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -78,6 +78,7 @@
import android.os.UserHandle;
import android.provider.Settings;
import android.security.NetworkSecurityPolicy;
+import android.security.net.config.NetworkSecurityConfigProvider;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
@@ -4831,6 +4832,11 @@
}
}
+ // Install the Network Security Config Provider. This must happen before the application
+ // code is loaded to prevent issues with instances of TLS objects being created before
+ // the provider is installed.
+ NetworkSecurityConfigProvider.install(appContext);
+
// Continue loading instrumentation.
if (ii != null) {
final ApplicationInfo instrApp = new ApplicationInfo();
diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index c0cc566..20f3495 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -575,14 +575,20 @@
setGhostVisibility(View.INVISIBLE);
mHasStopped = true;
mIsCanceled = true;
+ clearState();
+ return super.cancelPendingTransitions();
+ }
+
+ @Override
+ protected void clearState() {
+ mSharedElementsBundle = null;
+ mEnterViewsTransition = null;
mResultReceiver = null;
if (mBackgroundAnimator != null) {
mBackgroundAnimator.cancel();
mBackgroundAnimator = null;
}
- mActivity = null;
- clearState();
- return super.cancelPendingTransitions();
+ super.clearState();
}
private void makeOpaque() {
diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java
index 4b670cd..e93b40e 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -470,6 +470,11 @@
mActivity = null;
}
// Clear the state so that we can't hold any references accidentally and leak memory.
+ clearState();
+ }
+
+ @Override
+ protected void clearState() {
mHandler = null;
mSharedElementBundle = null;
if (mBackgroundAnimator != null) {
@@ -477,7 +482,7 @@
mBackgroundAnimator = null;
}
mExitSharedElementBundle = null;
- clearState();
+ super.clearState();
}
@Override
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 3d0fc92..db4f5c1 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -390,6 +390,7 @@
// Multi-user APIs
public boolean switchUser(int userid) throws RemoteException;
public boolean startUserInBackground(int userid) throws RemoteException;
+ public boolean unlockUser(int userid, byte[] token) throws RemoteException;
public int stopUser(int userid, IStopUserCallback callback) throws RemoteException;
public UserInfo getCurrentUser() throws RemoteException;
public boolean isUserRunning(int userid, int flags) throws RemoteException;
@@ -497,7 +498,7 @@
public void resizeTask(int taskId, Rect bounds, int resizeMode) throws RemoteException;
public Rect getTaskBounds(int taskId) throws RemoteException;
- public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException;
+ public Bitmap getTaskDescriptionIcon(String filename, int userId) throws RemoteException;
public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts)
throws RemoteException;
@@ -904,4 +905,5 @@
int REMOVE_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 348;
int MOVE_TOP_ACTIVITY_TO_PINNED_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 349;
int GET_APP_START_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 350;
+ int UNLOCK_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 351;
}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 30232da..84ddd9f 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -50,9 +50,6 @@
void setPackagePriority(String pkg, int uid, int priority);
int getPackagePriority(String pkg, int uid);
- void setPackagePeekable(String pkg, int uid, boolean peekable);
- boolean getPackagePeekable(String pkg, int uid);
-
void setPackageVisibilityOverride(String pkg, int uid, int visibility);
int getPackageVisibilityOverride(String pkg, int uid);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 390c280..4e6548b 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -838,13 +838,6 @@
public static final String EXTRA_PEOPLE = "android.people";
/**
- * {@link #extras} key: used to provide hints about the appropriateness of
- * displaying this notification as a heads-up notification.
- * @hide
- */
- public static final String EXTRA_AS_HEADS_UP = "headsup";
-
- /**
* Allow certain system-generated notifications to appear before the device is provisioned.
* Only available to notifications coming from the android package.
* @hide
@@ -887,32 +880,6 @@
*/
public static final String EXTRA_BUILDER_APPLICATION_INFO = "android.appInfo";
- /**
- * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification should not be
- * displayed in the heads up space.
- *
- * <p>
- * If this notification has a {@link #fullScreenIntent}, then it will always launch the
- * full-screen intent when posted.
- * </p>
- * @hide
- */
- public static final int HEADS_UP_NEVER = 0;
-
- /**
- * Default value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification may be
- * displayed as a heads up.
- * @hide
- */
- public static final int HEADS_UP_ALLOWED = 1;
-
- /**
- * Value for {@link #EXTRA_AS_HEADS_UP} that indicates this notification is a
- * good candidate for display as a heads up.
- * @hide
- */
- public static final int HEADS_UP_REQUESTED = 2;
-
private Icon mSmallIcon;
private Icon mLargeIcon;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 30fe531..9d941fd 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1426,6 +1426,36 @@
public static final String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE";
/**
+ * Activity Action: Launch ephemeral installer.
+ * <p>
+ * Input: The data must be a http: URI that the ephemeral application is registered
+ * to handle.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_INSTALL_EPHEMERAL_PACKAGE
+ = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE";
+
+ /**
+ * Service Action: Resolve ephemeral application.
+ * <p>
+ * The system will have a persistent connection to this service.
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String ACTION_RESOLVE_EPHEMERAL_PACKAGE
+ = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE";
+
+ /**
* Used as a string extra field with {@link #ACTION_INSTALL_PACKAGE} to install a
* package. Specifies the installer package name; this package will receive the
* {@link #ACTION_APP_ERROR} intent.
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index eda4136..1996e0f 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -982,7 +982,7 @@
.getAbsolutePath();
if ((privateFlags & PRIVATE_FLAG_FORCE_DEVICE_ENCRYPTED) != 0
- && SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false)) {
+ && StorageManager.isFileBasedEncryptionEnabled()) {
dataDir = deviceEncryptedDataDir;
} else {
dataDir = credentialEncryptedDataDir;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 566de4e..42fef3b 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -240,16 +240,15 @@
public static final int GET_ENCRYPTION_UNAWARE_COMPONENTS = 0x00040000;
/**
- * {@link PackageInfo} flag: return components as if the given user is
- * running with amnesia. This typically limits the component to only those
- * marked as {@link ComponentInfo#encryptionAware}, unless
+ * {@link PackageInfo} flag: return components that are marked as
+ * {@link ComponentInfo#encryptionAware}, unless
* {@link #GET_ENCRYPTION_UNAWARE_COMPONENTS} is also specified.
* <p>
* This flag is for internal use only.
*
* @hide
*/
- public static final int FLAG_USER_RUNNING_WITH_AMNESIA = 0x00080000;
+ public static final int MATCH_ENCRYPTION_AWARE_ONLY = 0x00080000;
/**
* Flag for {@link addCrossProfileIntentFilter}: if this flag is set:
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 2e43ffc..c6510f0 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -1301,23 +1301,6 @@
}
@Override
- public boolean isPerUserEncryptionEnabled() throws RemoteException {
- Parcel _data = Parcel.obtain();
- Parcel _reply = Parcel.obtain();
- boolean _result;
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- mRemote.transact(Stub.TRANSACTION_isPerUserEncryptionEnabled, _data, _reply, 0);
- _reply.readException();
- _result = 0 != _reply.readInt();
- } finally {
- _reply.recycle();
- _data.recycle();
- }
- return _result;
- }
-
- @Override
public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
@@ -1459,7 +1442,6 @@
static final int TRANSACTION_prepareUserStorage = IBinder.FIRST_CALL_TRANSACTION + 66;
- static final int TRANSACTION_isPerUserEncryptionEnabled = IBinder.FIRST_CALL_TRANSACTION + 67;
static final int TRANSACTION_isConvertibleToFBE = IBinder.FIRST_CALL_TRANSACTION + 68;
static final int TRANSACTION_mountAppFuse = IBinder.FIRST_CALL_TRANSACTION + 69;
@@ -2074,13 +2056,6 @@
reply.writeNoException();
return true;
}
- case TRANSACTION_isPerUserEncryptionEnabled: {
- data.enforceInterface(DESCRIPTOR);
- boolean result = isPerUserEncryptionEnabled();
- reply.writeNoException();
- reply.writeInt(result ? 1 : 0);
- return true;
- }
case TRANSACTION_mountAppFuse: {
data.enforceInterface(DESCRIPTOR);
String name = data.readString();
@@ -2411,7 +2386,5 @@
public void prepareUserStorage(String volumeUuid, int userId, int serialNumber)
throws RemoteException;
- public boolean isPerUserEncryptionEnabled() throws RemoteException;
-
public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException;
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 2d9090b..db12564 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -33,6 +33,7 @@
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
@@ -77,11 +78,9 @@
/** {@hide} */
public static final String PROP_HAS_ADOPTABLE = "vold.has_adoptable";
/** {@hide} */
- public static final String PROP_HAS_FBE = "vold.has_fbe";
- /** {@hide} */
public static final String PROP_FORCE_ADOPTABLE = "persist.fw.force_adoptable";
/** {@hide} */
- public static final String PROP_EMULATE_FBE = "vold.emulate_fbe";
+ public static final String PROP_EMULATE_FBE = "persist.sys.emulate_fbe";
/** {@hide} */
public static final String UUID_PRIVATE_INTERNAL = null;
@@ -1021,12 +1020,9 @@
}
/** {@hide} */
- public boolean isPerUserEncryptionEnabled() {
- try {
- return mMountService.isPerUserEncryptionEnabled();
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
+ public static boolean isFileBasedEncryptionEnabled() {
+ return "file".equals(SystemProperties.get("ro.crypto.type", "none"))
+ || SystemProperties.getBoolean(StorageManager.PROP_EMULATE_FBE, false);
}
/** {@hide} */
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index c368e5a..5997515 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -435,7 +435,7 @@
return null;
}
- final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT);
+ final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(uri);
intent.putExtra(DocumentsContract.EXTRA_SHOW_FILESIZE, true);
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 159ca01..af7f472 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -125,8 +125,7 @@
public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
/** {@hide} */
- public static final String
- ACTION_BROWSE_DOCUMENT_ROOT = "android.provider.action.BROWSE_DOCUMENT_ROOT";
+ public static final String ACTION_BROWSE = "android.provider.action.BROWSE";
/** {@hide} */
public static final String
diff --git a/core/java/android/service/quicksettings/IQSService.aidl b/core/java/android/service/quicksettings/IQSService.aidl
new file mode 100644
index 0000000..087eb61
--- /dev/null
+++ b/core/java/android/service/quicksettings/IQSService.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright 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 android.service.quicksettings;
+
+import android.content.ComponentName;
+import android.service.quicksettings.Tile;
+
+/**
+ * @hide
+ */
+interface IQSService {
+ void updateQsTile(in Tile tile);
+}
diff --git a/core/java/android/service/quicksettings/IQSTileService.aidl b/core/java/android/service/quicksettings/IQSTileService.aidl
new file mode 100644
index 0000000..6b46bee5
--- /dev/null
+++ b/core/java/android/service/quicksettings/IQSTileService.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright 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 android.service.quicksettings;
+
+import android.service.quicksettings.Tile;
+import android.service.quicksettings.IQSService;
+
+/**
+ * @hide
+ */
+oneway interface IQSTileService {
+ void setQSTile(in Tile tile);
+ void onTileAdded();
+ void onTileRemoved();
+ void onStartListening();
+ void onStopListening();
+ void onClick();
+}
diff --git a/core/java/android/service/quicksettings/Tile.aidl b/core/java/android/service/quicksettings/Tile.aidl
new file mode 100644
index 0000000..0373326
--- /dev/null
+++ b/core/java/android/service/quicksettings/Tile.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.service.quicksettings;
+
+parcelable Tile;
diff --git a/core/java/android/service/quicksettings/Tile.java b/core/java/android/service/quicksettings/Tile.java
new file mode 100644
index 0000000..c8ae171
--- /dev/null
+++ b/core/java/android/service/quicksettings/Tile.java
@@ -0,0 +1,186 @@
+/*
+ * 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 android.service.quicksettings;
+
+import android.content.ComponentName;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * A Tile holds the state of a tile that will be displayed
+ * in Quick Settings.
+ *
+ * A tile in Quick Settings exists as an icon with an accompanied label.
+ * It also may have content description for accessibility usability.
+ * The style and layout of the tile may change to match a given
+ * device.
+ */
+public final class Tile implements Parcelable {
+
+ private static final String TAG = "Tile";
+
+ private ComponentName mComponentName;
+ private IQSService mService;
+ private Icon mIcon;
+ private CharSequence mLabel;
+ private CharSequence mContentDescription;
+
+ /**
+ * @hide
+ */
+ public Tile(Parcel source) {
+ readFromParcel(source);
+ }
+
+ /**
+ * @hide
+ */
+ public Tile(ComponentName componentName, IQSService service) {
+ mComponentName = componentName;
+ mService = service;
+ }
+
+ /**
+ * @hide
+ */
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ /**
+ * Gets the current icon for the tile.
+ */
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Sets the current icon for the tile.
+ *
+ * This icon is expected to be white on alpha, and may be
+ * tinted by the system to match it's theme.
+ *
+ * Does not take effect until {@link #updateTile()} is called.
+ *
+ * @param icon New icon to show.
+ */
+ public void setIcon(Icon icon) {
+ this.mIcon = icon;
+ }
+
+ /**
+ * Gets the current label for the tile.
+ */
+ public CharSequence getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * Sets the current label for the tile.
+ *
+ * Does not take effect until {@link #updateTile()} is called.
+ *
+ * @param label New label to show.
+ */
+ public void setLabel(CharSequence label) {
+ this.mLabel = label;
+ }
+
+ /**
+ * Gets the current content description for the tile.
+ */
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * Sets the current content description for the tile.
+ *
+ * Does not take effect until {@link #updateTile()} is called.
+ *
+ * @param contentDescription New content description to use.
+ */
+ public void setContentDescription(CharSequence contentDescription) {
+ this.mContentDescription = contentDescription;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Pushes the state of the Tile to Quick Settings to be displayed.
+ */
+ public void updateTile() {
+ try {
+ mService.updateQsTile(this);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Couldn't update tile");
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongInterface(mService);
+ if (mComponentName != null) {
+ dest.writeByte((byte) 1);
+ mComponentName.writeToParcel(dest, flags);
+ } else {
+ dest.writeByte((byte) 0);
+ }
+ if (mIcon != null) {
+ dest.writeByte((byte) 1);
+ mIcon.writeToParcel(dest, flags);
+ } else {
+ dest.writeByte((byte) 0);
+ }
+ TextUtils.writeToParcel(mLabel, dest, flags);
+ TextUtils.writeToParcel(mContentDescription, dest, flags);
+ }
+
+ private void readFromParcel(Parcel source) {
+ mService = IQSService.Stub.asInterface(source.readStrongBinder());
+ if (source.readByte() != 0) {
+ mComponentName = ComponentName.CREATOR.createFromParcel(source);
+ } else {
+ mComponentName = null;
+ }
+ if (source.readByte() != 0) {
+ mIcon = Icon.CREATOR.createFromParcel(source);
+ } else {
+ mIcon = null;
+ }
+ mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ }
+
+ public static final Creator<Tile> CREATOR = new Creator<Tile>() {
+ @Override
+ public Tile createFromParcel(Parcel source) {
+ return new Tile(source);
+ }
+
+ @Override
+ public Tile[] newArray(int size) {
+ return new Tile[size];
+ }
+ };
+}
\ No newline at end of file
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
new file mode 100644
index 0000000..eba4c6f
--- /dev/null
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -0,0 +1,206 @@
+/*
+ * 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 android.service.quicksettings;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+
+/**
+ * A QSTileService provides the user a tile that can be added to Quick Settings.
+ * Quick Settings is a space provided that allows the user to change settings and
+ * take quick actions without leaving the context of their current app.
+ *
+ * <p>The lifecycle of a QSTileService is different from some other services in
+ * that it may be unbound during parts of its lifecycle. Any of the following
+ * lifecycle events can happen indepently in a separate binding/creation of the
+ * service.</p>
+ *
+ * <ul>
+ * <li>When a tile is added by the user its QSTileService will be bound to and
+ * {@link #onTileAdded()} will be called.</li>
+ *
+ * <li>When a tile should be up to date and listing will be indicated by
+ * {@link #onStartListening()} and {@link #onStopListening()}.</li>
+ *
+ * <li>When the user removes a tile from Quick Settings {@link #onStopListening()}
+ * will be called.</li>
+ * </ul>
+ * <p>QSTileService will be detected by tiles that match the {@value #ACTION_QS_TILE}
+ * and require the permission "android.permission.BIND_QUICK_SETTINGS_TILE".
+ * The label and icon for the service will be used as the default label and
+ * icon for the tile. Here is an example QSTileService declaration.</p>
+ * <pre class="prettyprint">
+ * {@literal
+ * <service
+ * android:name=".MyQSTileService"
+ * android:label="@string/my_default_tile_label"
+ * android:icon="@drawable/my_default_icon_label"
+ * android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+ * <intent-filter>
+ * <action android:name="android.intent.action.QS_TILE" />
+ * </intent-filter>
+ * </service>}
+ * </pre>
+ *
+ * @see Tile Tile for details about the UI of a Quick Settings Tile.
+ */
+public class TileService extends Service {
+
+ /**
+ * Action that identifies a Service as being a QSTileService.
+ */
+ public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
+
+ private final H mHandler = new H(Looper.getMainLooper());
+
+ private boolean mListening = false;
+ private Tile mTile;
+
+ /**
+ * Called when the user adds this tile to Quick Settings.
+ * <p/>
+ * Note that this is not guaranteed to be called between {@link #onCreate()}
+ * and {@link #onStartListening()}, it will only be called when the tile is added
+ * and not on subsequent binds.
+ */
+ public void onTileAdded() {
+ }
+
+ /**
+ * Called when the user removes this tile from Quick Settings.
+ */
+ public void onTileRemoved() {
+ }
+
+ /**
+ * Called when this tile moves into a listening state.
+ * <p/>
+ * When this tile is in a listening state it is expected to keep the
+ * UI up to date. Any listeners or callbacks needed to keep this tile
+ * up to date should be registered here and unregistered in {@link #onStopListening()}.
+ *
+ * @see #getQsTile()
+ * @see Tile#updateTile()
+ */
+ public void onStartListening() {
+ }
+
+ /**
+ * Called when this tile moves out of the listening state.
+ */
+ public void onStopListening() {
+ }
+
+ /**
+ * Called when the user clicks on this tile.
+ */
+ public void onClick() {
+ }
+
+ /**
+ * Gets the {@link Tile} for this service.
+ * <p/>
+ * This tile may be used to get or set the current state for this
+ * tile. This tile is only valid for updates between {@link #onStartListening()}
+ * and {@link #onStopListening()}.
+ */
+ public final Tile getQsTile() {
+ return mTile;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return new IQSTileService.Stub() {
+ @Override
+ public void setQSTile(Tile tile) throws RemoteException {
+ mHandler.obtainMessage(H.MSG_SET_TILE, tile).sendToTarget();
+ }
+
+ @Override
+ public void onTileRemoved() throws RemoteException {
+ mHandler.sendEmptyMessage(H.MSG_TILE_REMOVED);
+ }
+
+ @Override
+ public void onTileAdded() throws RemoteException {
+ mHandler.sendEmptyMessage(H.MSG_TILE_ADDED);
+ }
+
+ @Override
+ public void onStopListening() throws RemoteException {
+ mHandler.sendEmptyMessage(H.MSG_STOP_LISTENING);
+ }
+
+ @Override
+ public void onStartListening() throws RemoteException {
+ mHandler.sendEmptyMessage(H.MSG_START_LISTENING);
+ }
+
+ @Override
+ public void onClick() throws RemoteException {
+ mHandler.sendEmptyMessage(H.MSG_TILE_CLICKED);
+ }
+ };
+ }
+
+ private class H extends Handler {
+ private static final int MSG_SET_TILE = 1;
+ private static final int MSG_START_LISTENING = 2;
+ private static final int MSG_STOP_LISTENING = 3;
+ private static final int MSG_TILE_ADDED = 4;
+ private static final int MSG_TILE_REMOVED = 5;
+ private static final int MSG_TILE_CLICKED = 6;
+
+ public H(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SET_TILE:
+ mTile = (Tile) msg.obj;
+ break;
+ case MSG_TILE_ADDED:
+ TileService.this.onTileRemoved();
+ break;
+ case MSG_TILE_REMOVED:
+ TileService.this.onTileAdded();
+ break;
+ case MSG_START_LISTENING:
+ if (mListening) {
+ mListening = false;
+ TileService.this.onStopListening();
+ }
+ break;
+ case MSG_STOP_LISTENING:
+ if (!mListening) {
+ mListening = true;
+ TileService.this.onStartListening();
+ }
+ break;
+ case MSG_TILE_CLICKED:
+ TileService.this.onClick();
+ break;
+ }
+ }
+ }
+}
diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java
index f099479..f17a16c 100644
--- a/core/java/android/util/PathParser.java
+++ b/core/java/android/util/PathParser.java
@@ -15,10 +15,6 @@
package android.util;
import android.graphics.Path;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
/**
* @hide
@@ -45,663 +41,94 @@
}
/**
- * @param pathData The string representing a path, the same as "d" string in svg file.
- * @return an array of the PathDataNode.
+ * Interpret PathData as path commands and insert the commands to the given path.
+ *
+ * @param data The source PathData to be converted.
+ * @param outPath The Path object where path commands will be inserted.
*/
- public static PathDataNode[] createNodesFromPathData(String pathData) {
- if (pathData == null) {
- return null;
- }
- int start = 0;
- int end = 1;
-
- ArrayList<PathDataNode> list = new ArrayList<PathDataNode>();
- while (end < pathData.length()) {
- end = nextStart(pathData, end);
- String s = pathData.substring(start, end).trim();
- if (s.length() > 0) {
- float[] val = getFloats(s);
- addNode(list, s.charAt(0), val);
- }
-
- start = end;
- end++;
- }
- if ((end - start) == 1 && start < pathData.length()) {
- addNode(list, pathData.charAt(start), new float[0]);
- }
- return list.toArray(new PathDataNode[list.size()]);
+ public static void createPathFromPathData(Path outPath, PathData data) {
+ nCreatePathFromPathData(outPath.mNativePath, data.mNativePathData);
}
/**
- * @param source The array of PathDataNode to be duplicated.
- * @return a deep copy of the <code>source</code>.
- */
- public static PathDataNode[] deepCopyNodes(PathDataNode[] source) {
- if (source == null) {
- return null;
- }
- PathDataNode[] copy = new PathParser.PathDataNode[source.length];
- for (int i = 0; i < source.length; i ++) {
- copy[i] = new PathDataNode(source[i]);
- }
- return copy;
- }
-
- /**
- * @param nodesFrom The source path represented in an array of PathDataNode
- * @param nodesTo The target path represented in an array of PathDataNode
+ * @param pathDataFrom The source path represented in PathData
+ * @param pathDataTo The target path represented in PathData
* @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code>
*/
- public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) {
- if (nodesFrom == null || nodesTo == null) {
- return false;
- }
-
- if (nodesFrom.length != nodesTo.length) {
- return false;
- }
-
- for (int i = 0; i < nodesFrom.length; i ++) {
- if (nodesFrom[i].mType != nodesTo[i].mType
- || nodesFrom[i].mParams.length != nodesTo[i].mParams.length) {
- return false;
- }
- }
- return true;
+ public static boolean canMorph(PathData pathDataFrom, PathData pathDataTo) {
+ return nCanMorph(pathDataFrom.mNativePathData, pathDataTo.mNativePathData);
}
/**
- * Update the target's data to match the source.
- * Before calling this, make sure canMorph(target, source) is true.
+ * PathData class is a wrapper around the native PathData object, which contains
+ * the result of parsing a path string. Specifically, there are verbs and points
+ * associated with each verb stored in PathData. This data can then be used to
+ * generate commands to manipulate a Path.
+ */
+ public static class PathData {
+ long mNativePathData = 0;
+ public PathData() {
+ mNativePathData = nCreateEmptyPathData();
+ }
+
+ public PathData(PathData data) {
+ mNativePathData = nCreatePathData(data.mNativePathData);
+ }
+
+ public PathData(String pathString) {
+ mNativePathData = nCreatePathDataFromString(pathString, pathString.length());
+ if (mNativePathData == 0) {
+ throw new IllegalArgumentException("Invalid pathData: " + pathString);
+ }
+ }
+
+ /**
+ * Update the path data to match the source.
+ * Before calling this, make sure canMorph(target, source) is true.
+ *
+ * @param source The source path represented in PathData
+ */
+ public void setPathData(PathData source) {
+ nSetPathData(mNativePathData, source.mNativePathData);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (mNativePathData != 0) {
+ nFinalize(mNativePathData);
+ mNativePathData = 0;
+ }
+ super.finalize();
+ }
+ }
+
+ /**
+ * Interpolate between the <code>fromData</code> and <code>toData</code> according to the
+ * <code>fraction</code>, and put the resulting path data into <code>outData</code>.
*
- * @param target The target path represented in an array of PathDataNode
- * @param source The source path represented in an array of PathDataNode
+ * @param outData The resulting PathData of the interpolation
+ * @param fromData The start value as a PathData.
+ * @param toData The end value as a PathData
+ * @param fraction The fraction to interpolate.
*/
- public static void updateNodes(PathDataNode[] target, PathDataNode[] source) {
- for (int i = 0; i < source.length; i ++) {
- target[i].mType = source[i].mType;
- for (int j = 0; j < source[i].mParams.length; j ++) {
- target[i].mParams[j] = source[i].mParams[j];
- }
- }
+ public static boolean interpolatePathData(PathData outData, PathData fromData, PathData toData,
+ float fraction) {
+ return nInterpolatePathData(outData.mNativePathData, fromData.mNativePathData,
+ toData.mNativePathData, fraction);
}
- private static int nextStart(String s, int end) {
- char c;
-
- while (end < s.length()) {
- c = s.charAt(end);
- // Note that 'e' or 'E' are not valid path commands, but could be
- // used for floating point numbers' scientific notation.
- // Therefore, when searching for next command, we should ignore 'e'
- // and 'E'.
- if ((((c - 'A') * (c - 'Z') <= 0) || ((c - 'a') * (c - 'z') <= 0))
- && c != 'e' && c != 'E') {
- return end;
- }
- end++;
- }
- return end;
- }
-
- private static void addNode(ArrayList<PathDataNode> list, char cmd, float[] val) {
- list.add(new PathDataNode(cmd, val));
- }
-
- private static class ExtractFloatResult {
- // We need to return the position of the next separator and whether the
- // next float starts with a '-' or a '.'.
- int mEndPosition;
- boolean mEndWithNegOrDot;
- }
-
- /**
- * Parse the floats in the string.
- * This is an optimized version of parseFloat(s.split(",|\\s"));
- *
- * @param s the string containing a command and list of floats
- * @return array of floats
- */
- private static float[] getFloats(String s) {
- if (s.charAt(0) == 'z' || s.charAt(0) == 'Z') {
- return new float[0];
- }
- try {
- float[] results = new float[s.length()];
- int count = 0;
- int startPosition = 1;
- int endPosition = 0;
-
- ExtractFloatResult result = new ExtractFloatResult();
- int totalLength = s.length();
-
- // The startPosition should always be the first character of the
- // current number, and endPosition is the character after the current
- // number.
- while (startPosition < totalLength) {
- extract(s, startPosition, result);
- endPosition = result.mEndPosition;
-
- if (startPosition < endPosition) {
- results[count++] = Float.parseFloat(
- s.substring(startPosition, endPosition));
- }
-
- if (result.mEndWithNegOrDot) {
- // Keep the '-' or '.' sign with next number.
- startPosition = endPosition;
- } else {
- startPosition = endPosition + 1;
- }
- }
- return Arrays.copyOf(results, count);
- } catch (NumberFormatException e) {
- throw new RuntimeException("error in parsing \"" + s + "\"", e);
- }
- }
-
- /**
- * Calculate the position of the next comma or space or negative sign
- * @param s the string to search
- * @param start the position to start searching
- * @param result the result of the extraction, including the position of the
- * the starting position of next number, whether it is ending with a '-'.
- */
- private static void extract(String s, int start, ExtractFloatResult result) {
- // Now looking for ' ', ',', '.' or '-' from the start.
- int currentIndex = start;
- boolean foundSeparator = false;
- result.mEndWithNegOrDot = false;
- boolean secondDot = false;
- boolean isExponential = false;
- for (; currentIndex < s.length(); currentIndex++) {
- boolean isPrevExponential = isExponential;
- isExponential = false;
- char currentChar = s.charAt(currentIndex);
- switch (currentChar) {
- case ' ':
- case ',':
- foundSeparator = true;
- break;
- case '-':
- // The negative sign following a 'e' or 'E' is not a separator.
- if (currentIndex != start && !isPrevExponential) {
- foundSeparator = true;
- result.mEndWithNegOrDot = true;
- }
- break;
- case '.':
- if (!secondDot) {
- secondDot = true;
- } else {
- // This is the second dot, and it is considered as a separator.
- foundSeparator = true;
- result.mEndWithNegOrDot = true;
- }
- break;
- case 'e':
- case 'E':
- isExponential = true;
- break;
- }
- if (foundSeparator) {
- break;
- }
- }
- // When there is nothing found, then we put the end position to the end
- // of the string.
- result.mEndPosition = currentIndex;
- }
-
- /**
- * Each PathDataNode represents one command in the "d" attribute of the svg
- * file.
- * An array of PathDataNode can represent the whole "d" attribute.
- */
- public static class PathDataNode {
- private char mType;
- private float[] mParams;
-
- private PathDataNode(char type, float[] params) {
- mType = type;
- mParams = params;
- }
-
- private PathDataNode(PathDataNode n) {
- mType = n.mType;
- mParams = Arrays.copyOf(n.mParams, n.mParams.length);
- }
-
- /**
- * Convert an array of PathDataNode to Path.
- *
- * @param node The source array of PathDataNode.
- * @param path The target Path object.
- */
- public static void nodesToPath(PathDataNode[] node, Path path) {
- float[] current = new float[6];
- char previousCommand = 'm';
- for (int i = 0; i < node.length; i++) {
- addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
- previousCommand = node[i].mType;
- }
- }
-
- /**
- * The current PathDataNode will be interpolated between the
- * <code>nodeFrom</code> and <code>nodeTo</code> according to the
- * <code>fraction</code>.
- *
- * @param nodeFrom The start value as a PathDataNode.
- * @param nodeTo The end value as a PathDataNode
- * @param fraction The fraction to interpolate.
- */
- public void interpolatePathDataNode(PathDataNode nodeFrom,
- PathDataNode nodeTo, float fraction) {
- for (int i = 0; i < nodeFrom.mParams.length; i++) {
- mParams[i] = nodeFrom.mParams[i] * (1 - fraction)
- + nodeTo.mParams[i] * fraction;
- }
- }
-
- private static void addCommand(Path path, float[] current,
- char previousCmd, char cmd, float[] val) {
-
- int incr = 2;
- float currentX = current[0];
- float currentY = current[1];
- float ctrlPointX = current[2];
- float ctrlPointY = current[3];
- float currentSegmentStartX = current[4];
- float currentSegmentStartY = current[5];
- float reflectiveCtrlPointX;
- float reflectiveCtrlPointY;
-
- switch (cmd) {
- case 'z':
- case 'Z':
- path.close();
- // Path is closed here, but we need to move the pen to the
- // closed position. So we cache the segment's starting position,
- // and restore it here.
- currentX = currentSegmentStartX;
- currentY = currentSegmentStartY;
- ctrlPointX = currentSegmentStartX;
- ctrlPointY = currentSegmentStartY;
- path.moveTo(currentX, currentY);
- break;
- case 'm':
- case 'M':
- case 'l':
- case 'L':
- case 't':
- case 'T':
- incr = 2;
- break;
- case 'h':
- case 'H':
- case 'v':
- case 'V':
- incr = 1;
- break;
- case 'c':
- case 'C':
- incr = 6;
- break;
- case 's':
- case 'S':
- case 'q':
- case 'Q':
- incr = 4;
- break;
- case 'a':
- case 'A':
- incr = 7;
- break;
- }
-
- for (int k = 0; k < val.length; k += incr) {
- switch (cmd) {
- case 'm': // moveto - Start a new sub-path (relative)
- currentX += val[k + 0];
- currentY += val[k + 1];
- if (k > 0) {
- // According to the spec, if a moveto is followed by multiple
- // pairs of coordinates, the subsequent pairs are treated as
- // implicit lineto commands.
- path.rLineTo(val[k + 0], val[k + 1]);
- } else {
- path.rMoveTo(val[k + 0], val[k + 1]);
- currentSegmentStartX = currentX;
- currentSegmentStartY = currentY;
- }
- break;
- case 'M': // moveto - Start a new sub-path
- currentX = val[k + 0];
- currentY = val[k + 1];
- if (k > 0) {
- // According to the spec, if a moveto is followed by multiple
- // pairs of coordinates, the subsequent pairs are treated as
- // implicit lineto commands.
- path.lineTo(val[k + 0], val[k + 1]);
- } else {
- path.moveTo(val[k + 0], val[k + 1]);
- currentSegmentStartX = currentX;
- currentSegmentStartY = currentY;
- }
- break;
- case 'l': // lineto - Draw a line from the current point (relative)
- path.rLineTo(val[k + 0], val[k + 1]);
- currentX += val[k + 0];
- currentY += val[k + 1];
- break;
- case 'L': // lineto - Draw a line from the current point
- path.lineTo(val[k + 0], val[k + 1]);
- currentX = val[k + 0];
- currentY = val[k + 1];
- break;
- case 'h': // horizontal lineto - Draws a horizontal line (relative)
- path.rLineTo(val[k + 0], 0);
- currentX += val[k + 0];
- break;
- case 'H': // horizontal lineto - Draws a horizontal line
- path.lineTo(val[k + 0], currentY);
- currentX = val[k + 0];
- break;
- case 'v': // vertical lineto - Draws a vertical line from the current point (r)
- path.rLineTo(0, val[k + 0]);
- currentY += val[k + 0];
- break;
- case 'V': // vertical lineto - Draws a vertical line from the current point
- path.lineTo(currentX, val[k + 0]);
- currentY = val[k + 0];
- break;
- case 'c': // curveto - Draws a cubic Bézier curve (relative)
- path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
- val[k + 4], val[k + 5]);
-
- ctrlPointX = currentX + val[k + 2];
- ctrlPointY = currentY + val[k + 3];
- currentX += val[k + 4];
- currentY += val[k + 5];
-
- break;
- case 'C': // curveto - Draws a cubic Bézier curve
- path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
- val[k + 4], val[k + 5]);
- currentX = val[k + 4];
- currentY = val[k + 5];
- ctrlPointX = val[k + 2];
- ctrlPointY = val[k + 3];
- break;
- case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
- reflectiveCtrlPointX = 0;
- reflectiveCtrlPointY = 0;
- if (previousCmd == 'c' || previousCmd == 's'
- || previousCmd == 'C' || previousCmd == 'S') {
- reflectiveCtrlPointX = currentX - ctrlPointX;
- reflectiveCtrlPointY = currentY - ctrlPointY;
- }
- path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
- val[k + 0], val[k + 1],
- val[k + 2], val[k + 3]);
-
- ctrlPointX = currentX + val[k + 0];
- ctrlPointY = currentY + val[k + 1];
- currentX += val[k + 2];
- currentY += val[k + 3];
- break;
- case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
- reflectiveCtrlPointX = currentX;
- reflectiveCtrlPointY = currentY;
- if (previousCmd == 'c' || previousCmd == 's'
- || previousCmd == 'C' || previousCmd == 'S') {
- reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
- reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
- }
- path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
- val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
- ctrlPointX = val[k + 0];
- ctrlPointY = val[k + 1];
- currentX = val[k + 2];
- currentY = val[k + 3];
- break;
- case 'q': // Draws a quadratic Bézier (relative)
- path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
- ctrlPointX = currentX + val[k + 0];
- ctrlPointY = currentY + val[k + 1];
- currentX += val[k + 2];
- currentY += val[k + 3];
- break;
- case 'Q': // Draws a quadratic Bézier
- path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
- ctrlPointX = val[k + 0];
- ctrlPointY = val[k + 1];
- currentX = val[k + 2];
- currentY = val[k + 3];
- break;
- case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
- reflectiveCtrlPointX = 0;
- reflectiveCtrlPointY = 0;
- if (previousCmd == 'q' || previousCmd == 't'
- || previousCmd == 'Q' || previousCmd == 'T') {
- reflectiveCtrlPointX = currentX - ctrlPointX;
- reflectiveCtrlPointY = currentY - ctrlPointY;
- }
- path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
- val[k + 0], val[k + 1]);
- ctrlPointX = currentX + reflectiveCtrlPointX;
- ctrlPointY = currentY + reflectiveCtrlPointY;
- currentX += val[k + 0];
- currentY += val[k + 1];
- break;
- case 'T': // Draws a quadratic Bézier curve (reflective control point)
- reflectiveCtrlPointX = currentX;
- reflectiveCtrlPointY = currentY;
- if (previousCmd == 'q' || previousCmd == 't'
- || previousCmd == 'Q' || previousCmd == 'T') {
- reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
- reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
- }
- path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
- val[k + 0], val[k + 1]);
- ctrlPointX = reflectiveCtrlPointX;
- ctrlPointY = reflectiveCtrlPointY;
- currentX = val[k + 0];
- currentY = val[k + 1];
- break;
- case 'a': // Draws an elliptical arc
- // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
- drawArc(path,
- currentX,
- currentY,
- val[k + 5] + currentX,
- val[k + 6] + currentY,
- val[k + 0],
- val[k + 1],
- val[k + 2],
- val[k + 3] != 0,
- val[k + 4] != 0);
- currentX += val[k + 5];
- currentY += val[k + 6];
- ctrlPointX = currentX;
- ctrlPointY = currentY;
- break;
- case 'A': // Draws an elliptical arc
- drawArc(path,
- currentX,
- currentY,
- val[k + 5],
- val[k + 6],
- val[k + 0],
- val[k + 1],
- val[k + 2],
- val[k + 3] != 0,
- val[k + 4] != 0);
- currentX = val[k + 5];
- currentY = val[k + 6];
- ctrlPointX = currentX;
- ctrlPointY = currentY;
- break;
- }
- previousCmd = cmd;
- }
- current[0] = currentX;
- current[1] = currentY;
- current[2] = ctrlPointX;
- current[3] = ctrlPointY;
- current[4] = currentSegmentStartX;
- current[5] = currentSegmentStartY;
- }
-
- private static void drawArc(Path p,
- float x0,
- float y0,
- float x1,
- float y1,
- float a,
- float b,
- float theta,
- boolean isMoreThanHalf,
- boolean isPositiveArc) {
-
- /* Convert rotation angle from degrees to radians */
- double thetaD = Math.toRadians(theta);
- /* Pre-compute rotation matrix entries */
- double cosTheta = Math.cos(thetaD);
- double sinTheta = Math.sin(thetaD);
- /* Transform (x0, y0) and (x1, y1) into unit space */
- /* using (inverse) rotation, followed by (inverse) scale */
- double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
- double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
- double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
- double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
-
- /* Compute differences and averages */
- double dx = x0p - x1p;
- double dy = y0p - y1p;
- double xm = (x0p + x1p) / 2;
- double ym = (y0p + y1p) / 2;
- /* Solve for intersecting unit circles */
- double dsq = dx * dx + dy * dy;
- if (dsq == 0.0) {
- Log.w(LOGTAG, " Points are coincident");
- return; /* Points are coincident */
- }
- double disc = 1.0 / dsq - 1.0 / 4.0;
- if (disc < 0.0) {
- Log.w(LOGTAG, "Points are too far apart " + dsq);
- float adjust = (float) (Math.sqrt(dsq) / 1.99999);
- drawArc(p, x0, y0, x1, y1, a * adjust,
- b * adjust, theta, isMoreThanHalf, isPositiveArc);
- return; /* Points are too far apart */
- }
- double s = Math.sqrt(disc);
- double sdx = s * dx;
- double sdy = s * dy;
- double cx;
- double cy;
- if (isMoreThanHalf == isPositiveArc) {
- cx = xm - sdy;
- cy = ym + sdx;
- } else {
- cx = xm + sdy;
- cy = ym - sdx;
- }
-
- double eta0 = Math.atan2((y0p - cy), (x0p - cx));
-
- double eta1 = Math.atan2((y1p - cy), (x1p - cx));
-
- double sweep = (eta1 - eta0);
- if (isPositiveArc != (sweep >= 0)) {
- if (sweep > 0) {
- sweep -= 2 * Math.PI;
- } else {
- sweep += 2 * Math.PI;
- }
- }
-
- cx *= a;
- cy *= b;
- double tcx = cx;
- cx = cx * cosTheta - cy * sinTheta;
- cy = tcx * sinTheta + cy * cosTheta;
-
- arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
- }
-
- /**
- * Converts an arc to cubic Bezier segments and records them in p.
- *
- * @param p The target for the cubic Bezier segments
- * @param cx The x coordinate center of the ellipse
- * @param cy The y coordinate center of the ellipse
- * @param a The radius of the ellipse in the horizontal direction
- * @param b The radius of the ellipse in the vertical direction
- * @param e1x E(eta1) x coordinate of the starting point of the arc
- * @param e1y E(eta2) y coordinate of the starting point of the arc
- * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
- * @param start The start angle of the arc on the ellipse
- * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
- */
- private static void arcToBezier(Path p,
- double cx,
- double cy,
- double a,
- double b,
- double e1x,
- double e1y,
- double theta,
- double start,
- double sweep) {
- // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
- // and http://www.spaceroots.org/documents/ellipse/node22.html
-
- // Maximum of 45 degrees per cubic Bezier segment
- int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
-
- double eta1 = start;
- double cosTheta = Math.cos(theta);
- double sinTheta = Math.sin(theta);
- double cosEta1 = Math.cos(eta1);
- double sinEta1 = Math.sin(eta1);
- double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
- double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
-
- double anglePerSegment = sweep / numSegments;
- for (int i = 0; i < numSegments; i++) {
- double eta2 = eta1 + anglePerSegment;
- double sinEta2 = Math.sin(eta2);
- double cosEta2 = Math.cos(eta2);
- double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
- double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
- double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
- double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
- double tanDiff2 = Math.tan((eta2 - eta1) / 2);
- double alpha =
- Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
- double q1x = e1x + alpha * ep1x;
- double q1y = e1y + alpha * ep1y;
- double q2x = e2x - alpha * ep2x;
- double q2y = e2y - alpha * ep2y;
-
- p.cubicTo((float) q1x,
- (float) q1y,
- (float) q2x,
- (float) q2y,
- (float) e2x,
- (float) e2y);
- eta1 = eta2;
- e1x = e2x;
- e1y = e2y;
- ep1x = ep2x;
- ep1y = ep2y;
- }
- }
- }
-
+ // Native functions are defined below.
private static native boolean nParseStringForPath(long pathPtr, String pathString,
int stringLength);
+ private static native void nCreatePathFromPathData(long outPathPtr, long pathData);
+ private static native long nCreateEmptyPathData();
+ private static native long nCreatePathData(long nativePtr);
+ private static native long nCreatePathDataFromString(String pathString, int stringLength);
+ private static native boolean nInterpolatePathData(long outDataPtr, long fromDataPtr,
+ long toDataPtr, float fraction);
+ private static native void nFinalize(long nativePtr);
+ private static native boolean nCanMorph(long fromDataPtr, long toDataPtr);
+ private static native void nSetPathData(long outDataPtr, long fromDataPtr);
}
+
+
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index f81b5d0..3fc70cc 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -186,11 +186,6 @@
void reportDropResult(IWindow window, boolean consumed);
/**
- * Cancel a drag operation.
- */
- void cancelDrag(IBinder dragToken);
-
- /**
* Tell the OS that we've just dragged into a View that is willing to accept the drop
*/
void dragRecipientEntered(IWindow window);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 30408c6..227d8f2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -83,6 +83,7 @@
import android.view.AccessibilityIterators.CharacterTextSegmentIterator;
import android.view.AccessibilityIterators.WordTextSegmentIterator;
import android.view.AccessibilityIterators.ParagraphTextSegmentIterator;
+import android.view.ViewGroup.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityEventSource;
import android.view.accessibility.AccessibilityManager;
@@ -2416,6 +2417,8 @@
* 1 PFLAG3_SCROLL_INDICATOR_END
* 1 PFLAG3_ASSIST_BLOCKED
* 1111111 PFLAG3_POINTER_ICON_MASK
+ * 1 PFLAG3_PARTIAL_LAYOUT_REQUESTED
+ * 1 PFLAG3_LAYOUT_PARAMS_CHANGED
* |-------|-------|-------|-------|
*/
@@ -2504,6 +2507,7 @@
*/
static final int PFLAG3_SCROLL_INDICATOR_END = 0x2000;
+
/* End of masks for mPrivateFlags3 */
static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED;
@@ -2642,6 +2646,19 @@
private static final int PFLAG3_POINTER_ICON_VALUE_START = 3 << PFLAG3_POINTER_ICON_LSHIFT;
/**
+ * Flag indicating that this view has requested a partial layout and
+ * is added to the AttachInfo's list of views that need a partial layout
+ * request handled on the next traversal.
+ */
+ static final int PFLAG3_PARTIAL_LAYOUT_REQUESTED = 0x800000;
+
+ /**
+ * Flag indicating that this view's LayoutParams have been explicitly changed
+ * since the last layout pass.
+ */
+ static final int PFLAG3_LAYOUT_PARAMS_CHANGED = 0x1000000;
+
+ /**
* Always allow a user to over-scroll this view, provided it is a
* view that can scroll.
*
@@ -12622,10 +12639,14 @@
* ViewGroup.LayoutParams, and these correspond to the different subclasses
* of ViewGroup that are responsible for arranging their children.
*
- * This method may return null if this View is not attached to a parent
+ * <p>This method may return null if this View is not attached to a parent
* ViewGroup or {@link #setLayoutParams(android.view.ViewGroup.LayoutParams)}
* was not invoked successfully. When a View is attached to a parent
- * ViewGroup, this method must not return null.
+ * ViewGroup, this method must not return null.</p>
+ *
+ * <p>Callers that modify the returned LayoutParams object should call
+ * {@link #setLayoutParams(LayoutParams)} to explicitly inform the view that
+ * LayoutParams have changed.</p>
*
* @return The LayoutParams associated with this view, or null if no
* parameters have been set yet
@@ -12642,6 +12663,9 @@
* correspond to the different subclasses of ViewGroup that are responsible
* for arranging their children.
*
+ * <p>If the View's existing LayoutParams object as obtained by {@link #getLayoutParams()} is
+ * modified, you should call this method to inform the view that it has changed.</p>
+ *
* @param params The layout parameters for this view, cannot be null
*/
public void setLayoutParams(ViewGroup.LayoutParams params) {
@@ -12649,6 +12673,7 @@
throw new NullPointerException("Layout parameters cannot be null");
}
mLayoutParams = params;
+ mPrivateFlags3 |= PFLAG3_LAYOUT_PARAMS_CHANGED;
resolveLayoutParams();
if (mParent instanceof ViewGroup) {
((ViewGroup) mParent).onSetLayoutParams(this, params);
@@ -14324,7 +14349,12 @@
mParent.requestTransparentRegion(this);
}
- mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
+ if ((mPrivateFlags & PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH) != 0) {
+ initialAwakenScrollBars();
+ mPrivateFlags &= ~PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH;
+ }
+
+ mPrivateFlags3 &= ~(PFLAG3_IS_LAID_OUT | PFLAG3_PARTIAL_LAYOUT_REQUESTED);
jumpDrawablesToCurrentState();
@@ -14662,8 +14692,13 @@
*/
@CallSuper
protected void onDetachedFromWindowInternal() {
+ if (mAttachInfo != null && isPartialLayoutRequested()) {
+ mAttachInfo.mPartialLayoutViews.remove(this);
+ }
+
mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
- mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
+ mPrivateFlags3 &= ~(PFLAG3_IS_LAID_OUT | PFLAG3_PARTIAL_LAYOUT_REQUESTED
+ | PFLAG3_LAYOUT_PARAMS_CHANGED);
removeUnsetPressCallback();
removeLongPressCallback();
@@ -16850,6 +16885,29 @@
}
/**
+ * Indicates whether or not this view has requested a partial layout that
+ * may not affect its size or position within its parent. This state will be reset
+ * the next time this view is laid out.
+ *
+ * @return true if partial layout has been requested
+ */
+ public final boolean isPartialLayoutRequested() {
+ return (mPrivateFlags3 & PFLAG3_PARTIAL_LAYOUT_REQUESTED)
+ == PFLAG3_PARTIAL_LAYOUT_REQUESTED;
+ }
+
+ /**
+ * Returns true if this view's {@link ViewGroup.LayoutParams LayoutParams} changed
+ * since the last time this view was successfully laid out. Typically this happens as a
+ * result of a call to {@link #setLayoutParams(LayoutParams)}.
+ *
+ * @return true if this view's LayoutParams changed since last layout.
+ */
+ public final boolean didLayoutParamsChange() {
+ return (mPrivateFlags3 & PFLAG3_LAYOUT_PARAMS_CHANGED) == PFLAG3_LAYOUT_PARAMS_CHANGED;
+ }
+
+ /**
* Return true if o is a ViewGroup that is laying out using optical bounds.
* @hide
*/
@@ -16906,6 +16964,7 @@
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
+ mPrivateFlags3 &= ~PFLAG3_LAYOUT_PARAMS_CHANGED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
@@ -16919,6 +16978,7 @@
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
+ mPrivateFlags3 &= ~PFLAG3_PARTIAL_LAYOUT_REQUESTED;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
@@ -19012,7 +19072,7 @@
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
- mParent.requestLayout();
+ mParent.requestLayoutForChild(this);
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
@@ -19031,6 +19091,11 @@
mPrivateFlags |= PFLAG_INVALIDATED;
}
+ void forcePartialLayout() {
+ forceLayout();
+ mPrivateFlags3 |= PFLAG3_PARTIAL_LAYOUT_REQUESTED;
+ }
+
/**
* <p>
* This is called to find out how big a view should be. The parent
@@ -19905,11 +19970,11 @@
}
Surface surface = new Surface();
try {
- mAttachInfo.mDragToken = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
+ IBinder token = mAttachInfo.mSession.prepareDrag(mAttachInfo.mWindow,
flags, shadowSize.x, shadowSize.y, surface);
- if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token="
- + mAttachInfo.mDragToken + " surface=" + surface);
- if (mAttachInfo.mDragToken != null) {
+ if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "prepareDrag returned token=" + token
+ + " surface=" + surface);
+ if (token != null) {
Canvas canvas = surface.lockCanvas(null);
try {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
@@ -19926,7 +19991,7 @@
// repurpose 'shadowSize' for the last touch point
root.getLastTouchPoint(shadowSize);
- okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, mAttachInfo.mDragToken,
+ okay = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, token,
shadowSize.x, shadowSize.y,
shadowTouchPoint.x, shadowTouchPoint.y, data);
if (ViewDebug.DEBUG_DRAG) Log.d(VIEW_LOG_TAG, "performDrag returned " + okay);
@@ -19943,22 +20008,6 @@
return okay;
}
- public final void cancelDrag() {
- if (ViewDebug.DEBUG_DRAG) {
- Log.d(VIEW_LOG_TAG, "cancelDrag");
- }
- if (mAttachInfo.mDragToken != null) {
- try {
- mAttachInfo.mSession.cancelDrag(mAttachInfo.mDragToken);
- } catch (Exception e) {
- Log.e(VIEW_LOG_TAG, "Unable to cancel drag", e);
- }
- mAttachInfo.mDragToken = null;
- } else {
- Log.e(VIEW_LOG_TAG, "No active drag to cancel");
- }
- }
-
/**
* Starts a move from {startX, startY}, the amount of the movement will be the offset
* between {startX, startY} and the new cursor positon.
@@ -21867,6 +21916,7 @@
interface Callbacks {
void playSoundEffect(int effectId);
boolean performHapticFeedback(int effectId, boolean always);
+ void schedulePartialLayout();
}
/**
@@ -22237,9 +22287,10 @@
View mViewRequestingLayout;
/**
- * Used to track the identity of the current drag operation.
+ * Used to track views that need (at least) a partial relayout at their current size
+ * during the next traversal.
*/
- IBinder mDragToken;
+ final List<View> mPartialLayoutViews = new ArrayList<View>();
/**
* Creates a new set of attachment information with the specified
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 25df004..6812fd1 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -60,6 +60,8 @@
import java.util.List;
import java.util.Map;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
/**
* <p>
@@ -5517,6 +5519,172 @@
int l, int t, int r, int b);
/**
+ * {@inheritDoc}
+ *
+ * <p>Most subclasses should not need to override this method. The default implementation
+ * will call {@link #findDependentLayoutAxes(View, int)} to determine how
+ * to optimally proceed. If neither horizontal nor vertical layout depends on the given
+ * child, this method will call {@link #requestPartialLayoutForChild(View)}. If one or both
+ * do, it will call {@link #requestLayout()}.</p>
+ *
+ * @param child Child requesting a layout
+ */
+ @Override
+ public void requestLayoutForChild(View child) {
+ if (child == null || child.getParent() != this) {
+ throw new IllegalArgumentException(
+ "child parameter must be a direct child view of this ViewGroup");
+ }
+
+ // If we don't have a parent ourselves, record that we need a full layout.
+ // Our whole subtree is detached.
+ final ViewParent parent = getParent();
+ if (parent == null) {
+ requestLayout();
+ return;
+ }
+
+ // We can optimize the layout request for this child into a partial layout
+ // if the child has already been laid out at least once and neither horizontal nor
+ // vertical layout within ourselves is dependent on pending layout changes within
+ // this child. Otherwise we need to request a full layout for ourselves and continue
+ // to recurse up the view hierarchy.
+ if (child.isLaidOut() && findDependentLayoutAxes(child, FLAG_LAYOUT_AXIS_ANY) == 0) {
+ requestPartialLayoutForChild(child);
+ } else {
+ requestLayout();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>The default implementation returns {@link #FLAG_LAYOUT_AXIS_ANY}.
+ * Optimized implementations for specific ViewGroup subclasses may check if the child's
+ * {@link View#didLayoutParamsChange() LayoutParams changed} and in what ways.</p>
+ *
+ * @param child Direct child of this ViewParent to check
+ * @param axisFilter Which axes to check for dependencies. Can be
+ * {@link #FLAG_LAYOUT_AXIS_HORIZONTAL}, {@link #FLAG_LAYOUT_AXIS_VERTICAL}
+ * or {@link #FLAG_LAYOUT_AXIS_ANY}.
+ * @return Axes of this ViewParent that depend on the given child's layout changes
+ */
+ @Override
+ public int findDependentLayoutAxes(View child, int axisFilter) {
+ return FLAG_LAYOUT_AXIS_ANY;
+ }
+
+ /**
+ * This is a helper implementation for {@link #findDependentLayoutAxes(View, int)} that
+ * is not the default implementation in ViewGroup. This is to preserve compatibility with
+ * existing app-side ViewGroup subclasses that existed before the partial layout system was
+ * added to Android. It explicitly checks that the LayoutParams of the child are of the
+ * expected type so that subclasses of standard framework layouts do not erroneously
+ * start believing that it's safe to do a partial layout when that assertion can't
+ * reasonably be confirmed.
+ *
+ * <p>If you're reading this as an author of a custom ViewGroup's findDependentLayoutAxes
+ * method you might be frustrated to discover that it is not a part of the Android public API.
+ * Many ViewGroup implementations will need to make small but important modifications
+ * to an implementation like this one in order to be correct. Instead of encouraging
+ * view authors to call this method, then make their own redundant recursive calls to
+ * <code>getParent().findDependentLayoutAxes(...)</code> in addition to the one
+ * that can happen here, this method is hidden and only used internally.</p>
+ *
+ * <p>Do feel free to copy this implementation and adapt it to suit your own purposes.</p>
+ *
+ * @hide
+ */
+ protected final int findDependentLayoutAxesHelper(View child, int axisFilter,
+ Class<?> layoutParamsClass) {
+ if (!checkPartialLayoutParams(child, layoutParamsClass)) return axisFilter;
+ if (child.didLayoutParamsChange()) {
+ // Anything could have changed about our previous assumptions.
+ return axisFilter;
+ }
+
+ final LayoutParams lp = child.getLayoutParams();
+
+ // Our layout can always end up depending on a WRAP_CONTENT child.
+ final int wrapAxisFilter = ((lp.width == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0)
+ | (lp.height == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter;
+
+ if (wrapAxisFilter == axisFilter) {
+ // We know all queried axes are affected, just return early.
+ return wrapAxisFilter;
+ }
+
+ // Our layout *may* depend on a MATCH_PARENT child, depending on whether we can determine
+ // that our layout will remain stable within our parent. We need to ask.
+ final int matchAxisFilter = ((lp.width == MATCH_PARENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0)
+ | (lp.height == MATCH_PARENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter;
+
+ if (matchAxisFilter != 0) {
+ final ViewParent parent = getParent();
+ if (parent != null) {
+ // If our parent depends on us for an axis, then our layout can also be affected
+ // by a MATCH_PARENT child along that axis.
+ return getParent().findDependentLayoutAxes(this, matchAxisFilter)
+ | wrapAxisFilter;
+ }
+
+ // If we don't have a parent, assume we're affected
+ // in any determined affected direction.
+ return matchAxisFilter | wrapAxisFilter;
+ }
+
+ // Two exact sizes and LayoutParams didn't change. We're safe.
+ return 0;
+ }
+
+ /**
+ * Throw an IllegalArgumentException if the supplied view is not a direct child of
+ * this ViewGroup and return false if this view's LayoutParams is not of class lpClass.
+ * Implementations of {@link ViewGroup#findDependentLayoutAxes(View, int)} use this
+ * to check input parameters and defensively return the full axis filter mask themselves
+ * if the LayoutParams class is not of the exact expected type; e.g. it is a subclass
+ * of one of the standard framework layouts and we can't make assumptions.
+ * @hide
+ */
+ protected final boolean checkPartialLayoutParams(View child, Class<?> lpClass) {
+ if (child.getParent() != this) {
+ throw new IllegalArgumentException("View " + child
+ + " is not a direct child of " + this);
+ }
+ final ViewGroup.LayoutParams lp = child.getLayoutParams();
+ return lp != null || lp.getClass() == lpClass;
+ }
+
+ /**
+ * Called when a child of this ViewParent requires a relayout before the next frame
+ * is drawn, but the caller can guarantee that the size of the child will not change
+ * during a measure and layout pass.
+ *
+ * <p>A call to this method will schedule a partial layout for the supplied view as long as
+ * it is a direct child of this ViewGroup and this ViewGroup is attached to a window.
+ * On the next scheduled view hierarchy traversal the given child view will be re-measured
+ * at its current measured size and re-laid out at its current position within its parent.</p>
+ *
+ * @param child Child that requires a partial layout
+ */
+ public void requestPartialLayoutForChild(View child) {
+ if (!child.isPartialLayoutRequested()) {
+ child.forcePartialLayout();
+ if (mAttachInfo != null) {
+ final List<View> partialLayoutViews = mAttachInfo.mPartialLayoutViews;
+ final boolean schedule = partialLayoutViews.isEmpty();
+ partialLayoutViews.add(child);
+ if (schedule) {
+ mAttachInfo.mRootCallbacks.schedulePartialLayout();
+ }
+ child.invalidate();
+ } else {
+ requestLayout();
+ }
+ }
+ }
+
+ /**
* Indicates whether the view group has the ability to animate its children
* after the first layout.
*
@@ -5862,7 +6030,7 @@
* of its descendants
*/
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- return p;
+ return new LayoutParams(p);
}
/**
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 07f1e2c..6ae448a 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -26,6 +26,11 @@
*
*/
public interface ViewParent {
+ public static final int FLAG_LAYOUT_AXIS_HORIZONTAL = 1;
+ public static final int FLAG_LAYOUT_AXIS_VERTICAL = 2;
+ public static final int FLAG_LAYOUT_AXIS_ANY
+ = FLAG_LAYOUT_AXIS_HORIZONTAL | FLAG_LAYOUT_AXIS_VERTICAL;
+
/**
* Called when something has changed which has invalidated the layout of a
* child of this view parent. This will schedule a layout pass of the view
@@ -601,4 +606,48 @@
* @return true if the action was consumed by this ViewParent
*/
public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle arguments);
+
+ /**
+ * Called when a child of this ViewParent requires a relayout before
+ * the next frame is drawn. A call to {@link View#requestLayout() child.requestLayout()}
+ * will implicitly result in a call to
+ * <code>child.getParent().requestLayoutForChild(child)</code>. App code should not call this
+ * method directly. Call <code>child.requestLayout()</code> instead.
+ *
+ * <p>On versions of Android from API 23 and older, a call to {@link View#requestLayout()}
+ * would cause a matching call to <code>requestLayout</code> on each parent view up to
+ * the root. With the addition of <code>requestLayoutForChild</code> a view's parent may
+ * explicitly decide how to handle a layout request. This allows for optimizations when
+ * a view parent knows that a layout-altering change in a child will not affect its own
+ * measurement.</p>
+ *
+ * @param child Child requesting a layout
+ */
+ public void requestLayoutForChild(View child);
+
+ /**
+ * Determine which axes of this ViewParent's layout are dependent on the given
+ * direct child view. The returned value is a flag set that may contain
+ * {@link #FLAG_LAYOUT_AXIS_HORIZONTAL} and/or {@link #FLAG_LAYOUT_AXIS_VERTICAL}.
+ * {@link #FLAG_LAYOUT_AXIS_ANY} is provided as a shortcut for
+ * <code>FLAG_LAYOUT_AXIS_HORIZONTAL | FLAG_LAYOUT_AXIS_VERTICAL</code>.
+ *
+ * <p>The given child must be a direct child view. Implementations should throw
+ * {@link IllegalArgumentException} otherwise.</p>
+ *
+ * <p>The caller may specify which axes it cares about. This should be treated as a filter.
+ * Implementations should never return a result that would be different from
+ * <code>result & axisFilter</code>.</p>
+ *
+ * @param child Direct child of this ViewParent to check
+ * @param axisFilter Which axes to check for dependencies. Can be
+ * {@link #FLAG_LAYOUT_AXIS_HORIZONTAL}, {@link #FLAG_LAYOUT_AXIS_VERTICAL}
+ * or {@link #FLAG_LAYOUT_AXIS_ANY}.
+ * @return Axes of this ViewParent that depend on the given child's layout changes
+ *
+ * @see #FLAG_LAYOUT_AXIS_HORIZONTAL
+ * @see #FLAG_LAYOUT_AXIS_VERTICAL
+ * @see #FLAG_LAYOUT_AXIS_ANY
+ */
+ public int findDependentLayoutAxes(View child, int axisFilter);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f1d9f1ab..5bbfc3f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -91,6 +91,7 @@
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.HashSet;
+import java.util.List;
/**
* The top of a view hierarchy, implementing the needed protocol between View
@@ -952,6 +953,25 @@
}
@Override
+ public void requestLayoutForChild(View child) {
+ requestLayout();
+ }
+
+ @Override
+ public int findDependentLayoutAxes(View child, int axisFilter) {
+ if (child != mView) {
+ return 0;
+ }
+
+ final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) child.getLayoutParams();
+ final int horizontal = (lp.width == WindowManager.LayoutParams.WRAP_CONTENT
+ || lp.horizontalWeight != 0) ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0;
+ final int vertical = (lp.height == WindowManager.LayoutParams.WRAP_CONTENT
+ || lp.verticalWeight != 0) ? FLAG_LAYOUT_AXIS_VERTICAL : 0;
+ return (horizontal | vertical) & axisFilter;
+ }
+
+ @Override
public boolean isLayoutRequested() {
return mLayoutRequested;
}
@@ -1095,6 +1115,10 @@
}
}
+ public void schedulePartialLayout() {
+ scheduleTraversals();
+ }
+
/**
* Notifies the HardwareRenderer that a new frame will be coming soon.
* Currently only {@link ThreadedRenderer} cares about this, and uses
@@ -1934,7 +1958,48 @@
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
+ }
+ /*
+ * Handle partial layouts.
+ *
+ * Views that have requested partial layouts will not change size or position
+ * within their parent view, therefore we will re-measure and re-layout each one
+ * after any regularly scheduled layout pass. Any view that already had its
+ * isLayoutRequested bit cleared will be skipped, since this means the view has already
+ * been measured and laid out on this traversal pass naturally. Views won't be added
+ * to this list if layout was already requested when a partial layout is requested
+ * for a view, so there should not be duplicates in the list.
+ */
+ final List<View> partialLayoutViews = mAttachInfo.mPartialLayoutViews;
+ final boolean didPartialLayout;
+ if (!partialLayoutViews.isEmpty()) {
+ final int count = partialLayoutViews.size();
+ mInLayout = true;
+ for (int i = 0; i < count; i++) {
+ final View view = partialLayoutViews.get(i);
+
+ // Make sure the view is still attached and that it still has layout requested.
+ // We might have already serviced the layout request through the standard full-tree
+ // layout pass above or even through a previous partial layout view in this list.
+ if (view.isAttachedToWindow() && view.isLayoutRequested()) {
+ final int widthSpec = MeasureSpec.makeMeasureSpec(view.getMeasuredWidth(),
+ MeasureSpec.EXACTLY);
+ final int heightSpec = MeasureSpec.makeMeasureSpec(view.getMeasuredHeight(),
+ MeasureSpec.EXACTLY);
+ view.measure(widthSpec, heightSpec);
+ view.layout(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
+ }
+ }
+ mInLayout = false;
+ partialLayoutViews.clear();
+ didPartialLayout = true;
+ triggerGlobalLayoutListener = true;
+ } else {
+ didPartialLayout = false;
+ }
+
+ if (didLayout || didPartialLayout) {
// By this point all views have been sized and positioned
// We can compute the transparent area
@@ -1964,7 +2029,7 @@
if (DBG) {
System.out.println("======================================");
- System.out.println("performTraversals -- after setFrame");
+ System.out.println("performTraversals -- after performLayout/partial layout");
host.debug();
}
}
@@ -5307,10 +5372,10 @@
}
}
- // When the drag operation ends, reset drag-related state
+ // When the drag operation ends, release any local state object
+ // that may have been in use
if (what == DragEvent.ACTION_DRAG_ENDED) {
setLocalDragState(null);
- mAttachInfo.mDragToken = null;
}
}
}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 8259372..3c4d45a 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -276,6 +276,8 @@
private boolean mDestroyed;
+ private boolean mOverlayWithDecorCaption = false;
+
// The current window attributes.
private final WindowManager.LayoutParams mWindowAttributes =
new WindowManager.LayoutParams();
@@ -2044,4 +2046,18 @@
/** @hide */
public void setTheme(int resId) {
}
+
+ /**
+ * Whether the caption should be displayed directly on the content rather than push the content
+ * down. This affects only freeform windows since they display the caption.
+ * @hide
+ */
+ public void setOverlayDecorCaption(boolean overlayCaption) {
+ mOverlayWithDecorCaption = overlayCaption;
+ }
+
+ /** @hide */
+ public boolean getOverlayDecorCaption() {
+ return mOverlayWithDecorCaption;
+ }
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 9428f44..19a98f3 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -540,6 +540,13 @@
void deactivate() {
mActive = false;
}
+
+ @Override
+ public String toString() {
+ return "ControlledInputConnectionWrapper{mActive=" + mActive
+ + " mParentInputMethodManager.mActive=" + mParentInputMethodManager.mActive
+ + "}";
+ }
}
final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index b8faf0c..90de053 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2109,6 +2109,11 @@
}
@Override
+ public int findDependentLayoutAxes(View child, int axisFilter) {
+ return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+ }
+
+ @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mSelector == null) {
useDefaultSelector();
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index a018d26..9f94005 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -28,6 +28,8 @@
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.RemotableViewMethod;
@@ -447,6 +449,68 @@
return CheckedTextView.class.getName();
}
+ static class SavedState extends BaseSavedState {
+ boolean checked;
+
+ /**
+ * Constructor called from {@link CheckedTextView#onSaveInstanceState()}
+ */
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ /**
+ * Constructor called from {@link #CREATOR}
+ */
+ private SavedState(Parcel in) {
+ super(in);
+ checked = (Boolean)in.readValue(null);
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeValue(checked);
+ }
+
+ @Override
+ public String toString() {
+ return "CheckedTextView.SavedState{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " checked=" + checked + "}";
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR
+ = new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+
+ SavedState ss = new SavedState(superState);
+
+ ss.checked = isChecked();
+ return ss;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+
+ super.onRestoreInstanceState(ss.getSuperState());
+ setChecked(ss.checked);
+ requestLayout();
+ }
+
/** @hide */
@Override
public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index 280ff15..4d9f55c 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -21,12 +21,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.PorterDuff;
import android.graphics.Rect;
-import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
@@ -36,9 +32,6 @@
import android.view.ViewHierarchyEncoder;
import android.widget.RemoteViews.RemoteView;
-import com.android.internal.R;
-
-
/**
* FrameLayout is designed to block out an area on the screen to display
* a single item. Generally, FrameLayout should be used to hold a single child view, because it can
@@ -171,6 +164,10 @@
mPaddingBottom + mForegroundPaddingBottom;
}
+ @Override
+ public int findDependentLayoutAxes(View child, int axisFilter) {
+ return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+ }
/**
* {@inheritDoc}
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index ad939be..ba868a1 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.view.ViewParent;
import com.android.internal.R;
import android.annotation.IntDef;
@@ -37,6 +38,8 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
/**
* A Layout that arranges its children in a single column or a single row. The direction of
@@ -644,6 +647,60 @@
}
}
+ @Override
+ public int findDependentLayoutAxes(View child, int axisFilter) {
+ // This implementation is almost exactly equivalent to the default implementation
+ // offered to the rest of the framework in ViewGroup, but we treat weight to be
+ // functionally equivalent to MATCH_PARENT along the orientation axis.
+
+ if (!checkPartialLayoutParams(child, LayoutParams.class)) return axisFilter;
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (child.didLayoutParamsChange()) {
+ // Anything could have changed about our previous assumptions.
+ return axisFilter;
+ }
+
+ // Our layout can always end up depending on a WRAP_CONTENT child.
+ final int wrapAxisFilter = ((lp.width == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0)
+ | (lp.height == WRAP_CONTENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter;
+
+ if (wrapAxisFilter == axisFilter) {
+ // We know all queried axes are affected, just return early.
+ return wrapAxisFilter;
+ }
+
+ // Our layout *may* depend on a MATCH_PARENT child, depending on whether we can determine
+ // that our layout will remain stable within our parent. We need to ask.
+ int matchAxisFilter = ((lp.width == MATCH_PARENT ? FLAG_LAYOUT_AXIS_HORIZONTAL : 0)
+ | (lp.height == MATCH_PARENT ? FLAG_LAYOUT_AXIS_VERTICAL : 0)) & axisFilter;
+
+ // For LinearLayout, a nonzero weight is equivalent to MATCH_PARENT for this purpose.
+ if (lp.weight > 0) {
+ if (mOrientation == HORIZONTAL) {
+ matchAxisFilter |= FLAG_LAYOUT_AXIS_HORIZONTAL & axisFilter;
+ } else {
+ matchAxisFilter |= FLAG_LAYOUT_AXIS_VERTICAL & axisFilter;
+ }
+ }
+
+ if (matchAxisFilter != 0) {
+ final ViewParent parent = getParent();
+ if (parent != null) {
+ // If our parent depends on us for an axis, then our layout can also be affected
+ // by a MATCH_PARENT child along that axis.
+ return getParent().findDependentLayoutAxes(this, matchAxisFilter)
+ | wrapAxisFilter;
+ }
+
+ // If we don't have a parent, assume we're affected
+ // in any determined affected direction.
+ return matchAxisFilter | wrapAxisFilter;
+ }
+
+ // Two exact sizes and LayoutParams didn't change. We're safe.
+ return 0;
+ }
+
/**
* Determines where to position dividers between children.
*
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 53ca6d1..b43ea76 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1158,7 +1158,7 @@
final View child = obtainView(0, mIsScrap);
// Lay out child directly against the parent measure spec so that
- // we can obtain exected minimum width and height.
+ // we can obtain expected minimum width and height.
measureScrapChild(child, 0, widthMeasureSpec, heightSize);
childWidth = child.getMeasuredWidth();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3e6d121..eaf4fe2 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6801,10 +6801,11 @@
if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
if (!compressText(ellipsisWidth)) {
- final int height = mLayoutParams.height;
// If the size of the view does not depend on the size of the text, try to
// start the marquee immediately
- if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
+ final ViewParent parent = getParent();
+ if (parent != null && parent.findDependentLayoutAxes(this,
+ ViewParent.FLAG_LAYOUT_AXIS_VERTICAL) == 0) {
startMarquee();
} else {
// Defer the start of the marquee until we know our width (see setFrame())
@@ -7200,37 +7201,9 @@
* new view layout.
*/
private void checkForResize() {
- boolean sizeChanged = false;
-
- if (mLayout != null) {
- // Check if our width changed
- if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
- sizeChanged = true;
- invalidate();
- }
-
- // Check if our height changed
- if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
- int desiredHeight = getDesiredHeight();
-
- if (desiredHeight != this.getHeight()) {
- sizeChanged = true;
- }
- } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
- if (mDesiredHeightAtMeasure >= 0) {
- int desiredHeight = getDesiredHeight();
-
- if (desiredHeight != mDesiredHeightAtMeasure) {
- sizeChanged = true;
- }
- }
- }
- }
-
- if (sizeChanged) {
- requestLayout();
- // caller will have already invalidated
- }
+ // Always request a layout. The parent will perform the correct version
+ // of the intended optimizations as part of requestLayoutForChild.
+ requestLayout();
}
/**
@@ -7238,56 +7211,10 @@
* or merely a new text layout.
*/
private void checkForRelayout() {
- // If we have a fixed width, we can just swap in a new text layout
- // if the text height stays the same or if the view height is fixed.
-
- if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
- (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
- (mHint == null || mHintLayout != null) &&
- (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
- // Static width, so try making a new text layout.
-
- int oldht = mLayout.getHeight();
- int want = mLayout.getWidth();
- int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
-
- /*
- * No need to bring the text into view, since the size is not
- * changing (unless we do the requestLayout(), in which case it
- * will happen at measure).
- */
- makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
- mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
- false);
-
- if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
- // In a fixed-height view, so use our new text layout.
- if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
- mLayoutParams.height != LayoutParams.MATCH_PARENT) {
- invalidate();
- return;
- }
-
- // Dynamic height, but height has stayed the same,
- // so use our new text layout.
- if (mLayout.getHeight() == oldht &&
- (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
- invalidate();
- return;
- }
- }
-
- // We lose: the height has changed and we have a dynamic height.
- // Request a new view layout using our new text layout.
- requestLayout();
- invalidate();
- } else {
- // Dynamic width, so we have no choice but to request a new
- // view layout with a new text layout.
- nullLayouts();
- requestLayout();
- invalidate();
- }
+ // Always request a layout. The parent will perform the correct version
+ // of the intended optimizations as part of requestLayoutForChild.
+ nullLayouts();
+ requestLayout();
}
@Override
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index acbf5eb..6e56513 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -1368,6 +1368,11 @@
}
@Override
+ public int findDependentLayoutAxes(View child, int axisFilter) {
+ return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+ }
+
+ @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 0;
int height = 0;
diff --git a/core/java/com/android/internal/app/EphemeralResolveInfo.aidl b/core/java/com/android/internal/app/EphemeralResolveInfo.aidl
new file mode 100644
index 0000000..529527b
--- /dev/null
+++ b/core/java/com/android/internal/app/EphemeralResolveInfo.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 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.internal.app;
+
+parcelable EphemeralResolveInfo;
diff --git a/core/java/com/android/internal/app/EphemeralResolveInfo.java b/core/java/com/android/internal/app/EphemeralResolveInfo.java
new file mode 100644
index 0000000..0e7ef05
--- /dev/null
+++ b/core/java/com/android/internal/app/EphemeralResolveInfo.java
@@ -0,0 +1,113 @@
+/*
+ * 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.internal.app;
+
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Information that is returned when resolving ephemeral
+ * applications.
+ */
+public final class EphemeralResolveInfo implements Parcelable {
+ public static final String SHA_ALGORITHM = "SHA-256";
+ private byte[] mDigestBytes;
+ private int mDigestPrefix;
+ private final List<IntentFilter> mFilters = new ArrayList<IntentFilter>();
+
+ public EphemeralResolveInfo(Uri uri, List<IntentFilter> filters) {
+ generateDigest(uri);
+ mFilters.addAll(filters);
+ }
+
+ private EphemeralResolveInfo(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public byte[] getDigestBytes() {
+ return mDigestBytes;
+ }
+
+ public int getDigestPrefix() {
+ return mDigestPrefix;
+ }
+
+ public List<IntentFilter> getFilters() {
+ return mFilters;
+ }
+
+ private void generateDigest(Uri uri) {
+ try {
+ final MessageDigest digest = MessageDigest.getInstance(SHA_ALGORITHM);
+ final byte[] hostBytes = uri.getHost().getBytes();
+ final byte[] digestBytes = digest.digest(hostBytes);
+ mDigestBytes = digestBytes;
+ mDigestPrefix =
+ digestBytes[0] << 24
+ | digestBytes[1] << 16
+ | digestBytes[2] << 8
+ | digestBytes[3] << 0;
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("could not find digest algorithm");
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ if (mDigestBytes == null) {
+ out.writeInt(0);
+ } else {
+ out.writeInt(mDigestBytes.length);
+ out.writeByteArray(mDigestBytes);
+ }
+ out.writeInt(mDigestPrefix);
+ out.writeList(mFilters);
+ }
+
+ private void readFromParcel(Parcel in) {
+ int digestBytesSize = in.readInt();
+ if (digestBytesSize > 0) {
+ mDigestBytes = new byte[digestBytesSize];
+ in.readByteArray(mDigestBytes);
+ }
+ mDigestPrefix = in.readInt();
+ in.readList(mFilters, null /*loader*/);
+ }
+
+ public static final Parcelable.Creator<EphemeralResolveInfo> CREATOR
+ = new Parcelable.Creator<EphemeralResolveInfo>() {
+ public EphemeralResolveInfo createFromParcel(Parcel in) {
+ return new EphemeralResolveInfo(in);
+ }
+
+ public EphemeralResolveInfo[] newArray(int size) {
+ return new EphemeralResolveInfo[size];
+ }
+ };
+}
diff --git a/core/java/com/android/internal/app/EphemeralResolverService.java b/core/java/com/android/internal/app/EphemeralResolverService.java
new file mode 100644
index 0000000..65530f2
--- /dev/null
+++ b/core/java/com/android/internal/app/EphemeralResolverService.java
@@ -0,0 +1,98 @@
+/*
+ * 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.internal.app;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+
+import java.util.List;
+
+/**
+ * Base class for implementing the resolver service.
+ * @hide
+ */
+public abstract class EphemeralResolverService extends Service {
+ public static final String EXTRA_RESOLVE_INFO = "com.android.internal.app.RESOLVE_INFO";
+ public static final String EXTRA_SEQUENCE = "com.android.internal.app.SEQUENCE";
+ private Handler mHandler;
+
+ /**
+ * Called to retrieve resolve info for ephemeral applications.
+ *
+ * @param digestPrefix The hash prefix of the ephemeral's domain.
+ */
+ protected abstract List<EphemeralResolveInfo> getEphemeralResolveInfoList(int digestPrefix);
+
+ @Override
+ protected final void attachBaseContext(Context base) {
+ super.attachBaseContext(base);
+ mHandler = new ServiceHandler(base.getMainLooper());
+ }
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return new IEphemeralResolver.Stub() {
+ @Override
+ public void getEphemeralResolveInfoList(
+ IRemoteCallback callback, int digestPrefix, int sequence) {
+ mHandler.obtainMessage(ServiceHandler.MSG_GET_EPHEMERAL_RESOLVE_INFO,
+ digestPrefix, sequence, callback)
+ .sendToTarget();
+ }
+ };
+ }
+
+ private final class ServiceHandler extends Handler {
+ public static final int MSG_GET_EPHEMERAL_RESOLVE_INFO = 1;
+
+ public ServiceHandler(Looper looper) {
+ super(looper, null /*callback*/, true /*async*/);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void handleMessage(Message message) {
+ final int action = message.what;
+ switch (action) {
+ case MSG_GET_EPHEMERAL_RESOLVE_INFO: {
+ final IRemoteCallback callback = (IRemoteCallback) message.obj;
+ final List<EphemeralResolveInfo> resolveInfo =
+ getEphemeralResolveInfoList(message.arg1);
+ final Bundle data = new Bundle();
+ data.putInt(EXTRA_SEQUENCE, message.arg2);
+ data.putParcelableList(EXTRA_RESOLVE_INFO, resolveInfo);
+ try {
+ callback.sendResult(data);
+ } catch (RemoteException e) {
+ }
+ } break;
+
+ default: {
+ throw new IllegalArgumentException("Unknown message: " + action);
+ }
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/app/IEphemeralResolver.aidl b/core/java/com/android/internal/app/IEphemeralResolver.aidl
new file mode 100644
index 0000000..40429ee
--- /dev/null
+++ b/core/java/com/android/internal/app/IEphemeralResolver.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.internal.app;
+
+import android.content.Intent;
+import android.os.IRemoteCallback;
+
+oneway interface IEphemeralResolver {
+ void getEphemeralResolveInfoList(IRemoteCallback callback, int digestPrefix, int sequence);
+}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 077cebc..27fe03c 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -25,8 +25,8 @@
import com.android.internal.view.menu.MenuPopupHelper;
import com.android.internal.widget.ActionBarContextView;
import com.android.internal.widget.BackgroundFallback;
+import com.android.internal.widget.DecorCaptionView;
import com.android.internal.widget.FloatingToolbar;
-import com.android.internal.widget.NonClientDecorView;
import android.animation.Animator;
import android.animation.ObjectAnimator;
@@ -88,6 +88,18 @@
private static final boolean SWEEP_OPEN_MENU = false;
+ // The height of a window which has focus in DIP.
+ private final static int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20;
+ // The height of a window which has not in DIP.
+ private final static int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
+
+ // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer
+ // size calculation takes the shadow size into account. We set the elevation currently
+ // to max until the first layout command has been executed.
+ private boolean mAllowUpdateElevation = false;
+
+ private boolean mElevationAdjustedForStack = false;
+
int mDefaultOpacity = PixelFormat.OPAQUE;
/** The feature ID of the panel, or -1 if this is the application's DecorView */
@@ -101,8 +113,7 @@
private final Rect mFrameOffsets = new Rect();
- // True if a non client area decor exists.
- private boolean mHasNonClientDecor = false;
+ private boolean mHasCaption = false;
private boolean mChanging;
@@ -161,18 +172,18 @@
private Rect mTempRect;
private Rect mOutsets = new Rect();
- // This is the non client decor view for the window, containing the caption and window control
+ // This is the caption view for the window, containing the caption and window control
// buttons. The visibility of this decor depends on the workspace and the window type.
// If the window type does not require such a view, this member might be null.
- NonClientDecorView mNonClientDecorView;
+ DecorCaptionView mDecorCaptionView;
- // The non client decor needs to adapt to the used workspace. Since querying and changing the
- // workspace is expensive, this is the workspace value the window is currently set up for.
- int mWorkspaceId;
+ // Stack window is currently in. Since querying and changing the stack is expensive,
+ // this is the stack value the window is currently set up for.
+ int mStackId;
private boolean mWindowResizeCallbacksAdded = false;
- public BackdropFrameRenderer mBackdropFrameRenderer = null;
+ BackdropFrameRenderer mBackdropFrameRenderer = null;
private Drawable mResizingBackgroundDrawable;
private Drawable mCaptionBackgroundDrawable;
@@ -191,7 +202,7 @@
setWindow(window);
}
- public void setBackgroundFallback(int resId) {
+ void setBackgroundFallback(int resId) {
mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null);
setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback());
}
@@ -351,10 +362,10 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
- if (mHasNonClientDecor && mNonClientDecorView.mVisible) {
- // Don't dispatch ACTION_DOWN to the non client decor if the window is
- // resizable and the event was (starting) outside the window.
- // Window resizing events should be handled by WindowManager.
+ if (mHasCaption && isShowingCaption()) {
+ // Don't dispatch ACTION_DOWN to the captionr if the window is resizable and the event
+ // was (starting) outside the window. Window resizing events should be handled by
+ // WindowManager.
// TODO: Investigate how to handle the outside touch in window manager
// without generating these events.
// Currently we receive these because we need to enlarge the window's
@@ -630,6 +641,11 @@
if (mOutsets.top > 0) {
offsetTopAndBottom(-mOutsets.top);
}
+
+ // If the application changed its SystemUI metrics, we might also have to adapt
+ // our shadow elevation.
+ updateElevation();
+ mAllowUpdateElevation = true;
}
@Override
@@ -781,11 +797,11 @@
}
}
- public void startChanging() {
+ void startChanging() {
mChanging = true;
}
- public void finishChanging() {
+ void finishChanging() {
mChanging = false;
drawableChanged();
}
@@ -1138,7 +1154,7 @@
invalidate();
int opacity = PixelFormat.OPAQUE;
- if (windowHasShadow()) {
+ if (ActivityManager.StackId.hasWindowShadow(mStackId)) {
// If the window has a shadow, it must be translucent.
opacity = PixelFormat.TRANSLUCENT;
} else{
@@ -1213,6 +1229,8 @@
if (mFloatingActionMode != null) {
mFloatingActionMode.onWindowFocusChanged(hasWindowFocus);
}
+
+ updateElevation();
}
@Override
@@ -1495,38 +1513,19 @@
}
/**
- * Informs the decor if a non client decor is attached and visible.
+ * Informs the decor if the caption is attached and visible.
* @param attachedAndVisible true when the decor is visible.
- * Note that this will even be called if there is no non client decor.
+ * Note that this will even be called if there is no caption.
**/
- void enableNonClientDecor(boolean attachedAndVisible) {
- if (mHasNonClientDecor != attachedAndVisible) {
- mHasNonClientDecor = attachedAndVisible;
+ void enableCaption(boolean attachedAndVisible) {
+ if (mHasCaption != attachedAndVisible) {
+ mHasCaption = attachedAndVisible;
if (getForeground() != null) {
drawableChanged();
}
}
}
- /**
- * Returns true if the window has a non client decor.
- * @return If there is a non client decor - even if it is not visible.
- **/
- private boolean windowHasNonClientDecor() {
- return mHasNonClientDecor;
- }
-
- /**
- * Returns true if the Window is free floating and has a shadow (although at some times
- * it might not be displaying it, e.g. during a resize). Note that non overlapping windows
- * do not have a shadow since it could not be seen anyways (a small screen / tablet
- * "tiles" the windows side by side but does not overlap them).
- * @return Returns true when the window has a shadow created by the non client decor.
- **/
- private boolean windowHasShadow() {
- return windowHasNonClientDecor() && ActivityManager.StackId.hasWindowShadow(mWorkspaceId);
- }
-
void setWindow(PhoneWindow phoneWindow) {
mWindow = phoneWindow;
Context context = getContext();
@@ -1537,91 +1536,89 @@
}
void onConfigurationChanged() {
- if (mNonClientDecorView != null) {
- int workspaceId = getWorkspaceId();
- if (mWorkspaceId != workspaceId) {
- mWorkspaceId = workspaceId;
+ int workspaceId = getStackId();
+ if (mDecorCaptionView != null) {
+ if (mStackId != workspaceId) {
+ mStackId = workspaceId;
// We might have to change the kind of surface before we do anything else.
- mNonClientDecorView.onConfigurationChanged(
- ActivityManager.StackId.hasWindowDecor(mWorkspaceId),
- ActivityManager.StackId.hasWindowShadow(mWorkspaceId));
- enableNonClientDecor(ActivityManager.StackId.hasWindowDecor(workspaceId));
+ mDecorCaptionView.onConfigurationChanged(
+ ActivityManager.StackId.hasWindowDecor(mStackId));
+ enableCaption(ActivityManager.StackId.hasWindowDecor(workspaceId));
}
}
+ initializeElevation();
}
View onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
- mWorkspaceId = getWorkspaceId();
+ mStackId = getStackId();
mResizingBackgroundDrawable = getResizingBackgroundDrawable(
mWindow.mBackgroundResource, mWindow.mBackgroundFallbackResource);
mCaptionBackgroundDrawable =
- getContext().getDrawable(R.drawable.non_client_decor_title_focused);
+ getContext().getDrawable(R.drawable.decor_caption_title_focused);
if (mBackdropFrameRenderer != null) {
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable);
}
- mNonClientDecorView = createNonClientDecorView(inflater);
+ mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);
- if (mNonClientDecorView != null) {
- if (mNonClientDecorView.getParent() == null) {
- addView(mNonClientDecorView,
+ if (mDecorCaptionView != null) {
+ if (mDecorCaptionView.getParent() == null) {
+ addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
- mNonClientDecorView.addView(root,
- new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ mDecorCaptionView.addView(root,
+ new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
addView(root, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
+ initializeElevation();
return root;
}
- // Free floating overlapping windows require a non client decor with a caption and shadow..
- private NonClientDecorView createNonClientDecorView(LayoutInflater inflater) {
- NonClientDecorView nonClientDecorView = null;
- for (int i = getChildCount() - 1; i >= 0 && nonClientDecorView == null; i--) {
+ // Free floating overlapping windows require a caption.
+ private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {
+ DecorCaptionView DecorCaptionView = null;
+ for (int i = getChildCount() - 1; i >= 0 && DecorCaptionView == null; i--) {
View view = getChildAt(i);
- if (view instanceof NonClientDecorView) {
+ if (view instanceof DecorCaptionView) {
// The decor was most likely saved from a relaunch - so reuse it.
- nonClientDecorView = (NonClientDecorView) view;
+ DecorCaptionView = (DecorCaptionView) view;
removeViewAt(i);
}
}
final WindowManager.LayoutParams attrs = mWindow.getAttributes();
- boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
+ final boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
attrs.type == TYPE_APPLICATION;
- // Only a non floating application window on one of the allowed workspaces can get a non
- // client decor.
- final boolean hasNonClientDecor = ActivityManager.StackId.hasWindowDecor(mWorkspaceId);
- if (!mWindow.isFloating() && isApplication && hasNonClientDecor) {
+ // Only a non floating application window on one of the allowed workspaces can get a caption
+ if (!mWindow.isFloating() && isApplication
+ && ActivityManager.StackId.hasWindowDecor(mStackId)) {
// Dependent on the brightness of the used title we either use the
// dark or the light button frame.
- if (nonClientDecorView == null) {
+ if (DecorCaptionView == null) {
Context context = getContext();
TypedValue value = new TypedValue();
context.getTheme().resolveAttribute(R.attr.colorPrimary, value, true);
inflater = inflater.from(context);
if (Color.luminance(value.data) < 0.5) {
- nonClientDecorView = (NonClientDecorView) inflater.inflate(
- R.layout.non_client_decor_dark, null);
+ DecorCaptionView = (DecorCaptionView) inflater.inflate(
+ R.layout.decor_caption_dark, null);
} else {
- nonClientDecorView = (NonClientDecorView) inflater.inflate(
- R.layout.non_client_decor_light, null);
+ DecorCaptionView = (DecorCaptionView) inflater.inflate(
+ R.layout.decor_caption_light, null);
}
}
- nonClientDecorView.setPhoneWindow(mWindow,
- ActivityManager.StackId.hasWindowDecor(mWorkspaceId),
- ActivityManager.StackId.hasWindowShadow(mWorkspaceId));
+ DecorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/);
} else {
- nonClientDecorView = null;
+ DecorCaptionView = null;
}
- // Tell the decor if it has a visible non client decor.
- enableNonClientDecor(nonClientDecorView != null && hasNonClientDecor);
- return nonClientDecorView;
+ // Tell the decor if it has a visible caption.
+ enableCaption(DecorCaptionView != null);
+ return DecorCaptionView;
}
/**
@@ -1652,12 +1649,12 @@
}
/**
- * Returns the Id of the workspace which contains this window.
- * Note that if no workspace can be determined - which usually means that it was not
- * created for an activity - the fullscreen workspace ID will be returned.
- * @return Returns the workspace stack id which contains this window.
+ * Returns the Id of the stack which contains this window.
+ * Note that if no stack can be determined - which usually means that it was not
+ * created for an activity - the fullscreen stack ID will be returned.
+ * @return Returns the stack id which contains this window.
**/
- private int getWorkspaceId() {
+ private int getStackId() {
int workspaceId = INVALID_STACK_ID;
final Window.WindowControllerCallback callback = mWindow.getWindowControllerCallback();
if (callback != null) {
@@ -1674,12 +1671,10 @@
}
void clearContentView() {
- if (mNonClientDecorView != null) {
- if (mNonClientDecorView.getChildCount() > 1) {
- mNonClientDecorView.removeViewAt(1);
- }
+ if (mDecorCaptionView != null) {
+ mDecorCaptionView.removeContentView();
} else {
- // This window doesn't have non client decor, so we need to just remove the
+ // This window doesn't have caption, so we need to just remove the
// children of the decor view.
removeAllViews();
}
@@ -1749,18 +1744,60 @@
}
}
+ /**
+ * The elevation gets set for the first time and the framework needs to be informed that
+ * the surface layer gets created with the shadow size in mind.
+ */
+ private void initializeElevation() {
+ // TODO(skuhne): Call setMaxElevation here accordingly after b/22668382 got fixed.
+ mAllowUpdateElevation = false;
+ updateElevation();
+ }
+
private void updateElevation() {
- if (mNonClientDecorView != null) {
- mNonClientDecorView.updateElevation();
+ float elevation = 0;
+ final boolean wasAdjustedForStack = mElevationAdjustedForStack;
+ // Do not use a shadow when we are in resizing mode (mBackdropFrameRenderer not null)
+ // since the shadow is bound to the content size and not the target size.
+ if (ActivityManager.StackId.hasWindowShadow(mStackId)
+ && mBackdropFrameRenderer == null) {
+ elevation = hasWindowFocus() ?
+ DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
+ // TODO(skuhne): Remove this if clause once b/22668382 got fixed.
+ if (!mAllowUpdateElevation) {
+ elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
+ }
+ // Convert the DP elevation into physical pixels.
+ elevation = dipToPx(elevation);
+ mElevationAdjustedForStack = true;
+ } else {
+ mElevationAdjustedForStack = false;
+ }
+
+ // Don't change the elevation if we didn't previously adjust it for the stack it was in
+ // or it didn't change.
+ if ((wasAdjustedForStack || mElevationAdjustedForStack)
+ && getElevation() != elevation) {
+ mWindow.setElevation(elevation);
}
}
boolean isShowingCaption() {
- return mNonClientDecorView != null && mNonClientDecorView.isShowingDecor();
+ return mDecorCaptionView != null && mDecorCaptionView.isCaptionShowing();
}
int getCaptionHeight() {
- return isShowingCaption() ? mNonClientDecorView.getDecorCaptionHeight() : 0;
+ return isShowingCaption() ? mDecorCaptionView.getCaptionHeight() : 0;
+ }
+
+ /**
+ * Converts a DIP measure into physical pixels.
+ * @param dip The dip value.
+ * @return Returns the number of pixels.
+ */
+ private float dipToPx(float dip) {
+ return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
+ getResources().getDisplayMetrics());
}
private static class ColorViewState {
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index c3a7460..3e65320 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -360,6 +360,11 @@
}
@Override
+ public int findDependentLayoutAxes(View child, int axisFilter) {
+ return findDependentLayoutAxesHelper(child, axisFilter, LayoutParams.class);
+ }
+
+ @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
pullChildren();
diff --git a/core/java/com/android/internal/widget/DecorCaptionView.java b/core/java/com/android/internal/widget/DecorCaptionView.java
new file mode 100644
index 0000000..16e8296
--- /dev/null
+++ b/core/java/com/android/internal/widget/DecorCaptionView.java
@@ -0,0 +1,294 @@
+/*
+ * 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.internal.widget;
+
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.view.Window;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.android.internal.policy.PhoneWindow;
+
+/**
+ * This class represents the special screen elements to control a window on freeform
+ * environment.
+ * As such this class handles the following things:
+ * <ul>
+ * <li>The caption, containing the system buttons like maximize, close and such as well as
+ * allowing the user to drag the window around.</li>
+ * After creating the view, the function
+ * {@link #setPhoneWindow} needs to be called to make
+ * the connection to it's owning PhoneWindow.
+ * Note: At this time the application can change various attributes of the DecorView which
+ * will break things (in settle/unexpected ways):
+ * <ul>
+ * <li>setOutlineProvider</li>
+ * <li>setSurfaceFormat</li>
+ * <li>..</li>
+ * </ul>
+ */
+public class DecorCaptionView extends ViewGroup
+ implements View.OnClickListener, View.OnTouchListener {
+ private final static String TAG = "DecorCaptionView";
+ private PhoneWindow mOwner = null;
+ private boolean mShow = false;
+
+ // True if the window is being dragged.
+ private boolean mDragging = false;
+
+ // True when the left mouse button got released while dragging.
+ private boolean mLeftMouseButtonReleased;
+
+ private boolean mOverlayWithAppContent = false;
+
+ private View mCaption;
+ private View mContent;
+
+ public DecorCaptionView(Context context) {
+ super(context);
+ }
+
+ public DecorCaptionView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public DecorCaptionView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mCaption = getChildAt(0);
+ }
+
+ public void setPhoneWindow(PhoneWindow owner, boolean show) {
+ mOwner = owner;
+ mShow = show;
+ mOverlayWithAppContent = owner.getOverlayDecorCaption();
+ updateCaptionVisibility();
+ // By changing the outline provider to BOUNDS, the window can remove its
+ // background without removing the shadow.
+ mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
+
+ findViewById(R.id.maximize_window).setOnClickListener(this);
+ findViewById(R.id.close_window).setOnClickListener(this);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent e) {
+ // Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
+ // the old input device events get cancelled first. So no need to remember the kind of
+ // input device we are listening to.
+ switch (e.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ if (!mShow) {
+ // When there is no caption we should not react to anything.
+ return false;
+ }
+ // A drag action is started if we aren't dragging already and the starting event is
+ // either a left mouse button or any other input device.
+ if (!mDragging &&
+ (e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
+ (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) {
+ mDragging = true;
+ mLeftMouseButtonReleased = false;
+ startMovingTask(e.getRawX(), e.getRawY());
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mDragging && !mLeftMouseButtonReleased) {
+ if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
+ (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
+ // There is no separate mouse button up call and if the user mixes mouse
+ // button drag actions, we stop dragging once he releases the button.
+ mLeftMouseButtonReleased = true;
+ break;
+ }
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ if (!mDragging) {
+ break;
+ }
+ // Abort the ongoing dragging.
+ mDragging = false;
+ return true;
+ }
+ return mDragging;
+ }
+
+ /**
+ * The phone window configuration has changed and the caption needs to be updated.
+ * @param show True if the caption should be shown.
+ */
+ public void onConfigurationChanged(boolean show) {
+ mShow = show;
+ updateCaptionVisibility();
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view.getId() == R.id.maximize_window) {
+ maximizeWindow();
+ } else if (view.getId() == R.id.close_window) {
+ mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
+ }
+ }
+
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ if (!(params instanceof MarginLayoutParams)) {
+ throw new IllegalArgumentException(
+ "params " + params + " must subclass MarginLayoutParams");
+ }
+ // Make sure that we never get more then one client area in our view.
+ if (index >= 2 || getChildCount() >= 2) {
+ throw new IllegalStateException("DecorCaptionView can only handle 1 client view");
+ }
+ // To support the overlaying content in the caption, we need to put the content view as the
+ // first child to get the right Z-Ordering.
+ super.addView(child, 0, params);
+ mContent = child;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int captionHeight;
+ if (mCaption.getVisibility() != View.GONE) {
+ measureChildWithMargins(mCaption, widthMeasureSpec, 0, heightMeasureSpec, 0);
+ captionHeight = mCaption.getMeasuredHeight();
+ } else {
+ captionHeight = 0;
+ }
+ if (mContent != null) {
+ if (mOverlayWithAppContent) {
+ measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
+ } else {
+ measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec,
+ captionHeight);
+ }
+ }
+
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
+ MeasureSpec.getSize(heightMeasureSpec));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ final int captionHeight;
+ if (mCaption.getVisibility() != View.GONE) {
+ mCaption.layout(0, 0, mCaption.getMeasuredWidth(), mCaption.getMeasuredHeight());
+ captionHeight = mCaption.getBottom() - mCaption.getTop();
+ } else {
+ captionHeight = 0;
+ }
+
+ if (mContent != null) {
+ if (mOverlayWithAppContent) {
+ mContent.layout(0, 0, mContent.getMeasuredWidth(), mContent.getMeasuredHeight());
+ } else {
+ mContent.layout(0, captionHeight, mContent.getMeasuredWidth(),
+ captionHeight + mContent.getMeasuredHeight());
+ }
+ }
+ }
+ /**
+ * Determine if the workspace is entirely covered by the window.
+ * @return Returns true when the window is filling the entire screen/workspace.
+ **/
+ private boolean isFillingScreen() {
+ return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
+ (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
+ View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
+ }
+
+ /**
+ * Updates the visibility of the caption.
+ **/
+ private void updateCaptionVisibility() {
+ // Don't show the caption if the window has e.g. entered full screen.
+ boolean invisible = isFillingScreen() || !mShow;
+ mCaption.setVisibility(invisible ? GONE : VISIBLE);
+ mCaption.setOnTouchListener(this);
+ }
+
+ /**
+ * Maximize the window by moving it to the maximized workspace stack.
+ **/
+ private void maximizeWindow() {
+ Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
+ if (callback != null) {
+ try {
+ callback.changeWindowStack(FULLSCREEN_WORKSPACE_STACK_ID);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Cannot change task workspace.");
+ }
+ }
+ }
+
+ public boolean isCaptionShowing() {
+ return mShow;
+ }
+
+ public int getCaptionHeight() {
+ return (mCaption != null) ? mCaption.getHeight() : 0;
+ }
+
+ public void removeContentView() {
+ if (mContent != null) {
+ removeView(mContent);
+ mContent = null;
+ }
+ }
+
+ public View getCaption() {
+ return mCaption;
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new MarginLayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new MarginLayoutParams(MarginLayoutParams.MATCH_PARENT,
+ MarginLayoutParams.MATCH_PARENT);
+ }
+
+ @Override
+ protected LayoutParams generateLayoutParams(LayoutParams p) {
+ return new MarginLayoutParams(p);
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof MarginLayoutParams;
+ }
+}
diff --git a/core/java/com/android/internal/widget/NonClientDecorView.java b/core/java/com/android/internal/widget/NonClientDecorView.java
deleted file mode 100644
index 33b8e05..0000000
--- a/core/java/com/android/internal/widget/NonClientDecorView.java
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * 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.internal.widget;
-
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-
-import android.content.Context;
-import android.os.RemoteException;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.view.Window;
-import android.util.Log;
-import android.util.TypedValue;
-
-import com.android.internal.R;
-import com.android.internal.policy.DecorView;
-import com.android.internal.policy.PhoneWindow;
-
-/**
- * This class represents the special screen elements to control a window on free form
- * environment. All thse screen elements are added in the "non client area" which is the area of
- * the window which is handled by the OS and not the application.
- * As such this class handles the following things:
- * <ul>
- * <li>The caption, containing the system buttons like maximize, close and such as well as
- * allowing the user to drag the window around.</li>
- * <li>The shadow - which is changing dependent on the window focus.</li>
- * <li>The border around the client area (if there is one).</li>
- * <li>The resize handles which allow to resize the window.</li>
- * </ul>
- * After creating the view, the function
- * {@link #setPhoneWindow} needs to be called to make
- * the connection to it's owning PhoneWindow.
- * Note: At this time the application can change various attributes of the DecorView which
- * will break things (in settle/unexpected ways):
- * <ul>
- * <li>setElevation</li>
- * <li>setOutlineProvider</li>
- * <li>setSurfaceFormat</li>
- * <li>..</li>
- * </ul>
- * This will be mitigated once b/22527834 will be addressed.
- */
-public class NonClientDecorView extends LinearLayout
- implements View.OnClickListener, View.OnTouchListener {
- private final static String TAG = "NonClientDecorView";
- // The height of a window which has focus in DIP.
- private final int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20;
- // The height of a window which has not in DIP.
- private final int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
- private PhoneWindow mOwner = null;
- private boolean mWindowHasShadow = false;
- private boolean mShowDecor = false;
-
- // True if the window is being dragged.
- private boolean mDragging = false;
-
- // True when the left mouse button got released while dragging.
- private boolean mLeftMouseButtonReleased;
-
- // True if this window is resizable (which is currently only true when the decor is shown).
- public boolean mVisible = false;
-
- // The current focus state of the window for updating the window elevation.
- private boolean mWindowHasFocus = true;
-
- // Cludge to address b/22668382: Set the shadow size to the maximum so that the layer
- // size calculation takes the shadow size into account. We set the elevation currently
- // to max until the first layout command has been executed.
- private boolean mAllowUpdateElevation = false;
-
- public NonClientDecorView(Context context) {
- super(context);
- }
-
- public NonClientDecorView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public NonClientDecorView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public void setPhoneWindow(PhoneWindow owner, boolean showDecor, boolean windowHasShadow) {
- mOwner = owner;
- mWindowHasShadow = windowHasShadow;
- mShowDecor = showDecor;
- updateCaptionVisibility();
- if (mWindowHasShadow) {
- initializeElevation();
- }
- // By changing the outline provider to BOUNDS, the window can remove its
- // background without removing the shadow.
- mOwner.getDecorView().setOutlineProvider(ViewOutlineProvider.BOUNDS);
-
- findViewById(R.id.maximize_window).setOnClickListener(this);
- findViewById(R.id.close_window).setOnClickListener(this);
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent e) {
- // Note: There are no mixed events. When a new device gets used (e.g. 1. Mouse, 2. touch)
- // the old input device events get cancelled first. So no need to remember the kind of
- // input device we are listening to.
- switch (e.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- if (!mShowDecor) {
- // When there is no decor we should not react to anything.
- return false;
- }
- // A drag action is started if we aren't dragging already and the starting event is
- // either a left mouse button or any other input device.
- if (!mDragging &&
- (e.getToolType(e.getActionIndex()) != MotionEvent.TOOL_TYPE_MOUSE ||
- (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) != 0)) {
- mDragging = true;
- mLeftMouseButtonReleased = false;
- startMovingTask(e.getRawX(), e.getRawY());
- }
- break;
-
- case MotionEvent.ACTION_MOVE:
- if (mDragging && !mLeftMouseButtonReleased) {
- if (e.getToolType(e.getActionIndex()) == MotionEvent.TOOL_TYPE_MOUSE &&
- (e.getButtonState() & MotionEvent.BUTTON_PRIMARY) == 0) {
- // There is no separate mouse button up call and if the user mixes mouse
- // button drag actions, we stop dragging once he releases the button.
- mLeftMouseButtonReleased = true;
- break;
- }
- }
- break;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- if (!mDragging) {
- break;
- }
- // Abort the ongoing dragging.
- mDragging = false;
- return true;
- }
- return mDragging;
- }
-
- /**
- * The phone window configuration has changed and the decor needs to be updated.
- * @param showDecor True if the decor should be shown.
- * @param windowHasShadow True when the window should show a shadow.
- **/
- public void onConfigurationChanged(boolean showDecor, boolean windowHasShadow) {
- mShowDecor = showDecor;
- updateCaptionVisibility();
- if (windowHasShadow != mWindowHasShadow) {
- mWindowHasShadow = windowHasShadow;
- initializeElevation();
- }
- }
-
- @Override
- public void onClick(View view) {
- if (view.getId() == R.id.maximize_window) {
- maximizeWindow();
- } else if (view.getId() == R.id.close_window) {
- mOwner.dispatchOnWindowDismissed(true /*finishTask*/);
- }
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- mWindowHasFocus = hasWindowFocus;
- updateElevation();
- super.onWindowFocusChanged(hasWindowFocus);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- // If the application changed its SystemUI metrics, we might also have to adapt
- // our shadow elevation.
- updateElevation();
- mAllowUpdateElevation = true;
-
- super.onLayout(changed, left, top, right, bottom);
- }
-
- @Override
- public void addView(View child, int index, ViewGroup.LayoutParams params) {
- // Make sure that we never get more then one client area in our view.
- if (index >= 2 || getChildCount() >= 2) {
- throw new IllegalStateException("NonClientDecorView can only handle 1 client view");
- }
- super.addView(child, index, params);
- }
-
- /**
- * Determine if the workspace is entirely covered by the window.
- * @return Returns true when the window is filling the entire screen/workspace.
- **/
- private boolean isFillingScreen() {
- return (0 != ((getWindowSystemUiVisibility() | getSystemUiVisibility()) &
- (View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
- View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_LOW_PROFILE)));
- }
-
- /**
- * Updates the visibility of the caption.
- **/
- private void updateCaptionVisibility() {
- // Don't show the decor if the window has e.g. entered full screen.
- boolean invisible = isFillingScreen() || !mShowDecor;
- View caption = getChildAt(0);
- caption.setVisibility(invisible ? GONE : VISIBLE);
- caption.setOnTouchListener(this);
- mVisible = !invisible;
- }
-
- /**
- * The elevation gets set for the first time and the framework needs to be informed that
- * the surface layer gets created with the shadow size in mind.
- **/
- private void initializeElevation() {
- // TODO(skuhne): Call setMaxElevation here accordingly after b/22668382 got fixed.
- mAllowUpdateElevation = false;
- if (mWindowHasShadow) {
- updateElevation();
- } else {
- mOwner.setElevation(0);
- }
- }
-
- /**
- * The shadow height gets controlled by the focus to visualize highlighted windows.
- * Note: This will overwrite application elevation properties.
- * Note: Windows which have (temporarily) changed their attributes to cover the SystemUI
- * will get no shadow as they are expected to be "full screen".
- **/
- public void updateElevation() {
- float elevation = 0;
- // Do not use a shadow when we are in resizing mode (mRenderer not null) since the shadow
- // is bound to the content size and not the target size.
- if (mWindowHasShadow
- && ((DecorView) mOwner.getDecorView()).mBackdropFrameRenderer == null) {
- boolean fill = isFillingScreen();
- elevation = fill ? 0 :
- (mWindowHasFocus ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP :
- DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP);
- // TODO(skuhne): Remove this if clause once b/22668382 got fixed.
- if (!mAllowUpdateElevation && !fill) {
- elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
- }
- // Convert the DP elevation into physical pixels.
- elevation = dipToPx(elevation);
- }
- // Don't change the elevation if it didn't change since it can require some time.
- if (mOwner.getDecorView().getElevation() != elevation) {
- mOwner.setElevation(elevation);
- }
- }
-
- /**
- * Converts a DIP measure into physical pixels.
- * @param dip The dip value.
- * @return Returns the number of pixels.
- */
- private float dipToPx(float dip) {
- return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dip,
- getResources().getDisplayMetrics());
- }
-
- /**
- * Maximize the window by moving it to the maximized workspace stack.
- **/
- private void maximizeWindow() {
- Window.WindowControllerCallback callback = mOwner.getWindowControllerCallback();
- if (callback != null) {
- try {
- callback.changeWindowStack(FULLSCREEN_WORKSPACE_STACK_ID);
- } catch (RemoteException ex) {
- Log.e(TAG, "Cannot change task workspace.");
- }
- }
- }
-
- public boolean isShowingDecor() {
- return mShowDecor;
- }
-
- public int getDecorCaptionHeight() {
- final View caption = getChildAt(0);
- return (caption != null) ? caption.getHeight() : 0;
- }
-}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 65e8058..bd41c5d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -580,7 +580,8 @@
char heapminfreeOptsBuf[sizeof("-XX:HeapMinFree=")-1 + PROPERTY_VALUE_MAX];
char heapmaxfreeOptsBuf[sizeof("-XX:HeapMaxFree=")-1 + PROPERTY_VALUE_MAX];
char usejitOptsBuf[sizeof("-Xusejit:")-1 + PROPERTY_VALUE_MAX];
- char jitcodecachesizeOptsBuf[sizeof("-Xjitcodecachesize:")-1 + PROPERTY_VALUE_MAX];
+ char jitmaxsizeOptsBuf[sizeof("-Xjitmaxsize:")-1 + PROPERTY_VALUE_MAX];
+ char jitinitialsizeOptsBuf[sizeof("-Xjitinitialsize:")-1 + PROPERTY_VALUE_MAX];
char jitthresholdOptsBuf[sizeof("-Xjitthreshold:")-1 + PROPERTY_VALUE_MAX];
char gctypeOptsBuf[sizeof("-Xgc:")-1 + PROPERTY_VALUE_MAX];
char backgroundgcOptsBuf[sizeof("-XX:BackgroundGC=")-1 + PROPERTY_VALUE_MAX];
@@ -684,7 +685,8 @@
* JIT related options.
*/
parseRuntimeOption("dalvik.vm.usejit", usejitOptsBuf, "-Xusejit:");
- parseRuntimeOption("dalvik.vm.jitcodecachesize", jitcodecachesizeOptsBuf, "-Xjitcodecachesize:");
+ parseRuntimeOption("dalvik.vm.jitmaxsize", jitmaxsizeOptsBuf, "-Xjitmaxsize:");
+ parseRuntimeOption("dalvik.vm.jitinitialsize", jitinitialsizeOptsBuf, "-Xjitinitialsize:");
parseRuntimeOption("dalvik.vm.jitthreshold", jitthresholdOptsBuf, "-Xjitthreshold:");
property_get("ro.config.low_ram", propBuf, "");
diff --git a/core/jni/android_util_PathParser.cpp b/core/jni/android_util_PathParser.cpp
index 245aa0f..0927120 100644
--- a/core/jni/android_util_PathParser.cpp
+++ b/core/jni/android_util_PathParser.cpp
@@ -18,19 +18,22 @@
#include <PathParser.h>
#include <SkPath.h>
+#include <utils/VectorDrawableUtils.h>
#include <android/log.h>
#include "core_jni_helpers.h"
namespace android {
+using namespace uirenderer;
+
static bool parseStringForPath(JNIEnv* env, jobject, jlong skPathHandle, jstring inputPathStr,
jint strLength) {
const char* pathString = env->GetStringUTFChars(inputPathStr, NULL);
SkPath* skPath = reinterpret_cast<SkPath*>(skPathHandle);
- android::uirenderer::PathParser::ParseResult result;
- android::uirenderer::PathParser::parseStringForSkPath(skPath, &result, pathString, strLength);
+ PathParser::ParseResult result;
+ PathParser::parseStringForSkPath(skPath, &result, pathString, strLength);
env->ReleaseStringUTFChars(inputPathStr, pathString);
if (result.failureOccurred) {
ALOGE(result.failureMessage.c_str());
@@ -38,8 +41,74 @@
return !result.failureOccurred;
}
+static long createEmptyPathData(JNIEnv*, jobject) {
+ PathData* pathData = new PathData();
+ return reinterpret_cast<jlong>(pathData);
+}
+
+static long createPathData(JNIEnv*, jobject, jlong pathDataPtr) {
+ PathData* pathData = reinterpret_cast<PathData*>(pathDataPtr);
+ PathData* newPathData = new PathData(*pathData);
+ return reinterpret_cast<jlong>(newPathData);
+}
+
+static long createPathDataFromStringPath(JNIEnv* env, jobject, jstring inputStr, jint strLength) {
+ const char* pathString = env->GetStringUTFChars(inputStr, NULL);
+ PathData* pathData = new PathData();
+ PathParser::ParseResult result;
+ PathParser::getPathDataFromString(pathData, &result, pathString, strLength);
+ env->ReleaseStringUTFChars(inputStr, pathString);
+ if (!result.failureOccurred) {
+ return reinterpret_cast<jlong>(pathData);
+ } else {
+ delete pathData;
+ ALOGE(result.failureMessage.c_str());
+ return NULL;
+ }
+}
+
+static bool interpolatePathData(JNIEnv*, jobject, jlong outPathDataPtr, jlong fromPathDataPtr,
+ jlong toPathDataPtr, jfloat fraction) {
+ PathData* outPathData = reinterpret_cast<PathData*>(outPathDataPtr);
+ PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr);
+ PathData* toPathData = reinterpret_cast<PathData*>(toPathDataPtr);
+ return VectorDrawableUtils::interpolatePathData(outPathData, *fromPathData,
+ *toPathData, fraction);
+}
+
+static void deletePathData(JNIEnv*, jobject, jlong pathDataHandle) {
+ PathData* pathData = reinterpret_cast<PathData*>(pathDataHandle);
+ delete pathData;
+}
+
+static bool canMorphPathData(JNIEnv*, jobject, jlong fromPathDataPtr, jlong toPathDataPtr) {
+ PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr);
+ PathData* toPathData = reinterpret_cast<PathData*>(toPathDataPtr);
+ return VectorDrawableUtils::canMorph(*fromPathData, *toPathData);
+}
+
+static void setPathData(JNIEnv*, jobject, jlong outPathDataPtr, jlong fromPathDataPtr) {
+ PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr);
+ PathData* outPathData = reinterpret_cast<PathData*>(outPathDataPtr);
+ *outPathData = *fromPathData;
+}
+
+static void setSkPathFromPathData(JNIEnv*, jobject, jlong outPathPtr, jlong pathDataPtr) {
+ PathData* pathData = reinterpret_cast<PathData*>(pathDataPtr);
+ SkPath* skPath = reinterpret_cast<SkPath*>(outPathPtr);
+ VectorDrawableUtils::verbsToPath(skPath, *pathData);
+}
+
static const JNINativeMethod gMethods[] = {
- {"nParseStringForPath", "(JLjava/lang/String;I)Z", (void*)parseStringForPath}
+ {"nParseStringForPath", "(JLjava/lang/String;I)Z", (void*)parseStringForPath},
+ {"nCreateEmptyPathData", "!()J", (void*)createEmptyPathData},
+ {"nCreatePathData", "!(J)J", (void*)createPathData},
+ {"nCreatePathDataFromString", "(Ljava/lang/String;I)J", (void*)createPathDataFromStringPath},
+ {"nInterpolatePathData", "!(JJJF)Z", (void*)interpolatePathData},
+ {"nFinalize", "!(J)V", (void*)deletePathData},
+ {"nCanMorph", "!(JJ)Z", (void*)canMorphPathData},
+ {"nSetPathData", "!(JJ)V", (void*)setPathData},
+ {"nCreatePathFromPathData", "!(JJ)V", (void*)setSkPathFromPathData},
};
int register_android_util_PathParser(JNIEnv* env) {
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 3f1be456..73c7487 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -21,6 +21,7 @@
#include <linux/fs.h>
#include <list>
+#include <sstream>
#include <string>
#include <fcntl.h>
@@ -74,8 +75,10 @@
MOUNT_EXTERNAL_WRITE = 3,
};
-static void RuntimeAbort(JNIEnv* env) {
- env->FatalError("RuntimeAbort");
+static void RuntimeAbort(JNIEnv* env, int line, const char* msg) {
+ std::ostringstream oss;
+ oss << __FILE__ << ":" << line << ": " << msg;
+ env->FatalError(oss.str().c_str());
}
// This signal handler is for zygote mode, since the zygote must reap its children
@@ -169,12 +172,11 @@
ScopedIntArrayRO gids(env, javaGids);
if (gids.get() == NULL) {
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "Getting gids int array failed");
}
int rc = setgroups(gids.size(), reinterpret_cast<const gid_t*>(&gids[0]));
if (rc == -1) {
- ALOGE("setgroups failed");
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "setgroups failed");
}
}
@@ -194,8 +196,7 @@
ScopedLocalRef<jobject> javaRlimitObject(env, env->GetObjectArrayElement(javaRlimits, i));
ScopedIntArrayRO javaRlimit(env, reinterpret_cast<jintArray>(javaRlimitObject.get()));
if (javaRlimit.size() != 3) {
- ALOGE("rlimits array must have a second dimension of size 3");
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "rlimits array must have a second dimension of size 3");
}
rlim.rlim_cur = javaRlimit[1];
@@ -205,7 +206,7 @@
if (rc == -1) {
ALOGE("setrlimit(%d, {%ld, %ld}) failed", javaRlimit[0], rlim.rlim_cur,
rlim.rlim_max);
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "setrlimit failed");
}
}
}
@@ -216,8 +217,7 @@
static void EnableKeepCapabilities(JNIEnv* env) {
int rc = prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0);
if (rc == -1) {
- ALOGE("prctl(PR_SET_KEEPCAPS) failed");
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "prctl(PR_SET_KEEPCAPS) failed");
}
}
@@ -229,8 +229,7 @@
ALOGE("prctl(PR_CAPBSET_DROP) failed with EINVAL. Please verify "
"your kernel is compiled with file capabilities support");
} else {
- ALOGE("prctl(PR_CAPBSET_DROP) failed");
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "prctl(PR_CAPBSET_DROP) failed");
}
}
}
@@ -251,7 +250,7 @@
if (capset(&capheader, &capdata[0]) == -1) {
ALOGE("capset(%" PRId64 ", %" PRId64 ") failed", permitted, effective);
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "capset failed");
}
}
@@ -259,7 +258,7 @@
errno = -set_sched_policy(0, SP_DEFAULT);
if (errno != 0) {
ALOGE("set_sched_policy(0, SP_DEFAULT) failed");
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "set_sched_policy(0, SP_DEFAULT) failed");
}
}
@@ -370,8 +369,7 @@
jsize count = env->GetArrayLength(fdsToClose);
ScopedIntArrayRO ar(env, fdsToClose);
if (ar.get() == NULL) {
- ALOGE("Bad fd array");
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "Bad fd array");
}
jsize i;
int devnull;
@@ -379,13 +377,13 @@
devnull = open("/dev/null", O_RDWR);
if (devnull < 0) {
ALOGE("Failed to open /dev/null: %s", strerror(errno));
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "Failed to open /dev/null");
continue;
}
ALOGV("Switching descriptor %d to /dev/null: %s", ar[i], strerror(errno));
if (dup2(devnull, ar[i]) < 0) {
ALOGE("Failed dup2() on descriptor %d: %s", ar[i], strerror(errno));
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "Failed dup2()");
}
close(devnull);
}
@@ -493,8 +491,7 @@
// FUSE hasn't been created yet by init.
// In either case, continue without external storage.
} else {
- ALOGE("Cannot continue without emulated storage");
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "Cannot continue without emulated storage");
}
}
@@ -522,13 +519,13 @@
int rc = setresgid(gid, gid, gid);
if (rc == -1) {
ALOGE("setresgid(%d) failed: %s", gid, strerror(errno));
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "setresgid failed");
}
rc = setresuid(uid, uid, uid);
if (rc == -1) {
ALOGE("setresuid(%d) failed: %s", uid, strerror(errno));
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "setresuid failed");
}
if (NeedsNoRandomizeWorkaround()) {
@@ -550,8 +547,7 @@
se_info = new ScopedUtfChars(env, java_se_info);
se_info_c_str = se_info->c_str();
if (se_info_c_str == NULL) {
- ALOGE("se_info_c_str == NULL");
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "se_info_c_str == NULL");
}
}
const char* se_name_c_str = NULL;
@@ -560,15 +556,14 @@
se_name = new ScopedUtfChars(env, java_se_name);
se_name_c_str = se_name->c_str();
if (se_name_c_str == NULL) {
- ALOGE("se_name_c_str == NULL");
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "se_name_c_str == NULL");
}
}
rc = selinux_android_setcontext(uid, is_system_server, se_info_c_str, se_name_c_str);
if (rc == -1) {
ALOGE("selinux_android_setcontext(%d, %d, \"%s\", \"%s\") failed", uid,
is_system_server, se_info_c_str, se_name_c_str);
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "selinux_android_setcontext failed");
}
// Make it easier to debug audit logs by setting the main thread's name to the
@@ -588,8 +583,7 @@
env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, debug_flags,
is_system_server ? NULL : instructionSet);
if (env->ExceptionCheck()) {
- ALOGE("Error calling post fork hooks.");
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "Error calling post fork hooks.");
}
} else if (pid > 0) {
// the parent process
@@ -641,7 +635,7 @@
int status;
if (waitpid(pid, &status, WNOHANG) == pid) {
ALOGE("System server process %d has died. Restarting Zygote!", pid);
- RuntimeAbort(env);
+ RuntimeAbort(env, __LINE__, "System server process has died. Restarting Zygote!");
}
}
return pid;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6bdf71b..42ede31 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1831,6 +1831,12 @@
<permission android:name="android.permission.STATUS_BAR_SERVICE"
android:protectionLevel="signature" />
+ <!-- Allows an application to bind to third party quick settings tiles.
+ <p>Should only be requested by the System, should be required by
+ QSTileService declarations.-->
+ <permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to force a BACK operation on whatever is the
top activity.
<p>Not for use by third-party applications.
diff --git a/core/res/res/drawable/non_client_decor_title.xml b/core/res/res/drawable/decor_caption_title.xml
similarity index 84%
rename from core/res/res/drawable/non_client_decor_title.xml
rename to core/res/res/drawable/decor_caption_title.xml
index e50daea..591605d3 100644
--- a/core/res/res/drawable/non_client_decor_title.xml
+++ b/core/res/res/drawable/decor_caption_title.xml
@@ -16,6 +16,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_window_focused="true"
- android:drawable="@drawable/non_client_decor_title_focused" />
- <item android:drawable="@drawable/non_client_decor_title_unfocused" />
+ android:drawable="@drawable/decor_caption_title_focused" />
+ <item android:drawable="@drawable/decor_caption_title_unfocused" />
</selector>
diff --git a/core/res/res/drawable/non_client_decor_title_focused.xml b/core/res/res/drawable/decor_caption_title_focused.xml
similarity index 100%
rename from core/res/res/drawable/non_client_decor_title_focused.xml
rename to core/res/res/drawable/decor_caption_title_focused.xml
diff --git a/core/res/res/drawable/non_client_decor_title_unfocused.xml b/core/res/res/drawable/decor_caption_title_unfocused.xml
similarity index 100%
rename from core/res/res/drawable/non_client_decor_title_unfocused.xml
rename to core/res/res/drawable/decor_caption_title_unfocused.xml
diff --git a/core/res/res/layout/non_client_decor_dark.xml b/core/res/res/layout/decor_caption_dark.xml
similarity index 89%
rename from core/res/res/layout/non_client_decor_dark.xml
rename to core/res/res/layout/decor_caption_dark.xml
index 40b8960..273264d 100644
--- a/core/res/res/layout/non_client_decor_dark.xml
+++ b/core/res/res/layout/decor_caption_dark.xml
@@ -17,16 +17,17 @@
*/
-->
-<com.android.internal.widget.NonClientDecorView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="beforeDescendants" >
<LinearLayout
+ android:id="@+id/caption"
android:layout_width="match_parent"
android:layout_gravity="end"
android:layout_height="wrap_content"
- android:background="@drawable/non_client_decor_title"
+ android:background="@drawable/decor_caption_title"
android:focusable="false"
android:descendantFocusability="blocksDescendants" >
<LinearLayout
@@ -53,4 +54,4 @@
android:contentDescription="@string/close_button_text"
android:background="@drawable/decor_close_button_dark" />
</LinearLayout>
-</com.android.internal.widget.NonClientDecorView>
+</com.android.internal.widget.DecorCaptionView>
diff --git a/core/res/res/layout/non_client_decor_light.xml b/core/res/res/layout/decor_caption_light.xml
similarity index 89%
rename from core/res/res/layout/non_client_decor_light.xml
rename to core/res/res/layout/decor_caption_light.xml
index c75d526..fd9198e 100644
--- a/core/res/res/layout/non_client_decor_light.xml
+++ b/core/res/res/layout/decor_caption_light.xml
@@ -17,16 +17,17 @@
*/
-->
-<com.android.internal.widget.NonClientDecorView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.DecorCaptionView xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="beforeDescendants" >
<LinearLayout
+ android:id="@+id/caption"
android:layout_width="match_parent"
android:layout_gravity="end"
android:layout_height="wrap_content"
- android:background="@drawable/non_client_decor_title"
+ android:background="@drawable/decor_caption_title"
android:focusable="false"
android:descendantFocusability="blocksDescendants" >
<LinearLayout
@@ -53,4 +54,4 @@
android:contentDescription="@string/close_button_text"
android:background="@drawable/decor_close_button_light" />
</LinearLayout>
-</com.android.internal.widget.NonClientDecorView>
+</com.android.internal.widget.DecorCaptionView>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b87d9e2..057790a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1091,6 +1091,14 @@
This feature should be disabled for most devices. -->
<integer name="config_virtualKeyQuietTimeMillis">0</integer>
+ <!-- A list of potential packages, in priority order, that may contain an
+ ephemeral resolver. Each package will be be queried for a component
+ that has been granted the PACKAGE_EPHEMERAL_AGENT permission.
+ This may be empty if ephemeral apps are not supported. -->
+ <string-array name="config_ephemeralResolverPackage" translatable="false">
+ <!-- Add packages here -->
+ </string-array>
+
<!-- Component name of the default wallpaper. This will be ImageWallpaper if not
specified -->
<string name="default_wallpaper_component" translatable="false">@null</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 556467a..1e325b1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -628,6 +628,7 @@
<java-symbol type="string" name="widget_default_package_name" />
<java-symbol type="string" name="widget_default_class_name" />
<java-symbol type="string" name="emergency_calls_only" />
+ <java-symbol type="array" name="config_ephemeralResolverPackage" />
<java-symbol type="string" name="enable_accessibility_canceled" />
<java-symbol type="string" name="eventTypeAnniversary" />
<java-symbol type="string" name="eventTypeBirthday" />
@@ -1957,9 +1958,9 @@
<java-symbol type="id" name="maximize_window" />
<java-symbol type="id" name="close_window" />
<java-symbol type="id" name="client_decor_placeholder" />
- <java-symbol type="layout" name="non_client_decor_light" />
- <java-symbol type="layout" name="non_client_decor_dark" />
- <java-symbol type="drawable" name="non_client_decor_title_focused" />
+ <java-symbol type="layout" name="decor_caption_light" />
+ <java-symbol type="layout" name="decor_caption_dark" />
+ <java-symbol type="drawable" name="decor_caption_title_focused" />
<!-- From TelephonyProvider -->
<java-symbol type="xml" name="apns" />
diff --git a/docs/html/guide/components/services.jd b/docs/html/guide/components/services.jd
index 6e22be8..b8c105d 100644
--- a/docs/html/guide/components/services.jd
+++ b/docs/html/guide/components/services.jd
@@ -512,7 +512,7 @@
onStartCommand()} directly.)</p>
<p>For example, an activity can start the example service in the previous section ({@code
-HelloSevice}) using an explicit intent with {@link android.content.Context#startService
+HelloService}) using an explicit intent with {@link android.content.Context#startService
startService()}:</p>
<pre>
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index a50c945..0ee877e 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1424,7 +1424,7 @@
}
}
- static int resolveDensity(@NonNull Resources r, int parentDensity) {
+ static int resolveDensity(@Nullable Resources r, int parentDensity) {
final int densityDpi = r == null ? parentDensity : r.getDisplayMetrics().densityDpi;
return densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi;
}
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index eee9b24..f630055e 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -231,6 +231,9 @@
private int mDpiScaledHeight = 0;
private Insets mDpiScaledInsets = Insets.NONE;
+ /** Whether DPI-scaled width, height, and insets need to be updated. */
+ private boolean mDpiScaledDirty = true;
+
// Temp variable, only for saving "new" operation at the draw() time.
private final float[] mTmpFloats = new float[9];
private final Matrix mTmpMatrix = new Matrix();
@@ -259,9 +262,13 @@
* displayed, or {@code null} to use the constant state defaults
*/
private void updateLocalState(Resources res) {
- mTargetDensity = Drawable.resolveDensity(res, mVectorState.mVPathRenderer.mSourceDensity);
+ final int density = Drawable.resolveDensity(res, mVectorState.mVPathRenderer.mDensity);
+ if (mTargetDensity != density) {
+ mTargetDensity = density;
+ mDpiScaledDirty = true;
+ }
+
mTintFilter = updateTintFilter(mTintFilter, mVectorState.mTint, mVectorState.mTintMode);
- computeVectorSize();
}
@Override
@@ -422,17 +429,26 @@
@Override
public int getIntrinsicWidth() {
+ if (mDpiScaledDirty) {
+ computeVectorSize();
+ }
return mDpiScaledWidth;
}
@Override
public int getIntrinsicHeight() {
+ if (mDpiScaledDirty) {
+ computeVectorSize();
+ }
return mDpiScaledHeight;
}
/** @hide */
@Override
public Insets getOpticalInsets() {
+ if (mDpiScaledDirty) {
+ computeVectorSize();
+ }
return mDpiScaledInsets;
}
@@ -444,7 +460,7 @@
final VPathRenderer pathRenderer = mVectorState.mVPathRenderer;
final Insets opticalInsets = pathRenderer.mOpticalInsets;
- final int sourceDensity = pathRenderer.mSourceDensity;
+ final int sourceDensity = pathRenderer.mDensity;
final int targetDensity = mTargetDensity;
if (targetDensity != sourceDensity) {
mDpiScaledWidth = Drawable.scaleFromDensity(
@@ -465,6 +481,8 @@
mDpiScaledHeight = (int) pathRenderer.mBaseHeight;
mDpiScaledInsets = opticalInsets;
}
+
+ mDpiScaledDirty = false;
}
@Override
@@ -481,6 +499,11 @@
return;
}
+ final VPathRenderer path = state.mVPathRenderer;
+ final boolean changedDensity = path.setDensity(
+ Drawable.resolveDensity(t.getResources(), 0));
+ mDpiScaledDirty |= changedDensity;
+
if (state.mThemeAttrs != null) {
final TypedArray a = t.resolveAttributes(
state.mThemeAttrs, R.styleable.VectorDrawable);
@@ -492,6 +515,9 @@
} finally {
a.recycle();
}
+
+ // May have changed size.
+ mDpiScaledDirty = true;
}
// Apply theme to contained color state list.
@@ -499,7 +525,6 @@
state.mTint = state.mTint.obtainForTheme(t);
}
- final VPathRenderer path = state.mVPathRenderer;
if (path != null && path.canApplyTheme()) {
path.applyTheme(t);
}
@@ -565,21 +590,24 @@
}
@Override
- public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
+ public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
final VectorDrawableState state = mVectorState;
- final VPathRenderer pathRenderer = new VPathRenderer();
- state.mVPathRenderer = pathRenderer;
+ state.mVPathRenderer = new VPathRenderer();
+ state.mVPathRenderer.setDensity(Drawable.resolveDensity(r, 0));
- final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawable);
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawable);
updateStateFromTypedArray(a);
a.recycle();
+ mDpiScaledDirty = true;
+
state.mCacheDirty = true;
- inflateInternal(res, parser, attrs, theme);
+ inflateChildElements(r, parser, attrs, theme);
// Update local properties.
- updateLocalState(res);
+ updateLocalState(r);
}
private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
@@ -592,13 +620,6 @@
// Extract the theme attributes, if any.
state.mThemeAttrs = a.extractThemeAttrs();
- // The density may have changed since the last update (if any). Any
- // dimension-type attributes will need their default values scaled.
- final int targetDensity = Drawable.resolveDensity(a.getResources(), 0);
- final int sourceDensity = pathRenderer.mSourceDensity;
- final float densityScale = targetDensity / (float) sourceDensity;
- pathRenderer.mSourceDensity = targetDensity;
-
final int tintMode = a.getInt(R.styleable.VectorDrawable_tintMode, -1);
if (tintMode != -1) {
state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
@@ -626,11 +647,9 @@
}
pathRenderer.mBaseWidth = a.getDimension(
- R.styleable.VectorDrawable_width,
- pathRenderer.mBaseWidth * densityScale);
+ R.styleable.VectorDrawable_width, pathRenderer.mBaseWidth);
pathRenderer.mBaseHeight = a.getDimension(
- R.styleable.VectorDrawable_height,
- pathRenderer.mBaseHeight * densityScale);
+ R.styleable.VectorDrawable_height, pathRenderer.mBaseHeight);
if (pathRenderer.mBaseWidth <= 0) {
throw new XmlPullParserException(a.getPositionDescription() +
@@ -641,21 +660,17 @@
}
final int insetLeft = a.getDimensionPixelOffset(
- R.styleable.VectorDrawable_opticalInsetLeft,
- (int) (pathRenderer.mOpticalInsets.left * densityScale));
+ R.styleable.VectorDrawable_opticalInsetLeft, pathRenderer.mOpticalInsets.left);
final int insetTop = a.getDimensionPixelOffset(
- R.styleable.VectorDrawable_opticalInsetTop,
- (int) (pathRenderer.mOpticalInsets.top * densityScale));
+ R.styleable.VectorDrawable_opticalInsetTop, pathRenderer.mOpticalInsets.top);
final int insetRight = a.getDimensionPixelOffset(
- R.styleable.VectorDrawable_opticalInsetRight,
- (int) (pathRenderer.mOpticalInsets.right * densityScale));
+ R.styleable.VectorDrawable_opticalInsetRight, pathRenderer.mOpticalInsets.right);
final int insetBottom = a.getDimensionPixelOffset(
- R.styleable.VectorDrawable_opticalInsetBottom,
- (int) (pathRenderer.mOpticalInsets.bottom * densityScale));
+ R.styleable.VectorDrawable_opticalInsetBottom, pathRenderer.mOpticalInsets.bottom);
pathRenderer.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
- final float alphaInFloat = a.getFloat(R.styleable.VectorDrawable_alpha,
- pathRenderer.getAlpha());
+ final float alphaInFloat = a.getFloat(
+ R.styleable.VectorDrawable_alpha, pathRenderer.getAlpha());
pathRenderer.setAlpha(alphaInFloat);
final String name = a.getString(R.styleable.VectorDrawable_name);
@@ -665,7 +680,7 @@
}
}
- private void inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
+ private void inflateChildElements(Resources res, XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
final VectorDrawableState state = mVectorState;
final VPathRenderer pathRenderer = state.mVPathRenderer;
@@ -929,7 +944,7 @@
int mRootAlpha = 0xFF;
String mRootName = null;
- int mSourceDensity = DisplayMetrics.DENSITY_DEFAULT;
+ int mDensity = DisplayMetrics.DENSITY_DEFAULT;
final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>();
@@ -966,12 +981,37 @@
mChangingConfigurations = copy.mChangingConfigurations;
mRootAlpha = copy.mRootAlpha;
mRootName = copy.mRootName;
- mSourceDensity = copy.mSourceDensity;
+ mDensity = copy.mDensity;
if (copy.mRootName != null) {
mVGTargetsMap.put(copy.mRootName, this);
}
}
+ public final boolean setDensity(int targetDensity) {
+ if (mDensity != targetDensity) {
+ final int sourceDensity = mDensity;
+ mDensity = targetDensity;
+ applyDensityScaling(sourceDensity, targetDensity);
+ return true;
+ }
+ return false;
+ }
+
+ private void applyDensityScaling(int sourceDensity, int targetDensity) {
+ mBaseWidth = Drawable.scaleFromDensity(mBaseWidth, sourceDensity, targetDensity);
+ mBaseHeight = Drawable.scaleFromDensity(mBaseHeight, sourceDensity, targetDensity);
+
+ final int insetLeft = Drawable.scaleFromDensity(
+ mOpticalInsets.left, sourceDensity, targetDensity, false);
+ final int insetTop = Drawable.scaleFromDensity(
+ mOpticalInsets.top, sourceDensity, targetDensity, false);
+ final int insetRight = Drawable.scaleFromDensity(
+ mOpticalInsets.right, sourceDensity, targetDensity, false);
+ final int insetBottom = Drawable.scaleFromDensity(
+ mOpticalInsets.bottom, sourceDensity, targetDensity, false);
+ mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
+ }
+
public boolean canApplyTheme() {
return mRootGroup.canApplyTheme();
}
@@ -1321,7 +1361,7 @@
* Common Path information for clip path and normal path.
*/
private static abstract class VPath implements VObject {
- protected PathParser.PathDataNode[] mNodes = null;
+ protected PathParser.PathData mPathData = null;
String mPathName;
int mChangingConfigurations;
@@ -1332,7 +1372,7 @@
public VPath(VPath copy) {
mPathName = copy.mPathName;
mChangingConfigurations = copy.mChangingConfigurations;
- mNodes = PathParser.deepCopyNodes(copy.mNodes);
+ mPathData = copy.mPathData == null ? null : new PathParser.PathData(copy.mPathData);
}
public String getPathName() {
@@ -1345,18 +1385,14 @@
/* Setters and Getters, used by animator from AnimatedVectorDrawable. */
@SuppressWarnings("unused")
- public PathParser.PathDataNode[] getPathData() {
- return mNodes;
+ public PathParser.PathData getPathData() {
+ return mPathData;
}
+ // TODO: Move the PathEvaluator and this setter and the getter above into native.
@SuppressWarnings("unused")
- public void setPathData(PathParser.PathDataNode[] nodes) {
- if (!PathParser.canMorph(mNodes, nodes)) {
- // This should not happen in the middle of animation.
- mNodes = PathParser.deepCopyNodes(nodes);
- } else {
- PathParser.updateNodes(mNodes, nodes);
- }
+ public void setPathData(PathParser.PathData pathData) {
+ mPathData.setPathData(pathData);
}
@Override
@@ -1392,8 +1428,8 @@
* @param outPath the output path
*/
protected void toPath(TempState temp, Path outPath) {
- if (mNodes != null) {
- PathParser.PathDataNode.nodesToPath(mNodes, outPath);
+ if (mPathData != null) {
+ PathParser.createPathFromPathData(outPath, mPathData);
}
}
@@ -1488,9 +1524,9 @@
mPathName = pathName;
}
- final String pathData = a.getString(R.styleable.VectorDrawableClipPath_pathData);
- if (pathData != null) {
- mNodes = PathParser.createNodesFromPathData(pathData);
+ final String pathDataString = a.getString(R.styleable.VectorDrawableClipPath_pathData);
+ if (pathDataString != null) {
+ mPathData = new PathParser.PathData(pathDataString);
}
}
@@ -1719,9 +1755,9 @@
mPathName = pathName;
}
- final String pathData = a.getString(R.styleable.VectorDrawablePath_pathData);
- if (pathData != null) {
- mNodes = PathParser.createNodesFromPathData(pathData);
+ final String pathString = a.getString(R.styleable.VectorDrawablePath_pathData);
+ if (pathString != null) {
+ mPathData = new PathParser.PathData(pathString);
}
final ColorStateList fillColors = a.getColorStateList(
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 8565372..0a57d50 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -29,6 +29,8 @@
utils/NinePatchImpl.cpp \
utils/StringUtils.cpp \
utils/TestWindowContext.cpp \
+ utils/VectorDrawableUtils.cpp \
+ utils/TestUtils.cpp \
AmbientShadow.cpp \
AnimationContext.cpp \
Animator.cpp \
@@ -218,7 +220,7 @@
unit_tests/FatVectorTests.cpp \
unit_tests/LayerUpdateQueueTests.cpp \
unit_tests/LinearAllocatorTests.cpp \
- unit_tests/PathParserTests.cpp \
+ unit_tests/VectorDrawableTests.cpp \
unit_tests/OffscreenBufferPoolTests.cpp \
unit_tests/StringUtilsTests.cpp
@@ -252,9 +254,11 @@
LOCAL_SRC_FILES += \
tests/TestContext.cpp \
- tests/TreeContentAnimation.cpp \
+ tests/TestSceneRunner.cpp \
tests/main.cpp
+LOCAL_SRC_FILES += $(call all-cpp-files-under, tests/scenes)
+
include $(BUILD_EXECUTABLE)
# ------------------------
diff --git a/libs/hwui/PathParser.cpp b/libs/hwui/PathParser.cpp
index 35230ff..4e9ac9c 100644
--- a/libs/hwui/PathParser.cpp
+++ b/libs/hwui/PathParser.cpp
@@ -221,7 +221,7 @@
result->failureMessage = "No verbs found in the string for pathData";
return;
}
- VectorDrawablePath::verbsToPath(skPath, &pathData);
+ VectorDrawableUtils::verbsToPath(skPath, pathData);
return;
}
diff --git a/libs/hwui/PathParser.h b/libs/hwui/PathParser.h
index a9c1e60..4c87b18 100644
--- a/libs/hwui/PathParser.h
+++ b/libs/hwui/PathParser.h
@@ -18,6 +18,7 @@
#define ANDROID_HWUI_PATHPARSER_H
#include "VectorDrawablePath.h"
+#include "utils/VectorDrawableUtils.h"
#include <jni.h>
#include <android/log.h>
@@ -40,7 +41,7 @@
*/
ANDROID_API static void parseStringForSkPath(SkPath* outPath, ParseResult* result,
const char* pathStr, size_t strLength);
- static void getPathDataFromString(PathData* outData, ParseResult* result,
+ ANDROID_API static void getPathDataFromString(PathData* outData, ParseResult* result,
const char* pathStr, size_t strLength);
static void dump(const PathData& data);
};
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 716d536..3f24f44 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -288,6 +288,9 @@
bool transformUpdateNeeded = false;
if (!mLayer) {
mLayer = createLayer(info.canvasContext.getRenderState(), getWidth(), getHeight());
+#if !HWUI_NEW_OPS
+ applyLayerPropertiesToLayer(info);
+#endif
damageSelf(info);
transformUpdateNeeded = true;
} else if (!layerMatchesWidthAndHeight(mLayer, getWidth(), getHeight())) {
diff --git a/libs/hwui/VectorDrawablePath.cpp b/libs/hwui/VectorDrawablePath.cpp
index 05ea2da..c9a54ca 100644
--- a/libs/hwui/VectorDrawablePath.cpp
+++ b/libs/hwui/VectorDrawablePath.cpp
@@ -17,6 +17,7 @@
#include "VectorDrawablePath.h"
#include "PathParser.h"
+#include "utils/VectorDrawableUtils.h"
#include <math.h>
#include <utils/Log.h>
@@ -24,476 +25,35 @@
namespace android {
namespace uirenderer {
-class PathResolver {
-public:
- float currentX = 0;
- float currentY = 0;
- float ctrlPointX = 0;
- float ctrlPointY = 0;
- float currentSegmentStartX = 0;
- float currentSegmentStartY = 0;
- void addCommand(SkPath* outPath, char previousCmd,
- char cmd, const std::vector<float>* points, size_t start, size_t end);
-};
VectorDrawablePath::VectorDrawablePath(const char* pathStr, size_t strLength) {
PathParser::ParseResult result;
PathParser::getPathDataFromString(&mData, &result, pathStr, strLength);
if (!result.failureOccurred) {
- verbsToPath(&mSkPath, &mData);
+ VectorDrawableUtils::verbsToPath(&mSkPath, mData);
}
}
VectorDrawablePath::VectorDrawablePath(const PathData& data) {
mData = data;
// Now we need to construct a path
- verbsToPath(&mSkPath, &data);
+ VectorDrawableUtils::verbsToPath(&mSkPath, data);
}
VectorDrawablePath::VectorDrawablePath(const VectorDrawablePath& path) {
mData = path.mData;
- verbsToPath(&mSkPath, &mData);
+ VectorDrawableUtils::verbsToPath(&mSkPath, mData);
}
-bool VectorDrawablePath::canMorph(const PathData& morphTo) {
- if (mData.verbs.size() != morphTo.verbs.size()) {
- return false;
- }
- for (unsigned int i = 0; i < mData.verbs.size(); i++) {
- if (mData.verbs[i] != morphTo.verbs[i]
- || mData.verbSizes[i] != morphTo.verbSizes[i]) {
- return false;
- }
- }
- return true;
+bool VectorDrawablePath::canMorph(const PathData& morphTo) {
+ return VectorDrawableUtils::canMorph(mData, morphTo);
}
bool VectorDrawablePath::canMorph(const VectorDrawablePath& path) {
return canMorph(path.mData);
}
- /**
- * Convert an array of PathVerb to Path.
- */
-void VectorDrawablePath::verbsToPath(SkPath* outPath, const PathData* data) {
- PathResolver resolver;
- char previousCommand = 'm';
- size_t start = 0;
- outPath->reset();
- for (unsigned int i = 0; i < data->verbs.size(); i++) {
- size_t verbSize = data->verbSizes[i];
- resolver.addCommand(outPath, previousCommand, data->verbs[i], &data->points, start,
- start + verbSize);
- previousCommand = data->verbs[i];
- start += verbSize;
- }
-}
-/**
- * The current PathVerb will be interpolated between the
- * <code>nodeFrom</code> and <code>nodeTo</code> according to the
- * <code>fraction</code>.
- *
- * @param nodeFrom The start value as a PathVerb.
- * @param nodeTo The end value as a PathVerb
- * @param fraction The fraction to interpolate.
- */
-void VectorDrawablePath::interpolatePaths(PathData* outData,
- const PathData* from, const PathData* to, float fraction) {
- outData->points.resize(from->points.size());
- outData->verbSizes = from->verbSizes;
- outData->verbs = from->verbs;
-
- for (size_t i = 0; i < from->points.size(); i++) {
- outData->points[i] = from->points[i] * (1 - fraction) + to->points[i] * fraction;
- }
-}
-
-/**
- * Converts an arc to cubic Bezier segments and records them in p.
- *
- * @param p The target for the cubic Bezier segments
- * @param cx The x coordinate center of the ellipse
- * @param cy The y coordinate center of the ellipse
- * @param a The radius of the ellipse in the horizontal direction
- * @param b The radius of the ellipse in the vertical direction
- * @param e1x E(eta1) x coordinate of the starting point of the arc
- * @param e1y E(eta2) y coordinate of the starting point of the arc
- * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
- * @param start The start angle of the arc on the ellipse
- * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
- */
-static void arcToBezier(SkPath* p,
- double cx,
- double cy,
- double a,
- double b,
- double e1x,
- double e1y,
- double theta,
- double start,
- double sweep) {
- // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
- // and http://www.spaceroots.org/documents/ellipse/node22.html
-
- // Maximum of 45 degrees per cubic Bezier segment
- int numSegments = ceil(fabs(sweep * 4 / M_PI));
-
- double eta1 = start;
- double cosTheta = cos(theta);
- double sinTheta = sin(theta);
- double cosEta1 = cos(eta1);
- double sinEta1 = sin(eta1);
- double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
- double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
-
- double anglePerSegment = sweep / numSegments;
- for (int i = 0; i < numSegments; i++) {
- double eta2 = eta1 + anglePerSegment;
- double sinEta2 = sin(eta2);
- double cosEta2 = cos(eta2);
- double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
- double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
- double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
- double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
- double tanDiff2 = tan((eta2 - eta1) / 2);
- double alpha =
- sin(eta2 - eta1) * (sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
- double q1x = e1x + alpha * ep1x;
- double q1y = e1y + alpha * ep1y;
- double q2x = e2x - alpha * ep2x;
- double q2y = e2y - alpha * ep2y;
-
- p->cubicTo((float) q1x,
- (float) q1y,
- (float) q2x,
- (float) q2y,
- (float) e2x,
- (float) e2y);
- eta1 = eta2;
- e1x = e2x;
- e1y = e2y;
- ep1x = ep2x;
- ep1y = ep2y;
- }
-}
-
-inline double toRadians(float theta) { return theta * M_PI / 180;}
-
-static void drawArc(SkPath* p,
- float x0,
- float y0,
- float x1,
- float y1,
- float a,
- float b,
- float theta,
- bool isMoreThanHalf,
- bool isPositiveArc) {
-
- /* Convert rotation angle from degrees to radians */
- double thetaD = toRadians(theta);
- /* Pre-compute rotation matrix entries */
- double cosTheta = cos(thetaD);
- double sinTheta = sin(thetaD);
- /* Transform (x0, y0) and (x1, y1) into unit space */
- /* using (inverse) rotation, followed by (inverse) scale */
- double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
- double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
- double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
- double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
-
- /* Compute differences and averages */
- double dx = x0p - x1p;
- double dy = y0p - y1p;
- double xm = (x0p + x1p) / 2;
- double ym = (y0p + y1p) / 2;
- /* Solve for intersecting unit circles */
- double dsq = dx * dx + dy * dy;
- if (dsq == 0.0) {
- ALOGW("Points are coincident");
- return; /* Points are coincident */
- }
- double disc = 1.0 / dsq - 1.0 / 4.0;
- if (disc < 0.0) {
- ALOGW("Points are too far apart %f", dsq);
- float adjust = (float) (sqrt(dsq) / 1.99999);
- drawArc(p, x0, y0, x1, y1, a * adjust,
- b * adjust, theta, isMoreThanHalf, isPositiveArc);
- return; /* Points are too far apart */
- }
- double s = sqrt(disc);
- double sdx = s * dx;
- double sdy = s * dy;
- double cx;
- double cy;
- if (isMoreThanHalf == isPositiveArc) {
- cx = xm - sdy;
- cy = ym + sdx;
- } else {
- cx = xm + sdy;
- cy = ym - sdx;
- }
-
- double eta0 = atan2((y0p - cy), (x0p - cx));
-
- double eta1 = atan2((y1p - cy), (x1p - cx));
-
- double sweep = (eta1 - eta0);
- if (isPositiveArc != (sweep >= 0)) {
- if (sweep > 0) {
- sweep -= 2 * M_PI;
- } else {
- sweep += 2 * M_PI;
- }
- }
-
- cx *= a;
- cy *= b;
- double tcx = cx;
- cx = cx * cosTheta - cy * sinTheta;
- cy = tcx * sinTheta + cy * cosTheta;
-
- arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
-}
-
-// Use the given verb, and points in the range [start, end) to insert a command into the SkPath.
-void PathResolver::addCommand(SkPath* outPath, char previousCmd,
- char cmd, const std::vector<float>* points, size_t start, size_t end) {
-
- int incr = 2;
- float reflectiveCtrlPointX;
- float reflectiveCtrlPointY;
-
- switch (cmd) {
- case 'z':
- case 'Z':
- outPath->close();
- // Path is closed here, but we need to move the pen to the
- // closed position. So we cache the segment's starting position,
- // and restore it here.
- currentX = currentSegmentStartX;
- currentY = currentSegmentStartY;
- ctrlPointX = currentSegmentStartX;
- ctrlPointY = currentSegmentStartY;
- outPath->moveTo(currentX, currentY);
- break;
- case 'm':
- case 'M':
- case 'l':
- case 'L':
- case 't':
- case 'T':
- incr = 2;
- break;
- case 'h':
- case 'H':
- case 'v':
- case 'V':
- incr = 1;
- break;
- case 'c':
- case 'C':
- incr = 6;
- break;
- case 's':
- case 'S':
- case 'q':
- case 'Q':
- incr = 4;
- break;
- case 'a':
- case 'A':
- incr = 7;
- break;
- }
-
- for (unsigned int k = start; k < end; k += incr) {
- switch (cmd) {
- case 'm': // moveto - Start a new sub-path (relative)
- currentX += points->at(k + 0);
- currentY += points->at(k + 1);
- if (k > start) {
- // According to the spec, if a moveto is followed by multiple
- // pairs of coordinates, the subsequent pairs are treated as
- // implicit lineto commands.
- outPath->rLineTo(points->at(k + 0), points->at(k + 1));
- } else {
- outPath->rMoveTo(points->at(k + 0), points->at(k + 1));
- currentSegmentStartX = currentX;
- currentSegmentStartY = currentY;
- }
- break;
- case 'M': // moveto - Start a new sub-path
- currentX = points->at(k + 0);
- currentY = points->at(k + 1);
- if (k > start) {
- // According to the spec, if a moveto is followed by multiple
- // pairs of coordinates, the subsequent pairs are treated as
- // implicit lineto commands.
- outPath->lineTo(points->at(k + 0), points->at(k + 1));
- } else {
- outPath->moveTo(points->at(k + 0), points->at(k + 1));
- currentSegmentStartX = currentX;
- currentSegmentStartY = currentY;
- }
- break;
- case 'l': // lineto - Draw a line from the current point (relative)
- outPath->rLineTo(points->at(k + 0), points->at(k + 1));
- currentX += points->at(k + 0);
- currentY += points->at(k + 1);
- break;
- case 'L': // lineto - Draw a line from the current point
- outPath->lineTo(points->at(k + 0), points->at(k + 1));
- currentX = points->at(k + 0);
- currentY = points->at(k + 1);
- break;
- case 'h': // horizontal lineto - Draws a horizontal line (relative)
- outPath->rLineTo(points->at(k + 0), 0);
- currentX += points->at(k + 0);
- break;
- case 'H': // horizontal lineto - Draws a horizontal line
- outPath->lineTo(points->at(k + 0), currentY);
- currentX = points->at(k + 0);
- break;
- case 'v': // vertical lineto - Draws a vertical line from the current point (r)
- outPath->rLineTo(0, points->at(k + 0));
- currentY += points->at(k + 0);
- break;
- case 'V': // vertical lineto - Draws a vertical line from the current point
- outPath->lineTo(currentX, points->at(k + 0));
- currentY = points->at(k + 0);
- break;
- case 'c': // curveto - Draws a cubic Bézier curve (relative)
- outPath->rCubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3),
- points->at(k + 4), points->at(k + 5));
-
- ctrlPointX = currentX + points->at(k + 2);
- ctrlPointY = currentY + points->at(k + 3);
- currentX += points->at(k + 4);
- currentY += points->at(k + 5);
-
- break;
- case 'C': // curveto - Draws a cubic Bézier curve
- outPath->cubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3),
- points->at(k + 4), points->at(k + 5));
- currentX = points->at(k + 4);
- currentY = points->at(k + 5);
- ctrlPointX = points->at(k + 2);
- ctrlPointY = points->at(k + 3);
- break;
- case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
- reflectiveCtrlPointX = 0;
- reflectiveCtrlPointY = 0;
- if (previousCmd == 'c' || previousCmd == 's'
- || previousCmd == 'C' || previousCmd == 'S') {
- reflectiveCtrlPointX = currentX - ctrlPointX;
- reflectiveCtrlPointY = currentY - ctrlPointY;
- }
- outPath->rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
- points->at(k + 0), points->at(k + 1),
- points->at(k + 2), points->at(k + 3));
- ctrlPointX = currentX + points->at(k + 0);
- ctrlPointY = currentY + points->at(k + 1);
- currentX += points->at(k + 2);
- currentY += points->at(k + 3);
- break;
- case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
- reflectiveCtrlPointX = currentX;
- reflectiveCtrlPointY = currentY;
- if (previousCmd == 'c' || previousCmd == 's'
- || previousCmd == 'C' || previousCmd == 'S') {
- reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
- reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
- }
- outPath->cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
- points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
- ctrlPointX = points->at(k + 0);
- ctrlPointY = points->at(k + 1);
- currentX = points->at(k + 2);
- currentY = points->at(k + 3);
- break;
- case 'q': // Draws a quadratic Bézier (relative)
- outPath->rQuadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
- ctrlPointX = currentX + points->at(k + 0);
- ctrlPointY = currentY + points->at(k + 1);
- currentX += points->at(k + 2);
- currentY += points->at(k + 3);
- break;
- case 'Q': // Draws a quadratic Bézier
- outPath->quadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
- ctrlPointX = points->at(k + 0);
- ctrlPointY = points->at(k + 1);
- currentX = points->at(k + 2);
- currentY = points->at(k + 3);
- break;
- case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
- reflectiveCtrlPointX = 0;
- reflectiveCtrlPointY = 0;
- if (previousCmd == 'q' || previousCmd == 't'
- || previousCmd == 'Q' || previousCmd == 'T') {
- reflectiveCtrlPointX = currentX - ctrlPointX;
- reflectiveCtrlPointY = currentY - ctrlPointY;
- }
- outPath->rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
- points->at(k + 0), points->at(k + 1));
- ctrlPointX = currentX + reflectiveCtrlPointX;
- ctrlPointY = currentY + reflectiveCtrlPointY;
- currentX += points->at(k + 0);
- currentY += points->at(k + 1);
- break;
- case 'T': // Draws a quadratic Bézier curve (reflective control point)
- reflectiveCtrlPointX = currentX;
- reflectiveCtrlPointY = currentY;
- if (previousCmd == 'q' || previousCmd == 't'
- || previousCmd == 'Q' || previousCmd == 'T') {
- reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
- reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
- }
- outPath->quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
- points->at(k + 0), points->at(k + 1));
- ctrlPointX = reflectiveCtrlPointX;
- ctrlPointY = reflectiveCtrlPointY;
- currentX = points->at(k + 0);
- currentY = points->at(k + 1);
- break;
- case 'a': // Draws an elliptical arc
- // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
- drawArc(outPath,
- currentX,
- currentY,
- points->at(k + 5) + currentX,
- points->at(k + 6) + currentY,
- points->at(k + 0),
- points->at(k + 1),
- points->at(k + 2),
- points->at(k + 3) != 0,
- points->at(k + 4) != 0);
- currentX += points->at(k + 5);
- currentY += points->at(k + 6);
- ctrlPointX = currentX;
- ctrlPointY = currentY;
- break;
- case 'A': // Draws an elliptical arc
- drawArc(outPath,
- currentX,
- currentY,
- points->at(k + 5),
- points->at(k + 6),
- points->at(k + 0),
- points->at(k + 1),
- points->at(k + 2),
- points->at(k + 3) != 0,
- points->at(k + 4) != 0);
- currentX = points->at(k + 5);
- currentY = points->at(k + 6);
- ctrlPointX = currentX;
- ctrlPointY = currentY;
- break;
- }
- previousCmd = cmd;
- }
-}
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/VectorDrawablePath.h b/libs/hwui/VectorDrawablePath.h
index 40ce986..2e56349 100644
--- a/libs/hwui/VectorDrawablePath.h
+++ b/libs/hwui/VectorDrawablePath.h
@@ -17,15 +17,14 @@
#ifndef ANDROID_HWUI_VPATH_H
#define ANDROID_HWUI_VPATH_H
+#include <cutils/compiler.h>
#include "SkPath.h"
#include <vector>
namespace android {
namespace uirenderer {
-
-
-struct PathData {
+struct ANDROID_API PathData {
// TODO: Try using FatVector instead of std::vector and do a micro benchmark on the performance
// difference.
std::vector<char> verbs;
@@ -44,9 +43,7 @@
VectorDrawablePath(const char* path, size_t strLength);
bool canMorph(const PathData& path);
bool canMorph(const VectorDrawablePath& path);
- static void verbsToPath(SkPath* outPath, const PathData* data);
- static void interpolatePaths(PathData* outPathData, const PathData* from, const PathData* to,
- float fraction);
+
private:
PathData mData;
SkPath mSkPath;
diff --git a/libs/hwui/microbench/DisplayListCanvasBench.cpp b/libs/hwui/microbench/DisplayListCanvasBench.cpp
index 7a62037..4be1f99 100644
--- a/libs/hwui/microbench/DisplayListCanvasBench.cpp
+++ b/libs/hwui/microbench/DisplayListCanvasBench.cpp
@@ -23,7 +23,7 @@
#include "DisplayListCanvas.h"
#endif
#include "microbench/MicroBench.h"
-#include "unit_tests/TestUtils.h"
+#include "utils/TestUtils.h"
using namespace android;
using namespace android::uirenderer;
diff --git a/libs/hwui/microbench/OpReordererBench.cpp b/libs/hwui/microbench/OpReordererBench.cpp
index b24858e..eea0c7f 100644
--- a/libs/hwui/microbench/OpReordererBench.cpp
+++ b/libs/hwui/microbench/OpReordererBench.cpp
@@ -21,7 +21,7 @@
#include "OpReorderer.h"
#include "RecordedOp.h"
#include "RecordingCanvas.h"
-#include "unit_tests/TestUtils.h"
+#include "utils/TestUtils.h"
#include "microbench/MicroBench.h"
#include <vector>
diff --git a/libs/hwui/microbench/PathParserBench.cpp b/libs/hwui/microbench/PathParserBench.cpp
index 171078d..3d9fafa 100644
--- a/libs/hwui/microbench/PathParserBench.cpp
+++ b/libs/hwui/microbench/PathParserBench.cpp
@@ -17,21 +17,35 @@
#include <benchmark/Benchmark.h>
#include "PathParser.h"
+#include "VectorDrawablePath.h"
#include <SkPath.h>
using namespace android;
using namespace android::uirenderer;
-BENCHMARK_NO_ARG(BM_PathParser_parseStringPath);
-void BM_PathParser_parseStringPath::Run(int iter) {
- const char* pathString = "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10";
+static const char* sPathString = "M 1 1 m 2 2, l 3 3 L 3 3 H 4 h4 V5 v5, Q6 6 6 6 q 6 6 6 6t 7 7 T 7 7 C 8 8 8 8 8 8 c 8 8 8 8 8 8 S 9 9 9 9 s 9 9 9 9 A 10 10 0 1 1 10 10 a 10 10 0 1 1 10 10";
+
+BENCHMARK_NO_ARG(BM_PathParser_parseStringPathForSkPath);
+void BM_PathParser_parseStringPathForSkPath::Run(int iter) {
SkPath skPath;
- size_t length = strlen(pathString);
+ size_t length = strlen(sPathString);
PathParser::ParseResult result;
StartBenchmarkTiming();
for (int i = 0; i < iter; i++) {
- PathParser::parseStringForSkPath(&skPath, &result, pathString, length);
+ PathParser::parseStringForSkPath(&skPath, &result, sPathString, length);
+ }
+ StopBenchmarkTiming();
+}
+
+BENCHMARK_NO_ARG(BM_PathParser_parseStringPathForPathData);
+void BM_PathParser_parseStringPathForPathData::Run(int iter) {
+ size_t length = strlen(sPathString);
+ PathData outData;
+ PathParser::ParseResult result;
+ StartBenchmarkTiming();
+ for (int i = 0; i < iter; i++) {
+ PathParser::getPathDataFromString(&outData, &result, sPathString, length);
}
StopBenchmarkTiming();
}
diff --git a/libs/hwui/tests/Benchmark.h b/libs/hwui/tests/Benchmark.h
index e16310e..3f87d7f 100644
--- a/libs/hwui/tests/Benchmark.h
+++ b/libs/hwui/tests/Benchmark.h
@@ -16,6 +16,8 @@
#ifndef TESTS_BENCHMARK_H
#define TESTS_BENCHMARK_H
+#include "TestScene.h"
+
#include <string>
#include <vector>
@@ -26,12 +28,17 @@
int count;
};
-typedef void (*BenchmarkFunctor)(const BenchmarkOptions&);
+typedef test::TestScene* (*CreateScene)(const BenchmarkOptions&);
+
+template <class T>
+test::TestScene* simpleCreateScene(const BenchmarkOptions&) {
+ return new T();
+}
struct BenchmarkInfo {
std::string name;
std::string description;
- BenchmarkFunctor functor;
+ CreateScene createScene;
};
class Benchmark {
diff --git a/libs/hwui/tests/TestScene.h b/libs/hwui/tests/TestScene.h
new file mode 100644
index 0000000..b5d8954
--- /dev/null
+++ b/libs/hwui/tests/TestScene.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+#ifndef TESTS_TESTSCENE_H
+#define TESTS_TESTSCENE_H
+
+namespace android {
+namespace uirenderer {
+class RenderNode;
+
+#if HWUI_NEW_OPS
+class RecordingCanvas;
+typedef RecordingCanvas TestCanvas;
+#else
+class DisplayListCanvas;
+typedef DisplayListCanvas TestCanvas;
+#endif
+
+namespace test {
+
+class TestScene {
+public:
+ virtual ~TestScene() {}
+ virtual void createContent(int width, int height, TestCanvas& renderer) = 0;
+ virtual void doFrame(int frameNr) = 0;
+};
+
+} // namespace test
+} // namespace uirenderer
+} // namespace android
+
+#endif /* TESTS_TESTSCENE_H */
diff --git a/libs/hwui/tests/TestSceneRunner.cpp b/libs/hwui/tests/TestSceneRunner.cpp
new file mode 100644
index 0000000..0376e10
--- /dev/null
+++ b/libs/hwui/tests/TestSceneRunner.cpp
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+#include "AnimationContext.h"
+#include "Benchmark.h"
+#include "RenderNode.h"
+#include "TestContext.h"
+#include "scenes/TestSceneBase.h"
+#include "renderthread/RenderProxy.h"
+#include "renderthread/RenderTask.h"
+
+#include <cutils/log.h>
+#include <gui/Surface.h>
+#include <ui/PixelFormat.h>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+using namespace android::uirenderer::test;
+
+class ContextFactory : public IContextFactory {
+public:
+ virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
+ return new AnimationContext(clock);
+ }
+};
+
+void run(const BenchmarkInfo& info, const BenchmarkOptions& opts) {
+ // Switch to the real display
+ gDisplay = getBuiltInDisplay();
+
+ std::unique_ptr<TestScene> scene(info.createScene(opts));
+
+ TestContext testContext;
+
+ // create the native surface
+ const int width = gDisplay.w;
+ const int height = gDisplay.h;
+ sp<Surface> surface = testContext.surface();
+
+ sp<RenderNode> rootNode = TestUtils::createNode(0, 0, width, height,
+ [&scene, width, height](RenderProperties& props, TestCanvas& canvas) {
+ props.setClipToBounds(false);
+ scene->createContent(width, height, canvas);
+ });
+
+ ContextFactory factory;
+ std::unique_ptr<RenderProxy> proxy(new RenderProxy(false,
+ rootNode.get(), &factory));
+ proxy->loadSystemProperties();
+ proxy->initialize(surface);
+ float lightX = width / 2.0;
+ proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15);
+ proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)});
+
+ // Do a few cold runs then reset the stats so that the caches are all hot
+ for (int i = 0; i < 3; i++) {
+ testContext.waitForVsync();
+ nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
+ UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
+ proxy->syncAndDrawFrame();
+ }
+ proxy->resetProfileInfo();
+
+ for (int i = 0; i < opts.count; i++) {
+ testContext.waitForVsync();
+
+ ATRACE_NAME("UI-Draw Frame");
+ nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
+ UiFrameInfoBuilder(proxy->frameInfo()).setVsync(vsync, vsync);
+ scene->doFrame(i);
+ proxy->syncAndDrawFrame();
+ }
+
+ proxy->dumpProfileInfo(STDOUT_FILENO, 0);
+}
diff --git a/libs/hwui/tests/TreeContentAnimation.cpp b/libs/hwui/tests/TreeContentAnimation.cpp
deleted file mode 100644
index 81bf9ed..0000000
--- a/libs/hwui/tests/TreeContentAnimation.cpp
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * 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.
- */
-
-#include <cutils/log.h>
-#include <gui/Surface.h>
-#include <ui/PixelFormat.h>
-
-#include <AnimationContext.h>
-#include <DisplayListCanvas.h>
-#include <RecordingCanvas.h>
-#include <RenderNode.h>
-#include <renderthread/RenderProxy.h>
-#include <renderthread/RenderTask.h>
-#include <unit_tests/TestUtils.h>
-
-#include "Benchmark.h"
-#include "TestContext.h"
-
-#include "protos/hwui.pb.h"
-
-#include <stdio.h>
-#include <unistd.h>
-#include <getopt.h>
-#include <vector>
-
-using namespace android;
-using namespace android::uirenderer;
-using namespace android::uirenderer::renderthread;
-using namespace android::uirenderer::test;
-
-#if HWUI_NEW_OPS
-typedef RecordingCanvas TestCanvas;
-#else
-typedef DisplayListCanvas TestCanvas;
-#endif
-
-
-class ContextFactory : public IContextFactory {
-public:
- virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
- return new AnimationContext(clock);
- }
-};
-
-static void recordNode(RenderNode& node, std::function<void(TestCanvas&)> contentCallback) {
- TestCanvas canvas(node.stagingProperties().getWidth(), node.stagingProperties().getHeight());
- contentCallback(canvas);
- node.setStagingDisplayList(canvas.finishRecording());
-}
-
-class TreeContentAnimation {
-public:
- virtual ~TreeContentAnimation() {}
- int frameCount = 150;
- virtual int getFrameCount() { return frameCount; }
- virtual void setFrameCount(int fc) {
- if (fc > 0) {
- frameCount = fc;
- }
- }
- virtual void createContent(int width, int height, TestCanvas* canvas) = 0;
- virtual void doFrame(int frameNr) = 0;
-
- template <class T>
- static void run(const BenchmarkOptions& opts) {
- // Switch to the real display
- gDisplay = getBuiltInDisplay();
-
- T animation;
- animation.setFrameCount(opts.count);
-
- TestContext testContext;
-
- // create the native surface
- const int width = gDisplay.w;
- const int height = gDisplay.h;
- sp<Surface> surface = testContext.surface();
-
- RenderNode* rootNode = new RenderNode();
- rootNode->incStrong(nullptr);
- rootNode->mutateStagingProperties().setLeftTopRightBottom(0, 0, width, height);
- rootNode->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- rootNode->mutateStagingProperties().setClipToBounds(false);
- rootNode->setPropertyFieldsDirty(RenderNode::GENERIC);
-
- ContextFactory factory;
- std::unique_ptr<RenderProxy> proxy(new RenderProxy(false, rootNode, &factory));
- proxy->loadSystemProperties();
- proxy->initialize(surface);
- float lightX = width / 2.0;
- proxy->setup(width, height, dp(800.0f), 255 * 0.075, 255 * 0.15);
- proxy->setLightCenter((Vector3){lightX, dp(-200.0f), dp(800.0f)});
-
- recordNode(*rootNode, [&animation, width, height](TestCanvas& canvas) {
- animation.createContent(width, height, &canvas); //TODO: no&
- });
-
- // Do a few cold runs then reset the stats so that the caches are all hot
- for (int i = 0; i < 3; i++) {
- testContext.waitForVsync();
- proxy->syncAndDrawFrame();
- }
- proxy->resetProfileInfo();
-
- for (int i = 0; i < animation.getFrameCount(); i++) {
- testContext.waitForVsync();
-
- ATRACE_NAME("UI-Draw Frame");
- nsecs_t vsync = systemTime(CLOCK_MONOTONIC);
- UiFrameInfoBuilder(proxy->frameInfo())
- .setVsync(vsync, vsync);
- animation.doFrame(i);
- proxy->syncAndDrawFrame();
- }
-
- proxy->dumpProfileInfo(STDOUT_FILENO, 0);
- rootNode->decStrong(nullptr);
- }
-};
-
-class ShadowGridAnimation : public TreeContentAnimation {
-public:
- std::vector< sp<RenderNode> > cards;
- void createContent(int width, int height, TestCanvas* canvas) override {
- canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- canvas->insertReorderBarrier(true);
-
- for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
- for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
- sp<RenderNode> card = createCard(x, y, dp(100), dp(100));
- canvas->drawRenderNode(card.get());
- cards.push_back(card);
- }
- }
-
- canvas->insertReorderBarrier(false);
- }
- void doFrame(int frameNr) override {
- int curFrame = frameNr % 150;
- for (size_t ci = 0; ci < cards.size(); ci++) {
- cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
- cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
- cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- }
- }
-private:
- sp<RenderNode> createCard(int x, int y, int width, int height) {
- sp<RenderNode> node = new RenderNode();
- node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
- node->mutateStagingProperties().setElevation(dp(16));
- node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1);
- node->mutateStagingProperties().mutableOutline().setShouldClip(true);
- node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
-
- recordNode(*node, [](TestCanvas& canvas) {
- canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
- });
- return node;
- }
-};
-static Benchmark _ShadowGrid(BenchmarkInfo{
- "shadowgrid",
- "A grid of rounded rects that cast a shadow. Simplified scenario of an "
- "Android TV-style launcher interface. High CPU/GPU load.",
- TreeContentAnimation::run<ShadowGridAnimation>
-});
-
-class ShadowGrid2Animation : public TreeContentAnimation {
-public:
- std::vector< sp<RenderNode> > cards;
- void createContent(int width, int height, TestCanvas* canvas) override {
- canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- canvas->insertReorderBarrier(true);
-
- for (int x = dp(8); x < (width - dp(58)); x += dp(58)) {
- for (int y = dp(8); y < (height - dp(58)); y += dp(58)) {
- sp<RenderNode> card = createCard(x, y, dp(50), dp(50));
- canvas->drawRenderNode(card.get());
- cards.push_back(card);
- }
- }
-
- canvas->insertReorderBarrier(false);
- }
- void doFrame(int frameNr) override {
- int curFrame = frameNr % 150;
- for (size_t ci = 0; ci < cards.size(); ci++) {
- cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
- cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
- cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- }
- }
-private:
- sp<RenderNode> createCard(int x, int y, int width, int height) {
- sp<RenderNode> node = new RenderNode();
- node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
- node->mutateStagingProperties().setElevation(dp(16));
- node->mutateStagingProperties().mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
- node->mutateStagingProperties().mutableOutline().setShouldClip(true);
- node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y | RenderNode::Z);
-
- recordNode(*node, [](TestCanvas& canvas) {
- canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
- });
- return node;
- }
-};
-static Benchmark _ShadowGrid2(BenchmarkInfo{
- "shadowgrid2",
- "A dense grid of rounded rects that cast a shadow. This is a higher CPU load "
- "variant of shadowgrid. Very high CPU load, high GPU load.",
- TreeContentAnimation::run<ShadowGrid2Animation>
-});
-
-class RectGridAnimation : public TreeContentAnimation {
-public:
- sp<RenderNode> card = new RenderNode();
- void createContent(int width, int height, TestCanvas* canvas) override {
- canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- canvas->insertReorderBarrier(true);
-
- card->mutateStagingProperties().setLeftTopRightBottom(50, 50, 250, 250);
- card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- recordNode(*card, [](TestCanvas& canvas) {
- canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
-
- SkRegion region;
- for (int xOffset = 0; xOffset < 200; xOffset+=2) {
- for (int yOffset = 0; yOffset < 200; yOffset+=2) {
- region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op);
- }
- }
-
- SkPaint paint;
- paint.setColor(0xff00ffff);
- canvas.drawRegion(region, paint);
- });
- canvas->drawRenderNode(card.get());
-
- canvas->insertReorderBarrier(false);
- }
- void doFrame(int frameNr) override {
- int curFrame = frameNr % 150;
- card->mutateStagingProperties().setTranslationX(curFrame);
- card->mutateStagingProperties().setTranslationY(curFrame);
- card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- }
-};
-static Benchmark _RectGrid(BenchmarkInfo{
- "rectgrid",
- "A dense grid of 1x1 rects that should visually look like a single rect. "
- "Low CPU/GPU load.",
- TreeContentAnimation::run<RectGridAnimation>
-});
-
-class OvalAnimation : public TreeContentAnimation {
-public:
- sp<RenderNode> card = new RenderNode();
- void createContent(int width, int height, TestCanvas* canvas) override {
- canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
- canvas->insertReorderBarrier(true);
-
- card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200);
- card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- recordNode(*card, [](TestCanvas& canvas) {
- SkPaint paint;
- paint.setAntiAlias(true);
- paint.setColor(0xFF000000);
- canvas.drawOval(0, 0, 200, 200, paint);
- });
- canvas->drawRenderNode(card.get());
-
- canvas->insertReorderBarrier(false);
- }
-
- void doFrame(int frameNr) override {
- int curFrame = frameNr % 150;
- card->mutateStagingProperties().setTranslationX(curFrame);
- card->mutateStagingProperties().setTranslationY(curFrame);
- card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- }
-};
-static Benchmark _Oval(BenchmarkInfo{
- "oval",
- "Draws 1 oval.",
- TreeContentAnimation::run<OvalAnimation>
-});
-
-class PartialDamageTest : public TreeContentAnimation {
-public:
- std::vector< sp<RenderNode> > cards;
- void createContent(int width, int height, TestCanvas* canvas) override {
- static SkColor COLORS[] = {
- 0xFFF44336,
- 0xFF9C27B0,
- 0xFF2196F3,
- 0xFF4CAF50,
- };
-
- canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
-
- for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
- for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
- sp<RenderNode> card = createCard(x, y, dp(100), dp(100),
- COLORS[static_cast<int>((y / dp(116))) % 4]);
- canvas->drawRenderNode(card.get());
- cards.push_back(card);
- }
- }
- }
- void doFrame(int frameNr) override {
- int curFrame = frameNr % 150;
- cards[0]->mutateStagingProperties().setTranslationX(curFrame);
- cards[0]->mutateStagingProperties().setTranslationY(curFrame);
- cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
- recordNode(*cards[0], [curFrame](TestCanvas& canvas) {
- canvas.drawColor(interpolateColor(curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0),
- SkXfermode::kSrcOver_Mode);
- });
- }
-
- static SkColor interpolateColor(float fraction, SkColor start, SkColor end) {
- int startA = (start >> 24) & 0xff;
- int startR = (start >> 16) & 0xff;
- int startG = (start >> 8) & 0xff;
- int startB = start & 0xff;
-
- int endA = (end >> 24) & 0xff;
- int endR = (end >> 16) & 0xff;
- int endG = (end >> 8) & 0xff;
- int endB = end & 0xff;
-
- return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
- (int)((startR + (int)(fraction * (endR - startR))) << 16) |
- (int)((startG + (int)(fraction * (endG - startG))) << 8) |
- (int)((startB + (int)(fraction * (endB - startB))));
- }
-private:
- sp<RenderNode> createCard(int x, int y, int width, int height, SkColor color) {
- sp<RenderNode> node = new RenderNode();
- node->mutateStagingProperties().setLeftTopRightBottom(x, y, x + width, y + height);
- node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
-
- recordNode(*node, [color](TestCanvas& canvas) {
- canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
- });
- return node;
- }
-};
-static Benchmark _PartialDamage(BenchmarkInfo{
- "partialdamage",
- "Tests the partial invalidation path. Draws a grid of rects and animates 1 "
- "of them, should be low CPU & GPU load if EGL_EXT_buffer_age or "
- "EGL_KHR_partial_update is supported by the device & are enabled in hwui.",
- TreeContentAnimation::run<PartialDamageTest>
-});
-
-
-class SaveLayerAnimation : public TreeContentAnimation {
-public:
- sp<RenderNode> card = new RenderNode();
- void createContent(int width, int height, TestCanvas* canvas) override {
- canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
-
- card->mutateStagingProperties().setLeftTopRightBottom(0, 0, 200, 200);
- card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- recordNode(*card, [](TestCanvas& canvas) {
- canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag);
- canvas.drawColor(0xFF00FF00, SkXfermode::kSrcOver_Mode); // outer, unclipped
- canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
- canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); // inner, clipped
- canvas.restore();
- canvas.restore();
- });
-
- canvas->drawRenderNode(card.get());
- }
- void doFrame(int frameNr) override {
- int curFrame = frameNr % 150;
- card->mutateStagingProperties().setTranslationX(curFrame);
- card->mutateStagingProperties().setTranslationY(curFrame);
- card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- }
-};
-static Benchmark _SaveLayer(BenchmarkInfo{
- "savelayer",
- "A nested pair of clipped saveLayer operations. "
- "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.",
- TreeContentAnimation::run<SaveLayerAnimation>
-});
-
-
-class HwLayerAnimation : public TreeContentAnimation {
-public:
- sp<RenderNode> card = TestUtils::createNode<TestCanvas>(0, 0, 200, 200, [] (TestCanvas& canvas) {
- canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode);
- }, TestUtils::getHwLayerSetupCallback());
- void createContent(int width, int height, TestCanvas* canvas) override {
- canvas->drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
- canvas->drawRenderNode(card.get());
- }
- void doFrame(int frameNr) override {
- int curFrame = frameNr % 150;
- card->mutateStagingProperties().setTranslationX(curFrame);
- card->mutateStagingProperties().setTranslationY(curFrame);
- card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- }
-};
-static Benchmark _HwLayer(BenchmarkInfo{
- "hwlayer",
- "A nested pair of nodes with LAYER_TYPE_HARDWARE set on each. "
- "Tests the hardware layer codepath.",
- TreeContentAnimation::run<HwLayerAnimation>
-});
diff --git a/libs/hwui/tests/main.cpp b/libs/hwui/tests/main.cpp
index aee84de..48566e8 100644
--- a/libs/hwui/tests/main.cpp
+++ b/libs/hwui/tests/main.cpp
@@ -43,6 +43,8 @@
static int gRepeatCount = 1;
static std::vector<BenchmarkInfo> gRunTests;
+void run(const BenchmarkInfo& info, const BenchmarkOptions& opts);
+
static void printHelp() {
printf("\
USAGE: hwuitest [OPTIONS] <TESTNAME>\n\
@@ -186,7 +188,7 @@
opts.count = gFrameCount;
for (int i = 0; i < gRepeatCount; i++) {
for (auto&& test : gRunTests) {
- test.functor(opts);
+ run(test, opts);
}
}
printf("Success!\n");
diff --git a/libs/hwui/tests/scenes/HwLayerAnimation.cpp b/libs/hwui/tests/scenes/HwLayerAnimation.cpp
new file mode 100644
index 0000000..e316eca
--- /dev/null
+++ b/libs/hwui/tests/scenes/HwLayerAnimation.cpp
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+#include "TestSceneBase.h"
+
+class HwLayerAnimation;
+
+static Benchmark _HwLayer(BenchmarkInfo{
+ "hwlayer",
+ "A nested pair of nodes with LAYER_TYPE_HARDWARE set on each. "
+ "Tests the hardware layer codepath.",
+ simpleCreateScene<HwLayerAnimation>
+});
+
+class HwLayerAnimation : public TestScene {
+public:
+ sp<RenderNode> card;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ card = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, TestCanvas& canvas) {
+ props.mutateLayerProperties().setType(LayerType::RenderLayer);
+ canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode);
+ });
+ canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
+ canvas.drawRenderNode(card.get());
+ }
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ card->mutateStagingProperties().setTranslationX(curFrame);
+ card->mutateStagingProperties().setTranslationY(curFrame);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+};
diff --git a/libs/hwui/tests/scenes/OvalAnimation.cpp b/libs/hwui/tests/scenes/OvalAnimation.cpp
new file mode 100644
index 0000000..919a53d
--- /dev/null
+++ b/libs/hwui/tests/scenes/OvalAnimation.cpp
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#include "TestSceneBase.h"
+
+class OvalAnimation;
+
+static Benchmark _Oval(BenchmarkInfo{
+ "oval",
+ "Draws 1 oval.",
+ simpleCreateScene<OvalAnimation>
+});
+
+class OvalAnimation : public TestScene {
+public:
+ sp<RenderNode> card;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas.insertReorderBarrier(true);
+
+ card = TestUtils::createNode(0, 0, 200, 200, [](TestCanvas& canvas) {
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setColor(0xFF000000);
+ canvas.drawOval(0, 0, 200, 200, paint);
+ });
+
+ canvas.drawRenderNode(card.get());
+ canvas.insertReorderBarrier(false);
+ }
+
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ card->mutateStagingProperties().setTranslationX(curFrame);
+ card->mutateStagingProperties().setTranslationY(curFrame);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+};
diff --git a/libs/hwui/tests/scenes/PartialDamageAnimation.cpp b/libs/hwui/tests/scenes/PartialDamageAnimation.cpp
new file mode 100644
index 0000000..0fba4eb
--- /dev/null
+++ b/libs/hwui/tests/scenes/PartialDamageAnimation.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#include "TestSceneBase.h"
+
+class PartialDamageAnimation;
+
+static Benchmark _PartialDamage(BenchmarkInfo{
+ "partialdamage",
+ "Tests the partial invalidation path. Draws a grid of rects and animates 1 "
+ "of them, should be low CPU & GPU load if EGL_EXT_buffer_age or "
+ "EGL_KHR_partial_update is supported by the device & are enabled in hwui.",
+ simpleCreateScene<PartialDamageAnimation>
+});
+
+class PartialDamageAnimation : public TestScene {
+public:
+ std::vector< sp<RenderNode> > cards;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ static SkColor COLORS[] = {
+ 0xFFF44336,
+ 0xFF9C27B0,
+ 0xFF2196F3,
+ 0xFF4CAF50,
+ };
+
+ canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+
+ for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
+ for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
+ SkColor color = COLORS[static_cast<int>((y / dp(116))) % 4];
+ sp<RenderNode> card = TestUtils::createNode(x, y,
+ x + dp(100), y + dp(100),
+ [color](TestCanvas& canvas) {
+ canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
+ });
+ canvas.drawRenderNode(card.get());
+ cards.push_back(card);
+ }
+ }
+ }
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ cards[0]->mutateStagingProperties().setTranslationX(curFrame);
+ cards[0]->mutateStagingProperties().setTranslationY(curFrame);
+ cards[0]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+
+ TestUtils::recordNode(*cards[0], [curFrame](TestCanvas& canvas) {
+ SkColor color = TestUtils::interpolateColor(
+ curFrame / 150.0f, 0xFFF44336, 0xFFF8BBD0);
+ canvas.drawColor(color, SkXfermode::kSrcOver_Mode);
+ });
+ }
+};
diff --git a/libs/hwui/tests/scenes/RecentsAnimation.cpp b/libs/hwui/tests/scenes/RecentsAnimation.cpp
new file mode 100644
index 0000000..1e38d84
--- /dev/null
+++ b/libs/hwui/tests/scenes/RecentsAnimation.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#include "TestSceneBase.h"
+
+class RecentsAnimation;
+
+static Benchmark _Recents(BenchmarkInfo{
+ "recents",
+ "A recents-like scrolling list of textures. "
+ "Consists of updating a texture every frame",
+ simpleCreateScene<RecentsAnimation>
+});
+
+class RecentsAnimation : public TestScene {
+public:
+ void createContent(int width, int height, TestCanvas& renderer) override {
+ static SkColor COLORS[] = {
+ 0xFFF44336,
+ 0xFF9C27B0,
+ 0xFF2196F3,
+ 0xFF4CAF50,
+ };
+
+ thumbnailSize = std::min(std::min(width, height) / 2, 720);
+ int cardsize = std::min(width, height) - dp(64);
+
+ renderer.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ renderer.insertReorderBarrier(true);
+
+ int x = dp(32);
+ for (int i = 0; i < 4; i++) {
+ int y = (height / 4) * i;
+ SkBitmap thumb = TestUtils::createSkBitmap(thumbnailSize, thumbnailSize);
+ thumb.eraseColor(COLORS[i]);
+ sp<RenderNode> card = createCard(x, y, cardsize, cardsize, thumb);
+ card->mutateStagingProperties().setElevation(i * dp(8));
+ renderer.drawRenderNode(card.get());
+ mThumbnail = thumb;
+ mCards.push_back(card);
+ }
+
+ renderer.insertReorderBarrier(false);
+ }
+
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ for (size_t ci = 0; ci < mCards.size(); ci++) {
+ mCards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+ mCards[ci]->setPropertyFieldsDirty(RenderNode::Y);
+ }
+ mThumbnail.eraseColor(TestUtils::interpolateColor(
+ curFrame / 150.0f, 0xFF4CAF50, 0xFFFF5722));
+ }
+
+private:
+ sp<RenderNode> createCard(int x, int y, int width, int height,
+ const SkBitmap& thumb) {
+ return TestUtils::createNode(x, y, x + width, y + height,
+ [&thumb, width, height](RenderProperties& props, TestCanvas& canvas) {
+ props.setElevation(dp(16));
+ props.mutableOutline().setRoundRect(0, 0, width, height, dp(10), 1);
+ props.mutableOutline().setShouldClip(true);
+
+ canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+ canvas.drawBitmap(thumb, 0, 0, thumb.width(), thumb.height(),
+ 0, 0, width, height, nullptr);
+ });
+ }
+
+ SkBitmap mThumbnail;
+ std::vector< sp<RenderNode> > mCards;
+ int thumbnailSize;
+};
diff --git a/libs/hwui/tests/scenes/RectGridAnimation.cpp b/libs/hwui/tests/scenes/RectGridAnimation.cpp
new file mode 100644
index 0000000..254f828
--- /dev/null
+++ b/libs/hwui/tests/scenes/RectGridAnimation.cpp
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+
+#include "TestSceneBase.h"
+
+class RectGridAnimation;
+
+static Benchmark _RectGrid(BenchmarkInfo{
+ "rectgrid",
+ "A dense grid of 1x1 rects that should visually look like a single rect. "
+ "Low CPU/GPU load.",
+ simpleCreateScene<RectGridAnimation>
+});
+
+class RectGridAnimation : public TestScene {
+public:
+ sp<RenderNode> card;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas.insertReorderBarrier(true);
+
+ card = TestUtils::createNode(50, 50, 250, 250,
+ [](TestCanvas& canvas) {
+ canvas.drawColor(0xFFFF00FF, SkXfermode::kSrcOver_Mode);
+
+ SkRegion region;
+ for (int xOffset = 0; xOffset < 200; xOffset+=2) {
+ for (int yOffset = 0; yOffset < 200; yOffset+=2) {
+ region.op(xOffset, yOffset, xOffset + 1, yOffset + 1, SkRegion::kUnion_Op);
+ }
+ }
+
+ SkPaint paint;
+ paint.setColor(0xff00ffff);
+ canvas.drawRegion(region, paint);
+ });
+ canvas.drawRenderNode(card.get());
+
+ canvas.insertReorderBarrier(false);
+ }
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ card->mutateStagingProperties().setTranslationX(curFrame);
+ card->mutateStagingProperties().setTranslationY(curFrame);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+};
diff --git a/libs/hwui/tests/scenes/SaveLayerAnimation.cpp b/libs/hwui/tests/scenes/SaveLayerAnimation.cpp
new file mode 100644
index 0000000..c62dd19
--- /dev/null
+++ b/libs/hwui/tests/scenes/SaveLayerAnimation.cpp
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+#include "TestSceneBase.h"
+
+class SaveLayerAnimation;
+
+static Benchmark _SaveLayer(BenchmarkInfo{
+ "savelayer",
+ "A nested pair of clipped saveLayer operations. "
+ "Tests the clipped saveLayer codepath. Draws content into offscreen buffers and back again.",
+ simpleCreateScene<SaveLayerAnimation>
+});
+
+class SaveLayerAnimation : public TestScene {
+public:
+ sp<RenderNode> card;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode); // background
+
+ card = TestUtils::createNode(0, 0, 200, 200,
+ [](TestCanvas& canvas) {
+ canvas.saveLayerAlpha(0, 0, 200, 200, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.drawColor(0xFF00FF00, SkXfermode::kSrcOver_Mode); // outer, unclipped
+ canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
+ canvas.drawColor(0xFF0000FF, SkXfermode::kSrcOver_Mode); // inner, clipped
+ canvas.restore();
+ canvas.restore();
+ });
+
+ canvas.drawRenderNode(card.get());
+ }
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ card->mutateStagingProperties().setTranslationX(curFrame);
+ card->mutateStagingProperties().setTranslationY(curFrame);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+};
diff --git a/libs/hwui/tests/scenes/ShadowGrid2Animation.cpp b/libs/hwui/tests/scenes/ShadowGrid2Animation.cpp
new file mode 100644
index 0000000..26c86aa
--- /dev/null
+++ b/libs/hwui/tests/scenes/ShadowGrid2Animation.cpp
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+#include "TestSceneBase.h"
+
+class ShadowGrid2Animation;
+
+static Benchmark _ShadowGrid2(BenchmarkInfo{
+ "shadowgrid2",
+ "A dense grid of rounded rects that cast a shadow. This is a higher CPU load "
+ "variant of shadowgrid. Very high CPU load, high GPU load.",
+ simpleCreateScene<ShadowGrid2Animation>
+});
+
+class ShadowGrid2Animation : public TestScene {
+public:
+ std::vector< sp<RenderNode> > cards;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas.insertReorderBarrier(true);
+
+ for (int x = dp(8); x < (width - dp(58)); x += dp(58)) {
+ for (int y = dp(8); y < (height - dp(58)); y += dp(58)) {
+ sp<RenderNode> card = createCard(x, y, dp(50), dp(50));
+ canvas.drawRenderNode(card.get());
+ cards.push_back(card);
+ }
+ }
+
+ canvas.insertReorderBarrier(false);
+ }
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ for (size_t ci = 0; ci < cards.size(); ci++) {
+ cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
+ cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+ cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+ }
+private:
+ sp<RenderNode> createCard(int x, int y, int width, int height) {
+ return TestUtils::createNode(x, y, x + width, y + height,
+ [width, height](RenderProperties& props, TestCanvas& canvas) {
+ props.setElevation(dp(16));
+ props.mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
+ props.mutableOutline().setShouldClip(true);
+ canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+ });
+ }
+};
diff --git a/libs/hwui/tests/scenes/ShadowGridAnimation.cpp b/libs/hwui/tests/scenes/ShadowGridAnimation.cpp
new file mode 100644
index 0000000..ee3c590
--- /dev/null
+++ b/libs/hwui/tests/scenes/ShadowGridAnimation.cpp
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+#include "TestSceneBase.h"
+
+class ShadowGridAnimation;
+
+static Benchmark _ShadowGrid(BenchmarkInfo{
+ "shadowgrid",
+ "A grid of rounded rects that cast a shadow. Simplified scenario of an "
+ "Android TV-style launcher interface. High CPU/GPU load.",
+ simpleCreateScene<ShadowGridAnimation>
+});
+
+class ShadowGridAnimation : public TestScene {
+public:
+ std::vector< sp<RenderNode> > cards;
+ void createContent(int width, int height, TestCanvas& canvas) override {
+ canvas.drawColor(0xFFFFFFFF, SkXfermode::kSrcOver_Mode);
+ canvas.insertReorderBarrier(true);
+
+ for (int x = dp(16); x < (width - dp(116)); x += dp(116)) {
+ for (int y = dp(16); y < (height - dp(116)); y += dp(116)) {
+ sp<RenderNode> card = createCard(x, y, dp(100), dp(100));
+ canvas.drawRenderNode(card.get());
+ cards.push_back(card);
+ }
+ }
+
+ canvas.insertReorderBarrier(false);
+ }
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ for (size_t ci = 0; ci < cards.size(); ci++) {
+ cards[ci]->mutateStagingProperties().setTranslationX(curFrame);
+ cards[ci]->mutateStagingProperties().setTranslationY(curFrame);
+ cards[ci]->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ }
+ }
+private:
+ sp<RenderNode> createCard(int x, int y, int width, int height) {
+ return TestUtils::createNode(x, y, x + width, y + height,
+ [width, height](RenderProperties& props, TestCanvas& canvas) {
+ props.setElevation(dp(16));
+ props.mutableOutline().setRoundRect(0, 0, width, height, dp(6), 1);
+ props.mutableOutline().setShouldClip(true);
+ canvas.drawColor(0xFFEEEEEE, SkXfermode::kSrcOver_Mode);
+ });
+ }
+};
diff --git a/libs/hwui/tests/scenes/TestSceneBase.h b/libs/hwui/tests/scenes/TestSceneBase.h
new file mode 100644
index 0000000..a208509
--- /dev/null
+++ b/libs/hwui/tests/scenes/TestSceneBase.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+#ifndef TESTS_SCENES_TESTSCENEBASE_H
+#define TESTS_SCENES_TESTSCENEBASE_H
+
+#include "DisplayListCanvas.h"
+#include "RecordingCanvas.h"
+#include "RenderNode.h"
+#include "tests/Benchmark.h"
+#include "tests/TestContext.h"
+#include "tests/TestScene.h"
+#include "utils/TestUtils.h"
+
+#include <functional>
+
+using namespace android;
+using namespace android::uirenderer;
+using namespace android::uirenderer::renderthread;
+using namespace android::uirenderer::test;
+
+#endif /* TESTS_SCENES_TESTSCENEBASE_H_ */
diff --git a/libs/hwui/unit_tests/BakedOpStateTests.cpp b/libs/hwui/unit_tests/BakedOpStateTests.cpp
index 4e00fb3..7ad2f9b 100644
--- a/libs/hwui/unit_tests/BakedOpStateTests.cpp
+++ b/libs/hwui/unit_tests/BakedOpStateTests.cpp
@@ -18,7 +18,7 @@
#include <BakedOpState.h>
#include <RecordedOp.h>
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/unit_tests/FatVectorTests.cpp b/libs/hwui/unit_tests/FatVectorTests.cpp
index 3ef329a..c6ccf4d 100644
--- a/libs/hwui/unit_tests/FatVectorTests.cpp
+++ b/libs/hwui/unit_tests/FatVectorTests.cpp
@@ -17,7 +17,7 @@
#include <gtest/gtest.h>
#include <utils/FatVector.h>
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
using namespace android;
using namespace android::uirenderer;
diff --git a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
index ef205ec..05fd08a 100644
--- a/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
+++ b/libs/hwui/unit_tests/LayerUpdateQueueTests.cpp
@@ -19,7 +19,7 @@
#include <LayerUpdateQueue.h>
#include <RenderNode.h>
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/unit_tests/LinearAllocatorTests.cpp b/libs/hwui/unit_tests/LinearAllocatorTests.cpp
index 0f6b249..0591db6 100644
--- a/libs/hwui/unit_tests/LinearAllocatorTests.cpp
+++ b/libs/hwui/unit_tests/LinearAllocatorTests.cpp
@@ -17,7 +17,7 @@
#include <gtest/gtest.h>
#include <utils/LinearAllocator.h>
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
using namespace android;
using namespace android::uirenderer;
diff --git a/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp b/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp
index ba92157..de86aed 100644
--- a/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp
+++ b/libs/hwui/unit_tests/OffscreenBufferPoolTests.cpp
@@ -17,7 +17,7 @@
#include <gtest/gtest.h>
#include <renderstate/OffscreenBufferPool.h>
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
using namespace android;
using namespace android::uirenderer;
diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp
index 07a1855..ec8048d 100644
--- a/libs/hwui/unit_tests/OpReordererTests.cpp
+++ b/libs/hwui/unit_tests/OpReordererTests.cpp
@@ -21,7 +21,7 @@
#include <OpReorderer.h>
#include <RecordedOp.h>
#include <RecordingCanvas.h>
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
#include <unordered_map>
@@ -186,14 +186,14 @@
}
};
- sp<RenderNode> child = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
+ sp<RenderNode> child = TestUtils::createNode(10, 10, 110, 110, [](RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
});
RenderNode* childPtr = child.get();
- sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) {
+ sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200, [childPtr](RecordingCanvas& canvas) {
SkPaint paint;
paint.setColor(SK_ColorDKGRAY);
canvas.drawRect(0, 0, 200, 200, paint);
@@ -221,7 +221,7 @@
}
};
- sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200, [](RecordingCanvas& canvas) {
+ sp<RenderNode> node = TestUtils::createNode(0, 0, 200, 200, [](RecordingCanvas& canvas) {
SkBitmap bitmap = TestUtils::createSkBitmap(200, 200);
canvas.drawBitmap(bitmap, 0, 0, nullptr);
});
@@ -396,11 +396,13 @@
}
};
- sp<RenderNode> node = TestUtils::createNode<RecordingCanvas>(10, 10, 110, 110, [](RecordingCanvas& canvas) {
+ sp<RenderNode> node = TestUtils::createNode(10, 10, 110, 110,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ props.mutateLayerProperties().setType(LayerType::RenderLayer);
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
- }, TestUtils::getHwLayerSetupCallback());
+ });
OffscreenBuffer** layerHandle = node->getLayerHandle();
// create RenderNode's layer here in same way prepareTree would
@@ -483,18 +485,20 @@
}
};
- auto child = TestUtils::createNode<RecordingCanvas>(50, 50, 150, 150,
- [](RecordingCanvas& canvas) {
+ auto child = TestUtils::createNode(50, 50, 150, 150,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ props.mutateLayerProperties().setType(LayerType::RenderLayer);
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
- }, TestUtils::getHwLayerSetupCallback());
+ });
OffscreenBuffer childLayer(renderThread.renderState(), Caches::getInstance(), 100, 100);
*(child->getLayerHandle()) = &childLayer;
RenderNode* childPtr = child.get();
- auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
- [childPtr](RecordingCanvas& canvas) {
+ auto parent = TestUtils::createNode(0, 0, 200, 200,
+ [childPtr](RenderProperties& props, RecordingCanvas& canvas) {
+ props.mutateLayerProperties().setType(LayerType::RenderLayer);
SkPaint paint;
paint.setColor(SK_ColorDKGRAY);
canvas.drawRect(0, 0, 200, 200, paint);
@@ -502,7 +506,7 @@
canvas.saveLayerAlpha(50, 50, 150, 150, 128, SkCanvas::kClipToLayer_SaveFlag);
canvas.drawRenderNode(childPtr);
canvas.restore();
- }, TestUtils::getHwLayerSetupCallback());
+ });
OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200);
*(parent->getLayerHandle()) = &parentLayer;
@@ -529,7 +533,7 @@
canvas->drawRect(0, 0, 100, 100, paint);
}
static void drawOrderedNode(RecordingCanvas* canvas, uint8_t expectedDrawOrder, float z) {
- auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ auto node = TestUtils::createNode(0, 0, 100, 100,
[expectedDrawOrder](RecordingCanvas& canvas) {
drawOrderedRect(&canvas, expectedDrawOrder);
});
@@ -546,7 +550,7 @@
}
};
- auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+ auto parent = TestUtils::createNode(0, 0, 100, 100,
[](RecordingCanvas& canvas) {
drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
drawOrderedRect(&canvas, 1);
@@ -570,15 +574,13 @@
// creates a 100x100 shadow casting node with provided translationZ
static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) {
- return TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
- [](RecordingCanvas& canvas) {
+ return TestUtils::createNode(0, 0, 100, 100,
+ [translationZ] (RenderProperties& properties, RecordingCanvas& canvas) {
+ properties.setTranslationZ(translationZ);
+ properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f);
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
- }, [translationZ] (RenderProperties& properties) {
- properties.setTranslationZ(translationZ);
- properties.mutableOutline().setRoundRect(0, 0, 100, 100, 0.0f, 1.0f);
- return RenderNode::GENERIC | RenderNode::TRANSLATION_Z;
});
}
@@ -600,7 +602,7 @@
}
};
- sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+ sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
[] (RecordingCanvas& canvas) {
canvas.insertReorderBarrier(true);
canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
@@ -636,7 +638,7 @@
}
};
- sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+ sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
[] (RecordingCanvas& canvas) {
// save/restore outside of reorderBarrier, so they don't get moved out of place
canvas.translate(20, 10);
@@ -676,14 +678,15 @@
}
};
- sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(50, 60, 150, 160,
- [] (RecordingCanvas& canvas) {
+ sp<RenderNode> parent = TestUtils::createNode(50, 60, 150, 160,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ props.mutateLayerProperties().setType(LayerType::RenderLayer);
canvas.insertReorderBarrier(true);
canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
canvas.translate(20, 10);
canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
canvas.restore();
- }, TestUtils::getHwLayerSetupCallback());
+ });
OffscreenBuffer** layerHandle = parent->getLayerHandle();
// create RenderNode's layer here in same way prepareTree would, setting windowTransform
@@ -718,7 +721,7 @@
EXPECT_TRUE(index == 2 || index == 3);
}
};
- sp<RenderNode> parent = TestUtils::createNode<RecordingCanvas>(0, 0, 200, 200,
+ sp<RenderNode> parent = TestUtils::createNode(0, 0, 200, 200,
[] (RecordingCanvas& canvas) {
canvas.insertReorderBarrier(true);
canvas.drawRenderNode(createWhiteRectShadowCaster(5.0f).get());
@@ -732,7 +735,7 @@
EXPECT_EQ(4, renderer.getIndex());
}
-static void testProperty(TestUtils::PropSetupCallback propSetupCallback,
+static void testProperty(std::function<void(RenderProperties&)> propSetupCallback,
std::function<void(const RectOp&, const BakedOpState&)> opValidateCallback) {
class PropertyTestRenderer : public TestRendererBase {
public:
@@ -745,11 +748,13 @@
std::function<void(const RectOp&, const BakedOpState&)> mCallback;
};
- auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 100, 100,
+ [propSetupCallback](RenderProperties& props, RecordingCanvas& canvas) {
+ propSetupCallback(props);
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 100, 100, paint);
- }, propSetupCallback);
+ });
OpReorderer reorderer(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200,
createSyncedNodeList(node), sLightCenter);
@@ -762,7 +767,6 @@
testProperty([](RenderProperties& properties) {
properties.setAlpha(0.5f);
properties.setHasOverlappingRendering(false);
- return RenderNode::ALPHA | RenderNode::GENERIC;
}, [](const RectOp& op, const BakedOpState& state) {
EXPECT_EQ(0.5f, state.alpha) << "Alpha should be applied directly to op";
});
@@ -772,7 +776,6 @@
testProperty([](RenderProperties& properties) {
properties.setClipToBounds(true);
properties.setClipBounds(Rect(10, 20, 300, 400));
- return RenderNode::GENERIC;
}, [](const RectOp& op, const BakedOpState& state) {
EXPECT_EQ(Rect(10, 20, 100, 100), state.computedState.clippedBounds)
<< "Clip rect should be intersection of node bounds and clip bounds";
@@ -782,7 +785,6 @@
TEST(OpReorderer, renderPropRevealClip) {
testProperty([](RenderProperties& properties) {
properties.mutableRevealClip().set(true, 50, 50, 25);
- return RenderNode::GENERIC;
}, [](const RectOp& op, const BakedOpState& state) {
ASSERT_NE(nullptr, state.roundRectClipState);
EXPECT_TRUE(state.roundRectClipState->highPriority);
@@ -795,7 +797,6 @@
testProperty([](RenderProperties& properties) {
properties.mutableOutline().setShouldClip(true);
properties.mutableOutline().setRoundRect(10, 20, 30, 40, 5.0f, 0.5f);
- return RenderNode::GENERIC;
}, [](const RectOp& op, const BakedOpState& state) {
ASSERT_NE(nullptr, state.roundRectClipState);
EXPECT_FALSE(state.roundRectClipState->highPriority);
@@ -819,9 +820,6 @@
properties.setTranslationY(20);
properties.setScaleX(0.5f);
properties.setScaleY(0.7f);
- return RenderNode::GENERIC
- | RenderNode::TRANSLATION_X | RenderNode::TRANSLATION_Y
- | RenderNode::SCALE_X | RenderNode::SCALE_Y;
}, [](const RectOp& op, const BakedOpState& state) {
Matrix4 matrix;
matrix.loadTranslate(10, 10, 0); // left, top
@@ -857,7 +855,7 @@
* (for efficiency, and to fit in layer size constraints) based on parent clip.
*/
void testSaveLayerAlphaClip(SaveLayerAlphaData* outObservedData,
- TestUtils::PropSetupCallback propSetupCallback) {
+ std::function<void(RenderProperties&)> propSetupCallback) {
class SaveLayerAlphaClipTestRenderer : public TestRendererBase {
public:
SaveLayerAlphaClipTestRenderer(SaveLayerAlphaData* outData)
@@ -887,17 +885,16 @@
ASSERT_GT(10000, DeviceInfo::get()->maxTextureSize())
<< "Node must be bigger than max texture size to exercise saveLayer codepath";
- auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 10000, 10000, [](RecordingCanvas& canvas) {
+ auto node = TestUtils::createNode(0, 0, 10000, 10000,
+ [&propSetupCallback](RenderProperties& properties, RecordingCanvas& canvas) {
+ properties.setHasOverlappingRendering(true);
+ properties.setAlpha(0.5f); // force saveLayer, since too big for HW layer
+ // apply other properties
+ propSetupCallback(properties);
+
SkPaint paint;
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 10000, 10000, paint);
- }, [&propSetupCallback](RenderProperties& properties) {
- properties.setHasOverlappingRendering(true);
- properties.setAlpha(0.5f); // force saveLayer, since too big for HW layer
-
- // apply other properties
- int flags = propSetupCallback(properties);
- return RenderNode::GENERIC | RenderNode::ALPHA | flags;
});
auto nodes = createSyncedNodeList(node); // sync before querying height
@@ -914,7 +911,6 @@
testSaveLayerAlphaClip(&observedData, [](RenderProperties& properties) {
properties.setTranslationX(10); // offset rendering content
properties.setTranslationY(-2000); // offset rendering content
- return RenderNode::TRANSLATION_X | RenderNode::TRANSLATION_Y;
});
EXPECT_EQ(190u, observedData.layerWidth);
EXPECT_EQ(200u, observedData.layerHeight);
@@ -937,9 +933,6 @@
properties.setPivotX(0);
properties.setPivotY(0);
properties.setRotation(45);
- return RenderNode::GENERIC
- | RenderNode::TRANSLATION_X | RenderNode::TRANSLATION_Y
- | RenderNode::ROTATION;
});
// ceil(sqrt(2) / 2 * 200) = 142
EXPECT_EQ(142u, observedData.layerWidth);
@@ -955,7 +948,6 @@
properties.setPivotY(0);
properties.setScaleX(2);
properties.setScaleY(0.5f);
- return RenderNode::GENERIC | RenderNode::SCALE_X | RenderNode::SCALE_Y;
});
EXPECT_EQ(100u, observedData.layerWidth);
EXPECT_EQ(400u, observedData.layerHeight);
diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
index 83b37ab..22190f5 100644
--- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp
+++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp
@@ -18,7 +18,7 @@
#include <RecordedOp.h>
#include <RecordingCanvas.h>
-#include <unit_tests/TestUtils.h>
+#include <utils/TestUtils.h>
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/unit_tests/PathParserTests.cpp b/libs/hwui/unit_tests/VectorDrawableTests.cpp
similarity index 79%
rename from libs/hwui/unit_tests/PathParserTests.cpp
rename to libs/hwui/unit_tests/VectorDrawableTests.cpp
index c99d7b0..77dd73a 100644
--- a/libs/hwui/unit_tests/PathParserTests.cpp
+++ b/libs/hwui/unit_tests/VectorDrawableTests.cpp
@@ -17,7 +17,8 @@
#include <gtest/gtest.h>
#include "PathParser.h"
-#include "VectorDrawablePath.h"
+#include "utils/MathUtils.h"
+#include "utils/VectorDrawableUtils.h"
#include <functional>
@@ -177,6 +178,10 @@
{"1-2e34567", false}
};
+static bool hasSameVerbs(const PathData& from, const PathData& to) {
+ return from.verbs == to.verbs && from.verbSizes == to.verbSizes;
+}
+
TEST(PathParser, parseStringForData) {
for (TestData testData: sTestDataSet) {
PathParser::ParseResult result;
@@ -197,12 +202,12 @@
}
}
-TEST(PathParser, createSkPathFromPathData) {
+TEST(VectorDrawableUtils, createSkPathFromPathData) {
for (TestData testData: sTestDataSet) {
SkPath expectedPath;
testData.skPathLamda(&expectedPath);
SkPath actualPath;
- VectorDrawablePath::verbsToPath(&actualPath, &testData.pathData);
+ VectorDrawableUtils::verbsToPath(&actualPath, testData.pathData);
EXPECT_EQ(expectedPath, actualPath);
}
}
@@ -230,5 +235,55 @@
}
}
+TEST(VectorDrawableUtils, morphPathData) {
+ for (TestData fromData: sTestDataSet) {
+ for (TestData toData: sTestDataSet) {
+ bool canMorph = VectorDrawableUtils::canMorph(fromData.pathData, toData.pathData);
+ if (fromData.pathData == toData.pathData) {
+ EXPECT_TRUE(canMorph);
+ } else {
+ bool expectedToMorph = hasSameVerbs(fromData.pathData, toData.pathData);
+ EXPECT_EQ(expectedToMorph, canMorph);
+ }
+ }
+ }
+}
+
+TEST(VectorDrawableUtils, interpolatePathData) {
+ // Interpolate path data with itself and every other path data
+ for (TestData fromData: sTestDataSet) {
+ for (TestData toData: sTestDataSet) {
+ PathData outData;
+ bool success = VectorDrawableUtils::interpolatePathData(&outData, fromData.pathData,
+ toData.pathData, 0.5);
+ bool expectedToMorph = hasSameVerbs(fromData.pathData, toData.pathData);
+ EXPECT_EQ(expectedToMorph, success);
+ }
+ }
+
+ float fractions[] = {0, 0.00001, 0.28, 0.5, 0.7777, 0.9999999, 1};
+ // Now try to interpolate with a slightly modified version of self and expect success
+ for (TestData fromData : sTestDataSet) {
+ PathData toPathData = fromData.pathData;
+ for (size_t i = 0; i < toPathData.points.size(); i++) {
+ toPathData.points[i]++;
+ }
+ const PathData& fromPathData = fromData.pathData;
+ PathData outData;
+ // Interpolate the two path data with different fractions
+ for (float fraction : fractions) {
+ bool success = VectorDrawableUtils::interpolatePathData(
+ &outData, fromPathData, toPathData, fraction);
+ EXPECT_TRUE(success);
+ for (size_t i = 0; i < outData.points.size(); i++) {
+ float expectedResult = fromPathData.points[i] * (1.0 - fraction) +
+ toPathData.points[i] * fraction;
+ EXPECT_TRUE(MathUtils::areEqual(expectedResult, outData.points[i]));
+ }
+ }
+ }
+}
+
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/utils/TestUtils.cpp b/libs/hwui/utils/TestUtils.cpp
new file mode 100644
index 0000000..84230a7
--- /dev/null
+++ b/libs/hwui/utils/TestUtils.cpp
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#include "TestUtils.h"
+
+namespace android {
+namespace uirenderer {
+
+SkColor TestUtils::interpolateColor(float fraction, SkColor start, SkColor end) {
+ int startA = (start >> 24) & 0xff;
+ int startR = (start >> 16) & 0xff;
+ int startG = (start >> 8) & 0xff;
+ int startB = start & 0xff;
+
+ int endA = (end >> 24) & 0xff;
+ int endR = (end >> 16) & 0xff;
+ int endG = (end >> 8) & 0xff;
+ int endB = end & 0xff;
+
+ return (int)((startA + (int)(fraction * (endA - startA))) << 24)
+ | (int)((startR + (int)(fraction * (endR - startR))) << 16)
+ | (int)((startG + (int)(fraction * (endG - startG))) << 8)
+ | (int)((startB + (int)(fraction * (endB - startB))));
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/unit_tests/TestUtils.h b/libs/hwui/utils/TestUtils.h
similarity index 76%
rename from libs/hwui/unit_tests/TestUtils.h
rename to libs/hwui/utils/TestUtils.h
index 38bafd5..f7f4f2d 100644
--- a/libs/hwui/unit_tests/TestUtils.h
+++ b/libs/hwui/utils/TestUtils.h
@@ -27,8 +27,10 @@
#if HWUI_NEW_OPS
#include <RecordedOp.h>
+#include <RecordingCanvas.h>
#else
#include <DisplayListOp.h>
+#include <DisplayListCanvas.h>
#endif
#include <memory>
@@ -36,6 +38,12 @@
namespace android {
namespace uirenderer {
+#if HWUI_NEW_OPS
+typedef RecordingCanvas TestCanvas;
+#else
+typedef DisplayListCanvas TestCanvas;
+#endif
+
#define EXPECT_MATRIX_APPROX_EQ(a, b) \
EXPECT_TRUE(TestUtils::matricesAreApproxEqual(a, b))
@@ -97,7 +105,8 @@
static SkBitmap createSkBitmap(int width, int height) {
SkBitmap bitmap;
- SkImageInfo info = SkImageInfo::MakeUnknown(width, height);
+ SkImageInfo info = SkImageInfo::Make(width, height,
+ kN32_SkColorType, kPremul_SkAlphaType);
bitmap.setInfo(info);
bitmap.allocPixels(info);
return bitmap;
@@ -111,18 +120,8 @@
return std::unique_ptr<DisplayList>(canvas.finishRecording());
}
- typedef std::function<int(RenderProperties&)> PropSetupCallback;
-
- static PropSetupCallback getHwLayerSetupCallback() {
- static PropSetupCallback sLayerSetupCallback = [] (RenderProperties& properties) {
- properties.mutateLayerProperties().setType(LayerType::RenderLayer);
- return RenderNode::GENERIC;
- };
- return sLayerSetupCallback;
- }
-
static sp<RenderNode> createNode(int left, int top, int right, int bottom,
- PropSetupCallback propSetupCallback = nullptr) {
+ std::function<void(RenderProperties& props, TestCanvas& canvas)> setup = nullptr) {
#if HWUI_NULL_GPU
// if RenderNodes are being sync'd/used, device info will be needed, since
// DeviceInfo::maxTextureSize() affects layer property
@@ -130,25 +129,39 @@
#endif
sp<RenderNode> node = new RenderNode();
- node->mutateStagingProperties().setLeftTopRightBottom(left, top, right, bottom);
- node->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
- if (propSetupCallback) {
- node->setPropertyFieldsDirty(propSetupCallback(node->mutateStagingProperties()));
+ RenderProperties& props = node->mutateStagingProperties();
+ props.setLeftTopRightBottom(left, top, right, bottom);
+ if (setup) {
+ TestCanvas canvas(props.getWidth(), props.getHeight());
+ setup(props, canvas);
+ node->setStagingDisplayList(canvas.finishRecording());
}
+ node->setPropertyFieldsDirty(0xFFFFFFFF);
return node;
}
- template<class CanvasType>
static sp<RenderNode> createNode(int left, int top, int right, int bottom,
- std::function<void(CanvasType& canvas)> canvasCallback,
- PropSetupCallback propSetupCallback = nullptr) {
- sp<RenderNode> node = createNode(left, top, right, bottom, propSetupCallback);
+ std::function<void(RenderProperties& props)> setup) {
+ return createNode(left, top, right, bottom,
+ [&setup](RenderProperties& props, TestCanvas& canvas) {
+ setup(props);
+ });
+ }
- auto&& props = node->stagingProperties(); // staging, since not sync'd yet
- CanvasType canvas(props.getWidth(), props.getHeight());
- canvasCallback(canvas);
- node->setStagingDisplayList(canvas.finishRecording());
- return node;
+ static sp<RenderNode> createNode(int left, int top, int right, int bottom,
+ std::function<void(TestCanvas& canvas)> setup) {
+ return createNode(left, top, right, bottom,
+ [&setup](RenderProperties& props, TestCanvas& canvas) {
+ setup(canvas);
+ });
+ }
+
+ static void recordNode(RenderNode& node,
+ std::function<void(TestCanvas&)> contentCallback) {
+ TestCanvas canvas(node.stagingProperties().getWidth(),
+ node.stagingProperties().getHeight());
+ contentCallback(canvas);
+ node.setStagingDisplayList(canvas.finishRecording());
}
static void syncHierarchyPropertiesAndDisplayList(sp<RenderNode>& node) {
@@ -180,6 +193,9 @@
TestTask task(rtCallback);
renderthread::RenderThread::getInstance().queueAndWait(&task);
}
+
+ static SkColor interpolateColor(float fraction, SkColor start, SkColor end);
+
private:
static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) {
node->syncProperties();
diff --git a/libs/hwui/utils/VectorDrawableUtils.cpp b/libs/hwui/utils/VectorDrawableUtils.cpp
new file mode 100644
index 0000000..ca75c59
--- /dev/null
+++ b/libs/hwui/utils/VectorDrawableUtils.cpp
@@ -0,0 +1,491 @@
+/*
+ * 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.
+ */
+
+#include "VectorDrawableUtils.h"
+
+#include "PathParser.h"
+
+#include <math.h>
+#include <utils/Log.h>
+
+namespace android {
+namespace uirenderer {
+
+class PathResolver {
+public:
+ float currentX = 0;
+ float currentY = 0;
+ float ctrlPointX = 0;
+ float ctrlPointY = 0;
+ float currentSegmentStartX = 0;
+ float currentSegmentStartY = 0;
+ void addCommand(SkPath* outPath, char previousCmd,
+ char cmd, const std::vector<float>* points, size_t start, size_t end);
+};
+
+bool VectorDrawableUtils::canMorph(const PathData& morphFrom, const PathData& morphTo) {
+ if (morphFrom.verbs.size() != morphTo.verbs.size()) {
+ return false;
+ }
+
+ for (unsigned int i = 0; i < morphFrom.verbs.size(); i++) {
+ if (morphFrom.verbs[i] != morphTo.verbs[i]
+ || morphFrom.verbSizes[i] != morphTo.verbSizes[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool VectorDrawableUtils::interpolatePathData(PathData* outData, const PathData& morphFrom,
+ const PathData& morphTo, float fraction) {
+ if (!canMorph(morphFrom, morphTo)) {
+ return false;
+ }
+ interpolatePaths(outData, morphFrom, morphTo, fraction);
+ return true;
+}
+
+ /**
+ * Convert an array of PathVerb to Path.
+ */
+void VectorDrawableUtils::verbsToPath(SkPath* outPath, const PathData& data) {
+ PathResolver resolver;
+ char previousCommand = 'm';
+ size_t start = 0;
+ outPath->reset();
+ for (unsigned int i = 0; i < data.verbs.size(); i++) {
+ size_t verbSize = data.verbSizes[i];
+ resolver.addCommand(outPath, previousCommand, data.verbs[i], &data.points, start,
+ start + verbSize);
+ previousCommand = data.verbs[i];
+ start += verbSize;
+ }
+}
+
+/**
+ * The current PathVerb will be interpolated between the
+ * <code>nodeFrom</code> and <code>nodeTo</code> according to the
+ * <code>fraction</code>.
+ *
+ * @param nodeFrom The start value as a PathVerb.
+ * @param nodeTo The end value as a PathVerb
+ * @param fraction The fraction to interpolate.
+ */
+void VectorDrawableUtils::interpolatePaths(PathData* outData,
+ const PathData& from, const PathData& to, float fraction) {
+ outData->points.resize(from.points.size());
+ outData->verbSizes = from.verbSizes;
+ outData->verbs = from.verbs;
+
+ for (size_t i = 0; i < from.points.size(); i++) {
+ outData->points[i] = from.points[i] * (1 - fraction) + to.points[i] * fraction;
+ }
+}
+
+/**
+ * Converts an arc to cubic Bezier segments and records them in p.
+ *
+ * @param p The target for the cubic Bezier segments
+ * @param cx The x coordinate center of the ellipse
+ * @param cy The y coordinate center of the ellipse
+ * @param a The radius of the ellipse in the horizontal direction
+ * @param b The radius of the ellipse in the vertical direction
+ * @param e1x E(eta1) x coordinate of the starting point of the arc
+ * @param e1y E(eta2) y coordinate of the starting point of the arc
+ * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
+ * @param start The start angle of the arc on the ellipse
+ * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+ */
+static void arcToBezier(SkPath* p,
+ double cx,
+ double cy,
+ double a,
+ double b,
+ double e1x,
+ double e1y,
+ double theta,
+ double start,
+ double sweep) {
+ // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
+ // and http://www.spaceroots.org/documents/ellipse/node22.html
+
+ // Maximum of 45 degrees per cubic Bezier segment
+ int numSegments = ceil(fabs(sweep * 4 / M_PI));
+
+ double eta1 = start;
+ double cosTheta = cos(theta);
+ double sinTheta = sin(theta);
+ double cosEta1 = cos(eta1);
+ double sinEta1 = sin(eta1);
+ double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+ double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+
+ double anglePerSegment = sweep / numSegments;
+ for (int i = 0; i < numSegments; i++) {
+ double eta2 = eta1 + anglePerSegment;
+ double sinEta2 = sin(eta2);
+ double cosEta2 = cos(eta2);
+ double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
+ double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
+ double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+ double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+ double tanDiff2 = tan((eta2 - eta1) / 2);
+ double alpha =
+ sin(eta2 - eta1) * (sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+ double q1x = e1x + alpha * ep1x;
+ double q1y = e1y + alpha * ep1y;
+ double q2x = e2x - alpha * ep2x;
+ double q2y = e2y - alpha * ep2y;
+
+ p->cubicTo((float) q1x,
+ (float) q1y,
+ (float) q2x,
+ (float) q2y,
+ (float) e2x,
+ (float) e2y);
+ eta1 = eta2;
+ e1x = e2x;
+ e1y = e2y;
+ ep1x = ep2x;
+ ep1y = ep2y;
+ }
+}
+
+inline double toRadians(float theta) { return theta * M_PI / 180;}
+
+static void drawArc(SkPath* p,
+ float x0,
+ float y0,
+ float x1,
+ float y1,
+ float a,
+ float b,
+ float theta,
+ bool isMoreThanHalf,
+ bool isPositiveArc) {
+
+ /* Convert rotation angle from degrees to radians */
+ double thetaD = toRadians(theta);
+ /* Pre-compute rotation matrix entries */
+ double cosTheta = cos(thetaD);
+ double sinTheta = sin(thetaD);
+ /* Transform (x0, y0) and (x1, y1) into unit space */
+ /* using (inverse) rotation, followed by (inverse) scale */
+ double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+ double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+ double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+ double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+
+ /* Compute differences and averages */
+ double dx = x0p - x1p;
+ double dy = y0p - y1p;
+ double xm = (x0p + x1p) / 2;
+ double ym = (y0p + y1p) / 2;
+ /* Solve for intersecting unit circles */
+ double dsq = dx * dx + dy * dy;
+ if (dsq == 0.0) {
+ ALOGW("Points are coincident");
+ return; /* Points are coincident */
+ }
+ double disc = 1.0 / dsq - 1.0 / 4.0;
+ if (disc < 0.0) {
+ ALOGW("Points are too far apart %f", dsq);
+ float adjust = (float) (sqrt(dsq) / 1.99999);
+ drawArc(p, x0, y0, x1, y1, a * adjust,
+ b * adjust, theta, isMoreThanHalf, isPositiveArc);
+ return; /* Points are too far apart */
+ }
+ double s = sqrt(disc);
+ double sdx = s * dx;
+ double sdy = s * dy;
+ double cx;
+ double cy;
+ if (isMoreThanHalf == isPositiveArc) {
+ cx = xm - sdy;
+ cy = ym + sdx;
+ } else {
+ cx = xm + sdy;
+ cy = ym - sdx;
+ }
+
+ double eta0 = atan2((y0p - cy), (x0p - cx));
+
+ double eta1 = atan2((y1p - cy), (x1p - cx));
+
+ double sweep = (eta1 - eta0);
+ if (isPositiveArc != (sweep >= 0)) {
+ if (sweep > 0) {
+ sweep -= 2 * M_PI;
+ } else {
+ sweep += 2 * M_PI;
+ }
+ }
+
+ cx *= a;
+ cy *= b;
+ double tcx = cx;
+ cx = cx * cosTheta - cy * sinTheta;
+ cy = tcx * sinTheta + cy * cosTheta;
+
+ arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+}
+
+
+
+// Use the given verb, and points in the range [start, end) to insert a command into the SkPath.
+void PathResolver::addCommand(SkPath* outPath, char previousCmd,
+ char cmd, const std::vector<float>* points, size_t start, size_t end) {
+
+ int incr = 2;
+ float reflectiveCtrlPointX;
+ float reflectiveCtrlPointY;
+
+ switch (cmd) {
+ case 'z':
+ case 'Z':
+ outPath->close();
+ // Path is closed here, but we need to move the pen to the
+ // closed position. So we cache the segment's starting position,
+ // and restore it here.
+ currentX = currentSegmentStartX;
+ currentY = currentSegmentStartY;
+ ctrlPointX = currentSegmentStartX;
+ ctrlPointY = currentSegmentStartY;
+ outPath->moveTo(currentX, currentY);
+ break;
+ case 'm':
+ case 'M':
+ case 'l':
+ case 'L':
+ case 't':
+ case 'T':
+ incr = 2;
+ break;
+ case 'h':
+ case 'H':
+ case 'v':
+ case 'V':
+ incr = 1;
+ break;
+ case 'c':
+ case 'C':
+ incr = 6;
+ break;
+ case 's':
+ case 'S':
+ case 'q':
+ case 'Q':
+ incr = 4;
+ break;
+ case 'a':
+ case 'A':
+ incr = 7;
+ break;
+ }
+
+ for (unsigned int k = start; k < end; k += incr) {
+ switch (cmd) {
+ case 'm': // moveto - Start a new sub-path (relative)
+ currentX += points->at(k + 0);
+ currentY += points->at(k + 1);
+ if (k > start) {
+ // According to the spec, if a moveto is followed by multiple
+ // pairs of coordinates, the subsequent pairs are treated as
+ // implicit lineto commands.
+ outPath->rLineTo(points->at(k + 0), points->at(k + 1));
+ } else {
+ outPath->rMoveTo(points->at(k + 0), points->at(k + 1));
+ currentSegmentStartX = currentX;
+ currentSegmentStartY = currentY;
+ }
+ break;
+ case 'M': // moveto - Start a new sub-path
+ currentX = points->at(k + 0);
+ currentY = points->at(k + 1);
+ if (k > start) {
+ // According to the spec, if a moveto is followed by multiple
+ // pairs of coordinates, the subsequent pairs are treated as
+ // implicit lineto commands.
+ outPath->lineTo(points->at(k + 0), points->at(k + 1));
+ } else {
+ outPath->moveTo(points->at(k + 0), points->at(k + 1));
+ currentSegmentStartX = currentX;
+ currentSegmentStartY = currentY;
+ }
+ break;
+ case 'l': // lineto - Draw a line from the current point (relative)
+ outPath->rLineTo(points->at(k + 0), points->at(k + 1));
+ currentX += points->at(k + 0);
+ currentY += points->at(k + 1);
+ break;
+ case 'L': // lineto - Draw a line from the current point
+ outPath->lineTo(points->at(k + 0), points->at(k + 1));
+ currentX = points->at(k + 0);
+ currentY = points->at(k + 1);
+ break;
+ case 'h': // horizontal lineto - Draws a horizontal line (relative)
+ outPath->rLineTo(points->at(k + 0), 0);
+ currentX += points->at(k + 0);
+ break;
+ case 'H': // horizontal lineto - Draws a horizontal line
+ outPath->lineTo(points->at(k + 0), currentY);
+ currentX = points->at(k + 0);
+ break;
+ case 'v': // vertical lineto - Draws a vertical line from the current point (r)
+ outPath->rLineTo(0, points->at(k + 0));
+ currentY += points->at(k + 0);
+ break;
+ case 'V': // vertical lineto - Draws a vertical line from the current point
+ outPath->lineTo(currentX, points->at(k + 0));
+ currentY = points->at(k + 0);
+ break;
+ case 'c': // curveto - Draws a cubic Bézier curve (relative)
+ outPath->rCubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3),
+ points->at(k + 4), points->at(k + 5));
+
+ ctrlPointX = currentX + points->at(k + 2);
+ ctrlPointY = currentY + points->at(k + 3);
+ currentX += points->at(k + 4);
+ currentY += points->at(k + 5);
+
+ break;
+ case 'C': // curveto - Draws a cubic Bézier curve
+ outPath->cubicTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3),
+ points->at(k + 4), points->at(k + 5));
+ currentX = points->at(k + 4);
+ currentY = points->at(k + 5);
+ ctrlPointX = points->at(k + 2);
+ ctrlPointY = points->at(k + 3);
+ break;
+ case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
+ reflectiveCtrlPointX = 0;
+ reflectiveCtrlPointY = 0;
+ if (previousCmd == 'c' || previousCmd == 's'
+ || previousCmd == 'C' || previousCmd == 'S') {
+ reflectiveCtrlPointX = currentX - ctrlPointX;
+ reflectiveCtrlPointY = currentY - ctrlPointY;
+ }
+ outPath->rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ points->at(k + 0), points->at(k + 1),
+ points->at(k + 2), points->at(k + 3));
+ ctrlPointX = currentX + points->at(k + 0);
+ ctrlPointY = currentY + points->at(k + 1);
+ currentX += points->at(k + 2);
+ currentY += points->at(k + 3);
+ break;
+ case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
+ reflectiveCtrlPointX = currentX;
+ reflectiveCtrlPointY = currentY;
+ if (previousCmd == 'c' || previousCmd == 's'
+ || previousCmd == 'C' || previousCmd == 'S') {
+ reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+ reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+ }
+ outPath->cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
+ ctrlPointX = points->at(k + 0);
+ ctrlPointY = points->at(k + 1);
+ currentX = points->at(k + 2);
+ currentY = points->at(k + 3);
+ break;
+ case 'q': // Draws a quadratic Bézier (relative)
+ outPath->rQuadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
+ ctrlPointX = currentX + points->at(k + 0);
+ ctrlPointY = currentY + points->at(k + 1);
+ currentX += points->at(k + 2);
+ currentY += points->at(k + 3);
+ break;
+ case 'Q': // Draws a quadratic Bézier
+ outPath->quadTo(points->at(k + 0), points->at(k + 1), points->at(k + 2), points->at(k + 3));
+ ctrlPointX = points->at(k + 0);
+ ctrlPointY = points->at(k + 1);
+ currentX = points->at(k + 2);
+ currentY = points->at(k + 3);
+ break;
+ case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
+ reflectiveCtrlPointX = 0;
+ reflectiveCtrlPointY = 0;
+ if (previousCmd == 'q' || previousCmd == 't'
+ || previousCmd == 'Q' || previousCmd == 'T') {
+ reflectiveCtrlPointX = currentX - ctrlPointX;
+ reflectiveCtrlPointY = currentY - ctrlPointY;
+ }
+ outPath->rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ points->at(k + 0), points->at(k + 1));
+ ctrlPointX = currentX + reflectiveCtrlPointX;
+ ctrlPointY = currentY + reflectiveCtrlPointY;
+ currentX += points->at(k + 0);
+ currentY += points->at(k + 1);
+ break;
+ case 'T': // Draws a quadratic Bézier curve (reflective control point)
+ reflectiveCtrlPointX = currentX;
+ reflectiveCtrlPointY = currentY;
+ if (previousCmd == 'q' || previousCmd == 't'
+ || previousCmd == 'Q' || previousCmd == 'T') {
+ reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
+ reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
+ }
+ outPath->quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
+ points->at(k + 0), points->at(k + 1));
+ ctrlPointX = reflectiveCtrlPointX;
+ ctrlPointY = reflectiveCtrlPointY;
+ currentX = points->at(k + 0);
+ currentY = points->at(k + 1);
+ break;
+ case 'a': // Draws an elliptical arc
+ // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
+ drawArc(outPath,
+ currentX,
+ currentY,
+ points->at(k + 5) + currentX,
+ points->at(k + 6) + currentY,
+ points->at(k + 0),
+ points->at(k + 1),
+ points->at(k + 2),
+ points->at(k + 3) != 0,
+ points->at(k + 4) != 0);
+ currentX += points->at(k + 5);
+ currentY += points->at(k + 6);
+ ctrlPointX = currentX;
+ ctrlPointY = currentY;
+ break;
+ case 'A': // Draws an elliptical arc
+ drawArc(outPath,
+ currentX,
+ currentY,
+ points->at(k + 5),
+ points->at(k + 6),
+ points->at(k + 0),
+ points->at(k + 1),
+ points->at(k + 2),
+ points->at(k + 3) != 0,
+ points->at(k + 4) != 0);
+ currentX = points->at(k + 5);
+ currentY = points->at(k + 6);
+ ctrlPointX = currentX;
+ ctrlPointY = currentY;
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Unsupported command: %c", cmd);
+ break;
+ }
+ previousCmd = cmd;
+ }
+}
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/utils/VectorDrawableUtils.h b/libs/hwui/utils/VectorDrawableUtils.h
new file mode 100644
index 0000000..21c1cdc
--- /dev/null
+++ b/libs/hwui/utils/VectorDrawableUtils.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HWUI_VECTORDRAWABLE_UTILS_H
+#define ANDROID_HWUI_VECTORDRAWABLE_UTILS_H
+
+#include "VectorDrawablePath.h"
+
+#include <cutils/compiler.h>
+#include "SkPath.h"
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+class VectorDrawableUtils {
+public:
+ ANDROID_API static bool canMorph(const PathData& morphFrom, const PathData& morphTo);
+ ANDROID_API static bool interpolatePathData(PathData* outData, const PathData& morphFrom,
+ const PathData& morphTo, float fraction);
+ ANDROID_API static void verbsToPath(SkPath* outPath, const PathData& data);
+ static void interpolatePaths(PathData* outPathData, const PathData& from, const PathData& to,
+ float fraction);
+};
+} // namespace uirenderer
+} // namespace android
+#endif /* ANDROID_HWUI_VECTORDRAWABLE_UTILS_H*/
diff --git a/packages/DocumentsUI/res/drawable/item_root_background.xml b/packages/DocumentsUI/res/drawable/item_root_background.xml
deleted file mode 100644
index c403159..0000000
--- a/packages/DocumentsUI/res/drawable/item_root_background.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_focused="true" android:state_activated="true">
- <color android:color="@color/material_grey_300" />
- </item>
- <item android:state_focused="false" android:state_activated="true">
- <color android:color="@color/material_grey_300" />
- </item>
-</selector>
diff --git a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
index 381e1c89..fe06eaf 100644
--- a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
+++ b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
@@ -17,7 +17,7 @@
<com.android.documentsui.ListItem xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/item_doc_list_background"
+ android:background="@color/item_doc_background"
android:orientation="horizontal"
android:focusable="true">
diff --git a/packages/DocumentsUI/res/layout/drawer_layout.xml b/packages/DocumentsUI/res/layout/drawer_layout.xml
index 0dac0d5..0146f14 100644
--- a/packages/DocumentsUI/res/layout/drawer_layout.xml
+++ b/packages/DocumentsUI/res/layout/drawer_layout.xml
@@ -61,7 +61,7 @@
android:layout_gravity="start"
android:orientation="vertical"
android:elevation="16dp"
- android:background="@*android:color/white">
+ android:background="@color/window_background">
<Toolbar
android:id="@+id/roots_toolbar"
diff --git a/packages/DocumentsUI/res/layout/fixed_layout.xml b/packages/DocumentsUI/res/layout/fixed_layout.xml
index 403c667..3135977 100644
--- a/packages/DocumentsUI/res/layout/fixed_layout.xml
+++ b/packages/DocumentsUI/res/layout/fixed_layout.xml
@@ -50,9 +50,7 @@
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal"
- android:baselineAligned="false"
- android:divider="?android:attr/dividerVertical"
- android:showDividers="middle">
+ android:baselineAligned="false">
<FrameLayout
android:id="@+id/container_roots"
@@ -62,8 +60,7 @@
<include layout="@layout/directory_cluster"
android:layout_width="0dp"
android:layout_weight="1"
- android:elevation="8dp"
- android:background="@color/material_grey_50" />
+ android:elevation="8dp" />
</LinearLayout>
diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml
index ada7f49..f9bbccb 100644
--- a/packages/DocumentsUI/res/layout/fragment_directory.xml
+++ b/packages/DocumentsUI/res/layout/fragment_directory.xml
@@ -17,7 +17,6 @@
<com.android.documentsui.DirectoryView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/material_grey_50"
android:orientation="vertical"
android:animateLayoutChanges="true">
@@ -78,8 +77,7 @@
android:paddingBottom="0dp"
android:clipToPadding="false"
android:scrollbarStyle="outsideOverlay"
- android:drawSelectorOnTop="true"
- android:background="@color/directory_background" />
+ android:drawSelectorOnTop="true" />
</FrameLayout>
diff --git a/packages/DocumentsUI/res/layout/item_doc_grid.xml b/packages/DocumentsUI/res/layout/item_doc_grid.xml
index 1dfb34a..dcd5cfd 100644
--- a/packages/DocumentsUI/res/layout/item_doc_grid.xml
+++ b/packages/DocumentsUI/res/layout/item_doc_grid.xml
@@ -18,101 +18,101 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/grid_item_margin"
- android:background="@color/item_doc_grid_background"
+ android:background="@color/item_doc_background"
android:focusable="true">
<!-- Main item thumbnail. Comprised of two overlapping images, the
visibility of which is controlled by code in
DirectoryFragment.java. -->
+
<FrameLayout
android:id="@+id/thumbnail"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingBottom="8dp">
-
+ android:layout_height="wrap_content">
+
<com.android.documentsui.GridItemThumbnail
android:id="@+id/icon_thumb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:contentDescription="@null" />
-
- <ImageView
+
+ <com.android.documentsui.GridItemThumbnail
android:id="@+id/icon_mime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="centerInside"
android:contentDescription="@null" />
-
+
</FrameLayout>
-
+
<!-- Item nameplate. Has a mime-type icon and some text fields (title,
size, mod-time, etc). -->
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/thumbnail"
- android:layout_toEndOf="@android:id/icon1"
- android:singleLine="true"
- android:ellipsize="middle"
- android:textAlignment="viewStart"
- android:paddingEnd="12dp"
- android:textAppearance="@android:style/TextAppearance.Material.Subhead"
- android:textColor="@*android:color/primary_text_default_material_light" />
- <TextView
- android:id="@+id/size"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@android:id/title"
- android:layout_toEndOf="@android:id/icon1"
- android:paddingEnd="4dp"
- android:singleLine="true"
- android:ellipsize="end"
- android:textAlignment="viewStart"
- android:textAppearance="@android:style/TextAppearance.Material.Caption"
- android:textColor="@*android:color/primary_text_default_material_light" />
-
- <TextView
- android:id="@+id/date"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@android:id/title"
- android:layout_toEndOf="@id/size"
- android:paddingEnd="12dp"
- android:singleLine="true"
- android:ellipsize="end"
- android:textAlignment="viewStart"
- android:textAppearance="@android:style/TextAppearance.Material.Caption"
- android:textColor="@*android:color/primary_text_default_material_light" />
-
- <ImageView
- android:id="@android:id/icon1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:layout_below="@id/thumbnail"
- android:layout_alignParentLeft="true"
- android:layout_alignBottom="@id/size"
- android:layout_alignTop="@android:id/title"
- android:scaleType="centerInside"
- android:contentDescription="@null"
- android:paddingStart="12dp"
- android:paddingEnd="8dp"/>
-
- <!-- Use an explicit spacer so we can align things to it. -->
- <Space
- android:id="@+id/bottomPadding"
+ <RelativeLayout
+ android:id="@+id/nameplate"
android:layout_width="match_parent"
- android:layout_height="8dp"
- android:layout_below="@id/size" />
+ android:layout_height="wrap_content"
+ android:layout_below="@id/thumbnail"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:paddingLeft="12dp"
+ android:paddingRight="12dp">
+
+ <ImageView
+ android:id="@android:id/icon1"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="8dp"
+ android:layout_alignParentStart="true"
+ android:layout_centerVertical="true"
+ android:scaleType="centerInside"
+ android:contentDescription="@null"/>
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_toEndOf="@android:id/icon1"
+ android:singleLine="true"
+ android:ellipsize="middle"
+ android:textAlignment="viewStart"
+ android:textAppearance="@android:style/TextAppearance.Material.Subhead"
+ android:textColor="@*android:color/primary_text_default_material_light" />
+
+ <TextView
+ android:id="@+id/size"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toEndOf="@android:id/icon1"
+ android:layout_below="@android:id/title"
+ android:layout_marginEnd="4dp"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textAlignment="viewStart"
+ android:textAppearance="@android:style/TextAppearance.Material.Caption"
+ android:textColor="@*android:color/primary_text_default_material_light" />
+
+ <TextView
+ android:id="@+id/date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_toEndOf="@id/size"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textAlignment="viewStart"
+ android:textAppearance="@android:style/TextAppearance.Material.Caption"
+ android:textColor="@*android:color/primary_text_default_material_light" />
+
+ </RelativeLayout>
<!-- An overlay that draws the item border when it is focused. -->
<View
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignBottom="@id/bottomPadding"
+ android:layout_alignBottom="@id/nameplate"
android:layout_alignTop="@id/thumbnail"
android:layout_alignLeft="@id/thumbnail"
android:layout_alignRight="@id/thumbnail"
diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml
index c409166..e068423 100644
--- a/packages/DocumentsUI/res/layout/item_doc_list.xml
+++ b/packages/DocumentsUI/res/layout/item_doc_list.xml
@@ -17,10 +17,10 @@
<com.android.documentsui.ListItem xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/item_doc_list_background"
+ android:background="@color/item_doc_background"
android:orientation="horizontal"
android:focusable="true">
-
+
<View
android:id="@+id/focus_indicator"
android:layout_width="4dp"
diff --git a/packages/DocumentsUI/res/layout/item_root.xml b/packages/DocumentsUI/res/layout/item_root.xml
index 90b1ff6..ff80d07 100644
--- a/packages/DocumentsUI/res/layout/item_root.xml
+++ b/packages/DocumentsUI/res/layout/item_root.xml
@@ -22,8 +22,7 @@
android:paddingEnd="@dimen/list_item_padding"
android:gravity="center_vertical"
android:orientation="horizontal"
- android:baselineAligned="false"
- android:background="@drawable/item_root_background">
+ android:baselineAligned="false">
<FrameLayout
android:layout_width="@dimen/icon_size"
diff --git a/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml
index 68c8b65..153c673 100644
--- a/packages/DocumentsUI/res/values/colors.xml
+++ b/packages/DocumentsUI/res/values/colors.xml
@@ -17,14 +17,20 @@
<resources>
<color name="material_grey_400">#ffbdbdbd</color>
+ <!-- This is the window background, but also the background for anything
+ else that needs to manually declare a background matching the "default"
+ app background (e.g. the drawer overlay). -->
+ <color name="window_background">#fff1f1f1</color>
+
<color name="primary_dark">@*android:color/primary_dark_material_dark</color>
<color name="primary">@*android:color/material_blue_grey_900</color>
<color name="accent">@*android:color/accent_material_light</color>
<color name="action_mode">@color/material_grey_400</color>
-
- <color name="directory_background">@*android:color/material_grey_300</color>
- <color name="item_doc_grid_background">@android:color/white</color>
- <color name="item_doc_grid_protect_background">@android:color/white</color>
+
<color name="band_select_background">#88ffffff</color>
<color name="band_select_border">#44000000</color>
+
+ <color name="item_doc_background">#fffafafa</color>
+ <color name="item_doc_background_selected">#ffe0f2f1</color>
+
</resources>
diff --git a/packages/DocumentsUI/res/values/styles.xml b/packages/DocumentsUI/res/values/styles.xml
index 15d17cc..6712e2d 100644
--- a/packages/DocumentsUI/res/values/styles.xml
+++ b/packages/DocumentsUI/res/values/styles.xml
@@ -25,6 +25,7 @@
<item name="actionBarTheme">@style/ActionBarTheme</item>
<item name="actionBarPopupTheme">@style/ActionBarPopupTheme</item>
+ <item name="android:windowBackground">@color/window_background</item>
<item name="android:colorPrimaryDark">@color/primary_dark</item>
<item name="android:colorPrimary">@color/primary</item>
<item name="android:colorAccent">@color/accent</item>
@@ -44,6 +45,7 @@
<item name="actionBarTheme">@style/ActionBarTheme</item>
<item name="actionBarPopupTheme">@style/ActionBarPopupTheme</item>
+ <item name="android:windowBackground">@color/window_background</item>
<item name="android:colorPrimaryDark">@color/primary_dark</item>
<item name="android:colorPrimary">@color/primary</item>
<item name="android:colorAccent">@color/accent</item>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 0ee970d..91ac033 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -358,18 +358,6 @@
return mState;
}
- public static abstract class DocumentsIntent {
- /** Intent action name to open copy destination. */
- public static String ACTION_OPEN_COPY_DESTINATION =
- "com.android.documentsui.OPEN_COPY_DESTINATION";
-
- /**
- * Extra boolean flag for ACTION_OPEN_COPY_DESTINATION_STRING, which
- * specifies if the destination directory needs to create new directory or not.
- */
- public static String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY";
- }
-
void setDisplayAdvancedDevices(boolean display) {
LocalPreferences.setDisplayAdvancedDevices(this, display);
mState.showAdvanced = mState.forceAdvanced | display;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index 55a123f..55e2f44 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -179,8 +179,7 @@
if (mFailedFiles.size() > 0) {
Log.e(TAG, mFailedFiles.size() + " files failed to copy");
final Context context = getApplicationContext();
- final Intent navigateIntent = new Intent(context, FilesActivity.class);
- navigateIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
+ final Intent navigateIntent = buildNavigateIntent(context, stack);
navigateIntent.putExtra(EXTRA_FAILURE, FAILURE_COPY);
navigateIntent.putExtra(EXTRA_TRANSFER_MODE, transferMode);
navigateIntent.putParcelableArrayListExtra(EXTRA_SRC_LIST, mFailedFiles);
@@ -228,8 +227,7 @@
mIsCancelled = false;
final Context context = getApplicationContext();
- final Intent navigateIntent = new Intent(context, FilesActivity.class);
- navigateIntent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
+ final Intent navigateIntent = buildNavigateIntent(context, stack);
final String contentTitle = getString(copying ? R.string.copy_notification_title
: R.string.move_notification_title);
@@ -592,4 +590,14 @@
}
}
}
+
+ /**
+ * Creates an intent for navigating back to the destination directory.
+ */
+ private Intent buildNavigateIntent(Context context, DocumentStack stack) {
+ Intent intent = new Intent(context, FilesActivity.class);
+ intent.setAction(DocumentsContract.ACTION_BROWSE);
+ intent.putExtra(Shared.EXTRA_STACK, (Parcelable) stack);
+ return intent;
+ }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 13c481c..e965050 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -19,7 +19,7 @@
import static com.android.documentsui.State.ACTION_CREATE;
import static com.android.documentsui.State.ACTION_GET_CONTENT;
import static com.android.documentsui.State.ACTION_OPEN;
-import static com.android.documentsui.State.ACTION_OPEN_COPY_DESTINATION;
+import static com.android.documentsui.State.ACTION_PICK_COPY_DESTINATION;
import static com.android.documentsui.State.ACTION_OPEN_TREE;
import static com.android.documentsui.dirlist.DirectoryFragment.ANIM_NONE;
@@ -123,7 +123,7 @@
final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE);
SaveFragment.show(getFragmentManager(), mimeType, title);
} else if (mState.action == ACTION_OPEN_TREE ||
- mState.action == ACTION_OPEN_COPY_DESTINATION) {
+ mState.action == ACTION_PICK_COPY_DESTINATION) {
PickFragment.show(getFragmentManager());
}
@@ -135,7 +135,7 @@
} else if (mState.action == ACTION_OPEN ||
mState.action == ACTION_CREATE ||
mState.action == ACTION_OPEN_TREE ||
- mState.action == ACTION_OPEN_COPY_DESTINATION) {
+ mState.action == ACTION_PICK_COPY_DESTINATION) {
RootsFragment.show(getFragmentManager(), null);
}
@@ -163,8 +163,8 @@
state.action = ACTION_GET_CONTENT;
} else if (Intent.ACTION_OPEN_DOCUMENT_TREE.equals(action)) {
state.action = ACTION_OPEN_TREE;
- } else if (DocumentsIntent.ACTION_OPEN_COPY_DESTINATION.equals(action)) {
- state.action = ACTION_OPEN_COPY_DESTINATION;
+ } else if (Shared.ACTION_PICK_COPY_DESTINATION.equals(action)) {
+ state.action = ACTION_PICK_COPY_DESTINATION;
}
if (state.action == ACTION_OPEN || state.action == ACTION_GET_CONTENT) {
@@ -172,9 +172,9 @@
Intent.EXTRA_ALLOW_MULTIPLE, false);
}
- if (state.action == ACTION_OPEN_COPY_DESTINATION) {
+ if (state.action == ACTION_PICK_COPY_DESTINATION) {
state.directoryCopy = intent.getBooleanExtra(
- BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, false);
+ Shared.EXTRA_DIRECTORY_COPY, false);
state.transferMode = intent.getIntExtra(CopyService.EXTRA_TRANSFER_MODE,
CopyService.TRANSFER_MODE_COPY);
}
@@ -257,7 +257,7 @@
mState.action == ACTION_OPEN_TREE) {
mRootsToolbar.setTitle(R.string.title_open);
} else if (mState.action == ACTION_CREATE ||
- mState.action == ACTION_OPEN_COPY_DESTINATION) {
+ mState.action == ACTION_PICK_COPY_DESTINATION) {
mRootsToolbar.setTitle(R.string.title_save);
}
}
@@ -324,7 +324,7 @@
boolean recents = cwd == null;
boolean picking = mState.action == ACTION_CREATE
|| mState.action == ACTION_OPEN_TREE
- || mState.action == ACTION_OPEN_COPY_DESTINATION;
+ || mState.action == ACTION_PICK_COPY_DESTINATION;
createDir.setVisible(picking && !recents && cwd.isCreateSupported());
mSearchManager.showMenu(!picking);
@@ -361,7 +361,7 @@
// No directory means recents
if (mState.action == ACTION_CREATE ||
mState.action == ACTION_OPEN_TREE ||
- mState.action == ACTION_OPEN_COPY_DESTINATION) {
+ mState.action == ACTION_PICK_COPY_DESTINATION) {
RecentsCreateFragment.show(fm);
} else {
DirectoryFragment.showRecentsOpen(fm, anim);
@@ -391,7 +391,7 @@
}
if (mState.action == ACTION_OPEN_TREE ||
- mState.action == ACTION_OPEN_COPY_DESTINATION) {
+ mState.action == ACTION_PICK_COPY_DESTINATION) {
final PickFragment pick = PickFragment.get(fm);
if (pick != null) {
pick.setPickTarget(mState.action, mState.transferMode, cwd);
@@ -444,7 +444,7 @@
if (mState.action == ACTION_OPEN_TREE) {
result = DocumentsContract.buildTreeDocumentUri(
pickTarget.authority, pickTarget.documentId);
- } else if (mState.action == ACTION_OPEN_COPY_DESTINATION) {
+ } else if (mState.action == ACTION_PICK_COPY_DESTINATION) {
result = pickTarget.derivedUri;
} else {
// Should not be reached.
@@ -461,7 +461,7 @@
final byte[] rawStack = DurableUtils.writeToArrayOrNull(mState.stack);
if (mState.action == ACTION_CREATE ||
mState.action == ACTION_OPEN_TREE ||
- mState.action == ACTION_OPEN_COPY_DESTINATION) {
+ mState.action == ACTION_PICK_COPY_DESTINATION) {
// Remember stack for last create
values.clear();
values.put(RecentColumns.KEY, mState.stack.buildKey());
@@ -500,7 +500,7 @@
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
- } else if (mState.action == ACTION_OPEN_COPY_DESTINATION) {
+ } else if (mState.action == ACTION_PICK_COPY_DESTINATION) {
// Picking a copy destination is only used internally by us, so we
// don't need to extend permissions to the caller.
intent.putExtra(Shared.EXTRA_STACK, (Parcelable) mState.stack);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
index 48e28dc..bbf4682 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/PickFragment.java
@@ -110,7 +110,7 @@
mPick.setText(R.string.button_select);
mCancel.setVisibility(View.GONE);
break;
- case State.ACTION_OPEN_COPY_DESTINATION:
+ case State.ACTION_PICK_COPY_DESTINATION:
mPick.setText(R.string.button_copy);
mCancel.setVisibility(View.VISIBLE);
break;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index 4fc3788..72ee6cbab 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -360,7 +360,7 @@
// Exclude read-only devices when creating
if (state.action == State.ACTION_CREATE && !supportsCreate) continue;
- if (state.action == State.ACTION_OPEN_COPY_DESTINATION && !supportsCreate) continue;
+ if (state.action == State.ACTION_PICK_COPY_DESTINATION && !supportsCreate) continue;
// Exclude roots that don't support directory picking
if (state.action == State.ACTION_OPEN_TREE && !supportsIsChild) continue;
// Exclude advanced devices when not requested
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Shared.java b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
index a4d6dc5..570c9bf 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Shared.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
@@ -20,6 +20,16 @@
/** @hide */
public final class Shared {
+ /** Intent action name to pick a copy destination. */
+ public static final String ACTION_PICK_COPY_DESTINATION =
+ "com.android.documentsui.PICK_COPY_DESTINATION";
+
+ /**
+ * Extra boolean flag for {@link ACTION_PICK_COPY_DESTINATION}, which
+ * specifies if the destination directory needs to create new directory or not.
+ */
+ public static final String EXTRA_DIRECTORY_COPY = "com.android.documentsui.DIRECTORY_COPY";
+
public static final boolean DEBUG = true;
public static final String TAG = "Documents";
public static final String EXTRA_STACK = "com.android.documentsui.STACK";
diff --git a/packages/DocumentsUI/src/com/android/documentsui/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java
index 4306a0e..49a1e66 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/State.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -75,7 +75,7 @@
public static final int ACTION_OPEN_TREE = 4;
public static final int ACTION_MANAGE = 5;
public static final int ACTION_BROWSE = 6;
- public static final int ACTION_OPEN_COPY_DESTINATION = 8;
+ public static final int ACTION_PICK_COPY_DESTINATION = 8;
public static final int MODE_UNKNOWN = 0;
public static final int MODE_LIST = 1;
@@ -150,4 +150,4 @@
return new State[size];
}
};
-}
\ No newline at end of file
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 21420c8..8b3893f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -111,8 +111,8 @@
import com.android.documentsui.State;
import com.android.documentsui.ThumbnailCache;
import com.android.documentsui.BaseActivity.DocumentContext;
-import com.android.documentsui.BaseActivity.DocumentsIntent;
import com.android.documentsui.ProviderExecutor.Preemptable;
+import com.android.documentsui.Shared;
import com.android.documentsui.RecentsProvider.StateColumns;
import com.android.documentsui.dirlist.MultiSelectManager.Callback;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
@@ -897,7 +897,7 @@
// Pop up a dialog to pick a destination. This is inadequate but works for now.
// TODO: Implement a picker that is to spec.
final Intent intent = new Intent(
- BaseActivity.DocumentsIntent.ACTION_OPEN_COPY_DESTINATION,
+ Shared.ACTION_PICK_COPY_DESTINATION,
Uri.EMPTY,
getActivity(),
DocumentsActivity.class);
@@ -914,7 +914,7 @@
break;
}
}
- intent.putExtra(BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, directoryCopy);
+ intent.putExtra(Shared.EXTRA_DIRECTORY_COPY, directoryCopy);
intent.putExtra(CopyService.EXTRA_TRANSFER_MODE, mode);
startActivityForResult(intent, REQUEST_COPY_DESTINATION);
}
@@ -943,7 +943,6 @@
public void setSelected(boolean selected) {
itemView.setActivated(selected);
- itemView.setBackgroundColor(selected ? mSelectedItemColor : mDefaultItemColor);
}
@Override
@@ -1080,8 +1079,6 @@
holder.setSelected(isSelected(position));
- final View line2 = itemView.findViewById(R.id.line2);
-
final ImageView iconMime = (ImageView) itemView.findViewById(R.id.icon_mime);
final ImageView iconThumb = (ImageView) itemView.findViewById(R.id.icon_thumb);
final TextView title = (TextView) itemView.findViewById(android.R.id.title);
@@ -1138,14 +1135,11 @@
getDocumentIcon(mContext, docAuthority, docId, docMimeType, docIcon, state));
}
- boolean hasLine2 = false;
-
- final boolean hideTitle = (state.derivedMode == MODE_GRID) && mHideGridTitles;
- if (!hideTitle) {
+ if ((state.derivedMode == MODE_GRID) && mHideGridTitles) {
+ title.setVisibility(View.GONE);
+ } else {
title.setText(docDisplayName);
title.setVisibility(View.VISIBLE);
- } else {
- title.setVisibility(View.GONE);
}
Drawable iconDrawable = null;
@@ -1161,7 +1155,6 @@
if (alwaysShowSummary) {
summary.setText(root.getDirectoryString());
summary.setVisibility(View.VISIBLE);
- hasLine2 = true;
} else {
if (iconDrawable != null && roots.isIconUniqueBlocking(root)) {
// No summary needed if icon speaks for itself
@@ -1170,7 +1163,6 @@
summary.setText(root.getDirectoryString());
summary.setVisibility(View.VISIBLE);
summary.setTextAlignment(TextView.TEXT_ALIGNMENT_TEXT_END);
- hasLine2 = true;
}
}
}
@@ -1187,48 +1179,37 @@
if (docSummary != null) {
summary.setText(docSummary);
summary.setVisibility(View.VISIBLE);
- hasLine2 = true;
} else {
summary.setVisibility(View.INVISIBLE);
}
}
}
- if (icon1 != null) icon1.setVisibility(View.GONE);
-
if (iconDrawable != null) {
icon1.setVisibility(View.VISIBLE);
icon1.setImageDrawable(iconDrawable);
+ } else {
+ icon1.setVisibility(View.GONE);
}
if (docLastModified == -1) {
date.setText(null);
} else {
date.setText(formatTime(mContext, docLastModified));
- hasLine2 = true;
}
- if (state.showSize) {
- size.setVisibility(View.VISIBLE);
- if (Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) {
- size.setText(null);
- } else {
- size.setText(Formatter.formatFileSize(mContext, docSize));
- hasLine2 = true;
- }
- } else {
+ if (!state.showSize || Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) {
size.setVisibility(View.GONE);
- }
-
- if (line2 != null) {
- line2.setVisibility(hasLine2 ? View.VISIBLE : View.GONE);
+ } else {
+ size.setVisibility(View.VISIBLE);
+ size.setText(Formatter.formatFileSize(mContext, docSize));
}
setEnabledRecursive(itemView, enabled);
iconMime.setAlpha(iconAlpha);
iconThumb.setAlpha(iconAlpha);
- if (icon1 != null) icon1.setAlpha(iconAlpha);
+ icon1.setAlpha(iconAlpha);
if (DEBUG_ENABLE_DND) {
setupDragAndDropOnDocumentView(itemView, cursor);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java
index 0963845..1135c21 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryItemAnimator.java
@@ -25,6 +25,8 @@
import android.support.v7.widget.RecyclerView;
import android.util.TypedValue;
+import com.android.documentsui.R;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -43,12 +45,8 @@
private final Integer mSelectedColor;
public DirectoryItemAnimator(Context context) {
- mDefaultColor = context.getResources().getColor(android.R.color.transparent);
- // Get the accent color.
- TypedValue selColor = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.colorAccent, selColor, true);
- // Set the opacity to 10%.
- mSelectedColor = (selColor.data & 0x00ffffff) | 0x16000000;
+ mDefaultColor = context.getResources().getColor(R.color.item_doc_background);
+ mSelectedColor = context.getResources().getColor(R.color.item_doc_background_selected);
}
@Override
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index f0f8161..7c0676f 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -31,6 +31,7 @@
import android.provider.DocumentsProvider;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.io.FileNotFoundException;
@@ -59,6 +60,7 @@
private MtpManager mMtpManager;
private ContentResolver mResolver;
+ @GuardedBy("mDeviceToolkits")
private Map<Integer, DeviceToolkit> mDeviceToolkits;
private RootScanner mRootScanner;
private Resources mResources;
@@ -222,9 +224,11 @@
@Override
public void onTrimMemory(int level) {
- for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
- toolkit.mDocumentLoader.clearCompletedTasks();
- }
+ synchronized (mDeviceToolkits) {
+ for (final DeviceToolkit toolkit : mDeviceToolkits.values()) {
+ toolkit.mDocumentLoader.clearCompletedTasks();
+ }
+ }
}
@Override
@@ -254,21 +258,28 @@
}
void openDevice(int deviceId) throws IOException {
- mMtpManager.openDevice(deviceId);
- mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
- mRootScanner.scanNow();
+ synchronized (mDeviceToolkits) {
+ mMtpManager.openDevice(deviceId);
+ mDeviceToolkits.put(deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase));
+ }
+ mRootScanner.resume();
}
- void closeDevice(int deviceId) throws IOException {
+ void closeDevice(int deviceId) throws IOException, InterruptedException {
// TODO: Flush the device before closing (if not closed externally).
- getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
- mDeviceToolkits.remove(deviceId);
- mDatabase.removeDeviceRows(deviceId);
- mMtpManager.closeDevice(deviceId);
+ synchronized (mDeviceToolkits) {
+ getDeviceToolkit(deviceId).mDocumentLoader.clearTasks();
+ mDeviceToolkits.remove(deviceId);
+ mDatabase.removeDeviceRows(deviceId);
+ mMtpManager.closeDevice(deviceId);
+ }
mRootScanner.notifyChange();
+ if (!hasOpenedDevices()) {
+ mRootScanner.pause();
+ }
}
- void close() throws InterruptedException {
+ synchronized void closeAllDevices() throws InterruptedException {
boolean closed = false;
for (int deviceId : mMtpManager.getOpenedDeviceIds()) {
try {
@@ -282,15 +293,30 @@
}
if (closed) {
mRootScanner.notifyChange();
+ mRootScanner.pause();
}
- mRootScanner.close();
- mDatabase.close();
}
boolean hasOpenedDevices() {
return mMtpManager.getOpenedDeviceIds().length != 0;
}
+ /**
+ * Finalize the content provider for unit tests.
+ */
+ @Override
+ public void shutdown() {
+ try {
+ closeAllDevices();
+ } catch (InterruptedException e) {
+ // It should fail unit tests by throwing runtime exception.
+ throw new RuntimeException(e.getMessage());
+ } finally {
+ mDatabase.close();
+ super.shutdown();
+ }
+ }
+
private void notifyChildDocumentsChange(String parentDocumentId) {
mResolver.notifyChange(
DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId),
@@ -299,11 +325,13 @@
}
private DeviceToolkit getDeviceToolkit(int deviceId) throws FileNotFoundException {
- final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
- if (toolkit == null) {
- throw new FileNotFoundException();
+ synchronized (mDeviceToolkits) {
+ final DeviceToolkit toolkit = mDeviceToolkits.get(deviceId);
+ if (toolkit == null) {
+ throw new FileNotFoundException();
+ }
+ return toolkit;
}
- return toolkit;
}
private PipeManager getPipeManager(Identifier identifier) throws FileNotFoundException {
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
index 2d1af26..723dc14 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java
@@ -67,10 +67,10 @@
provider.openDevice(device.getDeviceId());
return START_STICKY;
} catch (IOException error) {
- Log.d(MtpDocumentsProvider.TAG, error.getMessage());
+ Log.e(MtpDocumentsProvider.TAG, error.getMessage());
}
} else {
- Log.d(MtpDocumentsProvider.TAG, "Received unknown intent action.");
+ Log.w(MtpDocumentsProvider.TAG, "Received unknown intent action.");
}
stopSelfIfNeeded();
return Service.START_NOT_STICKY;
@@ -82,7 +82,7 @@
unregisterReceiver(mReceiver);
mReceiver = null;
try {
- provider.close();
+ provider.closeAllDevices();
} catch (InterruptedException e) {
Log.e(MtpDocumentsProvider.TAG, e.getMessage());
}
@@ -105,8 +105,8 @@
final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
try {
provider.closeDevice(device.getDeviceId());
- } catch (IOException error) {
- Log.d(MtpDocumentsProvider.TAG, error.getMessage());
+ } catch (IOException | InterruptedException error) {
+ Log.e(MtpDocumentsProvider.TAG, error.getMessage());
}
stopSelfIfNeeded();
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index d9ed4ab..b0962dd 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -9,6 +9,10 @@
import android.util.Log;
import java.io.IOException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
final class RootScanner {
/**
@@ -27,13 +31,18 @@
*/
private final static long SHORT_POLLING_TIMES = 10;
+ /**
+ * Milliseconds we wait for background thread when pausing.
+ */
+ private final static long AWAIT_TERMINATION_TIMEOUT = 2000;
+
final ContentResolver mResolver;
final Resources mResources;
final MtpManager mManager;
final MtpDatabase mDatabase;
- boolean mClosed = false;
- int mPollingCount;
- Thread mBackgroundThread;
+
+ ExecutorService mExecutor;
+ FutureTask<Void> mCurrentTask;
RootScanner(
ContentResolver resolver,
@@ -46,6 +55,9 @@
mDatabase = database;
}
+ /**
+ * Notifies a change of the roots list via ContentResolver.
+ */
void notifyChange() {
final Uri uri =
DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY);
@@ -56,74 +68,81 @@
* Starts to check new changes right away.
* If the background thread has already gone, it restarts another background thread.
*/
- synchronized void scanNow() {
- if (mClosed) {
+ synchronized void resume() {
+ if (mExecutor == null) {
+ // Only single thread updates the database.
+ mExecutor = Executors.newSingleThreadExecutor();
+ }
+ if (mCurrentTask != null) {
+ // Cancel previous task.
+ mCurrentTask.cancel(true);
+ }
+ mCurrentTask = new FutureTask<Void>(new UpdateRootsRunnable(), null);
+ mExecutor.submit(mCurrentTask);
+ }
+
+ /**
+ * Stops background thread and wait for its termination.
+ * @throws InterruptedException
+ */
+ synchronized void pause() throws InterruptedException {
+ if (mExecutor == null) {
return;
}
- mPollingCount = 0;
- if (mBackgroundThread == null) {
- mBackgroundThread = new BackgroundLoaderThread();
- mBackgroundThread.start();
- } else {
- notify();
+ mExecutor.shutdownNow();
+ if (!mExecutor.awaitTermination(AWAIT_TERMINATION_TIMEOUT, TimeUnit.MILLISECONDS)) {
+ Log.e(MtpDocumentsProvider.TAG, "Failed to terminate RootScanner's background thread.");
}
+ mExecutor = null;
}
- void close() throws InterruptedException {
- Thread thread;
- synchronized (this) {
- mClosed = true;
- thread = mBackgroundThread;
- if (mBackgroundThread == null) {
- return;
- }
- notify();
- }
- thread.join();
- }
-
- private final class BackgroundLoaderThread extends Thread {
+ /**
+ * Runnable to scan roots and update the database information.
+ */
+ private final class UpdateRootsRunnable implements Runnable {
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- synchronized (RootScanner.this) {
- while (!mClosed) {
- final int[] deviceIds = mManager.getOpenedDeviceIds();
- if (deviceIds.length == 0) {
- break;
- }
- boolean changed = false;
- for (int deviceId : deviceIds) {
+ int pollingCount = 0;
+ while (!Thread.interrupted()) {
+ final int[] deviceIds = mManager.getOpenedDeviceIds();
+ if (deviceIds.length == 0) {
+ return;
+ }
+ boolean changed = false;
+ for (int deviceId : deviceIds) {
+ try {
+ final MtpRoot[] roots = mManager.getRoots(deviceId);
mDatabase.startAddingRootDocuments(deviceId);
try {
- changed = mDatabase.putRootDocuments(
- deviceId, mResources, mManager.getRoots(deviceId)) || changed;
- } catch (IOException|SQLiteException exp) {
- // The error may happen on the device. We would like to continue getting
- // roots for other devices.
- Log.e(MtpDocumentsProvider.TAG, exp.getMessage());
- continue;
+ if (mDatabase.putRootDocuments(deviceId, mResources, roots)) {
+ changed = true;
+ }
} finally {
- changed = mDatabase.stopAddingRootDocuments(deviceId) || changed;
+ if (mDatabase.stopAddingRootDocuments(deviceId)) {
+ changed = true;
+ }
}
- }
- if (changed) {
- notifyChange();
- }
- mPollingCount++;
- try {
- // Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is
- // more likely to add new root just after the device is added.
- // TODO: Use short interval only for a device that is just added.
- RootScanner.this.wait(
- mPollingCount > SHORT_POLLING_TIMES ?
- LONG_POLLING_INTERVAL : SHORT_POLLING_INTERVAL);
- } catch (InterruptedException exception) {
- break;
+ } catch (IOException | SQLiteException exception) {
+ // The error may happen on the device. We would like to continue getting
+ // roots for other devices.
+ Log.e(MtpDocumentsProvider.TAG, exception.getMessage());
}
}
-
- mBackgroundThread = null;
+ if (changed) {
+ notifyChange();
+ }
+ pollingCount++;
+ try {
+ // Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is
+ // more likely to add new root just after the device is added.
+ // TODO: Use short interval only for a device that is just added.
+ Thread.sleep(pollingCount > SHORT_POLLING_TIMES ?
+ LONG_POLLING_INTERVAL : SHORT_POLLING_INTERVAL);
+ } catch (InterruptedException exp) {
+ // The while condition handles the interrupted flag.
+ continue;
+ }
}
}
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 82e08cd..cabb08d 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -49,11 +49,7 @@
@Override
public void tearDown() {
- try {
- mProvider.close();
- } catch (InterruptedException e) {
- fail();
- }
+ mProvider.shutdown();
}
public void testOpenAndCloseDevice() throws Exception {
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index c7cf61a..5a6f1d1 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -61,7 +61,7 @@
<activity
android:name=".ui.PrintActivity"
- android:configChanges="orientation|screenSize"
+ android:configChanges="screenSize|smallestScreenSize|orientation"
android:permission="android.permission.BIND_PRINT_SPOOLER_SERVICE"
android:theme="@style/PrintActivity">
<intent-filter>
diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml
index 3db0848..4469d38 100644
--- a/packages/Shell/res/values/strings.xml
+++ b/packages/Shell/res/values/strings.xml
@@ -33,4 +33,8 @@
<!-- Title for documents backend that offers bugreports. -->
<string name="bugreport_storage_title">Bug reports</string>
+
+ <!-- Toast message sent when the bugreport file could be read. -->
+ <string name="bugreport_unreadable_text">Bug report file could not be read</string>
+
</resources>
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
index e90a3b5..c264372 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -37,6 +37,7 @@
import android.text.format.DateUtils;
import android.util.Log;
import android.util.Patterns;
+import android.widget.Toast;
import com.google.android.collect.Lists;
import libcore.io.Streams;
@@ -105,6 +106,13 @@
*/
private void triggerLocalNotification(final Context context, final File bugreportFile,
final File screenshotFile) {
+ if (!bugreportFile.exists() || !bugreportFile.canRead()) {
+ Log.e(TAG, "Could not read bugreport file " + bugreportFile);
+ Toast.makeText(context, context.getString(R.string.bugreport_unreadable_text),
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+
boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
if (!isPlainText) {
// Already zipped, send it right away.
@@ -141,10 +149,12 @@
intent.putExtra(Intent.EXTRA_TEXT, messageBody);
final ClipData clipData = new ClipData(null, new String[] { mimeType },
new ClipData.Item(null, null, null, bugreportUri));
- clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
+ final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri);
+ if (screenshotUri != null) {
+ clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
+ attachments.add(screenshotUri);
+ }
intent.setClipData(clipData);
-
- final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri, screenshotUri);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
final Account sendToAccount = findSendToAccount(context);
@@ -162,8 +172,8 @@
File screenshotFile) {
// Files are kept on private storage, so turn into Uris that we can
// grant temporary permissions for.
- final Uri bugreportUri = FileProvider.getUriForFile(context, AUTHORITY, bugreportFile);
- final Uri screenshotUri = FileProvider.getUriForFile(context, AUTHORITY, screenshotFile);
+ final Uri bugreportUri = getUri(context, bugreportFile);
+ final Uri screenshotUri = getUri(context, screenshotFile);
Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri);
Intent notifIntent;
@@ -272,6 +282,10 @@
return foundAccount;
}
+ private static Uri getUri(Context context, File file) {
+ return file != null ? FileProvider.getUriForFile(context, AUTHORITY, file) : null;
+ }
+
private static File getFileExtra(Intent intent, String key) {
final String path = intent.getStringExtra(key);
if (path != null) {
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index c36cab8..585f3c7 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -21,10 +21,6 @@
<PreferenceScreen
android:title="@string/quick_settings">
- <Preference
- android:key="qs_tuner"
- android:title="@string/qs_rearrange" />
-
<PreferenceCategory
android:title="@string/experimental">
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 049754e..bb2b8fc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -77,9 +77,9 @@
private Record mDetailRecord;
private Callback mCallback;
private BrightnessController mBrightnessController;
- private QSTileHost mHost;
+ protected QSTileHost mHost;
- private QSFooter mFooter;
+ protected QSFooter mFooter;
private boolean mGridContentVisible = true;
protected LinearLayout mQsContainer;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 5d928d6..7f45545 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -44,8 +44,8 @@
* state update pass on tile looper.
*/
public abstract class QSTile<TState extends State> implements Listenable {
- protected final String TAG = "QSTile." + getClass().getSimpleName();
- protected static final boolean DEBUG = Log.isLoggable("QSTile", Log.DEBUG);
+ protected final String TAG = "Tile." + getClass().getSimpleName();
+ protected static final boolean DEBUG = Log.isLoggable("Tile", Log.DEBUG);
protected final Host mHost;
protected final Context mContext;
@@ -332,7 +332,7 @@
Looper getLooper();
Context getContext();
Collection<QSTile<?>> getTiles();
- void setCallback(Callback callback);
+ void addCallback(Callback callback);
BluetoothController getBluetoothController();
LocationController getLocationController();
RotationLockController getRotationLockController();
@@ -453,9 +453,9 @@
public static class State {
public boolean visible;
public Icon icon;
- public String label;
- public String contentDescription;
- public String dualLabelContentDescription;
+ public CharSequence label;
+ public CharSequence contentDescription;
+ public CharSequence dualLabelContentDescription;
public boolean autoMirrorDrawable = true;
public boolean copyTo(State other) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java
new file mode 100644
index 0000000..55f4736
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileServiceWrapper.java
@@ -0,0 +1,76 @@
+package com.android.systemui.qs;
+
+import android.os.IBinder;
+import android.service.quicksettings.IQSTileService;
+import android.service.quicksettings.Tile;
+import android.util.Log;
+
+
+public class QSTileServiceWrapper implements IQSTileService {
+ private static final String TAG = "IQSTileServiceWrapper";
+
+ private final IQSTileService mService;
+
+ public QSTileServiceWrapper(IQSTileService service) {
+ mService = service;
+ }
+
+ @Override
+ public IBinder asBinder() {
+ return mService.asBinder();
+ }
+
+ @Override
+ public void setQSTile(Tile tile) {
+ try {
+ mService.setQSTile(tile);
+ } catch (Exception e) {
+ Log.d(TAG, "Caught exception from QSTileService", e);
+ }
+ }
+
+ @Override
+ public void onTileAdded() {
+ try {
+ mService.onTileAdded();
+ } catch (Exception e) {
+ Log.d(TAG, "Caught exception from QSTileService", e);
+ }
+ }
+
+ @Override
+ public void onTileRemoved() {
+ try {
+ mService.onTileRemoved();
+ } catch (Exception e) {
+ Log.d(TAG, "Caught exception from QSTileService", e);
+ }
+ }
+
+ @Override
+ public void onStartListening() {
+ try {
+ mService.onStartListening();
+ } catch (Exception e) {
+ Log.d(TAG, "Caught exception from QSTileService", e);
+ }
+ }
+
+ @Override
+ public void onStopListening() {
+ try {
+ mService.onStopListening();
+ } catch (Exception e) {
+ Log.d(TAG, "Caught exception from QSTileService", e);
+ }
+ }
+
+ @Override
+ public void onClick() {
+ try {
+ mService.onClick();
+ } catch (Exception e) {
+ Log.d(TAG, "Caught exception from QSTileService", e);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index cc264a0..f32cfdc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -17,6 +17,7 @@
package com.android.systemui.qs;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -24,22 +25,16 @@
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
import android.util.MathUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.TextView;
-
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile.AnimationIcon;
-import com.android.systemui.qs.QSTile.State;
import java.util.Objects;
@@ -227,6 +222,7 @@
final ImageView icon = new ImageView(mContext);
icon.setId(android.R.id.icon);
icon.setScaleType(ScaleType.CENTER_INSIDE);
+ icon.setImageTintList(ColorStateList.valueOf(getContext().getColor(android.R.color.white)));
return icon;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java
new file mode 100644
index 0000000..a4ff685
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/BlankCustomTile.java
@@ -0,0 +1,88 @@
+/*
+ * 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.qs.customize;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.qs.QSTile;
+
+public class BlankCustomTile extends QSTile<QSTile.State> {
+ public static final String PREFIX = "custom(";
+
+ private final ComponentName mComponent;
+
+ private BlankCustomTile(Host host, String action) {
+ super(host);
+ mComponent = ComponentName.unflattenFromString(action);
+ }
+
+ public static QSTile<?> create(Host host, String spec) {
+ if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
+ throw new IllegalArgumentException("Bad custom tile spec: " + spec);
+ }
+ final String action = spec.substring(PREFIX.length(), spec.length() - 1);
+ if (action.isEmpty()) {
+ throw new IllegalArgumentException("Empty custom tile spec action");
+ }
+ return new BlankCustomTile(host, action);
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ }
+
+ @Override
+ protected State newTileState() {
+ return new State();
+ }
+
+ @Override
+ protected void handleUserSwitch(int newUserId) {
+ super.handleUserSwitch(newUserId);
+ }
+
+ @Override
+ protected void handleClick() {
+ MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
+ }
+
+ @Override
+ protected void handleLongClick() {
+ }
+
+ @Override
+ protected void handleUpdateState(State state, Object arg) {
+ try {
+ PackageManager pm = mContext.getPackageManager();
+ ServiceInfo info = pm.getServiceInfo(mComponent, 0);
+ state.visible = true;
+ state.icon = new DrawableIcon(info.loadIcon(pm));
+ state.label = info.loadLabel(pm).toString();
+ state.contentDescription = state.label;
+ } catch (Exception e) {
+ state.visible = false;
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_INTENT;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
index 8866e55..422ae4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSPanel.java
@@ -15,16 +15,34 @@
*/
package com.android.systemui.qs.customize;
+import android.app.ActivityManager;
+import android.app.Service;
import android.content.ClipData;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.provider.Settings.Secure;
+import android.service.quicksettings.IQSTileService;
+import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTileServiceWrapper;
+import com.android.systemui.qs.tiles.CustomTile;
import com.android.systemui.statusbar.phone.QSTileHost;
+import com.android.systemui.tuner.TunerService;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
/**
* A version of QSPanel that allows tiles to be dragged around rather than
@@ -32,8 +50,14 @@
* and the saving/ordering is handled by the CustomQSTileHost.
*/
public class CustomQSPanel extends QSPanel {
+
+ private static final String TAG = "CustomQSPanel";
- private CustomQSTileHost mCustomHost;
+ private List<String> mSavedTiles;
+ private ArrayList<String> mStash;
+ private List<String> mTiles = new ArrayList<>();
+
+ private ArrayList<QSTile<?>> mCurrentTiles = new ArrayList<>();
public CustomQSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -41,12 +65,9 @@
.inflate(R.layout.qs_customize_layout, mQsContainer, false);
mQsContainer.addView((View) mTileLayout, 1 /* Between brightness and footer */);
((NonPagedTileLayout) mTileLayout).setCustomQsPanel(this);
- }
+ removeView(mFooter.getView());
- @Override
- public void setHost(QSTileHost host) {
- super.setHost(host);
- mCustomHost = (CustomQSTileHost) host;
+ TunerService.get(mContext).addTunable(this, QSTileHost.TILES_SETTING);
}
@Override
@@ -55,17 +76,16 @@
// No Brightness for you.
super.onTuningChanged(key, "0");
}
- }
-
- public CustomQSTileHost getCustomHost() {
- return mCustomHost;
+ if (QSTileHost.TILES_SETTING.equals(key)) {
+ mSavedTiles = QSTileHost.loadTileSpecs(mContext, newValue);
+ }
}
public void tileSelected(QSTile<?> tile, ClipData currentClip) {
String sourceSpec = getSpec(currentClip);
String destSpec = tile.getTileSpec();
if (!sourceSpec.equals(destSpec)) {
- mCustomHost.moveTo(sourceSpec, destSpec);
+ moveTo(sourceSpec, destSpec);
}
}
@@ -79,4 +99,124 @@
public String getSpec(ClipData data) {
return data.getItemAt(0).getText().toString();
}
+
+ public void setSavedTiles() {
+ setTiles(mSavedTiles);
+ }
+
+ public void saveCurrentTiles() {
+ for (int i = 0; i < mSavedTiles.size(); i++) {
+ String tileSpec = mSavedTiles.get(i);
+ if (!tileSpec.startsWith(CustomTile.PREFIX)) continue;
+ if (!mTiles.contains(tileSpec)) {
+ mContext.bindServiceAsUser(
+ new Intent().setComponent(CustomTile.getComponentFromSpec(tileSpec)),
+ new ServiceConnection() {
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ QSTileServiceWrapper wrapper = new QSTileServiceWrapper(
+ IQSTileService.Stub.asInterface(service));
+ wrapper.onStopListening();
+ wrapper.onTileRemoved();
+ mContext.unbindService(this);
+ }
+ }, Service.BIND_AUTO_CREATE,
+ new UserHandle(ActivityManager.getCurrentUser()));
+ }
+ }
+ for (int i = 0; i < mTiles.size(); i++) {
+ String tileSpec = mTiles.get(i);
+ if (!tileSpec.startsWith(CustomTile.PREFIX)) continue;
+ if (!mSavedTiles.contains(tileSpec)) {
+ mContext.bindServiceAsUser(
+ new Intent().setComponent(CustomTile.getComponentFromSpec(tileSpec)),
+ new ServiceConnection() {
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ QSTileServiceWrapper wrapper = new QSTileServiceWrapper(
+ IQSTileService.Stub.asInterface(service));
+ wrapper.onTileAdded();
+ mContext.unbindService(this);
+ }
+ }, Service.BIND_AUTO_CREATE,
+ new UserHandle(ActivityManager.getCurrentUser()));
+ }
+ }
+ Secure.putStringForUser(getContext().getContentResolver(), QSTileHost.TILES_SETTING,
+ TextUtils.join(",", mTiles), ActivityManager.getCurrentUser());
+ }
+
+ public void stashCurrentTiles() {
+ mStash = new ArrayList<>(mTiles);
+ }
+
+ public void unstashTiles() {
+ setTiles(mStash);
+ }
+
+ @Override
+ public void setTiles(Collection<QSTile<?>> tiles) {
+ setTilesInternal();
+ }
+
+ private void setTilesInternal() {
+ for (int i = 0; i < mCurrentTiles.size(); i++) {
+ mCurrentTiles.get(i).destroy();
+ }
+ mCurrentTiles.clear();
+ for (int i = 0; i < mTiles.size(); i++) {
+ if (mTiles.get(i).startsWith(CustomTile.PREFIX)) {
+ mCurrentTiles.add(BlankCustomTile.create(mHost, mTiles.get(i)));
+ } else {
+ mCurrentTiles.add(mHost.createTile(mTiles.get(i)));
+ }
+ mCurrentTiles.get(mCurrentTiles.size() - 1).setTileSpec(mTiles.get(i));
+ }
+ super.setTiles(mCurrentTiles);
+ }
+
+ public void addTile(String spec) {
+ mTiles.add(spec);
+ setTilesInternal();
+ }
+
+ public void moveTo(String from, String to) {
+ int fromIndex = mTiles.indexOf(from);
+ if (fromIndex < 0) {
+ Log.e(TAG, "Unknown from tile " + from);
+ return;
+ }
+ int index = mTiles.indexOf(to);
+ if (index < 0) {
+ Log.e(TAG, "Unknown to tile " + to);
+ return;
+ }
+ mTiles.remove(fromIndex);
+ mTiles.add(index, from);
+ setTilesInternal();
+ }
+
+ public void remove(String spec) {
+ if (!mTiles.remove(spec)) {
+ Log.e(TAG, "Unknown remove spec " + spec);
+ }
+ setTilesInternal();
+ }
+
+ public void setTiles(List<String> tiles) {
+ mTiles = new ArrayList<>(tiles);
+ setTilesInternal();
+ }
+
+ public Collection<QSTile<?>> getTiles() {
+ return mCurrentTiles;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
deleted file mode 100644
index 3f85982..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * 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.qs.customize;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.qs.QSTile;
-import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.statusbar.policy.SecurityController;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @see CustomQSPanel
- */
-public class CustomQSTileHost extends QSTileHost {
-
- private static final String TAG = "CustomHost";
- private List<String> mTiles;
- private List<String> mSavedTiles;
- private ArrayList<String> mStash;
-
- public CustomQSTileHost(Context context, QSTileHost host) {
- super(context, null, host.getBluetoothController(), host.getLocationController(),
- host.getRotationLockController(), host.getNetworkController(),
- host.getZenModeController(), host.getHotspotController(), host.getCastController(),
- host.getFlashlightController(), host.getUserSwitcherController(),
- host.getUserInfoController(), host.getKeyguardMonitor(),
- new BlankSecurityController(), host.getBatteryController());
- }
-
- @Override
- public QSTile<?> createTile(String tileSpec) {
- QSTile<?> tile = super.createTile(tileSpec);
- tile.setTileSpec(tileSpec);
- return tile;
- }
-
- @Override
- public void onTuningChanged(String key, String newValue) {
- // No Tunings For You.
- if (TILES_SETTING.equals(key)) {
- mSavedTiles = super.loadTileSpecs(newValue);
- }
- }
-
- public void setSavedTiles() {
- setTiles(mSavedTiles);
- }
-
- public void saveCurrentTiles() {
- Secure.putStringForUser(getContext().getContentResolver(), TILES_SETTING,
- TextUtils.join(",", mTiles), ActivityManager.getCurrentUser());
- }
-
- public void stashCurrentTiles() {
- mStash = new ArrayList<>(mTiles);
- }
-
- public void unstashTiles() {
- setTiles(mStash);
- }
-
- public void moveTo(String from, String to) {
- int fromIndex = mTiles.indexOf(from);
- if (fromIndex < 0) {
- Log.e(TAG, "Unknown from tile " + from);
- return;
- }
- int index = mTiles.indexOf(to);
- if (index < 0) {
- Log.e(TAG, "Unknown to tile " + to);
- return;
- }
- mTiles.remove(fromIndex);
- mTiles.add(index, from);
- super.onTuningChanged(TILES_SETTING, null);
- }
-
- public void remove(String spec) {
- if (!mTiles.remove(spec)) {
- Log.e(TAG, "Unknown remove spec " + spec);
- }
- super.onTuningChanged(TILES_SETTING, null);
- }
-
- public void setTiles(List<String> tiles) {
- mTiles = new ArrayList<>(tiles);
- super.onTuningChanged(TILES_SETTING, null);
- }
-
- @Override
- protected List<String> loadTileSpecs(String tileList) {
- return mTiles;
- }
-
- public void addTile(String spec) {
- mTiles.add(spec);
- super.onTuningChanged(TILES_SETTING, null);
- }
-
- public void replace(String oldTile, String newTile) {
- if (oldTile.equals(newTile)) {
- return;
- }
- MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REORDER, oldTile + ","
- + newTile);
- List<String> order = new ArrayList<>(mTileSpecs);
- int index = order.indexOf(oldTile);
- if (index < 0) {
- Log.e(TAG, "Can't find " + oldTile);
- return;
- }
- order.remove(newTile);
- order.add(index, newTile);
- setTiles(order);
- }
-
- /**
- * Blank so that the customizing QS view doesn't show any security messages in the footer.
- */
- private static class BlankSecurityController implements SecurityController {
- @Override
- public boolean hasDeviceOwner() {
- return false;
- }
-
- @Override
- public boolean hasProfileOwner() {
- return false;
- }
-
- @Override
- public String getDeviceOwnerName() {
- return null;
- }
-
- @Override
- public String getProfileOwnerName() {
- return null;
- }
-
- @Override
- public boolean isVpnEnabled() {
- return false;
- }
-
- @Override
- public boolean isVpnRestricted() {
- return false;
- }
-
- @Override
- public String getPrimaryVpnName() {
- return null;
- }
-
- @Override
- public String getProfileVpnName() {
- return null;
- }
-
- @Override
- public void onUserSwitched(int newUserId) {
- }
-
- @Override
- public void addCallback(SecurityControllerCallback callback) {
- }
-
- @Override
- public void removeCallback(SecurityControllerCallback callback) {
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
index 1669278..d0d5b54 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/NonPagedTileLayout.java
@@ -83,8 +83,8 @@
record.tileView.setVisibility(View.VISIBLE);
record.tileView.init(null, null, null);
record.tileView.setOnTouchListener(this);
- if (mCurrentClip != null
- && mCurrentClip.getItemAt(0).getText().toString().equals(record.tile.getTileSpec())) {
+ if (mCurrentClip != null && mCurrentClip.getItemAt(0)
+ .getText().toString().equals(record.tile.getTileSpec())) {
record.tileView.setAlpha(.3f);
mCurrentView = record.tileView;
}
@@ -180,7 +180,7 @@
case MotionEvent.ACTION_DOWN:
// Stash the current tiles, in case the drop is on info, that we can restore
// the previous state.
- mPanel.getCustomHost().stashCurrentTiles();
+ mPanel.stashCurrentTiles();
mCurrentView = v;
mCurrentClip = mPanel.getClip((QSTile<?>) v.getTag());
View.DragShadowBuilder shadow = new View.DragShadowBuilder(v);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index b5a885c..baad370 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -22,6 +22,7 @@
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnDismissListener;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.DragEvent;
@@ -71,11 +72,11 @@
private CustomQSPanel mQsPanel;
private boolean isShown;
- private CustomQSTileHost mHost;
private DropButton mInfoButton;
private DropButton mRemoveButton;
private FloatingActionButton mFab;
private SystemUIDialog mDialog;
+ private QSTileHost mHost;
public QSCustomizer(Context context, AttributeSet attrs) {
super(new ContextThemeWrapper(context, android.R.style.Theme_Material), attrs);
@@ -85,11 +86,11 @@
}
public void setHost(QSTileHost host) {
- mHost = new CustomQSTileHost(mContext, host);
- mHost.setCallback(this);
+ mHost = host;
+ mHost.addCallback(this);
mQsPanel.setTiles(mHost.getTiles());
mQsPanel.setHost(mHost);
- mHost.setSavedTiles();
+ mQsPanel.setSavedTiles();
}
@Override
@@ -129,7 +130,7 @@
public void show(int x, int y) {
isShown = true;
- mHost.setSavedTiles();
+ mQsPanel.setSavedTiles();
mPhoneStatusBar.getStatusBarWindow().addView(this);
mQsPanel.setListening(true);
mClipper.animateCircularClip(x, y, true, this);
@@ -150,7 +151,7 @@
for (String tile : QSPagingSwitch.QS_PAGE_TILES.split(",")) {
tiles.add(tile);
}
- mHost.setTiles(tiles);
+ mQsPanel.setTiles(tiles);
}
private void setDragging(boolean dragging) {
@@ -158,7 +159,8 @@
}
private void save() {
- mHost.saveCurrentTiles();
+ Log.d("CustomQSPanel", "Save!");
+ mQsPanel.saveCurrentTiles();
// TODO: At save button.
hide(0, 0);
}
@@ -167,6 +169,7 @@
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case MENU_SAVE:
+ Log.d("CustomQSPanel", "Save...");
save();
break;
case MENU_RESET:
@@ -179,7 +182,7 @@
@Override
public void onTileSelected(String spec) {
if (mDialog != null) {
- mHost.addTile(spec);
+ mQsPanel.addTile(spec);
mDialog.dismiss();
}
}
@@ -203,9 +206,9 @@
public void onDrop(View v, ClipData data) {
if (v == mRemoveButton) {
- mHost.remove(mQsPanel.getSpec(data));
+ mQsPanel.remove(mQsPanel.getSpec(data));
} else if (v == mInfoButton) {
- mHost.unstashTiles();
+ mQsPanel.unstashTiles();
SystemUIDialog dialog = new SystemUIDialog(mContext);
dialog.setTitle(mQsPanel.getSpec(data));
dialog.setPositiveButton(R.string.ok, null);
@@ -220,7 +223,7 @@
android.R.style.Theme_Material_Dialog);
View view = LayoutInflater.from(mContext).inflate(R.layout.qs_add_tiles_list, null);
ListView listView = (ListView) view.findViewById(android.R.id.list);
- TileAdapter adapter = new TileAdapter(mContext, mHost.getTiles(), mHost);
+ TileAdapter adapter = new TileAdapter(mContext, mQsPanel.getTiles(), mHost);
adapter.setListener(this);
listView.setDivider(null);
listView.setDividerHeight(0);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 579f58d..144b202 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -27,6 +27,7 @@
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
+import android.service.quicksettings.TileService;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -82,8 +83,10 @@
continue;
}
if (tileSpecs.contains(spec)) {
+ Log.d(TAG, "Skipping " + spec);
continue;
}
+ Log.d(TAG, "Trying " + spec);
final QSTile<?> tile = host.createTile(spec);
// Bad, bad, very bad.
tile.setListening(true);
@@ -156,7 +159,7 @@
Log.d(TAG, "Added " + mLabel);
}
- private void addTile(String spec, Drawable icon, String label) {
+ private void addTile(String spec, Drawable icon, CharSequence label) {
TileInfo info = new TileInfo();
info.label = label;
info.drawable = icon;
@@ -164,7 +167,7 @@
mTiles.add(info);
}
- private void addTile(String spec, Icon icon, String label, Context context) {
+ private void addTile(String spec, Icon icon, CharSequence label, Context context) {
addTile(spec, icon.getDrawable(context), label);
}
@@ -208,19 +211,17 @@
private static class TileInfo {
private String spec;
private Drawable drawable;
- private String label;
+ private CharSequence label;
}
private class QueryTilesTask extends AsyncTask<Void, Void, Collection<TileGroup>> {
- // TODO: Become non-prototype and an API.
- private static final String TILE_ACTION = "android.intent.action.QS_TILE";
-
@Override
protected Collection<TileGroup> doInBackground(Void... params) {
HashMap<String, TileGroup> pkgMap = new HashMap<>();
PackageManager pm = mContext.getPackageManager();
// TODO: Handle userness.
- List<ResolveInfo> services = pm.queryIntentServices(new Intent(TILE_ACTION), 0);
+ List<ResolveInfo> services = pm.queryIntentServices(
+ new Intent(TileService.ACTION_QS_TILE), 0);
for (ResolveInfo info : services) {
String packageName = info.serviceInfo.packageName;
ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index fd70d02..7f07ddc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -133,7 +133,7 @@
R.string.accessibility_quick_settings_bluetooth_off);
}
- String bluetoothName = state.label;
+ CharSequence bluetoothName = state.label;
if (connected) {
bluetoothName = state.dualLabelContentDescription = mContext.getString(
R.string.accessibility_bluetooth_name, state.label);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
index cf76ed4..b0d885a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
@@ -16,36 +16,93 @@
package com.android.systemui.qs.tiles;
+import android.app.ActivityManager;
+import android.app.Service;
import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.service.quicksettings.IQSTileService;
+import android.service.quicksettings.Tile;
+import android.util.Log;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTileServiceWrapper;
+import com.android.systemui.statusbar.phone.QSTileHost;
public class CustomTile extends QSTile<QSTile.State> {
public static final String PREFIX = "custom(";
- private final ComponentName mComponent;
+ // We don't want to thrash binding and unbinding if the user opens and closes the panel a lot.
+ // So instead we have a period of waiting.
+ private static final long UNBIND_DELAY = 30000;
- private CustomTile(Host host, String action) {
+ private final ComponentName mComponent;
+ private final Tile mTile;
+
+ private QSTileServiceWrapper mService;
+ private boolean mListening;
+ private boolean mBound;
+
+ private CustomTile(QSTileHost host, String action) {
super(host);
mComponent = ComponentName.unflattenFromString(action);
+ mTile = new Tile(mComponent, host);
+ try {
+ PackageManager pm = mContext.getPackageManager();
+ ServiceInfo info = pm.getServiceInfo(mComponent, 0);
+ mTile.setIcon(android.graphics.drawable.Icon
+ .createWithResource(mComponent.getPackageName(), info.icon));
+ mTile.setLabel(info.loadLabel(pm));
+ } catch (Exception e) {
+ }
}
- public static QSTile<?> create(Host host, String spec) {
- if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
- throw new IllegalArgumentException("Bad intent tile spec: " + spec);
- }
- final String action = spec.substring(PREFIX.length(), spec.length() - 1);
- if (action.isEmpty()) {
- throw new IllegalArgumentException("Empty intent tile spec action");
- }
- return new CustomTile(host, action);
+ public ComponentName getComponent() {
+ return mComponent;
+ }
+
+ public Tile getQsTile() {
+ return mTile;
+ }
+
+ public void updateState(Tile tile) {
+ Log.d("TileService", "Setting state " + tile.getLabel());
+ mTile.setIcon(tile.getIcon());
+ mTile.setLabel(tile.getLabel());
+ mTile.setContentDescription(tile.getContentDescription());
}
@Override
public void setListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
+ if (listening) {
+ mHandler.removeCallbacks(mUnbind);
+ if (!mBound) {
+ // TODO: Guarantee re-bind on user-switch.
+ mContext.bindServiceAsUser(new Intent().setComponent(mComponent),
+ mServiceConnection, Service.BIND_AUTO_CREATE,
+ new UserHandle(ActivityManager.getCurrentUser()));
+ mBound = true;
+ }
+ } else {
+ if (mService!= null) {
+ mService.onStopListening();
+ }
+ mHandler.postDelayed(mUnbind, UNBIND_DELAY);
+ }
+ }
+
+ @Override
+ protected void handleDestroy() {
+ super.handleDestroy();
+ mHandler.removeCallbacks(mUnbind);
+ mUnbind.run();
}
@Override
@@ -60,6 +117,11 @@
@Override
protected void handleClick() {
+ if (mService != null) {
+ mService.onClick();
+ } else {
+ Log.e(TAG, "Click with no service " + getTileSpec());
+ }
MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
}
@@ -69,16 +131,13 @@
@Override
protected void handleUpdateState(State state, Object arg) {
- // TODO: Actual things.
- try {
- PackageManager pm = mContext.getPackageManager();
- ServiceInfo info = pm.getServiceInfo(mComponent, 0);
- state.visible = true;
- state.icon = new DrawableIcon(info.loadIcon(pm));
- state.label = info.loadLabel(pm).toString();
+ state.visible = true;
+ state.icon = new DrawableIcon(mTile.getIcon().loadDrawable(mContext));
+ state.label = mTile.getLabel();
+ if (mTile.getContentDescription() != null) {
+ state.contentDescription = mTile.getContentDescription();
+ } else {
state.contentDescription = state.label;
- } catch (Exception e) {
- state.visible = false;
}
}
@@ -86,4 +145,48 @@
public int getMetricsCategory() {
return MetricsLogger.QS_INTENT;
}
+
+ private final ServiceConnection mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mService = new QSTileServiceWrapper(IQSTileService.Stub.asInterface(service));
+ if (mListening) {
+ mService.setQSTile(mTile);
+ mService.onStartListening();
+ } else {
+ mService.onStopListening();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+ };
+
+ private final Runnable mUnbind = new Runnable() {
+ @Override
+ public void run() {
+ mContext.unbindService(mServiceConnection);
+ mBound = false;
+ }
+ };
+
+ public static ComponentName getComponentFromSpec(String spec) {
+ final String action = spec.substring(PREFIX.length(), spec.length() - 1);
+ if (action.isEmpty()) {
+ throw new IllegalArgumentException("Empty custom tile spec action");
+ }
+ return ComponentName.unflattenFromString(action);
+ }
+
+ public static QSTile<?> create(QSTileHost host, String spec) {
+ if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
+ throw new IllegalArgumentException("Bad custom tile spec: " + spec);
+ }
+ final String action = spec.substring(PREFIX.length(), spec.length() - 1);
+ if (action.isEmpty()) {
+ throw new IllegalArgumentException("Empty custom tile spec action");
+ }
+ return new CustomTile(host, action);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 3763618..7f4442a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -166,7 +166,7 @@
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_wifi,
signalContentDescription);
- String wifiName = state.label;
+ CharSequence wifiName = state.label;
if (state.connected) {
wifiName = r.getString(R.string.accessibility_wifi_name, state.label);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index fe67fd9..965e7a67 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -239,7 +239,8 @@
SystemServicesProxy ssp, Resources res) {
Bitmap tdIcon = iconBitmap != null
? iconBitmap
- : ActivityManager.TaskDescription.loadTaskDescriptionIcon(iconFilename);
+ : ActivityManager.TaskDescription.loadTaskDescriptionIcon(iconFilename,
+ taskKey.userId);
if (tdIcon != null) {
return ssp.getBadgedIcon(new BitmapDrawable(res, tdIcon), taskKey.userId);
}
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 4ecb80a..f3c66a5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -17,6 +17,8 @@
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;
import android.graphics.Bitmap;
@@ -31,7 +33,6 @@
import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.WindowManagerGlobal;
import com.android.internal.annotations.GuardedBy;
-import com.android.systemui.recents.Constants;
import com.android.systemui.recents.ExitRecentsWindowFirstAnimationFrameEvent;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsDebugFlags;
@@ -48,6 +49,7 @@
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;
@@ -93,7 +95,7 @@
final boolean lockToTask, final Rect bounds, int destinationStack) {
final ActivityOptions opts = ActivityOptions.makeBasic();
if (bounds != null) {
- opts.setBounds(bounds.isEmpty() ? null : bounds);
+ opts.setLaunchBounds(bounds.isEmpty() ? null : bounds);
}
final ActivityOptions.OnAnimationStartedListener animStartedListener;
@@ -244,8 +246,7 @@
// Ensure we have a valid target stack id
final int targetStackId = destinationStack != INVALID_STACK_ID ?
destinationStack : task.key.stackId;
- if (targetStackId != FREEFORM_WORKSPACE_STACK_ID
- && targetStackId != FULLSCREEN_WORKSPACE_STACK_ID) {
+ if (!StackId.useAnimationSpecForAppTransition(targetStackId)) {
return null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index d3d9bef..f8ab35f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -2161,13 +2161,10 @@
boolean isHighPriority = sbn.getScore() >= INTERRUPTION_THRESHOLD;
boolean isFullscreen = notification.fullScreenIntent != null;
boolean hasTicker = mHeadsUpTicker && !TextUtils.isEmpty(notification.tickerText);
- boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP,
- Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER;
boolean accessibilityForcesLaunch = isFullscreen
&& mAccessibilityManager.isTouchExplorationEnabled();
boolean justLaunchedFullScreenIntent = entry.hasJustLaunchedFullScreenIntent();
boolean interrupt = (isFullscreen || (isHighPriority && (isNoisy || hasTicker)))
- && isAllowed
&& !accessibilityForcesLaunch
&& !justLaunchedFullScreenIntent
&& mPowerManager.isScreenOn()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 0cddf1d..51bcf0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -927,7 +927,7 @@
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);
mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
mHeader.setQSPanel(mQSPanel);
- qsh.setCallback(new QSTileHost.Callback() {
+ qsh.addCallback(new QSTileHost.Callback() {
@Override
public void onTilesChanged() {
mQSPanel.setTiles(qsh.getTiles());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 96b919e..57c2648 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -17,18 +17,56 @@
package com.android.systemui.statusbar.phone;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
+import android.os.Binder;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
+import android.os.RemoteException;
+import android.service.quicksettings.IQSService;
+import android.service.quicksettings.Tile;
import android.util.Log;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.tiles.*;
-import com.android.systemui.statusbar.policy.*;
+import com.android.systemui.qs.tiles.AirplaneModeTile;
+import com.android.systemui.qs.tiles.BatteryTile;
+import com.android.systemui.qs.tiles.BluetoothTile;
+import com.android.systemui.qs.tiles.CastTile;
+import com.android.systemui.qs.tiles.CellularTile;
+import com.android.systemui.qs.tiles.ColorInversionTile;
+import com.android.systemui.qs.tiles.CustomTile;
+import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.qs.tiles.FlashlightTile;
+import com.android.systemui.qs.tiles.HotspotTile;
+import com.android.systemui.qs.tiles.IntentTile;
+import com.android.systemui.qs.tiles.LocationTile;
+import com.android.systemui.qs.tiles.QAirplaneTile;
+import com.android.systemui.qs.tiles.QBluetoothTile;
+import com.android.systemui.qs.tiles.QFlashlightTile;
+import com.android.systemui.qs.tiles.QLockTile;
+import com.android.systemui.qs.tiles.QRotationLockTile;
+import com.android.systemui.qs.tiles.QWifiTile;
+import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.qs.tiles.UserTile;
+import com.android.systemui.qs.tiles.WifiTile;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -40,7 +78,7 @@
import java.util.Map;
/** Platform implementation of the quick settings tile host **/
-public class QSTileHost implements QSTile.Host, Tunable {
+public final class QSTileHost extends IQSService.Stub implements QSTile.Host, Tunable {
private static final String TAG = "QSTileHost";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -65,7 +103,7 @@
private final SecurityController mSecurity;
private final BatteryController mBattery;
- private Callback mCallback;
+ private final List<Callback> mCallbacks = new ArrayList<>();
public QSTileHost(Context context, PhoneStatusBar statusBar,
BluetoothController bluetooth, LocationController location,
@@ -107,8 +145,8 @@
}
@Override
- public void setCallback(Callback callback) {
- mCallback = callback;
+ public void addCallback(Callback callback) {
+ mCallbacks.add(callback);
}
@Override
@@ -209,14 +247,14 @@
public SecurityController getSecurityController() {
return mSecurity;
}
-
+
@Override
public void onTuningChanged(String key, String newValue) {
if (!TILES_SETTING.equals(key)) {
return;
}
if (DEBUG) Log.d(TAG, "Recreating tiles");
- final List<String> tileSpecs = loadTileSpecs(newValue);
+ final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
if (tileSpecs.equals(mTileSpecs)) return;
for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) {
if (!tileSpecs.contains(tile.getKey())) {
@@ -227,11 +265,15 @@
final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>();
for (String tileSpec : tileSpecs) {
if (mTiles.containsKey(tileSpec)) {
- newTiles.put(tileSpec, mTiles.get(tileSpec));
+ QSTile<?> tile = mTiles.get(tileSpec);
+ if (DEBUG) Log.d(TAG, "Adding " + tile);
+ newTiles.put(tileSpec, tile);
} else {
if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
try {
- newTiles.put(tileSpec, createTile(tileSpec));
+ QSTile<?> tile = createTile(tileSpec);
+ tile.setTileSpec(tileSpec);
+ newTiles.put(tileSpec, tile);
} catch (Throwable t) {
Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
}
@@ -241,11 +283,46 @@
mTileSpecs.addAll(tileSpecs);
mTiles.clear();
mTiles.putAll(newTiles);
- if (mCallback != null) {
- mCallback.onTilesChanged();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onTilesChanged();
}
}
+ @Override
+ public void updateQsTile(Tile tile) throws RemoteException {
+ verifyCaller(tile.getComponentName().getPackageName());
+ CustomTile customTile = getTileForComponent(tile.getComponentName());
+ if (customTile != null) {
+ Log.d("TileService", "Got tile update for " + tile.getComponentName());
+ customTile.updateState(tile);
+ customTile.refreshState();
+ }
+ }
+
+ private void verifyCaller(String packageName) {
+ try {
+ int uid = mContext.getPackageManager().getPackageUid(packageName,
+ Binder.getCallingUserHandle().getIdentifier());
+ if (Binder.getCallingUid() != uid) {
+ throw new SecurityException("Component outside caller's uid");
+ }
+ } catch (NameNotFoundException e) {
+ throw new SecurityException(e);
+ }
+ }
+
+ private CustomTile getTileForComponent(ComponentName component) {
+ // TODO: Build map for easier lookup.
+ for (QSTile<?> qsTile : mTiles.values()) {
+ if (qsTile instanceof CustomTile) {
+ if (((CustomTile) qsTile).getComponent().equals(component)) {
+ return (CustomTile) qsTile;
+ }
+ }
+ }
+ return null;
+ }
+
public QSTile<?> createTile(String tileSpec) {
if (tileSpec.equals("wifi")) return new WifiTile(this, false);
else if (tileSpec.equals("bt")) return new BluetoothTile(this, false);
@@ -276,8 +353,8 @@
else throw new IllegalArgumentException("Bad tile spec: " + tileSpec);
}
- protected List<String> loadTileSpecs(String tileList) {
- final Resources res = mContext.getResources();
+ public static List<String> loadTileSpecs(Context context, String tileList) {
+ final Resources res = context.getResources();
final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
if (tileList == null) {
tileList = res.getString(R.string.quick_settings_tiles);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 662dbd9..cc9f5c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -192,7 +192,7 @@
host.getBatteryController());
mHeaderQsPanel.setHost(myHost);
mHeaderQsPanel.setTiles(myHost.getTiles());
- myHost.setCallback(new QSTile.Host.Callback() {
+ myHost.addCallback(new QSTile.Host.Callback() {
@Override
public void onTilesChanged() {
mHeaderQsPanel.setTiles(myHost.getTiles());
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
deleted file mode 100644
index 05e3fd5..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
+++ /dev/null
@@ -1,547 +0,0 @@
-/*
- * 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.tuner;
-
-import android.app.ActivityManager;
-import android.app.AlertDialog;
-import android.app.Fragment;
-import android.content.ClipData;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.DragEvent;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnDragListener;
-import android.view.View.OnTouchListener;
-import android.view.ViewGroup;
-import android.widget.EditText;
-import android.widget.FrameLayout;
-import android.widget.ScrollView;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.systemui.R;
-import com.android.systemui.qs.QSPanel;
-import com.android.systemui.qs.QSTile;
-import com.android.systemui.qs.QSTile.Host.Callback;
-import com.android.systemui.qs.QSTile.ResourceIcon;
-import com.android.systemui.qs.QSTileBaseView;
-import com.android.systemui.qs.QSTileView;
-import com.android.systemui.qs.tiles.IntentTile;
-import com.android.systemui.statusbar.phone.QSTileHost;
-import com.android.systemui.statusbar.policy.SecurityController;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class QsTuner extends Fragment implements Callback {
-
- private static final String TAG = "QsTuner";
-
- private static final int MENU_RESET = Menu.FIRST;
-
- private DraggableQsPanel mQsPanel;
- private CustomHost mTileHost;
-
- private FrameLayout mDropTarget;
-
- private ScrollView mScrollRoot;
-
- private FrameLayout mAddTarget;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setHasOptionsMenu(true);
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- menu.add(0, MENU_RESET, 0, com.android.internal.R.string.reset);
- }
-
- public void onResume() {
- super.onResume();
- MetricsLogger.visibility(getContext(), MetricsLogger.TUNER_QS, true);
- }
-
- public void onPause() {
- super.onPause();
- MetricsLogger.visibility(getContext(), MetricsLogger.TUNER_QS, false);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case MENU_RESET:
- mTileHost.reset();
- break;
- case android.R.id.home:
- getFragmentManager().popBackStack();
- break;
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mScrollRoot = (ScrollView) inflater.inflate(R.layout.tuner_qs, container, false);
-
- mQsPanel = new DraggableQsPanel(getContext());
- mTileHost = new CustomHost(getContext());
- mTileHost.setCallback(this);
- mQsPanel.setTiles(mTileHost.getTiles());
- mQsPanel.setHost(mTileHost);
- mQsPanel.refreshAllTiles();
- ((ViewGroup) mScrollRoot.findViewById(R.id.all_details)).addView(mQsPanel, 0);
-
- mDropTarget = (FrameLayout) mScrollRoot.findViewById(R.id.remove_target);
- setupDropTarget();
- mAddTarget = (FrameLayout) mScrollRoot.findViewById(R.id.add_target);
- setupAddTarget();
- return mScrollRoot;
- }
-
- @Override
- public void onDestroyView() {
- mTileHost.destroy();
- super.onDestroyView();
- }
-
- private void setupDropTarget() {
- QSTileView tileView = new QSTileView(getContext());
- QSTile.State state = new QSTile.State();
- state.visible = true;
- state.icon = ResourceIcon.get(R.drawable.ic_delete);
- state.label = getString(com.android.internal.R.string.delete);
- tileView.onStateChanged(state);
- mDropTarget.addView(tileView);
- mDropTarget.setVisibility(View.GONE);
- new DragHelper(tileView, new DropListener() {
- @Override
- public void onDrop(String sourceText) {
- mTileHost.remove(sourceText);
- }
- });
- }
-
- private void setupAddTarget() {
- QSTileView tileView = new QSTileView(getContext());
- QSTile.State state = new QSTile.State();
- state.visible = true;
- state.icon = ResourceIcon.get(R.drawable.ic_add_circle_qs);
- state.label = getString(R.string.add_tile);
- tileView.onStateChanged(state);
- mAddTarget.addView(tileView);
- tileView.setClickable(true);
- tileView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mTileHost.showAddDialog();
- }
- });
- }
-
- public void onStartDrag() {
- mDropTarget.post(new Runnable() {
- @Override
- public void run() {
- mDropTarget.setVisibility(View.VISIBLE);
- mAddTarget.setVisibility(View.GONE);
- }
- });
- }
-
- public void stopDrag() {
- mDropTarget.post(new Runnable() {
- @Override
- public void run() {
- mDropTarget.setVisibility(View.GONE);
- mAddTarget.setVisibility(View.VISIBLE);
- }
- });
- }
-
- @Override
- public void onTilesChanged() {
- mQsPanel.setTiles(mTileHost.getTiles());
- }
-
- private static int getLabelResource(String spec) {
- if (spec.equals("wifi")) return R.string.quick_settings_wifi_label;
- else if (spec.equals("bt")) return R.string.quick_settings_bluetooth_label;
- else if (spec.equals("inversion")) return R.string.quick_settings_inversion_label;
- else if (spec.equals("cell")) return R.string.quick_settings_cellular_detail_title;
- else if (spec.equals("airplane")) return R.string.airplane_mode;
- else if (spec.equals("dnd")) return R.string.quick_settings_dnd_label;
- else if (spec.equals("rotation")) return R.string.quick_settings_rotation_locked_label;
- else if (spec.equals("flashlight")) return R.string.quick_settings_flashlight_label;
- else if (spec.equals("location")) return R.string.quick_settings_location_label;
- else if (spec.equals("cast")) return R.string.quick_settings_cast_title;
- else if (spec.equals("hotspot")) return R.string.quick_settings_hotspot_label;
- return 0;
- }
-
- private static class CustomHost extends QSTileHost {
-
- public CustomHost(Context context) {
- super(context, null, null, null, null, null, null, null, null, null, null,
- null, null, new BlankSecurityController(), null);
- }
-
- @Override
- public QSTile<?> createTile(String tileSpec) {
- return new DraggableTile(this, tileSpec);
- }
-
- public void replace(String oldTile, String newTile) {
- if (oldTile.equals(newTile)) {
- return;
- }
- MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REORDER, oldTile + ","
- + newTile);
- List<String> order = new ArrayList<>(mTileSpecs);
- int index = order.indexOf(oldTile);
- if (index < 0) {
- Log.e(TAG, "Can't find " + oldTile);
- return;
- }
- order.remove(newTile);
- order.add(index, newTile);
- setTiles(order);
- }
-
- public void remove(String tile) {
- MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REMOVE, tile);
- List<String> tiles = new ArrayList<>(mTileSpecs);
- tiles.remove(tile);
- setTiles(tiles);
- }
-
- public void add(String tile) {
- MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_ADD, tile);
- List<String> tiles = new ArrayList<>(mTileSpecs);
- tiles.add(tile);
- setTiles(tiles);
- }
-
- public void reset() {
- Secure.putStringForUser(getContext().getContentResolver(),
- TILES_SETTING, "default", ActivityManager.getCurrentUser());
- }
-
- private void setTiles(List<String> tiles) {
- Secure.putStringForUser(getContext().getContentResolver(), TILES_SETTING,
- TextUtils.join(",", tiles), ActivityManager.getCurrentUser());
- }
-
- public void showAddDialog() {
- List<String> tiles = mTileSpecs;
- int numBroadcast = 0;
- for (int i = 0; i < tiles.size(); i++) {
- if (tiles.get(i).startsWith(IntentTile.PREFIX)) {
- numBroadcast++;
- }
- }
- String[] defaults =
- getContext().getString(R.string.quick_settings_tiles_default).split(",");
- final String[] available = new String[defaults.length + 1
- - (tiles.size() - numBroadcast)];
- final String[] availableTiles = new String[available.length];
- int index = 0;
- for (int i = 0; i < defaults.length; i++) {
- if (tiles.contains(defaults[i])) {
- continue;
- }
- int resource = getLabelResource(defaults[i]);
- if (resource != 0) {
- availableTiles[index] = defaults[i];
- available[index++] = getContext().getString(resource);
- } else {
- availableTiles[index] = defaults[i];
- available[index++] = defaults[i];
- }
- }
- available[index++] = getContext().getString(R.string.broadcast_tile);
- new AlertDialog.Builder(getContext())
- .setTitle(R.string.add_tile)
- .setItems(available, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- if (which < available.length - 1) {
- add(availableTiles[which]);
- } else {
- showBroadcastTileDialog();
- }
- }
- }).show();
- }
-
- public void showBroadcastTileDialog() {
- final EditText editText = new EditText(getContext());
- new AlertDialog.Builder(getContext())
- .setTitle(R.string.broadcast_tile)
- .setView(editText)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- String action = editText.getText().toString();
- if (isValid(action)) {
- add(IntentTile.PREFIX + action + ')');
- }
- }
- }).show();
- }
-
- private boolean isValid(String action) {
- for (int i = 0; i < action.length(); i++) {
- char c = action.charAt(i);
- if (!Character.isAlphabetic(c) && !Character.isDigit(c) && c != '.') {
- return false;
- }
- }
- return true;
- }
-
- private static class BlankSecurityController implements SecurityController {
- @Override
- public boolean hasDeviceOwner() {
- return false;
- }
-
- @Override
- public boolean hasProfileOwner() {
- return false;
- }
-
- @Override
- public String getDeviceOwnerName() {
- return null;
- }
-
- @Override
- public String getProfileOwnerName() {
- return null;
- }
-
- @Override
- public boolean isVpnEnabled() {
- return false;
- }
-
- @Override
- public boolean isVpnRestricted() {
- return false;
- }
-
- @Override
- public String getPrimaryVpnName() {
- return null;
- }
-
- @Override
- public String getProfileVpnName() {
- return null;
- }
-
- @Override
- public void onUserSwitched(int newUserId) {
- }
-
- @Override
- public void addCallback(SecurityControllerCallback callback) {
- }
-
- @Override
- public void removeCallback(SecurityControllerCallback callback) {
- }
- }
- }
-
- private static class DraggableTile extends QSTile<QSTile.State>
- implements DropListener {
- private String mSpec;
- private QSTileBaseView mView;
-
- protected DraggableTile(QSTile.Host host, String tileSpec) {
- super(host);
- Log.d(TAG, "Creating tile " + tileSpec);
- mSpec = tileSpec;
- }
-
- @Override
- public QSTileBaseView createTileView(Context context) {
- mView = super.createTileView(context);
- return mView;
- }
-
- @Override
- public int getTileType() {
- return "wifi".equals(mSpec) || "bt".equals(mSpec) ? QSTileView.QS_TYPE_DUAL
- : QSTileView.QS_TYPE_NORMAL;
- }
-
- @Override
- public void setListening(boolean listening) {
- }
-
- @Override
- protected QSTile.State newTileState() {
- return new QSTile.State();
- }
-
- @Override
- protected void handleClick() {
- }
-
- @Override
- protected void handleUpdateState(QSTile.State state, Object arg) {
- state.visible = true;
- state.icon = ResourceIcon.get(getIcon());
- state.label = getLabel();
- }
-
- private String getLabel() {
- int resource = getLabelResource(mSpec);
- if (resource != 0) {
- return mContext.getString(resource);
- }
- if (mSpec.startsWith(IntentTile.PREFIX)) {
- int lastDot = mSpec.lastIndexOf('.');
- if (lastDot >= 0) {
- return mSpec.substring(lastDot + 1, mSpec.length() - 1);
- } else {
- return mSpec.substring(IntentTile.PREFIX.length(), mSpec.length() - 1);
- }
- }
- return mSpec;
- }
-
- private int getIcon() {
- if (mSpec.equals("wifi")) return R.drawable.ic_qs_wifi_full_3;
- else if (mSpec.equals("bt")) return R.drawable.ic_qs_bluetooth_connected;
- else if (mSpec.equals("inversion")) return R.drawable.ic_invert_colors_enable;
- else if (mSpec.equals("cell")) return R.drawable.ic_qs_signal_full_3;
- else if (mSpec.equals("airplane")) return R.drawable.ic_signal_airplane_enable;
- else if (mSpec.equals("dnd")) return R.drawable.ic_qs_dnd_on;
- else if (mSpec.equals("rotation")) return R.drawable.ic_portrait_from_auto_rotate;
- else if (mSpec.equals("flashlight")) return R.drawable.ic_signal_flashlight_enable;
- else if (mSpec.equals("location")) return R.drawable.ic_signal_location_enable;
- else if (mSpec.equals("cast")) return R.drawable.ic_qs_cast_on;
- else if (mSpec.equals("hotspot")) return R.drawable.ic_hotspot_enable;
- return R.drawable.android;
- }
-
- @Override
- public int getMetricsCategory() {
- return 20000;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof DraggableTile) {
- return mSpec.equals(((DraggableTile) o).mSpec);
- }
- return false;
- }
-
- @Override
- public void onDrop(String sourceText) {
- ((CustomHost) mHost).replace(mSpec, sourceText);
- }
-
- }
-
- private class DragHelper implements OnDragListener {
-
- private final View mView;
- private final DropListener mListener;
-
- public DragHelper(View view, DropListener dropListener) {
- mView = view;
- mListener = dropListener;
- mView.setOnDragListener(this);
- }
-
- @Override
- public boolean onDrag(View v, DragEvent event) {
- switch (event.getAction()) {
- case DragEvent.ACTION_DRAG_ENTERED:
- mView.setBackgroundColor(0x77ffffff);
- break;
- case DragEvent.ACTION_DRAG_ENDED:
- stopDrag();
- case DragEvent.ACTION_DRAG_EXITED:
- mView.setBackgroundColor(0x0);
- break;
- case DragEvent.ACTION_DROP:
- stopDrag();
- String text = event.getClipData().getItemAt(0).getText().toString();
- mListener.onDrop(text);
- break;
- }
- return true;
- }
-
- }
-
- public interface DropListener {
- void onDrop(String sourceText);
- }
-
- private class DraggableQsPanel extends QSPanel implements OnTouchListener {
- public DraggableQsPanel(Context context) {
- super(context);
- mBrightnessView.setVisibility(View.GONE);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- for (TileRecord r : mRecords) {
- new DragHelper(r.tileView, (DraggableTile) r.tile);
- r.tileView.setTag(r.tile);
- r.tileView.setOnTouchListener(this);
-
- for (int i = 0; i < r.tileView.getChildCount(); i++) {
- r.tileView.getChildAt(i).setClickable(false);
- }
- }
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- String tileSpec = (String) ((DraggableTile) v.getTag()).mSpec;
- ClipData data = ClipData.newPlainText(tileSpec, tileSpec);
- v.startDrag(data, new View.DragShadowBuilder(v), null, 0);
- onStartDrag();
- return true;
- }
- return false;
- }
- }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index dc7c967..b620b50b 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -59,8 +59,6 @@
private SwitchPreference mBatteryPct;
- private Preference mQsTuner;
-
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -68,17 +66,6 @@
getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
setHasOptionsMenu(true);
- mQsTuner = findPreference(KEY_QS_TUNER);
- mQsTuner.setOnPreferenceClickListener(new OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- FragmentTransaction ft = getFragmentManager().beginTransaction();
- ft.replace(android.R.id.content, new QsTuner(), "QsTuner");
- ft.addToBackStack(null);
- ft.commit();
- return true;
- }
- });
findPreference(KEY_DEMO_MODE).setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
@@ -96,13 +83,6 @@
new TunerWarningFragment().show(getFragmentManager(), WARNING_TAG);
}
}
- TunerService.get(getContext()).addTunable(mQsPaging, QSPanel.QS_THE_NEW_QS);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- TunerService.get(getContext()).removeTunable(mQsPaging);
}
@Override
@@ -175,14 +155,6 @@
}
};
- private final Tunable mQsPaging = new Tunable() {
- @Override
- public void onTuningChanged(String key, String newValue) {
- // Only enable QS rearranging when paging is off, because its very broken.
- mQsTuner.setEnabled(newValue == null || Integer.parseInt(newValue) == 0);
- }
- };
-
public static class TunerWarningFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
diff --git a/preloaded-classes b/preloaded-classes
index d6b4ec9..e48255c 100644
--- a/preloaded-classes
+++ b/preloaded-classes
@@ -1566,6 +1566,7 @@
android.security.keystore.AndroidKeyStoreKey
android.security.keystore.AndroidKeyStoreProvider
android.security.keystore.KeyStoreCryptoOperation
+android.security.net.config.NetworkSecurityConfigProvider
android.service.persistentdata.PersistentDataBlockManager
android.system.ErrnoException
android.system.GaiException
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index c131628..a5cef1a 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -47,7 +47,9 @@
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCommand;
import android.os.UserHandle;
import android.os.storage.MountServiceInternal;
import android.util.ArrayMap;
@@ -1554,15 +1556,300 @@
}
}
- private void dumpHelp(PrintWriter pw) {
- pw.println("AppOps service (appops) dump options:");
- pw.println(" [-h] [CMD]");
- pw.println(" -h: print this help text.");
- pw.println("Commands:");
+ static class Shell extends ShellCommand {
+ final IAppOpsService mInterface;
+ final AppOpsService mInternal;
+
+ int userId = UserHandle.USER_SYSTEM;
+ String packageName;
+ String opStr;
+ int op;
+ int packageUid;
+
+ Shell(IAppOpsService iface, AppOpsService internal) {
+ mInterface = iface;
+ mInternal = internal;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ return onShellCommand(this, cmd);
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ dumpCommandHelp(pw);
+ }
+
+ private int strOpToOp(String op, PrintWriter err) {
+ try {
+ return AppOpsManager.strOpToOp(op);
+ } catch (IllegalArgumentException e) {
+ }
+ try {
+ return Integer.parseInt(op);
+ } catch (NumberFormatException e) {
+ }
+ try {
+ return AppOpsManager.strDebugOpToOp(op);
+ } catch (IllegalArgumentException e) {
+ err.println("Error: " + e.getMessage());
+ return -1;
+ }
+ }
+
+ int parseUserPackageOp(boolean reqOp, PrintWriter err) throws RemoteException {
+ userId = UserHandle.USER_CURRENT;
+ packageName = null;
+ opStr = null;
+ for (String argument; (argument = getNextArg()) != null;) {
+ if ("--user".equals(argument)) {
+ userId = UserHandle.parseUserArg(getNextArgRequired());
+ } else {
+ if (packageName == null) {
+ packageName = argument;
+ } else if (opStr == null) {
+ opStr = argument;
+ break;
+ }
+ }
+ }
+ if (packageName == null) {
+ err.println("Error: Package name not specified.");
+ return -1;
+ } else if (opStr == null && reqOp) {
+ err.println("Error: Operation not specified.");
+ return -1;
+ }
+ if (opStr != null) {
+ op = strOpToOp(opStr, err);
+ if (op < 0) {
+ return -1;
+ }
+ } else {
+ op = AppOpsManager.OP_NONE;
+ }
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = ActivityManager.getCurrentUser();
+ }
+ if ("root".equals(packageName)) {
+ packageUid = 0;
+ } else {
+ packageUid = AppGlobals.getPackageManager().getPackageUid(packageName, userId);
+ }
+ if (packageUid < 0) {
+ err.println("Error: No UID for " + packageName + " in user " + userId);
+ return -1;
+ }
+ return 0;
+ }
+ }
+
+ @Override public void onShellCommand(FileDescriptor in, FileDescriptor out,
+ FileDescriptor err, String[] args, ResultReceiver resultReceiver) {
+ (new Shell(this, this)).exec(this, in, out, err, args, resultReceiver);
+ }
+
+ static void dumpCommandHelp(PrintWriter pw) {
+ pw.println("AppOps service (appops) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" set [--user <USER_ID>] <PACKAGE> <OP> <MODE>");
+ pw.println(" Set the mode for a particular application and operation.");
+ pw.println(" get [--user <USER_ID>] <PACKAGE> [<OP>]");
+ pw.println(" Return the mode for a particular application and optional operation.");
+ pw.println(" reset [--user <USER_ID>] [<PACKAGE>]");
+ pw.println(" Reset the given application or all applications to default modes.");
pw.println(" write-settings");
pw.println(" Immediately write pending changes to storage.");
pw.println(" read-settings");
pw.println(" Read the last written settings, replacing current state in RAM.");
+ pw.println(" options:");
+ pw.println(" <PACKAGE> an Android package name.");
+ pw.println(" <OP> an AppOps operation.");
+ pw.println(" <MODE> one of allow, ignore, deny, or default");
+ pw.println(" <USER_ID> the user id under which the package is installed. If --user is not");
+ pw.println(" specified, the current user is assumed.");
+ }
+
+ static int onShellCommand(Shell shell, String cmd) {
+ if (cmd == null) {
+ return shell.handleDefaultCommands(cmd);
+ }
+ PrintWriter pw = shell.getOutPrintWriter();
+ PrintWriter err = shell.getErrPrintWriter();
+ try {
+ switch (cmd) {
+ case "set": {
+ int res = shell.parseUserPackageOp(true, err);
+ if (res < 0) {
+ return res;
+ }
+ String modeStr = shell.getNextArg();
+ if (modeStr == null) {
+ err.println("Error: Mode not specified.");
+ return -1;
+ }
+
+ final int mode;
+ switch (modeStr) {
+ case "allow":
+ mode = AppOpsManager.MODE_ALLOWED;
+ break;
+ case "deny":
+ mode = AppOpsManager.MODE_ERRORED;
+ break;
+ case "ignore":
+ mode = AppOpsManager.MODE_IGNORED;
+ break;
+ case "default":
+ mode = AppOpsManager.MODE_DEFAULT;
+ break;
+ default:
+ err.println("Error: Mode " + modeStr + " is not valid,");
+ return -1;
+ }
+
+ shell.mInterface.setMode(shell.op, shell.packageUid, shell.packageName, mode);
+ return 0;
+ }
+ case "get": {
+ int res = shell.parseUserPackageOp(false, err);
+ if (res < 0) {
+ return res;
+ }
+
+ List<AppOpsManager.PackageOps> ops = shell.mInterface.getOpsForPackage(
+ shell.packageUid, shell.packageName,
+ shell.op != AppOpsManager.OP_NONE ? new int[] {shell.op} : null);
+ if (ops == null || ops.size() <= 0) {
+ pw.println("No operations.");
+ return 0;
+ }
+ final long now = System.currentTimeMillis();
+ for (int i=0; i<ops.size(); i++) {
+ List<AppOpsManager.OpEntry> entries = ops.get(i).getOps();
+ for (int j=0; j<entries.size(); j++) {
+ AppOpsManager.OpEntry ent = entries.get(j);
+ pw.print(AppOpsManager.opToName(ent.getOp()));
+ pw.print(": ");
+ switch (ent.getMode()) {
+ case AppOpsManager.MODE_ALLOWED:
+ pw.print("allow");
+ break;
+ case AppOpsManager.MODE_IGNORED:
+ pw.print("ignore");
+ break;
+ case AppOpsManager.MODE_ERRORED:
+ pw.print("deny");
+ break;
+ case AppOpsManager.MODE_DEFAULT:
+ pw.print("default");
+ break;
+ default:
+ pw.print("mode=");
+ pw.print(ent.getMode());
+ break;
+ }
+ if (ent.getTime() != 0) {
+ pw.print("; time=");
+ TimeUtils.formatDuration(now - ent.getTime(), pw);
+ pw.print(" ago");
+ }
+ if (ent.getRejectTime() != 0) {
+ pw.print("; rejectTime=");
+ TimeUtils.formatDuration(now - ent.getRejectTime(), pw);
+ pw.print(" ago");
+ }
+ if (ent.getDuration() == -1) {
+ pw.print(" (running)");
+ } else if (ent.getDuration() != 0) {
+ pw.print("; duration=");
+ TimeUtils.formatDuration(ent.getDuration(), pw);
+ }
+ pw.println();
+ }
+ }
+ return 0;
+ }
+ case "reset": {
+ String packageName = null;
+ int userId = UserHandle.USER_CURRENT;
+ for (String argument; (argument = shell.getNextArg()) != null;) {
+ if ("--user".equals(argument)) {
+ String userStr = shell.getNextArgRequired();
+ userId = UserHandle.parseUserArg(userStr);
+ } else {
+ if (packageName == null) {
+ packageName = argument;
+ } else {
+ err.println("Error: Unsupported argument: " + argument);
+ return -1;
+ }
+ }
+ }
+
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = ActivityManager.getCurrentUser();
+ }
+
+ shell.mInterface.resetAllModes(userId, packageName);
+ pw.print("Reset all modes for: ");
+ if (userId == UserHandle.USER_ALL) {
+ pw.print("all users");
+ } else {
+ pw.print("user "); pw.print(userId);
+ }
+ pw.print(", ");
+ if (packageName == null) {
+ pw.println("all packages");
+ } else {
+ pw.print("package "); pw.println(packageName);
+ }
+ return 0;
+ }
+ case "write-settings": {
+ shell.mInternal.mContext.enforcePermission(
+ android.Manifest.permission.UPDATE_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (shell.mInternal) {
+ shell.mInternal.mHandler.removeCallbacks(shell.mInternal.mWriteRunner);
+ }
+ shell.mInternal.writeState();
+ pw.println("Current settings written.");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return 0;
+ }
+ case "read-settings": {
+ shell.mInternal.mContext.enforcePermission(
+ android.Manifest.permission.UPDATE_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ long token = Binder.clearCallingIdentity();
+ try {
+ shell.mInternal.readState();
+ pw.println("Last settings read.");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return 0;
+ }
+ default:
+ return shell.handleDefaultCommands(cmd);
+ }
+ } catch (RemoteException e) {
+ pw.println("Remote exception: " + e);
+ }
+ return -1;
+ }
+
+ private void dumpHelp(PrintWriter pw) {
+ pw.println("AppOps service (appops) dump options:");
+ pw.println(" none");
}
@Override
@@ -1583,27 +1870,6 @@
return;
} else if ("-a".equals(arg)) {
// dump all data
- } else if ("write-settings".equals(arg)) {
- long token = Binder.clearCallingIdentity();
- try {
- synchronized (this) {
- mHandler.removeCallbacks(mWriteRunner);
- }
- writeState();
- pw.println("Current settings written.");
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- return;
- } else if ("read-settings".equals(arg)) {
- long token = Binder.clearCallingIdentity();
- try {
- readState();
- pw.println("Last settings read.");
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- return;
} else if (arg.length() > 0 && arg.charAt(0) == '-'){
pw.println("Unknown option: " + arg);
return;
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 485e26b..f5ed83e 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -107,6 +107,8 @@
private static final boolean COMPRESS_TIME = false;
+ private static final int EVENT_BUFFER_SIZE = 40;
+
private static final String ACTION_STEP_IDLE_STATE =
"com.android.server.device_idle.STEP_IDLE_STATE";
@@ -196,6 +198,8 @@
private long mNextIdlePendingDelay;
private long mNextIdleDelay;
private long mNextLightAlarmTime;
+ private long mCurIdleBudget;
+ private long mMaintenanceStartTime;
private int mActiveIdleOpCount;
private IBinder mDownloadServiceActive;
@@ -274,6 +278,25 @@
*/
private int[] mTempWhitelistAppIdArray = new int[0];
+ private static final int EVENT_NULL = 0;
+ private static final int EVENT_NORMAL = 1;
+ private static final int EVENT_LIGHT_IDLE = 2;
+ private static final int EVENT_LIGHT_MAINTENANCE = 3;
+ private static final int EVENT_FULL_IDLE = 4;
+ private static final int EVENT_FULL_MAINTENANCE = 5;
+
+ private int[] mEventCmds = new int[EVENT_BUFFER_SIZE];
+ private long[] mEventTimes = new long[EVENT_BUFFER_SIZE];
+
+ private void addEvent(int cmd) {
+ if (mEventCmds[0] != cmd) {
+ System.arraycopy(mEventCmds, 0, mEventCmds, 1, EVENT_BUFFER_SIZE - 1);
+ System.arraycopy(mEventTimes, 0, mEventTimes, 1, EVENT_BUFFER_SIZE - 1);
+ mEventCmds[0] = cmd;
+ mEventTimes[0] = SystemClock.elapsedRealtime();
+ }
+ }
+
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
@@ -424,7 +447,10 @@
private final class Constants extends ContentObserver {
// Key names stored in the settings value.
private static final String KEY_LIGHT_IDLE_TIMEOUT = "light_idle_to";
- private static final String KEY_LIGHT_IDLE_PENDING_TIMEOUT = "light_idle_pending_to";
+ private static final String KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET
+ = "light_idle_maintenance_min_budget";
+ private static final String KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET
+ = "light_idle_maintenance_max_budget";
private static final String KEY_INACTIVE_TIMEOUT = "inactive_to";
private static final String KEY_SENSING_TIMEOUT = "sensing_to";
private static final String KEY_LOCATING_TIMEOUT = "locating_to";
@@ -454,12 +480,24 @@
public long LIGHT_IDLE_TIMEOUT;
/**
- * This is the initial time, after light idle idle, that we will will sit in the
- * LIGHT_IDLE_MAINTENANCE period for the system to run normally before returning to idle.
+ * This is the minimum amount of time we want to make available for maintenance mode
+ * when lightly idling. That is, we will always have at least this amount of time
+ * available maintenance before timing out and cutting off maintenance mode.
* @see Settings.Global#DEVICE_IDLE_CONSTANTS
- * @see #KEY_LIGHT_IDLE_PENDING_TIMEOUT
+ * @see #KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET
*/
- public long LIGHT_IDLE_PENDING_TIMEOUT;
+ public long LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
+
+ /**
+ * This is the maximum amount of time we want to make available for maintenance mode
+ * when lightly idling. That is, if the system isn't using up its minimum maintenance
+ * budget and this time is being added to the budget reserve, this is the maximum
+ * reserve size we will allow to grow and thus the maximum amount of time we will
+ * allow for the maintenance window.
+ * @see Settings.Global#DEVICE_IDLE_CONSTANTS
+ * @see #KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET
+ */
+ public long LIGHT_IDLE_MAINTENANCE_MAX_BUDGET;
/**
* This is the time, after becoming inactive, at which we start looking at the
@@ -619,8 +657,12 @@
LIGHT_IDLE_TIMEOUT = mParser.getLong(KEY_LIGHT_IDLE_TIMEOUT,
!COMPRESS_TIME ? 15 * 60 * 1000L : 60 * 1000L);
- LIGHT_IDLE_PENDING_TIMEOUT = mParser.getLong(KEY_LIGHT_IDLE_PENDING_TIMEOUT,
+ LIGHT_IDLE_MAINTENANCE_MIN_BUDGET = mParser.getLong(
+ KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET,
!COMPRESS_TIME ? 1 * 60 * 1000L : 15 * 1000L);
+ LIGHT_IDLE_MAINTENANCE_MAX_BUDGET = mParser.getLong(
+ KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET,
+ !COMPRESS_TIME ? 5 * 60 * 1000L : 30 * 1000L);
INACTIVE_TIMEOUT = mParser.getLong(KEY_INACTIVE_TIMEOUT,
!COMPRESS_TIME ? 30 * 60 * 1000L : 3 * 60 * 1000L);
SENSING_TIMEOUT = mParser.getLong(KEY_SENSING_TIMEOUT,
@@ -662,8 +704,12 @@
TimeUtils.formatDuration(LIGHT_IDLE_TIMEOUT, pw);
pw.println();
- pw.print(" "); pw.print(KEY_LIGHT_IDLE_PENDING_TIMEOUT); pw.print("=");
- TimeUtils.formatDuration(LIGHT_IDLE_PENDING_TIMEOUT, pw);
+ pw.print(" "); pw.print(KEY_LIGHT_IDLE_MAINTENANCE_MIN_BUDGET); pw.print("=");
+ TimeUtils.formatDuration(LIGHT_IDLE_MAINTENANCE_MIN_BUDGET, pw);
+ pw.println();
+
+ pw.print(" "); pw.print(KEY_LIGHT_IDLE_MAINTENANCE_MAX_BUDGET); pw.print("=");
+ TimeUtils.formatDuration(LIGHT_IDLE_MAINTENANCE_MAX_BUDGET, pw);
pw.println();
pw.print(" "); pw.print(KEY_INACTIVE_TIMEOUT); pw.print("=");
@@ -1435,8 +1481,11 @@
mState = STATE_ACTIVE;
mLightState = LIGHT_STATE_ACTIVE;
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
+ mCurIdleBudget = 0;
+ mMaintenanceStartTime = 0;
resetIdleManagementLocked();
resetLightIdleManagementLocked();
+ addEvent(EVENT_NORMAL);
}
}
@@ -1496,21 +1545,43 @@
switch (mLightState) {
case LIGHT_STATE_INACTIVE:
+ mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
+ mMaintenanceStartTime = 0;
case LIGHT_STATE_IDLE_MAINTENANCE:
+ if (mMaintenanceStartTime != 0) {
+ long duration = SystemClock.elapsedRealtime() - mMaintenanceStartTime;
+ if (duration < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
+ // We didn't use up all of our minimum budget; add this to the reserve.
+ mCurIdleBudget += (mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET-duration);
+ } else {
+ // We used more than our minimum budget; this comes out of the reserve.
+ mCurIdleBudget -= (duration-mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET);
+ }
+ }
+ mMaintenanceStartTime = 0;
scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_TIMEOUT);
if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
mLightState = LIGHT_STATE_IDLE;
EventLogTags.writeDeviceIdleLight(mLightState, reason);
+ addEvent(EVENT_LIGHT_IDLE);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);
break;
case LIGHT_STATE_IDLE:
// We have been idling long enough, now it is time to do some work.
mActiveIdleOpCount = 1;
- scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_PENDING_TIMEOUT);
+ mMaintenanceStartTime = SystemClock.elapsedRealtime();
+ if (mCurIdleBudget < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
+ mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
+ } else if (mCurIdleBudget > mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET) {
+ mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET;
+ }
+ mMaintenanceStartTime = SystemClock.elapsedRealtime();
+ scheduleLightAlarmLocked(mCurIdleBudget);
if (DEBUG) Slog.d(TAG,
"Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE.");
mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
EventLogTags.writeDeviceIdleLight(mLightState, reason);
+ addEvent(EVENT_LIGHT_MAINTENANCE);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
break;
}
@@ -1600,6 +1671,7 @@
cancelLightAlarmLocked();
}
EventLogTags.writeDeviceIdle(mState, reason);
+ addEvent(EVENT_FULL_IDLE);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
break;
case STATE_IDLE:
@@ -1612,6 +1684,7 @@
(long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
mState = STATE_IDLE_MAINTENANCE;
EventLogTags.writeDeviceIdle(mState, reason);
+ addEvent(EVENT_FULL_MAINTENANCE);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
break;
}
@@ -1709,7 +1782,10 @@
scheduleReportActiveLocked(type, Process.myUid());
mState = STATE_ACTIVE;
mInactiveTimeout = timeout;
+ mCurIdleBudget = 0;
+ mMaintenanceStartTime = 0;
EventLogTags.writeDeviceIdle(mState, type);
+ addEvent(EVENT_NORMAL);
becomeInactive = true;
}
if (mLightState == LIGHT_STATE_OVERRIDE) {
@@ -2016,6 +2092,8 @@
pw.println(" Print this help text.");
pw.println(" step");
pw.println(" Immediately step to next state, without waiting for alarm.");
+ pw.println(" light-step");
+ pw.println(" Immediately step to next light idle state, without waiting for alarm.");
pw.println(" force-idle");
pw.println(" Force directly into idle mode, regardless of other device state.");
pw.println(" Use \"step\" to get out.");
@@ -2262,6 +2340,31 @@
synchronized (this) {
mConstants.dump(pw);
+ if (mEventCmds[0] != EVENT_NULL) {
+ pw.println(" Idling history:");
+ long now = SystemClock.elapsedRealtime();
+ for (int i=EVENT_BUFFER_SIZE-1; i>=0; i--) {
+ int cmd = mEventCmds[i];
+ if (cmd == EVENT_NULL) {
+ continue;
+ }
+ String label;
+ switch (mEventCmds[i]) {
+ case EVENT_NORMAL: label = " normal"; break;
+ case EVENT_LIGHT_IDLE: label = " light-idle"; break;
+ case EVENT_LIGHT_MAINTENANCE: label = "light-maint"; break;
+ case EVENT_FULL_IDLE: label = " full-idle"; break;
+ case EVENT_FULL_MAINTENANCE: label = " full-maint"; break;
+ default: label = " ??"; break;
+ }
+ pw.print(" ");
+ pw.print(label);
+ pw.print(": ");
+ TimeUtils.formatDuration(mEventTimes[i], now, pw);;
+ pw.println();
+ }
+ }
+
int size = mPowerSaveWhitelistAppsExceptIdle.size();
if (size > 0) {
pw.println(" Whitelist (except idle) system apps:");
@@ -2373,6 +2476,16 @@
TimeUtils.formatDuration(mNextLightAlarmTime, SystemClock.elapsedRealtime(), pw);
pw.println();
}
+ if (mCurIdleBudget != 0) {
+ pw.print(" mCurIdleBudget=");
+ TimeUtils.formatDuration(mCurIdleBudget, pw);
+ pw.println();
+ }
+ if (mMaintenanceStartTime != 0) {
+ pw.print(" mMaintenanceStartTime=");
+ TimeUtils.formatDuration(mMaintenanceStartTime, SystemClock.elapsedRealtime(), pw);
+ pw.println();
+ }
if (mSyncActive) {
pw.print(" mSyncActive="); pw.println(mSyncActive);
}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index ae9ddc0..5e4f2b2 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -309,11 +309,18 @@
ClientState mCurClient;
/**
- * The last window token that gained focus.
+ * The last window token that we confirmed to be focused. This is always updated upon reports
+ * from the input method client. If the window state is already changed before the report is
+ * handled, this field just keeps the last value.
*/
IBinder mCurFocusedWindow;
/**
+ * The client by which {@link #mCurFocusedWindow} was reported. Used only for debugging.
+ */
+ ClientState mCurFocusedWindowClient;
+
+ /**
* The input context last provided by the current client.
*/
IInputContext mCurInputContext;
@@ -1196,6 +1203,12 @@
ClientState cs = mClients.remove(client.asBinder());
if (cs != null) {
clearClientSessionLocked(cs);
+ if (mCurClient == cs) {
+ mCurClient = null;
+ }
+ if (mCurFocusedWindowClient == cs) {
+ mCurFocusedWindowClient = null;
+ }
}
}
}
@@ -2191,6 +2204,7 @@
return null;
}
mCurFocusedWindow = windowToken;
+ mCurFocusedWindowClient = cs;
// Should we auto-show the IME even if the caller has not
// specified what should be done with it?
@@ -3702,6 +3716,7 @@
IInputMethod method;
ClientState client;
+ ClientState focusedWindowClient;
final Printer p = new PrintWriterPrinter(pw);
@@ -3726,6 +3741,8 @@
client = mCurClient;
p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq);
p.println(" mCurFocusedWindow=" + mCurFocusedWindow);
+ focusedWindowClient = mCurFocusedWindowClient;
+ p.println(" mCurFocusedWindowClient=" + focusedWindowClient);
p.println(" mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
+ " mBoundToMethod=" + mBoundToMethod);
p.println(" mCurToken=" + mCurToken);
@@ -3757,6 +3774,20 @@
p.println("No input method client.");
}
+ if (focusedWindowClient != null && client != focusedWindowClient) {
+ p.println(" ");
+ p.println("Warning: Current input method client doesn't match the last focused. "
+ + "window.");
+ p.println("Dumping input method client in the last focused window just in case.");
+ p.println(" ");
+ pw.flush();
+ try {
+ focusedWindowClient.client.asBinder().dump(fd, args);
+ } catch (RemoteException e) {
+ p.println("Input method client in focused window dead: " + e);
+ }
+ }
+
p.println(" ");
if (method != null) {
pw.flush();
diff --git a/services/core/java/com/android/server/LockSettingsStorage.java b/services/core/java/com/android/server/LockSettingsStorage.java
index 6acec6b..eb49a78 100644
--- a/services/core/java/com/android/server/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/LockSettingsStorage.java
@@ -389,7 +389,7 @@
private int getUserParentOrSelfId(int userId) {
// Device supports per user encryption, so lock is applied to the given user.
- if (mContext.getSystemService(StorageManager.class).isPerUserEncryptionEnabled()) {
+ if (StorageManager.isFileBasedEncryptionEnabled()) {
return userId;
}
// Device uses Block Based Encryption, and the parent user's lock is used for the whole
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index f89155d..a32bb2f 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -1904,16 +1904,18 @@
enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
waitForReady();
- synchronized (mLock) {
- if ((mask & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0) {
- mForceAdoptable = (flags & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0;
- }
- if ((mask & StorageManager.DEBUG_EMULATE_FBE) != 0) {
- // TODO: persist through vold and reboot
- }
+ if ((mask & StorageManager.DEBUG_EMULATE_FBE) != 0) {
+ final boolean emulateFbe = (flags & StorageManager.DEBUG_EMULATE_FBE) != 0;
+ SystemProperties.set(StorageManager.PROP_EMULATE_FBE, Boolean.toString(emulateFbe));
+ }
- writeSettingsLocked();
- mHandler.obtainMessage(H_RESET).sendToTarget();
+ if ((mask & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0) {
+ synchronized (mLock) {
+ mForceAdoptable = (flags & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0;
+
+ writeSettingsLocked();
+ mHandler.obtainMessage(H_RESET).sendToTarget();
+ }
}
}
@@ -2738,7 +2740,7 @@
@Override
public boolean isUserKeyUnlocked(int userId) {
- if (SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false)) {
+ if (StorageManager.isFileBasedEncryptionEnabled()) {
synchronized (mLock) {
return ArrayUtils.contains(mUnlockedUsers, userId);
}
@@ -2761,14 +2763,6 @@
}
@Override
- public boolean isPerUserEncryptionEnabled() {
- // TODO: switch this over to a single property; currently using two to
- // handle the emulated case
- return "file".equals(SystemProperties.get("ro.crypto.type", "none"))
- || SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false);
- }
-
- @Override
public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException {
// TODO: Invoke vold to mount app fuse.
throw new UnsupportedOperationException();
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 9ac4ba3..37d47c3 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -88,6 +88,7 @@
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
@@ -114,6 +115,7 @@
public class AccountManagerService
extends IAccountManager.Stub
implements RegisteredServicesCacheListener<AuthenticatorDescription> {
+
private static final String TAG = "AccountManagerService";
private static final String DATABASE_NAME = "accounts.db";
@@ -2283,6 +2285,193 @@
}
}
+ @Override
+ public void startAddAccountSession(final IAccountManagerResponse response, final String accountType,
+ final String authTokenType, final String[] requiredFeatures,
+ final boolean expectActivityLaunch,
+ final Bundle optionsIn) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG,
+ "startAddAccountSession: accountType " + accountType
+ + ", response " + response
+ + ", authTokenType " + authTokenType
+ + ", requiredFeatures " + stringArrayToString(requiredFeatures)
+ + ", expectActivityLaunch " + expectActivityLaunch
+ + ", caller's uid " + Binder.getCallingUid()
+ + ", pid " + Binder.getCallingPid());
+ }
+ if (response == null) {
+ throw new IllegalArgumentException("response is null");
+ }
+ if (accountType == null) {
+ throw new IllegalArgumentException("accountType is null");
+ }
+
+ int userId = Binder.getCallingUserHandle().getIdentifier();
+ if (!canUserModifyAccounts(userId)) {
+ try {
+ response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED,
+ "User is not allowed to add an account!");
+ } catch (RemoteException re) {
+ }
+ showCantAddAccount(AccountManager.ERROR_CODE_USER_RESTRICTED, userId);
+ return;
+ }
+ if (!canUserModifyAccountsForType(userId, accountType)) {
+ try {
+ response.onError(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE,
+ "User cannot modify accounts of this type (policy).");
+ } catch (RemoteException re) {
+ }
+ showCantAddAccount(AccountManager.ERROR_CODE_MANAGEMENT_DISABLED_FOR_ACCOUNT_TYPE,
+ userId);
+ return;
+ }
+
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn;
+ options.putInt(AccountManager.KEY_CALLER_UID, uid);
+ options.putInt(AccountManager.KEY_CALLER_PID, pid);
+
+ int usrId = UserHandle.getCallingUserId();
+ long identityToken = clearCallingIdentity();
+ try {
+ UserAccounts accounts = getUserAccounts(usrId);
+ logRecordWithUid(accounts, DebugDbHelper.ACTION_CALLED_START_ACCOUNT_ADD,
+ TABLE_ACCOUNTS, uid);
+ new StartAccountSession(accounts, response, accountType, expectActivityLaunch,
+ null /* accountName */, false /* authDetailsRequired */,
+ true /* updateLastAuthenticationTime */) {
+ @Override
+ public void run() throws RemoteException {
+ mAuthenticator.startAddAccountSession(this, mAccountType, authTokenType,
+ requiredFeatures, options);
+ }
+
+ @Override
+ protected String toDebugString(long now) {
+ String requiredFeaturesStr = TextUtils.join(",", requiredFeatures);
+ return super.toDebugString(now) + ", startAddAccountSession" + ", accountType "
+ + accountType + ", requiredFeatures "
+ + (requiredFeatures != null ? requiredFeaturesStr : null);
+ }
+ }.bind();
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ /** Session that will encrypt the KEY_ACCOUNT_SESSION_BUNDLE in result. */
+ private abstract class StartAccountSession extends Session {
+
+ public StartAccountSession(UserAccounts accounts, IAccountManagerResponse response,
+ String accountType, boolean expectActivityLaunch, String accountName,
+ boolean authDetailsRequired, boolean updateLastAuthenticationTime) {
+ super(accounts, response, accountType, expectActivityLaunch,
+ true /* stripAuthTokenFromResult */, accountName, authDetailsRequired,
+ updateLastAuthenticationTime);
+ }
+
+ @Override
+ public void onResult(Bundle result) {
+ mNumResults++;
+ Intent intent = null;
+
+ if (result != null
+ && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
+ /*
+ * The Authenticator API allows third party authenticators to
+ * supply arbitrary intents to other apps that they can run,
+ * this can be very bad when those apps are in the system like
+ * the System Settings.
+ */
+ int authenticatorUid = Binder.getCallingUid();
+ long bid = Binder.clearCallingIdentity();
+ try {
+ PackageManager pm = mContext.getPackageManager();
+ ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId);
+ int targetUid = resolveInfo.activityInfo.applicationInfo.uid;
+ if (PackageManager.SIGNATURE_MATCH != pm.checkSignatures(authenticatorUid,
+ targetUid)) {
+ throw new SecurityException("Activity to be started with KEY_INTENT must "
+ + "share Authenticator's signatures");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(bid);
+ }
+ }
+
+ IAccountManagerResponse response;
+ if (mExpectActivityLaunch && result != null
+ && result.containsKey(AccountManager.KEY_INTENT)) {
+ response = mResponse;
+ } else {
+ response = getResponseAndClose();
+ }
+ if (response == null) {
+ return;
+ }
+ if (result == null) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, getClass().getSimpleName() + " calling onError() on response "
+ + response);
+ }
+ sendErrorResponse(response, AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ "null bundle returned");
+ return;
+ }
+
+ if ((result.getInt(AccountManager.KEY_ERROR_CODE, -1) > 0) && (intent == null)) {
+ // All AccountManager error codes are greater
+ // than 0
+ sendErrorResponse(response, result.getInt(AccountManager.KEY_ERROR_CODE),
+ result.getString(AccountManager.KEY_ERROR_MESSAGE));
+ return;
+ }
+
+ // Strip auth token from result.
+ result.remove(AccountManager.KEY_AUTHTOKEN);
+
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG,
+ getClass().getSimpleName() + " calling onResult() on response " + response);
+ }
+
+ // Get the session bundle created by authenticator. The
+ // bundle contains data necessary for finishing the session
+ // later. The session bundle will be encrypted here and
+ // decrypted later when trying to finish the session.
+ Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
+ if (sessionBundle != null) {
+ String accountType = sessionBundle.getString(AccountManager.KEY_ACCOUNT_TYPE);
+ if (TextUtils.isEmpty(accountType)
+ && !mAccountType.equalsIgnoreCase(mAccountType)) {
+ Log.w(TAG, "Account type in session bundle doesn't match request.");
+ }
+ // Add accountType info to session bundle. This will
+ // override any value set by authenticator.
+ sessionBundle.putString(AccountManager.KEY_ACCOUNT_TYPE, mAccountType);
+
+ // Encrypt session bundle before returning to caller.
+ try {
+ CryptoHelper cryptoHelper = CryptoHelper.getInstance();
+ Bundle encryptedBundle = cryptoHelper.encryptBundle(sessionBundle);
+ result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, encryptedBundle);
+ } catch (GeneralSecurityException e) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.v(TAG, "Failed to encrypt session bundle!", e);
+ }
+ sendErrorResponse(response, AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ "failed to encrypt session bundle");
+ return;
+ }
+ }
+
+ sendResponse(response, result);
+ }
+ }
+
private void showCantAddAccount(int errorCode, int userId) {
Intent cantAddAccount = new Intent(mContext, CantAddAccountActivity.class);
cantAddAccount.putExtra(CantAddAccountActivity.EXTRA_ERROR_CODE, errorCode);
@@ -3336,6 +3525,11 @@
private static String ACTION_CALLED_ACCOUNT_ADD = "action_called_account_add";
private static String ACTION_CALLED_ACCOUNT_REMOVE = "action_called_account_remove";
+ // TODO: This action doesn't add account to accountdb. Account is only
+ // added in finishAddAccount or finishAddAccountAsUser which may be in
+ // a different user profile.
+ private static String ACTION_CALLED_START_ACCOUNT_ADD = "action_called_start_account_add";
+
private static SimpleDateFormat dateFromat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static void createDebugTable(SQLiteDatabase db) {
@@ -4300,4 +4494,29 @@
return mContext;
}
}
+
+ private void sendResponse(IAccountManagerResponse response, Bundle result) {
+ try {
+ response.onResult(result);
+ } catch (RemoteException e) {
+ // if the caller is dead then there is no one to care about remote
+ // exceptions
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "failure while notifying response", e);
+ }
+ }
+ }
+
+ private void sendErrorResponse(IAccountManagerResponse response, int errorCode,
+ String errorMessage) {
+ try {
+ response.onError(errorCode, errorMessage);
+ } catch (RemoteException e) {
+ // if the caller is dead then there is no one to care about remote
+ // exceptions
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "failure while notifying response", e);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/accounts/CryptoHelper.java b/services/core/java/com/android/server/accounts/CryptoHelper.java
new file mode 100644
index 0000000..2b59b74
--- /dev/null
+++ b/services/core/java/com/android/server/accounts/CryptoHelper.java
@@ -0,0 +1,140 @@
+package com.android.server.accounts;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * A crypto helper for encrypting and decrypting bundle with in-memory symmetric
+ * key for {@link AccountManagerService}.
+ */
+/* default */ class CryptoHelper {
+ private static final String TAG = "Account";
+
+ private static final String KEY_CIPHER = "cipher";
+ private static final String KEY_MAC = "mac";
+ private static final String KEY_ALGORITHM = "AES";
+ private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
+ private static final String MAC_ALGORITHM = "HMACSHA256";
+ private static final int IV_LENGTH = 16;
+
+ private static CryptoHelper sInstance;
+ // Keys used for encrypting and decrypting data returned in a Bundle.
+ private final SecretKeySpec mCipherKeySpec;
+ private final SecretKeySpec mMacKeySpec;
+ private final IvParameterSpec mIv;
+
+ /* default */ synchronized static CryptoHelper getInstance() throws NoSuchAlgorithmException {
+ if (sInstance == null) {
+ sInstance = new CryptoHelper();
+ }
+ return sInstance;
+ }
+
+ private CryptoHelper() throws NoSuchAlgorithmException {
+ KeyGenerator kgen = KeyGenerator.getInstance(KEY_ALGORITHM);
+ SecretKey skey = kgen.generateKey();
+ mCipherKeySpec = new SecretKeySpec(skey.getEncoded(), KEY_ALGORITHM);
+
+ kgen = KeyGenerator.getInstance(MAC_ALGORITHM);
+ skey = kgen.generateKey();
+ mMacKeySpec = new SecretKeySpec(skey.getEncoded(), MAC_ALGORITHM);
+
+ // Create random iv
+ byte[] iv = new byte[IV_LENGTH];
+ SecureRandom secureRandom = new SecureRandom();
+ secureRandom.nextBytes(iv);
+ mIv = new IvParameterSpec(iv);
+ }
+
+ @NonNull
+ /* default */ Bundle encryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException {
+ Preconditions.checkNotNull(bundle, "Cannot encrypt null bundle.");
+ Parcel parcel = Parcel.obtain();
+ bundle.writeToParcel(parcel, 0);
+ byte[] bytes = parcel.marshall();
+ parcel.recycle();
+
+ Bundle encryptedBundle = new Bundle();
+
+ byte[] cipher = encrypt(bytes);
+ byte[] mac = createMac(cipher);
+
+ encryptedBundle.putByteArray(KEY_CIPHER, cipher);
+ encryptedBundle.putByteArray(KEY_MAC, mac);
+
+ return encryptedBundle;
+ }
+
+ @Nullable
+ /* default */ Bundle decryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException {
+ Preconditions.checkNotNull(bundle, "Cannot decrypt null bundle.");
+ byte[] cipherArray = bundle.getByteArray(KEY_CIPHER);
+ byte[] macArray = bundle.getByteArray(KEY_MAC);
+
+ if (!verifyMac(cipherArray, macArray)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Escrow mac mismatched!");
+ }
+ return null;
+ }
+
+ Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+ cipher.init(Cipher.DECRYPT_MODE, mCipherKeySpec, mIv);
+ byte[] decryptedBytes = cipher.doFinal(cipherArray);
+
+ Parcel decryptedParcel = Parcel.obtain();
+ decryptedParcel.unmarshall(decryptedBytes, 0, decryptedBytes.length);
+ decryptedParcel.setDataPosition(0);
+ Bundle decryptedBundle = new Bundle();
+ decryptedBundle.readFromParcel(decryptedParcel);
+ decryptedParcel.recycle();
+ return decryptedBundle;
+ }
+
+ private boolean verifyMac(@Nullable byte[] cipherArray, @Nullable byte[] macArray)
+ throws GeneralSecurityException {
+
+ if (cipherArray == null || cipherArray.length == 0 || macArray == null
+ || macArray.length == 0) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Cipher or MAC is empty!");
+ }
+ return false;
+ }
+ Mac mac = Mac.getInstance(MAC_ALGORITHM);
+ mac.init(mMacKeySpec);
+ mac.update(cipherArray);
+ return Arrays.equals(macArray, mac.doFinal());
+ }
+
+ @NonNull
+ private byte[] encrypt(@NonNull byte[] data) throws GeneralSecurityException {
+ Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
+ cipher.init(Cipher.ENCRYPT_MODE, mCipherKeySpec, mIv);
+ return cipher.doFinal(data);
+ }
+
+ @NonNull
+ private byte[] createMac(@NonNull byte[] cipher) throws GeneralSecurityException {
+ Mac mac = Mac.getInstance(MAC_ALGORITHM);
+ mac.init(mMacKeySpec);
+ return mac.doFinal(cipher);
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 4d05f9a..17b3d2a 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1094,15 +1094,20 @@
}
r = smap.mServicesByName.get(name);
if (r == null && createIfNeeded) {
- // Before going further -- if this app is not allowed to run in the background,
- // then at this point we aren't going to let it period.
- if (!mAm.checkAllowBackgroundLocked(sInfo.applicationInfo.uid,
- sInfo.packageName, callingPid)) {
- Slog.w(TAG, "Background execution not allowed: service "
- + r.intent + " to " + name.flattenToShortString()
- + " from pid=" + callingPid + " uid=" + callingUid
- + " pkg=" + callingPackage);
- return null;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // Before going further -- if this app is not allowed to run in the
+ // background, then at this point we aren't going to let it period.
+ if (!mAm.checkAllowBackgroundLocked(sInfo.applicationInfo.uid,
+ sInfo.packageName, callingPid)) {
+ Slog.w(TAG, "Background execution not allowed: service "
+ + r.intent + " to " + name.flattenToShortString()
+ + " from pid=" + callingPid + " uid=" + callingUid
+ + " pkg=" + callingPackage);
+ return null;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
Intent.FilterComparison filter
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6e6bfb7..3a0d80b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16,72 +16,8 @@
package com.android.server.am;
-import static android.Manifest.permission.INTERACT_ACROSS_USERS;
-import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
-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.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static com.android.internal.util.XmlUtils.readBooleanAttribute;
-import static com.android.internal.util.XmlUtils.readIntAttribute;
-import static com.android.internal.util.XmlUtils.readLongAttribute;
-import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
-import static com.android.internal.util.XmlUtils.writeIntAttribute;
-import static com.android.internal.util.XmlUtils.writeLongAttribute;
-import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
-import static com.android.server.am.ActivityManagerDebugConfig.*;
-import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS;
-import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
-import static com.android.server.am.ActivityStackSupervisor.RESTORE_FROM_RECENTS;
-import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
-import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
-import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
-import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
-import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
-import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
-import static org.xmlpull.v1.XmlPullParser.START_TAG;
-
-import android.Manifest;
-import android.app.ActivityManager.StackId;
-import android.app.AppOpsManager;
-import android.app.ApplicationThreadNative;
-import android.app.BroadcastOptions;
-import android.app.IActivityContainer;
-import android.app.IActivityContainerCallback;
-import android.app.IAppTask;
-import android.app.ITaskStackListener;
-import android.app.ProfilerInfo;
-import android.app.assist.AssistContent;
-import android.app.assist.AssistStructure;
-import android.app.usage.UsageEvents;
-import android.app.usage.UsageStatsManagerInternal;
-import android.appwidget.AppWidgetManager;
-import android.content.pm.PermissionInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.BatteryStats;
-import android.os.PersistableBundle;
-import android.os.PowerManager;
-import android.os.ResultReceiver;
-import android.os.Trace;
-import android.os.TransactionTooLargeException;
-import android.os.WorkSource;
-import android.os.storage.IMountService;
-import android.os.storage.MountServiceInternal;
-import android.os.storage.StorageManager;
-import android.provider.Settings.Global;
-import android.service.voice.IVoiceInteractionSession;
-import android.service.voice.VoiceInteractionSession;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.DebugUtils;
-import android.view.Display;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -118,19 +54,16 @@
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.AppTransition;
import com.android.server.wm.WindowManagerService;
-import com.google.android.collect.Lists;
-import com.google.android.collect.Maps;
-
-import libcore.io.IoUtils;
-import libcore.util.EmptyArray;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityManager.StackId;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityManager.TaskThumbnailInfo;
import android.app.ActivityManagerInternal;
@@ -140,24 +73,37 @@
import android.app.ActivityThread;
import android.app.AlertDialog;
import android.app.AppGlobals;
+import android.app.AppOpsManager;
import android.app.ApplicationErrorReport;
+import android.app.ApplicationThreadNative;
+import android.app.BroadcastOptions;
import android.app.Dialog;
+import android.app.IActivityContainer;
+import android.app.IActivityContainerCallback;
import android.app.IActivityController;
+import android.app.IAppTask;
import android.app.IApplicationThread;
import android.app.IInstrumentationWatcher;
import android.app.INotificationManager;
import android.app.IProcessObserver;
import android.app.IServiceConnection;
import android.app.IStopUserCallback;
-import android.app.IUidObserver;
+import android.app.ITaskStackListener;
import android.app.IUiAutomationConnection;
+import android.app.IUidObserver;
import android.app.IUserSwitchObserver;
import android.app.Instrumentation;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.backup.IBackupManager;
+import android.app.ProfilerInfo;
import android.app.admin.DevicePolicyManager;
+import android.app.assist.AssistContent;
+import android.app.assist.AssistStructure;
+import android.app.backup.IBackupManager;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManagerInternal;
+import android.appwidget.AppWidgetManager;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ClipData;
@@ -181,18 +127,24 @@
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ParceledListSlice;
-import android.content.pm.UserInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ParceledListSlice;
import android.content.pm.PathPermission;
+import android.content.pm.PermissionInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.net.Proxy;
import android.net.ProxyInfo;
import android.net.Uri;
+import android.os.BatteryStats;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -210,21 +162,35 @@
import android.os.Message;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.TransactionTooLargeException;
import android.os.UpdateLock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.WorkSource;
+import android.os.storage.IMountService;
+import android.os.storage.MountServiceInternal;
+import android.os.storage.StorageManager;
import android.provider.Settings;
+import android.service.voice.IVoiceInteractionSession;
+import android.service.voice.VoiceInteractionSession;
import android.text.format.DateUtils;
import android.text.format.Time;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.AtomicFile;
+import android.util.DebugUtils;
import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
@@ -233,13 +199,12 @@
import android.util.SparseArray;
import android.util.TimeUtils;
import android.util.Xml;
+import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
-import dalvik.system.VMRuntime;
-
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
@@ -269,6 +234,100 @@
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
+import dalvik.system.VMRuntime;
+import libcore.io.IoUtils;
+import libcore.util.EmptyArray;
+
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.app.ActivityManager.RESIZE_MODE_PRESERVE_WINDOW;
+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.HOME_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
+import static android.provider.Settings.Global.DEBUG_APP;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL;
+import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
+import static com.android.internal.util.XmlUtils.readBooleanAttribute;
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeLongAttribute;
+import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_BACKGROUND;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CLEANUP;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_IMMERSIVE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKSCREEN;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROVIDER;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_URI_PERMISSION;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USAGE_STATS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBILITY;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBLE_BEHIND;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CLEANUP;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_FOCUS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_IMMERSIVE;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKSCREEN;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LRU;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_OOM_ADJ;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_POWER;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESSES;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROCESS_OBSERVERS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PROVIDER;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PSS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SERVICE;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SWITCH;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_UID_OBSERVERS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_URI_PERMISSION;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILITY;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS;
+import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
+import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.am.ActivityStackSupervisor.RESTORE_FROM_RECENTS;
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
+import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
public final class ActivityManagerService extends ActivityManagerNative
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
@@ -1211,7 +1270,8 @@
String mOrigDebugApp = null;
boolean mOrigWaitForDebugger = false;
boolean mAlwaysFinishActivities = false;
- boolean mForceResizableActivites;
+ boolean mForceResizableActivities;
+ boolean mSupportsFreeformWindowManagement;
IActivityController mController = null;
String mProfileApp = null;
ProcessRecord mProfileProc = null;
@@ -8827,12 +8887,19 @@
}
@Override
- public Bitmap getTaskDescriptionIcon(String filename) {
- if (!FileUtils.isValidExtFilename(filename)
- || !filename.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) {
- throw new IllegalArgumentException("Bad filename: " + filename);
+ public Bitmap getTaskDescriptionIcon(String filePath, int userId) {
+ if (userId != UserHandle.getCallingUserId()) {
+ enforceCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "getTaskDescriptionIcon");
}
- return mTaskPersister.getTaskDescriptionIcon(filename);
+ final File passedIconFile = new File(filePath);
+ final File legitIconFile = new File(TaskPersister.getUserImagesDir(userId),
+ passedIconFile.getName());
+ if (!legitIconFile.getPath().equals(filePath)
+ || !filePath.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) {
+ throw new IllegalArgumentException("Bad file path: " + filePath);
+ }
+ return mTaskPersister.getTaskDescriptionIcon(filePath);
}
@Override
@@ -11805,21 +11872,21 @@
private void retrieveSettings() {
final ContentResolver resolver = mContext.getContentResolver();
- String debugApp = Settings.Global.getString(resolver, Settings.Global.DEBUG_APP);
- boolean waitForDebugger = Settings.Global.getInt(
- resolver, Settings.Global.WAIT_FOR_DEBUGGER, 0) != 0;
- boolean alwaysFinishActivities = Settings.Global.getInt(
- resolver, Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0) != 0;
- boolean forceRtl = Settings.Global.getInt(
- resolver, Settings.Global.DEVELOPMENT_FORCE_RTL, 0) != 0;
- int defaultForceResizable = Build.IS_DEBUGGABLE ? 1 : 0;
- boolean forceResizable = Settings.Global.getInt(
- resolver, Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES,
- defaultForceResizable) != 0;
- // Transfer any global setting for forcing RTL layout, into a System Property
- SystemProperties.set(Settings.Global.DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0");
+ final boolean freeformWindowManagement =
+ mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT);
- Configuration configuration = new Configuration();
+ final String debugApp = Settings.Global.getString(resolver, DEBUG_APP);
+ 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 forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0;
+ final int defaultForceResizable = Build.IS_DEBUGGABLE ? 1 : 0;
+ final boolean forceResizable = Settings.Global.getInt(
+ resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, defaultForceResizable) != 0;
+ // Transfer any global setting for forcing RTL layout, into a System Property
+ SystemProperties.set(DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0");
+
+ final Configuration configuration = new Configuration();
Settings.System.getConfiguration(resolver, configuration);
if (forceRtl) {
// This will take care of setting the correct layout direction flags
@@ -11830,7 +11897,8 @@
mDebugApp = mOrigDebugApp = debugApp;
mWaitForDebugger = mOrigWaitForDebugger = waitForDebugger;
mAlwaysFinishActivities = alwaysFinishActivities;
- mForceResizableActivites = forceResizable;
+ mForceResizableActivities = forceResizable;
+ mSupportsFreeformWindowManagement = freeformWindowManagement || forceResizable;
// This happens before any activities are started, so we can
// change mConfiguration in-place.
updateConfigurationLocked(configuration, null, true);
@@ -20152,6 +20220,11 @@
}
@Override
+ public boolean unlockUser(int userId, byte[] token) {
+ return mUserController.unlockUser(userId, token);
+ }
+
+ @Override
public boolean switchUser(final int userId) {
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
String userName;
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index aa04bd7..ea8a12f 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -61,6 +61,7 @@
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -1212,8 +1213,10 @@
if (_taskDescription.getIconFilename() == null &&
(icon = _taskDescription.getIcon()) != null) {
final String iconFilename = createImageFilename(createTime, task.taskId);
- mStackSupervisor.mService.mTaskPersister.saveImage(icon, iconFilename);
- _taskDescription.setIconFilename(iconFilename);
+ final File iconFile = new File(TaskPersister.getUserImagesDir(userId), iconFilename);
+ final String iconFilePath = iconFile.getAbsolutePath();
+ mStackSupervisor.mService.mTaskPersister.saveImage(icon, iconFilePath);
+ _taskDescription.setIconFilename(iconFilePath);
}
taskDescription = _taskDescription;
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 0ec4b18..124d2ef 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -99,7 +99,6 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.Trace;
import android.os.TransactionTooLargeException;
import android.os.UserHandle;
@@ -1666,9 +1665,7 @@
UserInfo user = getUserInfo(userId);
// TODO: Timeout for work challenge
- if (user.isManagedProfile()
- && mService.mContext.getSystemService(StorageManager.class)
- .isPerUserEncryptionEnabled()) {
+ if (user.isManagedProfile() && StorageManager.isFileBasedEncryptionEnabled()) {
KeyguardManager km = (KeyguardManager) mService.mContext
.getSystemService(Context.KEYGUARD_SERVICE);
@@ -1926,9 +1923,9 @@
boolean overrideBounds = false;
Rect newBounds = null;
if (options != null && (r.info.resizeable || (inTask != null && inTask.mResizeable))) {
- if (options.hasBounds()) {
+ if (canUseActivityOptionsLaunchBounds(options)) {
overrideBounds = true;
- newBounds = options.getBounds();
+ newBounds = options.getLaunchBounds();
}
}
@@ -2930,8 +2927,8 @@
}
if (task.mResizeable && options != null) {
- if (options.hasBounds()) {
- Rect bounds = options.getBounds();
+ if (canUseActivityOptionsLaunchBounds(options)) {
+ Rect bounds = options.getLaunchBounds();
task.updateOverrideConfiguration(bounds);
final int stackId = task.getLaunchStackId();
if (stackId != task.stack.mStackId) {
@@ -2955,6 +2952,12 @@
"findTaskToMoveToFront: moved to front of stack=" + task.stack);
}
+ private boolean canUseActivityOptionsLaunchBounds(ActivityOptions options) {
+ // We use the launch bounds in the activity options is the device supports freeform
+ // window management.
+ return options.hasLaunchBounds() && mService.mSupportsFreeformWindowManagement;
+ }
+
ActivityStack getStack(int stackId) {
return getStack(stackId, !CREATE_IF_NEEDED, !ON_TOP);
}
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 150baf0..9a00075 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -16,10 +16,11 @@
package com.android.server.am;
-import android.content.pm.IPackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Debug;
+import android.os.Environment;
+import android.os.FileUtils;
import android.os.SystemClock;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -27,7 +28,6 @@
import android.util.Xml;
import android.os.Process;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
@@ -44,6 +44,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
+import java.util.List;
import libcore.io.IoUtils;
@@ -54,8 +55,10 @@
/** When not flushing don't write out files faster than this */
private static final long INTER_WRITE_DELAY_MS = 500;
- /** When not flushing delay this long before writing the first file out. This gives the next
- * task being launched a chance to load its resources without this occupying IO bandwidth. */
+ /**
+ * When not flushing delay this long before writing the first file out. This gives the next task
+ * being launched a chance to load its resources without this occupying IO bandwidth.
+ */
private static final long PRE_TASK_DELAY_MS = 3000;
/** The maximum number of entries to keep in the queue before draining it automatically. */
@@ -72,24 +75,23 @@
private static final String TAG_TASK = "task";
- static File sImagesDir;
- static File sTasksDir;
-
private final ActivityManagerService mService;
private final ActivityStackSupervisor mStackSupervisor;
private final RecentTasks mRecentTasks;
- /** Value determines write delay mode as follows:
- * < 0 We are Flushing. No delays between writes until the image queue is drained and all
- * tasks needing persisting are written to disk. There is no delay between writes.
- * == 0 We are Idle. Next writes will be delayed by #PRE_TASK_DELAY_MS.
- * > 0 We are Actively writing. Next write will be at this time. Subsequent writes will be
- * delayed by #INTER_WRITE_DELAY_MS. */
+ /**
+ * Value determines write delay mode as follows: < 0 We are Flushing. No delays between writes
+ * until the image queue is drained and all tasks needing persisting are written to disk. There
+ * is no delay between writes. == 0 We are Idle. Next writes will be delayed by
+ * #PRE_TASK_DELAY_MS. > 0 We are Actively writing. Next write will be at this time. Subsequent
+ * writes will be delayed by #INTER_WRITE_DELAY_MS.
+ */
private long mNextWriteTime = 0;
private final LazyTaskWriterThread mLazyTaskWriterThread;
private static class WriteQueueItem {}
+
private static class TaskWriteQueueItem extends WriteQueueItem {
final TaskRecord mTask;
@@ -97,12 +99,13 @@
mTask = task;
}
}
+
private static class ImageWriteQueueItem extends WriteQueueItem {
- final String mFilename;
+ final String mFilePath;
Bitmap mImage;
- ImageWriteQueueItem(String filename, Bitmap image) {
- mFilename = filename;
+ ImageWriteQueueItem(String filePath, Bitmap image) {
+ mFilePath = filePath;
mImage = image;
}
}
@@ -111,19 +114,18 @@
TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor,
RecentTasks recentTasks) {
- sTasksDir = new File(systemDir, TASKS_DIRNAME);
- if (!sTasksDir.exists()) {
- if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
- if (!sTasksDir.mkdir()) {
- Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
+
+ final File legacyImagesDir = new File(systemDir, IMAGES_DIRNAME);
+ if (legacyImagesDir.exists()) {
+ if (!FileUtils.deleteContents(legacyImagesDir) || !legacyImagesDir.delete()) {
+ Slog.i(TAG, "Failure deleting legacy images directory: " + legacyImagesDir);
}
}
- sImagesDir = new File(systemDir, IMAGES_DIRNAME);
- if (!sImagesDir.exists()) {
- if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir);
- if (!sImagesDir.mkdir()) {
- Slog.e(TAG, "Failure creating images directory " + sImagesDir);
+ final File legacyTasksDir = new File(systemDir, TASKS_DIRNAME);
+ if (legacyTasksDir.exists()) {
+ if (!FileUtils.deleteContents(legacyTasksDir) || !legacyTasksDir.delete()) {
+ Slog.i(TAG, "Failure deleting legacy tasks directory: " + legacyTasksDir);
}
}
@@ -144,8 +146,8 @@
for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
final WriteQueueItem item = mWriteQueue.get(queueNdx);
if (item instanceof ImageWriteQueueItem &&
- ((ImageWriteQueueItem) item).mFilename.startsWith(taskString)) {
- if (DEBUG) Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilename +
+ ((ImageWriteQueueItem) item).mFilePath.startsWith(taskString)) {
+ if (DEBUG) Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilePath +
" from write queue");
mWriteQueue.remove(queueNdx);
}
@@ -213,14 +215,14 @@
}
}
- void saveImage(Bitmap image, String filename) {
+ void saveImage(Bitmap image, String filePath) {
synchronized (this) {
int queueNdx;
for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
final WriteQueueItem item = mWriteQueue.get(queueNdx);
if (item instanceof ImageWriteQueueItem) {
ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
- if (imageWriteQueueItem.mFilename.equals(filename)) {
+ if (imageWriteQueueItem.mFilePath.equals(filePath)) {
// replace the Bitmap with the new one.
imageWriteQueueItem.mImage = image;
break;
@@ -228,14 +230,14 @@
}
}
if (queueNdx < 0) {
- mWriteQueue.add(new ImageWriteQueueItem(filename, image));
+ mWriteQueue.add(new ImageWriteQueueItem(filePath, image));
}
if (mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
mNextWriteTime = FLUSH_QUEUE;
} else if (mNextWriteTime == 0) {
mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
}
- if (DEBUG) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
+ if (DEBUG) Slog.d(TAG, "saveImage: filePath=" + filePath + " now=" +
SystemClock.uptimeMillis() + " mNextWriteTime=" +
mNextWriteTime + " Callers=" + Debug.getCallers(4));
notifyAll();
@@ -244,22 +246,22 @@
yieldIfQueueTooDeep();
}
- Bitmap getTaskDescriptionIcon(String filename) {
+ Bitmap getTaskDescriptionIcon(String filePath) {
// See if it is in the write queue
- final Bitmap icon = getImageFromWriteQueue(filename);
+ final Bitmap icon = getImageFromWriteQueue(filePath);
if (icon != null) {
return icon;
}
- return restoreImage(filename);
+ return restoreImage(filePath);
}
- Bitmap getImageFromWriteQueue(String filename) {
+ Bitmap getImageFromWriteQueue(String filePath) {
synchronized (this) {
for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
final WriteQueueItem item = mWriteQueue.get(queueNdx);
if (item instanceof ImageWriteQueueItem) {
ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
- if (imageWriteQueueItem.mFilename.equals(filename)) {
+ if (imageWriteQueueItem.mFilePath.equals(filePath)) {
return imageWriteQueueItem.mImage;
}
}
@@ -275,7 +277,7 @@
xmlSerializer.setOutput(stringWriter);
if (DEBUG) xmlSerializer.setFeature(
- "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ "http://xmlpull.org/v1/doc/features.html#indent-output", true);
// save task
xmlSerializer.startDocument(null, true);
@@ -321,19 +323,22 @@
return null;
}
- ArrayList<TaskRecord> restoreTasksLocked(final int [] validUserIds) {
- final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
+ private List<TaskRecord> restoreTasksForUserLocked(final int userId) {
+ final List<TaskRecord> tasks = new ArrayList<TaskRecord>();
ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
- File[] recentFiles = sTasksDir.listFiles();
+ File userTasksDir = getUserTasksDir(userId);
+
+ File[] recentFiles = userTasksDir.listFiles();
if (recentFiles == null) {
- Slog.e(TAG, "Unable to list files from " + sTasksDir);
+ Slog.e(TAG, "restoreTasksForUser: Unable to list files from " + userTasksDir);
return tasks;
}
for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
File taskFile = recentFiles[taskNdx];
- if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
+ if (DEBUG) Slog.d(TAG, "restoreTasksForUser: userId=" + userId
+ + ", taskFile=" + taskFile.getName());
BufferedReader reader = null;
boolean deleteFile = false;
try {
@@ -348,30 +353,29 @@
if (event == XmlPullParser.START_TAG) {
if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
if (TAG_TASK.equals(name)) {
- final TaskRecord task =
- TaskRecord.restoreFromXml(in, mStackSupervisor);
- if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" +
- task);
+ final TaskRecord task = TaskRecord.restoreFromXml(in, mStackSupervisor);
+ if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task="
+ + task);
if (task != null) {
// XXX Don't add to write queue... there is no reason to write
// out the stuff we just read, if we don't write it we will
// read the same thing again.
- //mWriteQueue.add(new TaskWriteQueueItem(task));
+ // mWriteQueue.add(new TaskWriteQueueItem(task));
final int taskId = task.taskId;
mStackSupervisor.setNextTaskId(taskId);
// Check if it's a valid user id. Don't add tasks for removed users.
- if (ArrayUtils.contains(validUserIds, task.userId)) {
+ if (userId == task.userId) {
task.isPersistable = true;
tasks.add(task);
recoveredTaskIds.add(taskId);
}
} else {
- Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " +
- fileToString(taskFile));
+ Slog.e(TAG, "restoreTasksForUser: Unable to restore taskFile="
+ + taskFile + ": " + fileToString(taskFile));
}
} else {
- Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event +
- " name=" + name);
+ Slog.wtf(TAG, "restoreTasksForUser: Unknown xml event=" + event
+ + " name=" + name);
}
}
XmlUtils.skipCurrentTag(in);
@@ -390,10 +394,19 @@
}
if (!DEBUG) {
- removeObsoleteFiles(recoveredTaskIds);
+ removeObsoleteFiles(recoveredTaskIds, userTasksDir.listFiles());
+ }
+ return tasks;
+ }
+
+ ArrayList<TaskRecord> restoreTasksLocked(final int[] validUserIds) {
+ final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
+
+ for (int userId : validUserIds) {
+ tasks.addAll(restoreTasksForUserLocked(userId));
}
- // Fixup task affiliation from taskIds
+ // Fix up task affiliation from taskIds
for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = tasks.get(taskNdx);
task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks));
@@ -420,7 +433,7 @@
}
private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
- if (DEBUG) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds=" + persistentTaskIds +
+ if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: persistentTaskIds=" + persistentTaskIds +
" files=" + files);
if (files == null) {
Slog.e(TAG, "File error accessing recents directory (too many files open?).");
@@ -434,14 +447,14 @@
final int taskId;
try {
taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
- if (DEBUG) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
+ if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: Found taskId=" + taskId);
} catch (Exception e) {
- Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
+ Slog.wtf(TAG, "removeObsoleteFiles: Can't parse file=" + file.getName());
file.delete();
continue;
}
if (!persistentTaskIds.contains(taskId)) {
- if (DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" + file.getName());
+ if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: deleting file=" + file.getName());
file.delete();
}
}
@@ -449,13 +462,39 @@
}
private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
- removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles());
- removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles());
+ for (int userId : mService.getRunningUserIds()) {
+ removeObsoleteFiles(persistentTaskIds, getUserImagesDir(userId).listFiles());
+ removeObsoleteFiles(persistentTaskIds, getUserTasksDir(userId).listFiles());
+ }
}
static Bitmap restoreImage(String filename) {
if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
- return BitmapFactory.decodeFile(sImagesDir + File.separator + filename);
+ return BitmapFactory.decodeFile(filename);
+ }
+
+ static File getUserTasksDir(int userId) {
+ File userTasksDir = new File(Environment.getUserSystemDirectory(userId), TASKS_DIRNAME);
+
+ if (!userTasksDir.exists()) {
+ if (!userTasksDir.mkdir()) {
+ Slog.e(TAG, "Failure creating tasks directory for user " + userId + ": "
+ + userTasksDir);
+ }
+ }
+ return userTasksDir;
+ }
+
+ static File getUserImagesDir(int userId) {
+ File userImagesDir = new File(Environment.getUserSystemDirectory(userId), IMAGES_DIRNAME);
+
+ if (!userImagesDir.exists()) {
+ if (!userImagesDir.mkdir()) {
+ Slog.e(TAG, "Failure creating images directory for user " + userId + ": "
+ + userImagesDir);
+ }
+ }
+ return userImagesDir;
}
private class LazyTaskWriterThread extends Thread {
@@ -508,7 +547,6 @@
INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")");
}
-
while (mWriteQueue.isEmpty()) {
if (mNextWriteTime != 0) {
mNextWriteTime = 0; // idle.
@@ -542,15 +580,15 @@
if (item instanceof ImageWriteQueueItem) {
ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
- final String filename = imageWriteQueueItem.mFilename;
+ final String filePath = imageWriteQueueItem.mFilePath;
final Bitmap bitmap = imageWriteQueueItem.mImage;
- if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filename);
+ if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filePath);
FileOutputStream imageFile = null;
try {
- imageFile = new FileOutputStream(new File(sImagesDir, filename));
+ imageFile = new FileOutputStream(new File(filePath));
bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
} catch (Exception e) {
- Slog.e(TAG, "saveImage: unable to save " + filename, e);
+ Slog.e(TAG, "saveImage: unable to save " + filePath, e);
} finally {
IoUtils.closeQuietly(imageFile);
}
@@ -575,18 +613,21 @@
FileOutputStream file = null;
AtomicFile atomicFile = null;
try {
- atomicFile = new AtomicFile(new File(sTasksDir, String.valueOf(
- task.taskId) + RECENTS_FILENAME + TASK_EXTENSION));
+ atomicFile = new AtomicFile(new File(
+ getUserTasksDir(task.userId),
+ String.valueOf(task.taskId) + RECENTS_FILENAME
+ + TASK_EXTENSION));
file = atomicFile.startWrite();
file.write(stringWriter.toString().getBytes());
file.write('\n');
atomicFile.finishWrite(file);
+
} catch (IOException e) {
if (file != null) {
atomicFile.failWrite(file);
}
- Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " +
- e);
+ Slog.e(TAG,
+ "Unable to open " + atomicFile + " for persisting. " + e);
}
}
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index b214080..3fc6846 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -27,15 +27,23 @@
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
-import static com.android.server.am.ActivityManagerDebugConfig.*;
-import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_ADD_REMOVE;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
+import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.StackId;
-import android.app.ActivityManager.TaskThumbnail;
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityManager.TaskThumbnail;
import android.app.ActivityManager.TaskThumbnailInfo;
@@ -58,8 +66,10 @@
import android.service.voice.IVoiceInteractionSession;
import android.util.DisplayMetrics;
import android.util.Slog;
+
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.util.XmlUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -237,7 +247,8 @@
mService = service;
mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
TaskPersister.IMAGE_EXTENSION;
- mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename);
+ userId = UserHandle.getUserId(info.applicationInfo.uid);
+ mLastThumbnailFile = new File(TaskPersister.getUserImagesDir(userId), mFilename);
mLastThumbnailInfo = new TaskThumbnailInfo();
taskId = _taskId;
mAffiliatedTaskId = _taskId;
@@ -256,7 +267,8 @@
mService = service;
mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
TaskPersister.IMAGE_EXTENSION;
- mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename);
+ userId = UserHandle.getUserId(info.applicationInfo.uid);
+ mLastThumbnailFile = new File(TaskPersister.getUserImagesDir(userId), mFilename);
mLastThumbnailInfo = thumbnailInfo;
taskId = _taskId;
mAffiliatedTaskId = _taskId;
@@ -276,7 +288,6 @@
taskType = APPLICATION_ACTIVITY_TYPE;
mTaskToReturnTo = HOME_ACTIVITY_TYPE;
- userId = UserHandle.getUserId(info.applicationInfo.uid);
lastTaskDescription = _taskDescription;
mMinimalSize = info != null && info.layout != null ? info.layout.minimalSize : -1;
}
@@ -294,7 +305,7 @@
mService = service;
mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
TaskPersister.IMAGE_EXTENSION;
- mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename);
+ mLastThumbnailFile = new File(TaskPersister.getUserImagesDir(_userId), mFilename);
mLastThumbnailInfo = lastThumbnailInfo;
taskId = _taskId;
intent = _intent;
@@ -326,7 +337,7 @@
mNextAffiliateTaskId = nextTaskId;
mCallingUid = callingUid;
mCallingPackage = callingPackage;
- mResizeable = resizeable || mService.mForceResizableActivites;
+ mResizeable = resizeable || mService.mForceResizableActivities;
mPrivileged = privileged;
ActivityInfo info = (mActivities.size() > 0) ? mActivities.get(0).info : null;
mMinimalSize = info != null && info.layout != null ? info.layout.minimalSize : -1;
@@ -428,7 +439,7 @@
} else {
autoRemoveRecents = false;
}
- mResizeable = info.resizeable || mService.mForceResizableActivites;
+ mResizeable = info.resizeable || mService.mForceResizableActivities;
mLockTaskMode = info.lockTaskLaunchMode;
mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0;
setLockTaskAuth();
@@ -537,7 +548,7 @@
mLastThumbnailFile.delete();
}
} else {
- mService.mTaskPersister.saveImage(thumbnail, mFilename);
+ mService.mTaskPersister.saveImage(thumbnail, mLastThumbnailFile.getAbsolutePath());
}
return true;
}
@@ -549,7 +560,8 @@
thumbs.thumbnailInfo = mLastThumbnailInfo;
thumbs.thumbnailFileDescriptor = null;
if (mLastThumbnail == null) {
- thumbs.mainThumbnail = mService.mTaskPersister.getImageFromWriteQueue(mFilename);
+ thumbs.mainThumbnail = mService.mTaskPersister.getImageFromWriteQueue(
+ mLastThumbnailFile.getAbsolutePath());
}
// Only load the thumbnail file if we don't have a thumbnail
if (thumbs.mainThumbnail == null && mLastThumbnailFile.exists()) {
@@ -682,7 +694,7 @@
// Only set this based on the first activity
if (mActivities.isEmpty()) {
taskType = r.mActivityType;
- if (taskType == HOME_ACTIVITY_TYPE && mService.mForceResizableActivites) {
+ if (taskType == HOME_ACTIVITY_TYPE && mService.mForceResizableActivities) {
mResizeable = r.info.resizeable;
}
isPersistable = r.isPersistable();
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index d6fced6..e04f138 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -66,6 +66,7 @@
import android.util.SparseIntArray;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.server.pm.UserManagerService;
@@ -99,7 +100,9 @@
/**
* Which users have been started, so are allowed to run code.
*/
+ @GuardedBy("mService")
private final SparseArray<UserState> mStartedUsers = new SparseArray<>();
+
/**
* LRU list of history of current users. Most recently current is at the end.
*/
@@ -415,7 +418,7 @@
private void updateUserUnlockedState(UserState uss) {
final IMountService mountService = IMountService.Stub
- .asInterface(ServiceManager.getService(Context.STORAGE_SERVICE));
+ .asInterface(ServiceManager.getService("mount"));
if (mountService != null) {
try {
uss.unlocked = mountService.isUserKeyUnlocked(uss.mHandle.getIdentifier());
@@ -424,7 +427,7 @@
}
} else {
// System isn't fully booted yet, so guess based on property
- uss.unlocked = !SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false);
+ uss.unlocked = !StorageManager.isFileBasedEncryptionEnabled();
}
}
@@ -606,6 +609,35 @@
return result;
}
+ boolean unlockUser(final int userId, byte[] token) {
+ if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
+ != PackageManager.PERMISSION_GRANTED) {
+ String msg = "Permission Denial: unlockUser() from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires " + INTERACT_ACROSS_USERS_FULL;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+
+ final UserInfo userInfo = getUserInfo(userId);
+ final IMountService mountService = IMountService.Stub
+ .asInterface(ServiceManager.getService("mount"));
+ try {
+ mountService.unlockUserKey(userId, userInfo.serialNumber, token);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to unlock: " + e.getMessage());
+ throw e.rethrowAsRuntimeException();
+ }
+
+ synchronized (mService) {
+ final UserState uss = mStartedUsers.get(userId);
+ updateUserUnlockedState(uss);
+ }
+
+ return true;
+ }
+
void showUserSwitchDialog(int userId, String userName) {
// The dialog will show and then initiate the user switch by calling startUserInForeground
Dialog d = new UserSwitchingDialog(mService, mService.mContext, userId, userName,
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index fd1e9dd..4424838b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1224,19 +1224,6 @@
}
@Override
- public void setPackagePeekable(String pkg, int uid, boolean peekable) {
- checkCallerIsSystem();
-
- mRankingHelper.setPackagePeekable(pkg, uid, peekable);
- }
-
- @Override
- public boolean getPackagePeekable(String pkg, int uid) {
- checkCallerIsSystem();
- return mRankingHelper.getPackagePeekable(pkg, uid);
- }
-
- @Override
public void setPackageVisibilityOverride(String pkg, int uid, int visibility) {
checkCallerIsSystem();
mRankingHelper.setPackageVisibilityOverride(pkg, uid, visibility);
@@ -2157,14 +2144,6 @@
notification.priority = Notification.PRIORITY_HIGH;
}
}
- // force no heads up per package config
- if (!mRankingHelper.getPackagePeekable(pkg, callingUid)) {
- if (notification.extras == null) {
- notification.extras = new Bundle();
- }
- notification.extras.putInt(Notification.EXTRA_AS_HEADS_UP,
- Notification.HEADS_UP_NEVER);
- }
// 1. initial score: buckets of 10, around the app [-20..20]
final int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER;
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 803db10..aea137b 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -20,10 +20,6 @@
void setPackagePriority(String packageName, int uid, int priority);
- boolean getPackagePeekable(String packageName, int uid);
-
- void setPackagePeekable(String packageName, int uid, boolean peekable);
-
int getPackageVisibilityOverride(String packageName, int uid);
void setPackageVisibilityOverride(String packageName, int uid, int visibility);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 66381f5..f8b661f 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -49,11 +49,9 @@
private static final String ATT_NAME = "name";
private static final String ATT_UID = "uid";
private static final String ATT_PRIORITY = "priority";
- private static final String ATT_PEEKABLE = "peekable";
private static final String ATT_VISIBILITY = "visibility";
private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
- private static final boolean DEFAULT_PEEKABLE = true;
private static final int DEFAULT_VISIBILITY =
NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
@@ -141,7 +139,6 @@
if (TAG_PACKAGE.equals(tag)) {
int uid = safeInt(parser, ATT_UID, Record.UNKNOWN_UID);
int priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
- boolean peekable = safeBool(parser, ATT_PEEKABLE, DEFAULT_PEEKABLE);
int vis = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
String name = parser.getAttributeValue(null, ATT_NAME);
@@ -167,9 +164,6 @@
if (priority != DEFAULT_PRIORITY) {
r.priority = priority;
}
- if (peekable != DEFAULT_PEEKABLE) {
- r.peekable = peekable;
- }
if (vis != DEFAULT_VISIBILITY) {
r.visibility = vis;
}
@@ -200,8 +194,7 @@
final int N = mRecords.size();
for (int i = N - 1; i >= 0; i--) {
final Record r = mRecords.valueAt(i);
- if (r.priority == DEFAULT_PRIORITY && r.peekable == DEFAULT_PEEKABLE
- && r.visibility == DEFAULT_VISIBILITY) {
+ if (r.priority == DEFAULT_PRIORITY && r.visibility == DEFAULT_VISIBILITY) {
mRecords.remove(i);
}
}
@@ -223,9 +216,6 @@
if (r.priority != DEFAULT_PRIORITY) {
out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
}
- if (r.peekable != DEFAULT_PEEKABLE) {
- out.attribute(null, ATT_PEEKABLE, Boolean.toString(r.peekable));
- }
if (r.visibility != DEFAULT_VISIBILITY) {
out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
}
@@ -348,22 +338,6 @@
}
@Override
- public boolean getPackagePeekable(String packageName, int uid) {
- final Record r = mRecords.get(recordKey(packageName, uid));
- return r != null ? r.peekable : DEFAULT_PEEKABLE;
- }
-
- @Override
- public void setPackagePeekable(String packageName, int uid, boolean peekable) {
- if (peekable == getPackagePeekable(packageName, uid)) {
- return;
- }
- getOrCreateRecord(packageName, uid).peekable = peekable;
- removeDefaultRecords();
- updateConfig();
- }
-
- @Override
public int getPackageVisibilityOverride(String packageName, int uid) {
final Record r = mRecords.get(recordKey(packageName, uid));
return r != null ? r.visibility : DEFAULT_VISIBILITY;
@@ -415,10 +389,6 @@
pw.print(" priority=");
pw.print(Notification.priorityToString(r.priority));
}
- if (r.peekable != DEFAULT_PEEKABLE) {
- pw.print(" peekable=");
- pw.print(r.peekable);
- }
if (r.visibility != DEFAULT_VISIBILITY) {
pw.print(" visibility=");
pw.print(Notification.visibilityToString(r.visibility));
@@ -460,7 +430,6 @@
String pkg;
int uid = UNKNOWN_UID;
int priority = DEFAULT_PRIORITY;
- boolean peekable = DEFAULT_PEEKABLE;
int visibility = DEFAULT_VISIBILITY;
}
diff --git a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
index 0420269..c9e1315 100644
--- a/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
+++ b/services/core/java/com/android/server/notification/ValidateNotificationPeople.java
@@ -41,6 +41,9 @@
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
+import android.os.SystemClock;
+import com.android.internal.logging.MetricsLogger;
+
/**
* This {@link NotificationSignalExtractor} attempts to validate
* people references. Also elevates the priority of real people.
@@ -218,6 +221,7 @@
private PeopleRankingReconsideration validatePeople(Context context, String key, Bundle extras,
float[] affinityOut) {
+ long start = SystemClock.elapsedRealtime();
float affinity = NONE;
if (extras == null) {
return null;
@@ -251,6 +255,9 @@
// record the best available data, so far:
affinityOut[0] = affinity;
+ MetricsLogger.histogram(mBaseContext, "validate_people_cache_latency",
+ (int) (SystemClock.elapsedRealtime() - start));
+
if (pendingLookups.isEmpty()) {
if (VERBOSE) Slog.i(TAG, "final affinity: " + affinity);
return null;
@@ -430,6 +437,7 @@
@Override
public void work() {
+ long start = SystemClock.elapsedRealtime();
if (VERBOSE) Slog.i(TAG, "Executing: validation for: " + mKey);
long timeStartMs = System.currentTimeMillis();
for (final String handle: mPendingLookups) {
@@ -468,6 +476,9 @@
mUsageStats.registerPeopleAffinity(mRecord, mContactAffinity > NONE,
mContactAffinity == STARRED_CONTACT, false /* cached */);
}
+
+ MetricsLogger.histogram(mBaseContext, "validate_people_lookup_latency",
+ (int) (SystemClock.elapsedRealtime() - start));
}
@Override
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index 8fac9da..073b4f03 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -57,6 +57,8 @@
private static final String TAG = "DefaultPermGrantPolicy"; // must be <= 23 chars
private static final boolean DEBUG = false;
+ private static final int DEFAULT_FLAGS = PackageManager.GET_ENCRYPTION_UNAWARE_COMPONENTS;
+
private static final String AUDIO_MIME_TYPE = "audio/mpeg";
private static final Set<String> PHONE_PERMISSIONS = new ArraySet<>();
@@ -696,7 +698,7 @@
private PackageParser.Package getDefaultSystemHandlerActivityPackageLPr(
Intent intent, int userId) {
ResolveInfo handler = mService.resolveIntent(intent,
- intent.resolveType(mService.mContext.getContentResolver()), 0, userId);
+ intent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS, userId);
if (handler == null || handler.activityInfo == null) {
return null;
}
@@ -711,7 +713,7 @@
private PackageParser.Package getDefaultSystemHandlerServicePackageLPr(
Intent intent, int userId) {
List<ResolveInfo> handlers = mService.queryIntentServices(intent,
- intent.resolveType(mService.mContext.getContentResolver()), 0, userId);
+ intent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS, userId);
if (handlers == null) {
return null;
}
@@ -738,7 +740,8 @@
homeIntent.setPackage(syncAdapterPackageName);
ResolveInfo homeActivity = mService.resolveIntent(homeIntent,
- homeIntent.resolveType(mService.mContext.getContentResolver()), 0, userId);
+ homeIntent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS,
+ userId);
if (homeActivity != null) {
continue;
}
@@ -754,7 +757,7 @@
private PackageParser.Package getDefaultProviderAuthorityPackageLPr(
String authority, int userId) {
- ProviderInfo provider = mService.resolveContentProvider(authority, 0, userId);
+ ProviderInfo provider = mService.resolveContentProvider(authority, DEFAULT_FLAGS, userId);
if (provider != null) {
return getSystemPackageLPr(provider.packageName);
}
diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
new file mode 100644
index 0000000..628ad0e
--- /dev/null
+++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java
@@ -0,0 +1,187 @@
+/*
+ * 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.server.pm;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.TimedRemoteCaller;
+
+import com.android.internal.app.EphemeralResolverService;
+import com.android.internal.app.EphemeralResolveInfo;
+import com.android.internal.app.IEphemeralResolver;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Represents a remote ephemeral resolver. It is responsible for binding to the remote
+ * service and handling all interactions in a timely manner.
+ * @hide
+ */
+final class EphemeralResolverConnection {
+ // This is running in a critical section and the timeout must be sufficiently low
+ private static final long BIND_SERVICE_TIMEOUT_MS =
+ ("eng".equals(Build.TYPE)) ? 300 : 200;
+
+ private final Object mLock = new Object();
+ private final GetEphemeralResolveInfoCaller mGetEphemeralResolveInfoCaller =
+ new GetEphemeralResolveInfoCaller();
+ private final ServiceConnection mServiceConnection = new MyServiceConnection();
+ private final Context mContext;
+ /** Intent used to bind to the service */
+ private final Intent mIntent;
+
+ private IEphemeralResolver mRemoteInstance;
+
+ public EphemeralResolverConnection(Context context, ComponentName componentName) {
+ mContext = context;
+ mIntent = new Intent().setComponent(componentName);
+ }
+
+ public final List<EphemeralResolveInfo> getEphemeralResolveInfoList(int hashPrefix) {
+ throwIfCalledOnMainThread();
+ try {
+ return mGetEphemeralResolveInfoCaller.getEphemeralResolveInfoList(
+ getRemoteInstanceLazy(), hashPrefix);
+ } catch (RemoteException re) {
+ } catch (TimeoutException te) {
+ } finally {
+ synchronized (mLock) {
+ mLock.notifyAll();
+ }
+ }
+ return null;
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String prefix) {
+ synchronized (mLock) {
+ pw.append(prefix).append("bound=")
+ .append((mRemoteInstance != null) ? "true" : "false").println();
+
+ pw.flush();
+
+ try {
+ getRemoteInstanceLazy().asBinder().dump(fd, new String[] { prefix });
+ } catch (TimeoutException te) {
+ /* ignore */
+ } catch (RemoteException re) {
+ /* ignore */
+ }
+ }
+ }
+
+ private IEphemeralResolver getRemoteInstanceLazy() throws TimeoutException {
+ synchronized (mLock) {
+ if (mRemoteInstance != null) {
+ return mRemoteInstance;
+ }
+ bindLocked();
+ return mRemoteInstance;
+ }
+ }
+
+ private void bindLocked() throws TimeoutException {
+ if (mRemoteInstance != null) {
+ return;
+ }
+
+ mContext.bindServiceAsUser(mIntent, mServiceConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, UserHandle.SYSTEM);
+
+ final long startMillis = SystemClock.uptimeMillis();
+ while (true) {
+ if (mRemoteInstance != null) {
+ break;
+ }
+ final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
+ final long remainingMillis = BIND_SERVICE_TIMEOUT_MS - elapsedMillis;
+ if (remainingMillis <= 0) {
+ throw new TimeoutException("Didn't bind to resolver in time.");
+ }
+ try {
+ mLock.wait(remainingMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+
+ mLock.notifyAll();
+ }
+
+ private void throwIfCalledOnMainThread() {
+ if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
+ throw new RuntimeException("Cannot invoke on the main thread");
+ }
+ }
+
+ private final class MyServiceConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (mLock) {
+ mRemoteInstance = IEphemeralResolver.Stub.asInterface(service);
+ mLock.notifyAll();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (mLock) {
+ mRemoteInstance = null;
+ }
+ }
+ }
+
+ private static final class GetEphemeralResolveInfoCaller
+ extends TimedRemoteCaller<List<EphemeralResolveInfo>> {
+ private final IRemoteCallback mCallback;
+
+ public GetEphemeralResolveInfoCaller() {
+ super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
+ mCallback = new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ final ArrayList<EphemeralResolveInfo> resolveList =
+ data.getParcelableArrayList(
+ EphemeralResolverService.EXTRA_RESOLVE_INFO);
+ int sequence =
+ data.getInt(EphemeralResolverService.EXTRA_SEQUENCE, -1);
+ onRemoteMethodResult(resolveList, sequence);
+ }
+ };
+ }
+
+ public List<EphemeralResolveInfo> getEphemeralResolveInfoList(
+ IEphemeralResolver target, int hashPrefix)
+ throws RemoteException, TimeoutException {
+ final int sequence = onBeforeRemoteCall();
+ target.getEphemeralResolveInfoList(mCallback, hashPrefix, sequence);
+ return getResultTimed(sequence);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 992919e..4f3544b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -209,6 +209,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.EphemeralResolveInfo;
import com.android.internal.app.IMediaContainerService;
import com.android.internal.app.ResolverActivity;
import com.android.internal.content.NativeLibraryHelper;
@@ -252,6 +253,7 @@
import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
@@ -301,6 +303,7 @@
private static final boolean DEBUG_VERIFY = false;
private static final boolean DEBUG_DEXOPT = false;
private static final boolean DEBUG_ABI_SELECTION = false;
+ private static final boolean DEBUG_EPHEMERAL = false;
static final boolean CLEAR_RUNTIME_PERMISSIONS_ON_UPGRADE = false;
@@ -598,6 +601,16 @@
private final ComponentName mIntentFilterVerifierComponent;
private int mIntentFilterVerificationToken = 0;
+ /** Component that knows whether or not an ephemeral application exists */
+ final ComponentName mEphemeralResolverComponent;
+ /** The service connection to the ephemeral resolver */
+ final EphemeralResolverConnection mEphemeralResolverConnection;
+
+ /** Component used to install ephemeral applications */
+ final ComponentName mEphemeralInstallerComponent;
+ final ActivityInfo mEphemeralInstallerActivity = new ActivityInfo();
+ final ResolveInfo mEphemeralInstallerInfo = new ResolveInfo();
+
final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates
= new SparseArray<IntentFilterVerificationState>();
@@ -2346,6 +2359,33 @@
mIntentFilterVerifier = new IntentVerifierProxy(mContext,
mIntentFilterVerifierComponent);
+ final ComponentName ephemeralResolverComponent = getEphemeralResolverLPr();
+ final ComponentName ephemeralInstallerComponent = getEphemeralInstallerLPr();
+ // both the installer and resolver must be present to enable ephemeral
+ if (ephemeralInstallerComponent != null && ephemeralResolverComponent != null) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.i(TAG, "Ephemeral activated; resolver: " + ephemeralResolverComponent
+ + " installer:" + ephemeralInstallerComponent);
+ }
+ mEphemeralResolverComponent = ephemeralResolverComponent;
+ mEphemeralInstallerComponent = ephemeralInstallerComponent;
+ setUpEphemeralInstallerActivityLP(mEphemeralInstallerComponent);
+ mEphemeralResolverConnection =
+ new EphemeralResolverConnection(mContext, mEphemeralResolverComponent);
+ } else {
+ if (DEBUG_EPHEMERAL) {
+ final String missingComponent =
+ (ephemeralResolverComponent == null)
+ ? (ephemeralInstallerComponent == null)
+ ? "resolver and installer"
+ : "resolver"
+ : "installer";
+ Slog.i(TAG, "Ephemeral deactivated; missing " + missingComponent);
+ }
+ mEphemeralResolverComponent = null;
+ mEphemeralInstallerComponent = null;
+ mEphemeralResolverConnection = null;
+ }
} // synchronized (mPackages)
} // synchronized (mInstallLock)
@@ -2484,6 +2524,89 @@
return verifierComponentName;
}
+ private ComponentName getEphemeralResolverLPr() {
+ final String[] packageArray =
+ mContext.getResources().getStringArray(R.array.config_ephemeralResolverPackage);
+ if (packageArray.length == 0) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "Ephemeral resolver NOT found; empty package list");
+ }
+ return null;
+ }
+
+ Intent resolverIntent = new Intent(Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE);
+ final List<ResolveInfo> resolvers = queryIntentServices(resolverIntent,
+ null /*resolvedType*/, 0 /*flags*/, UserHandle.USER_SYSTEM);
+
+ final int N = resolvers.size();
+ if (N == 0) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "Ephemeral resolver NOT found; no matching intent filters");
+ }
+ return null;
+ }
+
+ final Set<String> possiblePackages = new ArraySet<>(Arrays.asList(packageArray));
+ for (int i = 0; i < N; i++) {
+ final ResolveInfo info = resolvers.get(i);
+
+ if (info.serviceInfo == null) {
+ continue;
+ }
+
+ final String packageName = info.serviceInfo.packageName;
+ if (!possiblePackages.contains(packageName)) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "Ephemeral resolver not in allowed package list;"
+ + " pkg: " + packageName + ", info:" + info);
+ }
+ continue;
+ }
+
+ if (DEBUG_EPHEMERAL) {
+ Slog.v(TAG, "Ephemeral resolver found;"
+ + " pkg: " + packageName + ", info:" + info);
+ }
+ return new ComponentName(packageName, info.serviceInfo.name);
+ }
+ if (DEBUG_EPHEMERAL) {
+ Slog.v(TAG, "Ephemeral resolver NOT found");
+ }
+ return null;
+ }
+
+ private ComponentName getEphemeralInstallerLPr() {
+ Intent installerIntent = new Intent(Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE);
+ installerIntent.addCategory(Intent.CATEGORY_DEFAULT);
+ installerIntent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE);
+ final List<ResolveInfo> installers = queryIntentActivities(installerIntent,
+ PACKAGE_MIME_TYPE, 0 /*flags*/, 0 /*userId*/);
+
+ ComponentName ephemeralInstaller = null;
+
+ final int N = installers.size();
+ for (int i = 0; i < N; i++) {
+ final ResolveInfo info = installers.get(i);
+ final String packageName = info.activityInfo.packageName;
+
+ if (!info.activityInfo.applicationInfo.isSystemApp()) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "Ephemeral installer is not system app;"
+ + " pkg: " + packageName + ", info:" + info);
+ }
+ continue;
+ }
+
+ if (ephemeralInstaller != null) {
+ throw new RuntimeException("There must only be one ephemeral installer");
+ }
+
+ ephemeralInstaller = new ComponentName(packageName, info.activityInfo.name);
+ }
+
+ return ephemeralInstaller;
+ }
+
private void primeDomainVerificationsLPw(int userId) {
if (DEBUG_DOMAIN_VERIFICATION) {
Slog.d(TAG, "Priming domain verifications in user " + userId);
@@ -3021,18 +3144,19 @@
* purposefully done before acquiring {@link #mPackages} lock.
*/
private int augmentFlagsForUser(int flags, int userId) {
- if (SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false)) {
+ if (StorageManager.isFileBasedEncryptionEnabled()) {
final IMountService mount = IMountService.Stub
- .asInterface(ServiceManager.getService(Context.STORAGE_SERVICE));
+ .asInterface(ServiceManager.getService("mount"));
if (mount == null) {
// We must be early in boot, so the best we can do is assume the
// user is fully running.
+ Slog.w(TAG, "Early during boot, assuming not encrypted");
return flags;
}
final long token = Binder.clearCallingIdentity();
try {
if (!mount.isUserKeyUnlocked(userId)) {
- flags |= PackageManager.FLAG_USER_RUNNING_WITH_AMNESIA;
+ flags |= PackageManager.MATCH_ENCRYPTION_AWARE_ONLY;
}
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
@@ -4270,8 +4394,97 @@
false, false, false, userId);
}
+ private boolean isEphemeralAvailable(Intent intent, String resolvedType, int userId) {
+ MessageDigest digest = null;
+ try {
+ digest = MessageDigest.getInstance(EphemeralResolveInfo.SHA_ALGORITHM);
+ } catch (NoSuchAlgorithmException e) {
+ // If we can't create a digest, ignore ephemeral apps.
+ return false;
+ }
+
+ final byte[] hostBytes = intent.getData().getHost().getBytes();
+ final byte[] digestBytes = digest.digest(hostBytes);
+ int shaPrefix =
+ digestBytes[0] << 24
+ | digestBytes[1] << 16
+ | digestBytes[2] << 8
+ | digestBytes[3] << 0;
+ final List<EphemeralResolveInfo> ephemeralResolveInfoList =
+ mEphemeralResolverConnection.getEphemeralResolveInfoList(shaPrefix);
+ if (ephemeralResolveInfoList == null || ephemeralResolveInfoList.size() == 0) {
+ // No hash prefix match; there are no ephemeral apps for this domain.
+ return false;
+ }
+ for (int i = ephemeralResolveInfoList.size() - 1; i >= 0; --i) {
+ EphemeralResolveInfo ephemeralApplication = ephemeralResolveInfoList.get(i);
+ if (!Arrays.equals(digestBytes, ephemeralApplication.getDigestBytes())) {
+ continue;
+ }
+ final List<IntentFilter> filters = ephemeralApplication.getFilters();
+ // No filters; this should never happen.
+ if (filters.isEmpty()) {
+ continue;
+ }
+ // We have a domain match; resolve the filters to see if anything matches.
+ final EphemeralIntentResolver ephemeralResolver = new EphemeralIntentResolver();
+ for (int j = filters.size() - 1; j >= 0; --j) {
+ ephemeralResolver.addFilter(filters.get(j));
+ }
+ List<ResolveInfo> ephemeralResolveList = ephemeralResolver.queryIntent(
+ intent, resolvedType, false /*defaultOnly*/, userId);
+ return !ephemeralResolveList.isEmpty();
+ }
+ // Hash or filter mis-match; no ephemeral apps for this domain.
+ return false;
+ }
+
private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
int flags, List<ResolveInfo> query, int userId) {
+ final boolean isWebUri = hasWebURI(intent);
+ // Check whether or not an ephemeral app exists to handle the URI.
+ if (isWebUri && mEphemeralResolverConnection != null) {
+ // Deny ephemeral apps if the user choose _ALWAYS or _ALWAYS_ASK for intent resolution.
+ boolean hasAlwaysHandler = false;
+ synchronized (mPackages) {
+ final int count = query.size();
+ for (int n=0; n<count; n++) {
+ ResolveInfo info = query.get(n);
+ String packageName = info.activityInfo.packageName;
+ PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps != null) {
+ // Try to get the status from User settings first
+ long packedStatus = getDomainVerificationStatusLPr(ps, userId);
+ int status = (int) (packedStatus >> 32);
+ if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS
+ || status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) {
+ hasAlwaysHandler = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // Only consider installing an ephemeral app if there isn't already a verified handler.
+ // We've determined that there's an ephemeral app available for the URI, ignore any
+ // ResolveInfo's and just return the ephemeral installer
+ if (!hasAlwaysHandler && isEphemeralAvailable(intent, resolvedType, userId)) {
+ if (DEBUG_EPHEMERAL) {
+ Slog.v(TAG, "Resolving to the ephemeral installer");
+ }
+ // ditch the result and return a ResolveInfo to launch the ephemeral installer
+ ResolveInfo ri = new ResolveInfo(mEphemeralInstallerInfo);
+ ri.activityInfo = new ActivityInfo(ri.activityInfo);
+ // make a deep copy of the applicationInfo
+ ri.activityInfo.applicationInfo = new ApplicationInfo(
+ ri.activityInfo.applicationInfo);
+ if (userId != 0) {
+ ri.activityInfo.applicationInfo.uid = UserHandle.getUid(userId,
+ UserHandle.getAppId(ri.activityInfo.applicationInfo.uid));
+ }
+ return ri;
+ }
+ }
if (query != null) {
final int N = query.size();
if (N == 1) {
@@ -6302,22 +6515,25 @@
return true;
}
- private int createDataDirsLI(String volumeUuid, String packageName, int uid, String seinfo) {
- int[] users = sUserManager.getUserIds();
+ private void createDataDirsLI(String volumeUuid, String packageName, int uid, String seinfo)
+ throws PackageManagerException {
int res = mInstaller.install(volumeUuid, packageName, uid, uid, seinfo);
- if (res < 0) {
- return res;
+ if (res != 0) {
+ throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
+ "Failed to install " + packageName + ": " + res);
}
+
+ final int[] users = sUserManager.getUserIds();
for (int user : users) {
if (user != 0) {
res = mInstaller.createUserData(volumeUuid, packageName,
UserHandle.getUid(user, uid), user, seinfo);
- if (res < 0) {
- return res;
+ if (res != 0) {
+ throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
+ "Failed to createUserData " + packageName + ": " + res);
}
}
}
- return res;
}
private int removeDataDirsLI(String volumeUuid, String packageName) {
@@ -6887,18 +7103,6 @@
+ pkg.applicationInfo.uid + "; old data erased";
reportSettingsProblem(Log.WARN, msg);
recovered = true;
-
- // And now re-install the app.
- ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
- pkg.applicationInfo.seinfo);
- if (ret == -1) {
- // Ack should not happen!
- msg = prefix + pkg.packageName
- + " could not have data directory re-created after delete.";
- reportSettingsProblem(Log.WARN, msg);
- throw new PackageManagerException(
- INSTALL_FAILED_INSUFFICIENT_STORAGE, msg);
- }
}
if (!recovered) {
mHasSystemUidErrors = true;
@@ -6931,6 +7135,10 @@
}
}
+ // Ensure that directories are prepared
+ createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
+ pkg.applicationInfo.seinfo);
+
if (mShouldRestoreconData) {
Slog.i(TAG, "SELinux relabeling of " + pkg.packageName + " issued.");
mInstaller.restoreconData(pkg.volumeUuid, pkg.packageName,
@@ -6941,14 +7149,8 @@
if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
Log.v(TAG, "Want this data dir: " + dataPath);
}
- //invoke installer to do the actual installation
- int ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
+ createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
pkg.applicationInfo.seinfo);
- if (ret < 0) {
- // Error from installer
- throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
- "Unable to create data dirs [errorCode=" + ret + "]");
- }
}
// Get all of our default paths setup
@@ -7780,6 +7982,30 @@
}
}
+ private void setUpEphemeralInstallerActivityLP(ComponentName installerComponent) {
+ final PackageParser.Package pkg = mPackages.get(installerComponent.getPackageName());
+
+ // Set up information for ephemeral installer activity
+ mEphemeralInstallerActivity.applicationInfo = pkg.applicationInfo;
+ mEphemeralInstallerActivity.name = mEphemeralInstallerComponent.getClassName();
+ mEphemeralInstallerActivity.packageName = pkg.applicationInfo.packageName;
+ mEphemeralInstallerActivity.processName = pkg.applicationInfo.packageName;
+ mEphemeralInstallerActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+ mEphemeralInstallerActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS |
+ ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
+ mEphemeralInstallerActivity.theme = 0;
+ mEphemeralInstallerActivity.exported = true;
+ mEphemeralInstallerActivity.enabled = true;
+ mEphemeralInstallerInfo.activityInfo = mEphemeralInstallerActivity;
+ mEphemeralInstallerInfo.priority = 0;
+ mEphemeralInstallerInfo.preferredOrder = 0;
+ mEphemeralInstallerInfo.match = 0;
+
+ if (DEBUG_EPHEMERAL) {
+ Slog.d(TAG, "Set ephemeral installer activity: " + mEphemeralInstallerComponent);
+ }
+ }
+
private static String calculateBundledApkRoot(final String codePathString) {
final File codePath = new File(codePathString);
final File codeRoot;
@@ -9336,7 +9562,28 @@
private final ArrayMap<ComponentName, PackageParser.Provider> mProviders
= new ArrayMap<ComponentName, PackageParser.Provider>();
private int mFlags;
- };
+ }
+
+ private static final class EphemeralIntentResolver
+ extends IntentResolver<IntentFilter, ResolveInfo> {
+ @Override
+ protected IntentFilter[] newArray(int size) {
+ return new IntentFilter[size];
+ }
+
+ @Override
+ protected boolean isPackageForFilter(String packageName, IntentFilter info) {
+ return true;
+ }
+
+ @Override
+ protected ResolveInfo newResult(IntentFilter info, int match, int userId) {
+ if (!sUserManager.exists(userId)) return null;
+ final ResolveInfo res = new ResolveInfo();
+ res.filter = info;
+ return res;
+ }
+ }
private static final Comparator<ResolveInfo> mResolvePrioritySorter =
new Comparator<ResolveInfo>() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 2cedc9c..dbb5818 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -1000,7 +1000,7 @@
pw.println(" the text in FILTER.");
pw.println(" Options:");
pw.println(" -f: see their associated file");
- pw.println(" -d: filter to only show disbled packages");
+ pw.println(" -d: filter to only show disabled packages");
pw.println(" -e: filter to only show enabled packages");
pw.println(" -s: filter to only show system packages");
pw.println(" -3: filter to only show third party packages");
@@ -1055,4 +1055,3 @@
}
}
}
-
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 1d299d7..99aa30b 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3802,8 +3802,7 @@
if ((flags & PackageManager.GET_ENCRYPTION_UNAWARE_COMPONENTS) != 0) {
return true;
}
- if ((flags & PackageManager.FLAG_USER_RUNNING_WITH_AMNESIA) != 0) {
- // When running with amnesia, we can only run encryption-aware apps
+ if ((flags & PackageManager.MATCH_ENCRYPTION_AWARE_ONLY) != 0) {
return componentInfo.encryptionAware;
}
return true;
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ab0b182..baeccb4 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -322,7 +322,7 @@
final int userSize = mUsers.size();
for (int i = 0; i < userSize; i++) {
UserInfo ui = mUsers.valueAt(i);
- if (ui.isPrimary()) {
+ if (ui.isPrimary() && !mRemovingUserIds.get(ui.id)) {
return ui;
}
}
@@ -392,7 +392,7 @@
@Override
public int getCredentialOwnerProfile(int userHandle) {
checkManageUsersPermission("get the credential owner");
- if (!mContext.getSystemService(StorageManager.class).isPerUserEncryptionEnabled()) {
+ if (!StorageManager.isFileBasedEncryptionEnabled()) {
synchronized (mUsersLock) {
UserInfo profileParent = getProfileParentLU(userHandle);
if (profileParent != null) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 121ef21..ae6874f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5318,11 +5318,9 @@
}
private boolean shouldDispatchInputWhenNonInteractive() {
- if (mDisplay == null || mDisplay.getState() == Display.STATE_OFF) {
- return false;
- }
- // Send events to keyguard while the screen is on and it's showing.
- if (isKeyguardShowingAndNotOccluded()) {
+ // Send events to keyguard while the screen is on.
+ if (isKeyguardShowingAndNotOccluded() && mDisplay != null
+ && mDisplay.getState() != Display.STATE_OFF) {
return true;
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 943c9ed..8b1a830 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -1480,7 +1480,9 @@
mNextAppTransitionFutureCallback, null /* finishedCallback */,
mNextAppTransitionScaleUp);
mNextAppTransitionFutureCallback = null;
- mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp);
+ if (specs != null) {
+ mService.prolongAnimationsFromSpecs(specs, mNextAppTransitionScaleUp);
+ }
}
mService.requestTraversal();
}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index b85a6923..1caeca0 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -401,32 +401,6 @@
}
}
- public void cancelDrag(IBinder dragToken) {
- if (WindowManagerService.DEBUG_DRAG) {
- Slog.d(WindowManagerService.TAG, "cancel drag");
- }
-
- synchronized (mService.mWindowMap) {
- long ident = Binder.clearCallingIdentity();
- try {
- if (mService.mDragState == null) {
- Slog.w(WindowManagerService.TAG, "cancelDrag() without prepareDrag()");
- throw new IllegalStateException("cancelDrag() without prepareDrag()");
- }
-
- if (mService.mDragState.mToken != dragToken) {
- Slog.w(WindowManagerService.TAG, "cancelDrag() does not match prepareDrag()");
- throw new IllegalStateException("cancelDrag() does not match prepareDrag()");
- }
-
- mService.mDragState.mDragResult = false;
- mService.mDragState.endDragLw();
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
- }
-
public void dragRecipientEntered(IWindow window) {
if (WindowManagerService.DEBUG_DRAG) {
Slog.d(WindowManagerService.TAG, "Drag into new candidate view @ " + window.asBinder());
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3ad2610..253fdad 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -55,6 +55,7 @@
import android.Manifest;
import android.animation.ValueAnimator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManagerNative;
import android.app.AppOpsManager;
@@ -3700,14 +3701,10 @@
}
}
- void prolongAnimationsFromSpecs(AppTransitionAnimationSpec[] specs, boolean scaleUp) {
+ void prolongAnimationsFromSpecs(@NonNull AppTransitionAnimationSpec[] specs, boolean scaleUp) {
// This is used by freeform <-> recents windows transition. We need to synchronize
// the animation with the appearance of the content of recents, so we will make
// animation stay on the first or last frame a little longer.
- if (specs == null) {
- Slog.wtf(TAG, "prolongAnimationsFromSpecs: AppTransitionAnimationSpec is null!");
- return;
- }
mTmpTaskIds.clear();
for (int i = specs.length - 1; i >= 0; i--) {
mTmpTaskIds.put(specs[i].taskId, 0);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 31c3670..844cca5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3852,7 +3852,7 @@
}
enforceCrossUserPermission(userHandle);
// Managed Profile password can only be changed when per user encryption is present.
- if (!mContext.getSystemService(StorageManager.class).isPerUserEncryptionEnabled()) {
+ if (!StorageManager.isFileBasedEncryptionEnabled()) {
enforceNotManagedProfile(userHandle, "set the active password");
}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index e0f95cf..c734fab 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -772,7 +772,8 @@
}
private void updateUsbNotification() {
- if (mNotificationManager == null || !mUseUsbNotification) return;
+ if (mNotificationManager == null || !mUseUsbNotification
+ || ("0".equals(SystemProperties.get("persist.charging.notify")))) return;
int id = 0;
Resources r = mContext.getResources();
if (mConnected || mHostConnected) {
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
index ae4a57d..7ef7566 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
@@ -17,6 +17,7 @@
package com.android.tools.layoutlib.create;
import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -40,6 +41,7 @@
private final String mClassName;
private final Set<String> mDelegateMethods;
private final Log mLog;
+ private boolean mIsStaticInnerClass;
/**
* Creates a new {@link DelegateClassAdapter} that can transform some methods
@@ -62,16 +64,30 @@
mLog = log;
mClassName = className;
mDelegateMethods = delegateMethods;
+ // If this is an inner class, by default, we assume it's static. If it's not we will detect
+ // by looking at the fields (see visitField)
+ mIsStaticInnerClass = className.contains("$");
}
//----------------------------------
// Methods from the ClassAdapter
@Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature,
+ Object value) {
+ if (mIsStaticInnerClass && "this$0".equals(name)) {
+ // Having a "this$0" field, proves that this class is not a static inner class.
+ mIsStaticInnerClass = false;
+ }
+
+ return super.visitField(access, name, desc, signature, value);
+ }
+
+ @Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
- boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+ boolean isStaticMethod = (access & Opcodes.ACC_STATIC) != 0;
boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) ||
@@ -96,7 +112,8 @@
MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions);
DelegateMethodAdapter a = new DelegateMethodAdapter(
- mLog, null, mwDelegate, mClassName, name, desc, isStatic);
+ mLog, null, mwDelegate, mClassName, name, desc, isStaticMethod,
+ mIsStaticInnerClass);
// A native has no code to visit, so we need to generate it directly.
a.generateDelegateCode();
@@ -120,6 +137,7 @@
desc, signature, exceptions);
return new DelegateMethodAdapter(
- mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStatic);
+ mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStaticMethod,
+ mIsStaticInnerClass);
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
index 12690db..cca9e57 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
@@ -85,6 +85,8 @@
private String mDesc;
/** True if the original method is static. */
private final boolean mIsStatic;
+ /** True if the method is contained in a static inner class */
+ private final boolean mIsStaticInnerClass;
/** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
private final String mClassName;
/** The method name. */
@@ -120,7 +122,8 @@
String className,
String methodName,
String desc,
- boolean isStatic) {
+ boolean isStatic,
+ boolean isStaticClass) {
super(Opcodes.ASM4);
mLog = log;
mOrgWriter = mvOriginal;
@@ -129,6 +132,7 @@
mMethodName = methodName;
mDesc = desc;
mIsStatic = isStatic;
+ mIsStaticInnerClass = isStaticClass;
}
/**
@@ -206,7 +210,7 @@
// by the 'this' of any outer class, if any.
if (!mIsStatic) {
- if (outerType != null) {
+ if (outerType != null && !mIsStaticInnerClass) {
// The first-level inner class has a package-protected member called 'this$0'
// that points to the outer class.
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
index 648cea43..e37a09b 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -27,6 +27,7 @@
import com.android.tools.layoutlib.create.dataclass.ClassWithNative;
import com.android.tools.layoutlib.create.dataclass.OuterClass;
import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass;
+import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass;
import org.junit.Before;
import org.junit.Test;
@@ -56,6 +57,8 @@
private static final String OUTER_CLASS_NAME = OuterClass.class.getCanonicalName();
private static final String INNER_CLASS_NAME = OuterClass.class.getCanonicalName() + "$" +
InnerClass.class.getSimpleName();
+ private static final String STATIC_INNER_CLASS_NAME =
+ OuterClass.class.getCanonicalName() + "$" + StaticInnerClass.class.getSimpleName();
@Before
public void setUp() throws Exception {
@@ -294,6 +297,61 @@
}
}
+ @Test
+ public void testDelegateStaticInner() throws Throwable {
+ // We'll delegate the "get" method of both the inner and outer class.
+ HashSet<String> delegateMethods = new HashSet<String>();
+ delegateMethods.add("get");
+
+ // Generate the delegate for the outer class.
+ ClassWriter cwOuter = new ClassWriter(0 /*flags*/);
+ String outerClassName = OUTER_CLASS_NAME.replace('.', '/');
+ DelegateClassAdapter cvOuter = new DelegateClassAdapter(
+ mLog, cwOuter, outerClassName, delegateMethods);
+ ClassReader cr = new ClassReader(OUTER_CLASS_NAME);
+ cr.accept(cvOuter, 0 /* flags */);
+
+ // Generate the delegate for the static inner class.
+ ClassWriter cwInner = new ClassWriter(0 /*flags*/);
+ String innerClassName = STATIC_INNER_CLASS_NAME.replace('.', '/');
+ DelegateClassAdapter cvInner = new DelegateClassAdapter(
+ mLog, cwInner, innerClassName, delegateMethods);
+ cr = new ClassReader(STATIC_INNER_CLASS_NAME);
+ cr.accept(cvInner, 0 /* flags */);
+
+ // Load the generated classes in a different class loader and try them
+ ClassLoader2 cl2 = null;
+ try {
+ cl2 = new ClassLoader2() {
+ @Override
+ public void testModifiedInstance() throws Exception {
+
+ // Check the outer class
+ Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME);
+ Object o2 = outerClazz2.newInstance();
+ assertNotNull(o2);
+
+ // Check the inner class. Since it's not a static inner class, we need
+ // to use the hidden constructor that takes the outer class as first parameter.
+ Class<?> innerClazz2 = loadClass(STATIC_INNER_CLASS_NAME);
+ Constructor<?> innerCons = innerClazz2.getConstructor();
+ Object i2 = innerCons.newInstance();
+ assertNotNull(i2);
+
+ // The original StaticInner.get returns 100+10+20,
+ // but the delegate makes it return 6+10+20
+ assertEquals(6+10+20, callGet(i2, 10, 20));
+ assertEquals(100+10+20, callGet_Original(i2, 10, 20));
+ }
+ };
+ cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray());
+ cl2.add(STATIC_INNER_CLASS_NAME, cwInner.toByteArray());
+ cl2.testModifiedInstance();
+ } catch (Throwable t) {
+ throw dumpGeneratedClass(t, cl2);
+ }
+ }
+
//-------
/**
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
index f083e76..6dfb816 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
@@ -45,6 +45,16 @@
}
}
+ public static class StaticInnerClass {
+ public StaticInnerClass() {
+ }
+
+ // StaticInnerClass.get returns 100 + a + b
+ public int get(int a, long b) {
+ return 100 + a + (int) b;
+ }
+ }
+
@SuppressWarnings("unused")
private String privateMethod() {
return "outerPrivateMethod";
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java
new file mode 100644
index 0000000..a29439e
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java
@@ -0,0 +1,30 @@
+/*
+ * 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.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass;
+
+/**
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class OuterClass_StaticInnerClass_Delegate {
+ // The delegate override of Inner.get return 6 + a + b
+ public static int get(StaticInnerClass inner, int a, long b) {
+ return 6 + a + (int) b;
+ }
+}