summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk1
-rw-r--r--api/current.txt12
-rw-r--r--api/system-current.txt13
-rw-r--r--api/test-current.txt12
-rw-r--r--cmds/am/src/com/android/commands/am/Am.java19
-rw-r--r--core/java/android/accounts/AccountManager.java124
-rw-r--r--core/java/android/animation/AnimatorSet.java14
-rw-r--r--core/java/android/animation/PathKeyframes.java4
-rw-r--r--core/java/android/animation/PropertyValuesHolder.java56
-rw-r--r--core/java/android/app/Activity.java3
-rw-r--r--core/java/android/app/ActivityThread.java8
-rw-r--r--core/java/android/app/ApplicationPackageManager.java10
-rw-r--r--core/java/android/app/Fragment.java33
-rw-r--r--core/java/android/app/FragmentController.java22
-rw-r--r--core/java/android/app/FragmentManager.java26
-rw-r--r--core/java/android/app/INotificationManager.aidl1
-rw-r--r--core/java/android/app/Notification.java7
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java3
-rw-r--r--core/java/android/content/pm/IOtaDexopt.aidl49
-rw-r--r--core/java/android/content/pm/PackageParser.java40
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java23
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java6
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyResultMapper.java10
-rw-r--r--core/java/android/net/ConnectivityManager.java13
-rw-r--r--core/java/android/os/Process.java6
-rw-r--r--core/java/android/os/RecoverySystem.java16
-rw-r--r--core/java/android/os/UserManager.java146
-rw-r--r--core/java/android/os/storage/VolumeInfo.java5
-rw-r--r--core/java/android/print/PrintAttributes.java18
-rw-r--r--core/java/android/provider/BlockedNumberContract.java33
-rwxr-xr-xcore/java/android/provider/Settings.java9
-rw-r--r--core/java/android/util/PathParser.java1
-rw-r--r--core/java/android/view/NotificationHeaderView.java49
-rw-r--r--core/java/android/view/RenderNode.java9
-rw-r--r--core/java/android/view/RenderNodeAnimatorSetHelper.java47
-rw-r--r--core/java/android/view/View.java7
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java44
-rw-r--r--core/java/com/android/internal/app/AlertController.java22
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java73
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java102
-rw-r--r--core/java/com/android/internal/app/ResolverComparator.java32
-rw-r--r--core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java98
-rw-r--r--core/java/com/android/internal/policy/BackdropFrameRenderer.java13
-rw-r--r--core/java/com/android/internal/policy/DecorView.java41
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java9
-rw-r--r--core/jni/Android.mk1
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp194
-rw-r--r--core/jni/android_graphics_drawable_VectorDrawable.cpp6
-rw-r--r--core/jni/android_hardware_SoundTrigger.cpp28
-rw-r--r--core/res/AndroidManifest.xml4
-rw-r--r--core/res/res/drawable-hdpi/ic_user_secure.pngbin0 -> 1069 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_user_secure.pngbin0 -> 747 bytes
-rw-r--r--core/res/res/drawable-xhdpi/ic_user_secure.pngbin0 -> 1311 bytes
-rw-r--r--core/res/res/drawable-xxhdpi/ic_user_secure.pngbin0 -> 1842 bytes
-rw-r--r--core/res/res/drawable-xxxhdpi/ic_user_secure.pngbin0 -> 1095 bytes
-rw-r--r--core/res/res/layout/notification_template_header.xml2
-rw-r--r--core/res/res/values/arrays.xml11
-rw-r--r--core/res/res/values/attrs_manifest.xml6
-rw-r--r--core/res/res/values/public.xml1
-rw-r--r--core/res/res/values/strings.xml19
-rw-r--r--core/res/res/values/symbols.xml11
-rw-r--r--docs/html/preview/support.jd25
-rw-r--r--docs/html/training/id-auth/identify.jd65
-rw-r--r--graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java437
-rw-r--r--graphics/java/android/graphics/drawable/VectorDrawable.java88
-rw-r--r--libs/hwui/Android.mk2
-rw-r--r--libs/hwui/Animator.cpp11
-rw-r--r--libs/hwui/Animator.h26
-rw-r--r--libs/hwui/BakedOpDispatcher.cpp39
-rw-r--r--libs/hwui/Canvas.h12
-rw-r--r--libs/hwui/DisplayListCanvas.cpp13
-rw-r--r--libs/hwui/DisplayListCanvas.h3
-rw-r--r--libs/hwui/FrameBuilder.cpp13
-rw-r--r--libs/hwui/FrameBuilder.h3
-rw-r--r--libs/hwui/PropertyValuesAnimatorSet.cpp129
-rw-r--r--libs/hwui/PropertyValuesAnimatorSet.h85
-rw-r--r--libs/hwui/PropertyValuesHolder.cpp99
-rw-r--r--libs/hwui/PropertyValuesHolder.h121
-rw-r--r--libs/hwui/RecordedOp.h13
-rw-r--r--libs/hwui/RecordingCanvas.cpp11
-rw-r--r--libs/hwui/RecordingCanvas.h2
-rw-r--r--libs/hwui/SkiaCanvas.cpp11
-rw-r--r--libs/hwui/VectorDrawable.cpp281
-rw-r--r--libs/hwui/VectorDrawable.h153
-rw-r--r--media/java/android/media/browse/MediaBrowser.java34
-rw-r--r--media/java/android/media/browse/MediaBrowserUtils.java8
-rw-r--r--media/java/android/service/media/MediaBrowserService.java34
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2SurfaceViewTestCase.java14
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java11
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java27
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2ReprocessCaptureTest.java30
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2StillCaptureTest.java19
-rw-r--r--packages/DocumentsUI/lint.xml8
-rw-r--r--packages/DocumentsUI/res/drawable/ic_sd_storage.xml24
-rw-r--r--packages/DocumentsUI/res/menu/activity.xml11
-rw-r--r--packages/DocumentsUI/res/values/strings.xml4
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java48
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java16
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java2
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/Events.java21
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java40
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/Metrics.java2
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java2
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java135
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java22
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java228
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java5
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java15
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java21
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java1
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DocumentHolderTest.java6
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java18
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java11
-rw-r--r--packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java10
-rw-r--r--packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java10
-rw-r--r--packages/PrintSpooler/res/color/item_text_color.xml20
-rw-r--r--packages/PrintSpooler/res/drawable/ic_info.xml8
-rw-r--r--packages/PrintSpooler/res/layout/printer_dropdown_item.xml2
-rw-r--r--packages/PrintSpooler/res/layout/printer_list_item.xml33
-rw-r--r--packages/PrintSpooler/res/values-ca/arrays.xml33
-rw-r--r--packages/PrintSpooler/res/values-en-rCA/arrays.xml33
-rw-r--r--packages/PrintSpooler/res/values-en-rUS/arrays.xml33
-rw-r--r--packages/PrintSpooler/res/values-es-rUS/arrays.xml33
-rw-r--r--packages/PrintSpooler/res/values-ja/arrays.xml41
-rw-r--r--packages/PrintSpooler/res/values/arrays.xml34
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java23
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java21
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java10
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java5
-rw-r--r--packages/SystemUI/AndroidManifest.xml6
-rw-r--r--packages/SystemUI/res/layout/tv_pip_menu.xml70
-rw-r--r--packages/SystemUI/res/layout/tv_pip_onboarding.xml51
-rw-r--r--packages/SystemUI/res/layout/tv_pip_overlay.xml22
-rw-r--r--packages/SystemUI/res/values/config.xml12
-rw-r--r--packages/SystemUI/res/values/strings_tv.xml11
-rw-r--r--packages/SystemUI/src/com/android/systemui/Prefs.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java227
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java165
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java65
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java98
-rw-r--r--services/accessibility/java/com/android/server/accessibility/TouchExplorer.java151
-rw-r--r--services/core/java/com/android/server/LockSettingsService.java2
-rw-r--r--services/core/java/com/android/server/MountService.java5
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java62
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java2
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityRecord.java11
-rw-r--r--services/core/java/com/android/server/am/ActivityStarter.java8
-rw-r--r--services/core/java/com/android/server/content/SyncManager.java13
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java22
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java16
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java6
-rw-r--r--services/core/java/com/android/server/notification/RankingConfig.java2
-rw-r--r--services/core/java/com/android/server/notification/RankingHelper.java11
-rw-r--r--services/core/java/com/android/server/pm/Installer.java2
-rw-r--r--services/core/java/com/android/server/pm/OtaDexoptService.java274
-rw-r--r--services/core/java/com/android/server/pm/OtaDexoptShellCommand.java100
-rw-r--r--services/core/java/com/android/server/pm/PackageDexOptimizer.java175
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java132
-rw-r--r--services/core/java/com/android/server/wm/AppWindowToken.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java12
-rw-r--r--services/core/jni/com_android_server_AlarmManagerService.cpp8
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java76
-rw-r--r--services/java/com/android/server/SystemServer.java13
-rw-r--r--services/net/java/android/net/ip/IpManager.java38
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java39
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java8
-rw-r--r--telecomm/java/android/telecom/Phone.java5
-rw-r--r--telecomm/java/com/android/internal/telecom/ITelecomService.aidl5
-rw-r--r--telephony/java/android/telephony/DisconnectCause.java11
-rw-r--r--telephony/java/com/android/internal/telephony/RILConstants.java15
-rw-r--r--tools/aapt2/Android.mk2
-rw-r--r--tools/aapt2/Locale.cpp2
-rw-r--r--tools/aapt2/Locale.h4
-rw-r--r--tools/aapt2/filter/ConfigFilter.cpp77
-rw-r--r--tools/aapt2/filter/ConfigFilter.h61
-rw-r--r--tools/aapt2/filter/ConfigFilter_test.cpp112
-rw-r--r--tools/aapt2/link/Link.cpp208
-rw-r--r--tools/aapt2/link/TableMerger.cpp13
-rw-r--r--tools/aapt2/link/TableMerger.h6
-rw-r--r--tools/aapt2/link/TableMerger_test.cpp33
-rw-r--r--tools/aapt2/test/Builders.h5
196 files changed, 5259 insertions, 1903 deletions
diff --git a/Android.mk b/Android.mk
index ad164e207b9b..d68da97451c5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -136,6 +136,7 @@ LOCAL_SRC_FILES += \
core/java/android/content/ISyncStatusObserver.aidl \
core/java/android/content/pm/ILauncherApps.aidl \
core/java/android/content/pm/IOnAppsChangedListener.aidl \
+ core/java/android/content/pm/IOtaDexopt.aidl \
core/java/android/content/pm/IPackageDataObserver.aidl \
core/java/android/content/pm/IPackageDeleteObserver.aidl \
core/java/android/content/pm/IPackageDeleteObserver2.aidl \
diff --git a/api/current.txt b/api/current.txt
index c7f0a75dcc53..18a1582167f7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -68,7 +68,7 @@ package android {
field public static final java.lang.String DUMP = "android.permission.DUMP";
field public static final java.lang.String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR";
field public static final java.lang.String FACTORY_TEST = "android.permission.FACTORY_TEST";
- field public static final deprecated java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
+ field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
field public static final deprecated java.lang.String GET_TASKS = "android.permission.GET_TASKS";
@@ -4422,9 +4422,11 @@ package android.app {
method public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle);
method public deprecated void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle);
method public void onLowMemory();
+ method public void onMultiWindowChanged(boolean);
method public boolean onOptionsItemSelected(android.view.MenuItem);
method public void onOptionsMenuClosed(android.view.Menu);
method public void onPause();
+ method public void onPictureInPictureChanged(boolean);
method public void onPrepareOptionsMenu(android.view.Menu);
method public void onRequestPermissionsResult(int, java.lang.String[], int[]);
method public void onResume();
@@ -4505,9 +4507,11 @@ package android.app {
method public void dispatchDestroy();
method public void dispatchDestroyView();
method public void dispatchLowMemory();
+ method public void dispatchMultiWindowChanged(boolean);
method public boolean dispatchOptionsItemSelected(android.view.MenuItem);
method public void dispatchOptionsMenuClosed(android.view.Menu);
method public void dispatchPause();
+ method public void dispatchPictureInPictureChanged(boolean);
method public boolean dispatchPrepareOptionsMenu(android.view.Menu);
method public void dispatchResume();
method public void dispatchStart();
@@ -23390,6 +23394,7 @@ package android.net {
method public void unregisterNetworkCallback(android.app.PendingIntent);
field public static final deprecated java.lang.String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
field public static final java.lang.String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL";
+ field public static final java.lang.String ACTION_RESTRICT_BACKGROUND_CHANGED = "android.net.conn.RESTRICT_BACKGROUND_CHANGED";
field public static final java.lang.String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
field public static final deprecated int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1
field public static final java.lang.String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL";
@@ -30136,6 +30141,7 @@ package android.provider {
}
public class BlockedNumberContract {
+ method public static boolean canCurrentUserBlockNumbers(android.content.Context);
method public static boolean isBlocked(android.content.Context, java.lang.String);
field public static final java.lang.String AUTHORITY = "com.android.blockednumber";
field public static final android.net.Uri AUTHORITY_URI;
@@ -30145,7 +30151,6 @@ package android.provider {
field public static final java.lang.String COLUMN_E164_NUMBER = "e164_number";
field public static final java.lang.String COLUMN_ID = "_id";
field public static final java.lang.String COLUMN_ORIGINAL_NUMBER = "original_number";
- field public static final java.lang.String COLUMN_STRIPPED_NUMBER = "stripped_number";
field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/blocked_number";
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/blocked_numbers";
field public static final android.net.Uri CONTENT_URI;
@@ -43689,6 +43694,7 @@ package android.view.accessibility {
method public boolean isEnabled();
method public boolean isFocusable();
method public boolean isFocused();
+ method public boolean isImportantForAccessibility();
method public boolean isLongClickable();
method public boolean isMultiLine();
method public boolean isPassword();
@@ -43727,6 +43733,7 @@ package android.view.accessibility {
method public void setError(java.lang.CharSequence);
method public void setFocusable(boolean);
method public void setFocused(boolean);
+ method public void setImportantForAccessibility(boolean);
method public void setInputType(int);
method public void setLabelFor(android.view.View);
method public void setLabelFor(android.view.View, int);
@@ -52537,7 +52544,6 @@ package java.net {
method public static void setURLStreamHandlerFactory(java.net.URLStreamHandlerFactory);
method public java.lang.String toExternalForm();
method public java.net.URI toURI() throws java.net.URISyntaxException;
- method public java.net.URI toURILenient() throws java.net.URISyntaxException;
}
public class URLClassLoader extends java.security.SecureClassLoader implements java.io.Closeable {
diff --git a/api/system-current.txt b/api/system-current.txt
index 3c5e59e6a4c3..7c181c80fd9e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -96,7 +96,7 @@ package android {
field public static final java.lang.String FACTORY_TEST = "android.permission.FACTORY_TEST";
field public static final java.lang.String FORCE_BACK = "android.permission.FORCE_BACK";
field public static final java.lang.String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
- field public static final deprecated java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
+ field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
field public static final java.lang.String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS";
field public static final java.lang.String GET_PACKAGE_IMPORTANCE = "android.permission.GET_PACKAGE_IMPORTANCE";
@@ -293,6 +293,7 @@ package android {
public static final class R.attr {
ctor public R.attr();
+ field public static final int abiOverride = 16844054; // 0x1010516
field public static final int absListViewStyle = 16842858; // 0x101006a
field public static final int accessibilityEventTypes = 16843648; // 0x1010380
field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
@@ -4554,9 +4555,11 @@ package android.app {
method public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle);
method public deprecated void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle);
method public void onLowMemory();
+ method public void onMultiWindowChanged(boolean);
method public boolean onOptionsItemSelected(android.view.MenuItem);
method public void onOptionsMenuClosed(android.view.Menu);
method public void onPause();
+ method public void onPictureInPictureChanged(boolean);
method public void onPrepareOptionsMenu(android.view.Menu);
method public void onRequestPermissionsResult(int, java.lang.String[], int[]);
method public void onResume();
@@ -4637,9 +4640,11 @@ package android.app {
method public void dispatchDestroy();
method public void dispatchDestroyView();
method public void dispatchLowMemory();
+ method public void dispatchMultiWindowChanged(boolean);
method public boolean dispatchOptionsItemSelected(android.view.MenuItem);
method public void dispatchOptionsMenuClosed(android.view.Menu);
method public void dispatchPause();
+ method public void dispatchPictureInPictureChanged(boolean);
method public boolean dispatchPrepareOptionsMenu(android.view.Menu);
method public void dispatchResume();
method public void dispatchStart();
@@ -24957,6 +24962,7 @@ package android.net {
method public void unregisterNetworkCallback(android.app.PendingIntent);
field public static final deprecated java.lang.String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
field public static final java.lang.String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL";
+ field public static final java.lang.String ACTION_RESTRICT_BACKGROUND_CHANGED = "android.net.conn.RESTRICT_BACKGROUND_CHANGED";
field public static final java.lang.String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
field public static final deprecated int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1
field public static final java.lang.String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL";
@@ -32183,6 +32189,7 @@ package android.provider {
}
public class BlockedNumberContract {
+ method public static boolean canCurrentUserBlockNumbers(android.content.Context);
method public static boolean isBlocked(android.content.Context, java.lang.String);
field public static final java.lang.String AUTHORITY = "com.android.blockednumber";
field public static final android.net.Uri AUTHORITY_URI;
@@ -32192,7 +32199,6 @@ package android.provider {
field public static final java.lang.String COLUMN_E164_NUMBER = "e164_number";
field public static final java.lang.String COLUMN_ID = "_id";
field public static final java.lang.String COLUMN_ORIGINAL_NUMBER = "original_number";
- field public static final java.lang.String COLUMN_STRIPPED_NUMBER = "stripped_number";
field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/blocked_number";
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/blocked_numbers";
field public static final android.net.Uri CONTENT_URI;
@@ -46110,6 +46116,7 @@ package android.view.accessibility {
method public boolean isEnabled();
method public boolean isFocusable();
method public boolean isFocused();
+ method public boolean isImportantForAccessibility();
method public boolean isLongClickable();
method public boolean isMultiLine();
method public boolean isPassword();
@@ -46148,6 +46155,7 @@ package android.view.accessibility {
method public void setError(java.lang.CharSequence);
method public void setFocusable(boolean);
method public void setFocused(boolean);
+ method public void setImportantForAccessibility(boolean);
method public void setInputType(int);
method public void setLabelFor(android.view.View);
method public void setLabelFor(android.view.View, int);
@@ -55291,7 +55299,6 @@ package java.net {
method public static void setURLStreamHandlerFactory(java.net.URLStreamHandlerFactory);
method public java.lang.String toExternalForm();
method public java.net.URI toURI() throws java.net.URISyntaxException;
- method public java.net.URI toURILenient() throws java.net.URISyntaxException;
}
public class URLClassLoader extends java.security.SecureClassLoader implements java.io.Closeable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 3e755ae299c7..8551950c1c9e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -68,7 +68,7 @@ package android {
field public static final java.lang.String DUMP = "android.permission.DUMP";
field public static final java.lang.String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR";
field public static final java.lang.String FACTORY_TEST = "android.permission.FACTORY_TEST";
- field public static final deprecated java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
+ field public static final java.lang.String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
field public static final java.lang.String GET_ACCOUNTS_PRIVILEGED = "android.permission.GET_ACCOUNTS_PRIVILEGED";
field public static final java.lang.String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
field public static final deprecated java.lang.String GET_TASKS = "android.permission.GET_TASKS";
@@ -4422,9 +4422,11 @@ package android.app {
method public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle);
method public deprecated void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle);
method public void onLowMemory();
+ method public void onMultiWindowChanged(boolean);
method public boolean onOptionsItemSelected(android.view.MenuItem);
method public void onOptionsMenuClosed(android.view.Menu);
method public void onPause();
+ method public void onPictureInPictureChanged(boolean);
method public void onPrepareOptionsMenu(android.view.Menu);
method public void onRequestPermissionsResult(int, java.lang.String[], int[]);
method public void onResume();
@@ -4505,9 +4507,11 @@ package android.app {
method public void dispatchDestroy();
method public void dispatchDestroyView();
method public void dispatchLowMemory();
+ method public void dispatchMultiWindowChanged(boolean);
method public boolean dispatchOptionsItemSelected(android.view.MenuItem);
method public void dispatchOptionsMenuClosed(android.view.Menu);
method public void dispatchPause();
+ method public void dispatchPictureInPictureChanged(boolean);
method public boolean dispatchPrepareOptionsMenu(android.view.Menu);
method public void dispatchResume();
method public void dispatchStart();
@@ -23399,6 +23403,7 @@ package android.net {
method public void unregisterNetworkCallback(android.app.PendingIntent);
field public static final deprecated java.lang.String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED";
field public static final java.lang.String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL";
+ field public static final java.lang.String ACTION_RESTRICT_BACKGROUND_CHANGED = "android.net.conn.RESTRICT_BACKGROUND_CHANGED";
field public static final java.lang.String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
field public static final deprecated int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1
field public static final java.lang.String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL";
@@ -30149,6 +30154,7 @@ package android.provider {
}
public class BlockedNumberContract {
+ method public static boolean canCurrentUserBlockNumbers(android.content.Context);
method public static boolean isBlocked(android.content.Context, java.lang.String);
field public static final java.lang.String AUTHORITY = "com.android.blockednumber";
field public static final android.net.Uri AUTHORITY_URI;
@@ -30158,7 +30164,6 @@ package android.provider {
field public static final java.lang.String COLUMN_E164_NUMBER = "e164_number";
field public static final java.lang.String COLUMN_ID = "_id";
field public static final java.lang.String COLUMN_ORIGINAL_NUMBER = "original_number";
- field public static final java.lang.String COLUMN_STRIPPED_NUMBER = "stripped_number";
field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/blocked_number";
field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/blocked_numbers";
field public static final android.net.Uri CONTENT_URI;
@@ -43706,6 +43711,7 @@ package android.view.accessibility {
method public boolean isEnabled();
method public boolean isFocusable();
method public boolean isFocused();
+ method public boolean isImportantForAccessibility();
method public boolean isLongClickable();
method public boolean isMultiLine();
method public boolean isPassword();
@@ -43744,6 +43750,7 @@ package android.view.accessibility {
method public void setError(java.lang.CharSequence);
method public void setFocusable(boolean);
method public void setFocused(boolean);
+ method public void setImportantForAccessibility(boolean);
method public void setInputType(int);
method public void setLabelFor(android.view.View);
method public void setLabelFor(android.view.View, int);
@@ -52554,7 +52561,6 @@ package java.net {
method public static void setURLStreamHandlerFactory(java.net.URLStreamHandlerFactory);
method public java.lang.String toExternalForm();
method public java.net.URI toURI() throws java.net.URISyntaxException;
- method public java.net.URI toURILenient() throws java.net.URISyntaxException;
}
public class URLClassLoader extends java.security.SecureClassLoader implements java.io.Closeable {
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index ba93b2a2cafe..9d81c438360f 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -21,10 +21,12 @@ package com.android.commands.am;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
import static android.app.ActivityManager.RESIZE_MODE_USER;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import android.app.ActivityManager;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityManagerNative;
+import android.app.ActivityOptions;
import android.app.IActivityContainer;
import android.app.IActivityController;
import android.app.IActivityManager;
@@ -106,6 +108,7 @@ public class Am extends BaseCommand {
private String mProfileFile;
private int mSamplingInterval;
private boolean mAutoStop;
+ private int mStackId;
/**
* Command-line entry point.
@@ -191,6 +194,7 @@ public class Am extends BaseCommand {
" --track-allocation: enable tracking of object allocations\n" +
" --user <USER_ID> | current: Specify which user to run as; if not\n" +
" specified then run as the current user.\n" +
+ " --stack <STACK_ID>: Specify into which stack should the activity be put." +
"\n" +
"am startservice: start a Service. Options are:\n" +
" --user <USER_ID> | current: Specify which user to run as; if not\n" +
@@ -467,6 +471,7 @@ public class Am extends BaseCommand {
mSamplingInterval = 0;
mAutoStop = false;
mUserId = defUser;
+ mStackId = FULLSCREEN_WORKSPACE_STACK_ID;
return Intent.parseCommandArgs(mArgs, new Intent.CommandOptionHandler() {
@Override
@@ -493,6 +498,8 @@ public class Am extends BaseCommand {
mUserId = parseUserArg(nextArgRequired());
} else if (opt.equals("--receiver-permission")) {
mReceiverPermission = nextArgRequired();
+ } else if (opt.equals("--stack")) {
+ mStackId = Integer.parseInt(nextArgRequired());
} else {
return false;
}
@@ -550,6 +557,7 @@ public class Am extends BaseCommand {
mimeType = mAm.getProviderMimeType(intent.getData(), mUserId);
}
+
do {
if (mStopOption) {
String packageName;
@@ -604,13 +612,20 @@ public class Am extends BaseCommand {
IActivityManager.WaitResult result = null;
int res;
final long startTime = SystemClock.uptimeMillis();
+ ActivityOptions options = null;
+ if (mStackId != FULLSCREEN_WORKSPACE_STACK_ID) {
+ options = ActivityOptions.makeBasic();
+ options.setLaunchStackId(mStackId);
+ }
if (mWaitOption) {
result = mAm.startActivityAndWait(null, null, intent, mimeType,
- null, null, 0, mStartFlags, profilerInfo, null, mUserId);
+ null, null, 0, mStartFlags, profilerInfo,
+ options != null ? options.toBundle() : null, mUserId);
res = result.result;
} else {
res = mAm.startActivityAsUser(null, null, intent, mimeType,
- null, null, 0, mStartFlags, profilerInfo, null, mUserId);
+ null, null, 0, mStartFlags, profilerInfo,
+ options != null ? options.toBundle() : null, mUserId);
}
final long endTime = SystemClock.uptimeMillis();
PrintStream out = mWaitOption ? System.out : System.err;
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 35695c47d19b..1d9e3bb4b4bb 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -430,46 +430,47 @@ public class AccountManager {
}
/**
- * List every {@link Account} registered on the device that are managed by
- * applications whose signatures match the caller.
+ * Lists all accounts of any type registered on the device.
+ * Equivalent to getAccountsByType(null).
*
- * <p>This method can be called safely from the main thread. It is
- * equivalent to calling <code>getAccountsByType(null)</code>.
+ * <p>It is safe to call this method from the main thread.
*
- * <p><b>NOTE:</b> Apps declaring a {@code targetSdkVersion<=23} in their
- * manifests will continue to behave as they did on devices that support
- * API level 23. In particular the GET_ACCOUNTS permission is required to
- * see all the Accounts registered with the AccountManager. See docs for
- * this function in API level 23 for more information.
+ * <p>Clients of this method that have not been granted the
+ * {@link android.Manifest.permission#GET_ACCOUNTS} permission,
+ * will only see those accounts managed by AbstractAccountAuthenticators whose
+ * signature matches the client.
*
- * @return Array of Accounts. The array may be empty if no accounts are
- * available to the caller.
+ * @return An array of {@link Account}, one for each account. Empty
+ * (never null) if no accounts have been added.
*/
@NonNull
+ @RequiresPermission(GET_ACCOUNTS)
public Account[] getAccounts() {
- return getAccountsByType(null);
+ try {
+ return mService.getAccounts(null, mContext.getOpPackageName());
+ } catch (RemoteException e) {
+ // won't ever happen
+ throw new RuntimeException(e);
+ }
}
/**
* @hide
- * List every {@link Account} registered on the device for a specific User
- * that are managed by applications whose signatures match the caller.
+ * Lists all accounts of any type registered on the device for a given
+ * user id. Equivalent to getAccountsByType(null).
*
- * <p><b>NOTE:</b> Apps declaring a {@code targetSdkVersion<=23} in their
- * manifests will continue to behave as they did on devices that support
- * API level 23. In particular the GET_ACCOUNTS permission is required to
- * see all the Accounts registered with the AccountManager for the
- * specified userId. See docs for this function in API level 23 for more
- * information.
+ * <p>It is safe to call this method from the main thread.
*
- * <p>This method can be called safely from the main thread.
+ * <p>Clients of this method that have not been granted the
+ * {@link android.Manifest.permission#GET_ACCOUNTS} permission,
+ * will only see those accounts managed by AbstractAccountAuthenticators whose
+ * signature matches the client.
*
- * @param int userId associated with the User whose accounts should be
- * queried.
- * @return Array of Accounts. The array may be empty if no accounts are
- * available to the caller.
+ * @return An array of {@link Account}, one for each account. Empty
+ * (never null) if no accounts have been added.
*/
@NonNull
+ @RequiresPermission(GET_ACCOUNTS)
public Account[] getAccountsAsUser(int userId) {
try {
return mService.getAccountsAsUser(null, userId, mContext.getOpPackageName());
@@ -500,11 +501,10 @@ public class AccountManager {
/**
* Returns the accounts visible to the specified package, in an environment where some apps
* are not authorized to view all accounts. This method can only be called by system apps.
- *
* @param type The type of accounts to return, null to retrieve all accounts
* @param packageName The package name of the app for which the accounts are to be returned
- * @return Array of Accounts. The array may be empty if no accounts of th
- * specified type are visible to the caller.
+ * @return An array of {@link Account}, one per matching account. Empty
+ * (never null) if no accounts of the specified type have been added.
*/
@NonNull
public Account[] getAccountsByTypeForPackage(String type, String packageName) {
@@ -518,22 +518,29 @@ public class AccountManager {
}
/**
- * List every {@link Account} of a specified type managed by applications
- * whose signatures match the caller.
+ * Lists all accounts of a particular type. The account type is a
+ * string token corresponding to the authenticator and useful domain
+ * of the account. For example, there are types corresponding to Google
+ * and Facebook. The exact string token to use will be published somewhere
+ * associated with the authenticator in question.
*
- * <p><b>NOTE:</b> Apps declaring a {@code targetSdkVersion<=23} in their
- * manifests will continue to behave as they did on devices that support
- * API level 23. See docs for this function in API level 23 for more
- * information.
+ * <p>It is safe to call this method from the main thread.
+ *
+ * <p>Clients of this method that have not been granted the
+ * {@link android.Manifest.permission#GET_ACCOUNTS} permission,
+ * will only see those accounts managed by AbstractAccountAuthenticators whose
+ * signature matches the client.
*
- * <p>This method can be called safely from the main thread.
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * GET_ACCOUNTS permission is needed for those platforms, irrespective of uid
+ * or signature match. See docs for this function in API level 22.
*
- * @param type String denoting the type of the accounts to return,
- * {@code null} to retrieve all accounts visible to the caller.
- * @return An array of Accounts. Empty (never null) if no accounts
- * are available to the caller.
+ * @param type The type of accounts to return, null to retrieve all accounts
+ * @return An array of {@link Account}, one per matching account. Empty
+ * (never null) if no accounts of the specified type have been added.
*/
@NonNull
+ @RequiresPermission(GET_ACCOUNTS)
public Account[] getAccountsByType(String type) {
return getAccountsByTypeAsUser(type, Process.myUserHandle());
}
@@ -579,7 +586,6 @@ public class AccountManager {
* @return a future containing the label string
* @hide
*/
- @NonNull
public AccountManagerFuture<String> getAuthTokenLabel(
final String accountType, final String authTokenType,
AccountManagerCallback<String> callback, Handler handler) {
@@ -612,13 +618,9 @@ public class AccountManager {
* <p>This method may be called from any thread, but the returned
* {@link AccountManagerFuture} must not be used on the main thread.
*
- * <p><b>Note:</b>The specified account must be managed by an application
- * whose signature matches the caller.
- *
- * <p><b>Further note:</b>Apps targeting API level 23 or earlier will continue to
- * behave as they did on devices that support API level 23. In particular
- * they may still require the GET_ACCOUNTS permission. See docs for this
- * function in API level 23.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#GET_ACCOUNTS} or be a signature
+ * match with the AbstractAccountAuthenticator that manages the account.
*
* @param account The {@link Account} to test
* @param features An array of the account features to check
@@ -627,11 +629,9 @@ public class AccountManager {
* @param handler {@link Handler} identifying the callback thread,
* null for the main thread
* @return An {@link AccountManagerFuture} which resolves to a Boolean,
- * true if the account exists and has all of the specified features.
- * @throws SecurityException if the specified account is managed by an
- * application whose signature doesn't match the caller's signature.
+ * true if the account exists and has all of the specified features.
*/
- @NonNull
+ @RequiresPermission(GET_ACCOUNTS)
public AccountManagerFuture<Boolean> hasFeatures(final Account account,
final String[] features,
AccountManagerCallback<Boolean> callback, Handler handler) {
@@ -654,10 +654,9 @@ public class AccountManager {
/**
* Lists all accounts of a type which have certain features. The account
- * type identifies the authenticator (see {@link #getAccountsByType}). Said
- * authenticator must be in a package whose signature matches the callers
- * package signature. Account features are authenticator-specific string tokens
- * identifying boolean account properties (see {@link #hasFeatures}).
+ * type identifies the authenticator (see {@link #getAccountsByType}).
+ * Account features are authenticator-specific string tokens identifying
+ * boolean account properties (see {@link #hasFeatures}).
*
* <p>Unlike {@link #getAccountsByType}, this method calls the authenticator,
* which may contact the server or do other work to check account features,
@@ -666,14 +665,19 @@ public class AccountManager {
* <p>This method may be called from any thread, but the returned
* {@link AccountManagerFuture} must not be used on the main thread.
*
- * <p><b>NOTE:</b> Apps targeting API level 23 or earlier will continue to
- * behave as they did on devices that support API level 23. In particular
- * they may still require the GET_ACCOUNTS permission. See docs for this
- * function in API level 23.
+ * <p>Clients of this method that have not been granted the
+ * {@link android.Manifest.permission#GET_ACCOUNTS} permission,
+ * will only see those accounts managed by AbstractAccountAuthenticators whose
+ * signature matches the client.
*
* @param type The type of accounts to return, must not be null
* @param features An array of the account features to require,
* may be null or empty
+ *
+ * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+ * GET_ACCOUNTS permission is needed for those platforms, irrespective of uid
+ * or signature match. See docs for this function in API level 22.
+ *
* @param callback Callback to invoke when the request completes,
* null for no callback
* @param handler {@link Handler} identifying the callback thread,
@@ -682,7 +686,7 @@ public class AccountManager {
* {@link Account}, one per account of the specified type which
* matches the requested features.
*/
- @NonNull
+ @RequiresPermission(GET_ACCOUNTS)
public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures(
final String type, final String[] features,
AccountManagerCallback<Account[]> callback, Handler handler) {
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 1ab55dd7c1d9..980329fe31c8 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -1030,6 +1030,20 @@ public final class AnimatorSet extends Animator {
}
}
+ /**
+ * @hide
+ * TODO: For animatorSet defined in XML, we can use a flag to indicate what the play order
+ * if defined (i.e. sequential or together), then we can use the flag instead of calculate
+ * dynamically.
+ * @return whether all the animators in the set are supposed to play together
+ */
+ public boolean shouldPlayTogether() {
+ updateAnimatorsDuration();
+ createDependencyGraph();
+ // All the child nodes are set out to play right after the delay animation
+ return mRootNode.mChildNodes.size() == mNodes.size() - 1;
+ }
+
@Override
public long getTotalDuration() {
updateAnimatorsDuration();
diff --git a/core/java/android/animation/PathKeyframes.java b/core/java/android/animation/PathKeyframes.java
index 2a47b68a41b0..8230ac5bbfc5 100644
--- a/core/java/android/animation/PathKeyframes.java
+++ b/core/java/android/animation/PathKeyframes.java
@@ -231,7 +231,7 @@ class PathKeyframes implements Keyframes {
}
}
- private abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes {
+ abstract static class IntKeyframesBase extends SimpleKeyframes implements IntKeyframes {
@Override
public Class getType() {
return Integer.class;
@@ -243,7 +243,7 @@ class PathKeyframes implements Keyframes {
}
}
- private abstract static class FloatKeyframesBase extends SimpleKeyframes
+ abstract static class FloatKeyframesBase extends SimpleKeyframes
implements FloatKeyframes {
@Override
public Class getType() {
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
index e993cca9e325..6ba5b968dfe7 100644
--- a/core/java/android/animation/PropertyValuesHolder.java
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -21,6 +21,7 @@ import android.graphics.PointF;
import android.util.FloatProperty;
import android.util.IntProperty;
import android.util.Log;
+import android.util.PathParser;
import android.util.Property;
import java.lang.reflect.InvocationTargetException;
@@ -1046,6 +1047,43 @@ public class PropertyValuesHolder implements Cloneable {
return mAnimatedValue;
}
+ /**
+ * PropertyValuesHolder is Animators use to hold internal animation related data.
+ * Therefore, in order to replicate the animation behavior, we need to get data out of
+ * PropertyValuesHolder.
+ * @hide
+ */
+ public void getPropertyValues(PropertyValues values) {
+ init();
+ values.propertyName = mPropertyName;
+ values.type = mValueType;
+ values.startValue = mKeyframes.getValue(0);
+ if (values.startValue instanceof PathParser.PathData) {
+ // PathData evaluator returns the same mutable PathData object when query fraction,
+ // so we have to make a copy here.
+ values.startValue = new PathParser.PathData((PathParser.PathData) values.startValue);
+ }
+ values.endValue = mKeyframes.getValue(1);
+ if (values.endValue instanceof PathParser.PathData) {
+ // PathData evaluator returns the same mutable PathData object when query fraction,
+ // so we have to make a copy here.
+ values.endValue = new PathParser.PathData((PathParser.PathData) values.endValue);
+ }
+ // TODO: We need a better way to get data out of keyframes.
+ if (mKeyframes instanceof PathKeyframes.FloatKeyframesBase
+ || mKeyframes instanceof PathKeyframes.IntKeyframesBase) {
+ // property values will animate based on external data source (e.g. Path)
+ values.dataSource = new PropertyValues.DataSource() {
+ @Override
+ public Object getValueAtFraction(float fraction) {
+ return mKeyframes.getValue(fraction);
+ }
+ };
+ } else {
+ values.dataSource = null;
+ }
+ }
+
@Override
public String toString() {
return mPropertyName + ": " + mKeyframes.toString();
@@ -1601,6 +1639,24 @@ public class PropertyValuesHolder implements Cloneable {
}
};
+ /**
+ * @hide
+ */
+ public static class PropertyValues {
+ public String propertyName;
+ public Class type;
+ public Object startValue;
+ public Object endValue;
+ public DataSource dataSource = null;
+ public interface DataSource {
+ Object getValueAtFraction(float fraction);
+ }
+ public String toString() {
+ return ("property name: " + propertyName + ", type: " + type + ", startValue: "
+ + startValue.toString() + ", endValue: " + endValue.toString());
+ }
+ }
+
native static private long nGetIntMethod(Class targetClass, String methodName);
native static private long nGetFloatMethod(Class targetClass, String methodName);
native static private long nGetMultipleIntMethod(Class targetClass, String methodName,
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 378e44880023..acd4ab8286fc 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1837,6 +1837,7 @@ public class Activity extends ContextThemeWrapper
public void onMultiWindowChanged(boolean inMultiWindow) {
if (DEBUG_LIFECYCLE) Slog.v(TAG,
"onMultiWindowChanged " + this + ": " + inMultiWindow);
+ mFragments.dispatchMultiWindowChanged(inMultiWindow);
if (mWindow != null) {
mWindow.onMultiWindowChanged();
}
@@ -1862,9 +1863,11 @@ public class Activity extends ContextThemeWrapper
*
* @param inPictureInPicture True if the activity is in picture-in-picture mode.
*/
+ @CallSuper
public void onPictureInPictureChanged(boolean inPictureInPicture) {
if (DEBUG_LIFECYCLE) Slog.v(TAG,
"onPictureInPictureChanged " + this + ": " + inPictureInPicture);
+ mFragments.dispatchPictureInPictureChanged(inPictureInPicture);
}
/**
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b5b27536d4c6..c22a94187fe0 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5078,11 +5078,11 @@ public final class ActivityThread {
Log.e(TAG, "Unable to setupGraphicsSupport and setupJitProfileSupport " +
"due to missing code-cache directory");
}
- }
- // Add the lib dir path to hardware renderer so that vulkan layers
- // can be searched for within that directory.
- ThreadedRenderer.setLibDir(data.info.getLibDir());
+ // Add the lib dir path to hardware renderer so that vulkan layers
+ // can be searched for within that directory.
+ ThreadedRenderer.setLibDir(data.info.getLibDir());
+ }
// 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
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 220fb607289a..c071162ca5b9 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -326,8 +326,7 @@ public class ApplicationPackageManager extends PackageManager {
// This is a temporary hack. Callers must use
// createPackageContext(packageName).getApplicationInfo() to
// get the right paths.
- maybeAdjustApplicationInfo(ai);
- return ai;
+ return maybeAdjustApplicationInfo(ai);
}
} catch (RemoteException e) {
throw new RuntimeException("Package manager has died", e);
@@ -336,7 +335,7 @@ public class ApplicationPackageManager extends PackageManager {
throw new NameNotFoundException(packageName);
}
- private static void maybeAdjustApplicationInfo(ApplicationInfo info) {
+ private static ApplicationInfo maybeAdjustApplicationInfo(ApplicationInfo info) {
// If we're dealing with a multi-arch application that has both
// 32 and 64 bit shared libraries, we might need to choose the secondary
// depending on what the current runtime's instruction set is.
@@ -353,9 +352,12 @@ public class ApplicationPackageManager extends PackageManager {
// Everything will be set up correctly because info.nativeLibraryDir will
// correspond to the right ISA.
if (runtimeIsa.equals(secondaryIsa)) {
- info.nativeLibraryDir = info.secondaryNativeLibraryDir;
+ ApplicationInfo modified = new ApplicationInfo(info);
+ modified.nativeLibraryDir = info.secondaryNativeLibraryDir;
+ return modified;
}
}
+ return info;
}
@Override
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 20eaf0b8dca4..688c9f8e4fc4 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -1576,6 +1576,25 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
public void onSaveInstanceState(Bundle outState) {
}
+ /**
+ * Called when the Fragment's activity changes from fullscreen mode to multi-window mode and
+ * visa-versa. This is generally tied to {@link Activity#onMultiWindowChanged} of the containing
+ * Activity.
+ *
+ * @param inMultiWindow True if the activity is in multi-window mode.
+ */
+ public void onMultiWindowChanged(boolean inMultiWindow) {
+ }
+
+ /**
+ * Called by the system when the activity changes to and from picture-in-picture mode. This is
+ * generally tied to {@link Activity#onPictureInPictureChanged} of the containing Activity.
+ *
+ * @param inPictureInPicture True if the activity is in picture-in-picture mode.
+ */
+ public void onPictureInPictureChanged(boolean inPictureInPicture) {
+ }
+
public void onConfigurationChanged(Configuration newConfig) {
mCalled = true;
}
@@ -2308,6 +2327,20 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
}
}
+ void performMultiWindowChanged(boolean inMultiWindow) {
+ onMultiWindowChanged(inMultiWindow);
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.dispatchMultiWindowChanged(inMultiWindow);
+ }
+ }
+
+ void performPictureInPictureChanged(boolean inPictureInPicture) {
+ onPictureInPictureChanged(inPictureInPicture);
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.dispatchPictureInPictureChanged(inPictureInPicture);
+ }
+ }
+
void performConfigurationChanged(Configuration newConfig) {
onConfigurationChanged(newConfig);
if (mChildFragmentManager != null) {
diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java
index 28dadfa78b32..a9270bc43e09 100644
--- a/core/java/android/app/FragmentController.java
+++ b/core/java/android/app/FragmentController.java
@@ -219,6 +219,28 @@ public class FragmentController {
}
/**
+ * Lets all Fragments managed by the controller's FragmentManager know the multi-window mode of
+ * the activity changed.
+ * <p>Call when the multi-window mode of the activity changed.
+ *
+ * @see Fragment#onMultiWindowChanged
+ */
+ public void dispatchMultiWindowChanged(boolean inMultiWindow) {
+ mHost.mFragmentManager.dispatchMultiWindowChanged(inMultiWindow);
+ }
+
+ /**
+ * Lets all Fragments managed by the controller's FragmentManager know the picture-in-picture
+ * mode of the activity changed.
+ * <p>Call when the picture-in-picture mode of the activity changed.
+ *
+ * @see Fragment#onPictureInPictureChanged
+ */
+ public void dispatchPictureInPictureChanged(boolean inPictureInPicture) {
+ mHost.mFragmentManager.dispatchPictureInPictureChanged(inPictureInPicture);
+ }
+
+ /**
* Lets all Fragments managed by the controller's FragmentManager
* know a configuration change occurred.
* <p>Call when there is a configuration change.
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 84ae09d7e988..32fec842ca75 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -1987,7 +1987,31 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate
mContainer = null;
mParent = null;
}
-
+
+ public void dispatchMultiWindowChanged(boolean inMultiWindow) {
+ if (mAdded == null) {
+ return;
+ }
+ for (int i = mAdded.size() - 1; i >= 0; --i) {
+ final Fragment f = mAdded.get(i);
+ if (f != null) {
+ f.performMultiWindowChanged(inMultiWindow);
+ }
+ }
+ }
+
+ public void dispatchPictureInPictureChanged(boolean inPictureInPicture) {
+ if (mAdded == null) {
+ return;
+ }
+ for (int i = mAdded.size() - 1; i >= 0; --i) {
+ final Fragment f = mAdded.get(i);
+ if (f != null) {
+ f.performPictureInPictureChanged(inPictureInPicture);
+ }
+ }
+ }
+
public void dispatchConfigurationChanged(Configuration newConfig) {
if (mAdded != null) {
for (int i=0; i<mAdded.size(); i++) {
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 3288cd91856d..40e58af57204 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -55,6 +55,7 @@ interface INotificationManager
void setImportance(String pkg, int uid, in Notification.Topic topic, int importance);
int getImportance(String pkg, int uid, in Notification.Topic topic);
boolean doesAppUseTopics(String pkg, int uid);
+ boolean hasBannedTopics(String pkg, int uid);
// TODO: Remove this when callers have been migrated to the equivalent
// INotificationListener method.
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 7f037f22aa8d..34c90c165490 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2623,9 +2623,6 @@ public class Notification implements Parcelable
if (onMs != 0 || offMs != 0) {
mN.flags |= FLAG_SHOW_LIGHTS;
}
- if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
- mN.flags |= FLAG_SHOW_LIGHTS;
- }
return this;
}
@@ -3603,6 +3600,10 @@ public class Notification implements Parcelable
mStyle.buildStyled(mN);
}
+ if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
+ mN.flags |= FLAG_SHOW_LIGHTS;
+ }
+
return mN;
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f04e76e22074..9bad2f9dc238 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3474,8 +3474,7 @@ public class DevicePolicyManager {
public boolean isProfileOwnerApp(String packageName) {
if (mService != null) {
try {
- ComponentName profileOwner = mService.getProfileOwner(
- Process.myUserHandle().getIdentifier());
+ ComponentName profileOwner = mService.getProfileOwner(myUserId());
return profileOwner != null
&& profileOwner.getPackageName().equals(packageName);
} catch (RemoteException re) {
diff --git a/core/java/android/content/pm/IOtaDexopt.aidl b/core/java/android/content/pm/IOtaDexopt.aidl
new file mode 100644
index 000000000000..8f38d6f90a7d
--- /dev/null
+++ b/core/java/android/content/pm/IOtaDexopt.aidl
@@ -0,0 +1,49 @@
+/*
+**
+** Copyright 2016, 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.content.pm;
+
+/**
+ * A/B OTA dexopting service.
+ *
+ * {@hide}
+ */
+interface IOtaDexopt {
+ /**
+ * Prepare for A/B OTA dexopt. Initialize internal structures.
+ *
+ * Calls to the other methods are only valid after a call to prepare. You may not call
+ * prepare twice without a cleanup call.
+ */
+ void prepare();
+
+ /**
+ * Clean up all internal state.
+ */
+ void cleanup();
+
+ /**
+ * Check whether all updates have been performed.
+ */
+ boolean isDone();
+
+ /**
+ * Optimize the next package. Note: this command is synchronous, that is, only returns after
+ * the package has been dexopted (or dexopting failed).
+ */
+ void dexoptNextPackage();
+}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 09b85c93e050..3802db85c1c0 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -291,6 +291,7 @@ public class PackageParser {
public final boolean coreApp;
public final boolean multiArch;
+ public final String abiOverride;
public final boolean extractNativeLibs;
public PackageLite(String codePath, ApkLite baseApk, String[] splitNames,
@@ -307,6 +308,7 @@ public class PackageParser {
this.splitRevisionCodes = splitRevisionCodes;
this.coreApp = baseApk.coreApp;
this.multiArch = baseApk.multiArch;
+ this.abiOverride = baseApk.abiOverride;
this.extractNativeLibs = baseApk.extractNativeLibs;
}
@@ -334,11 +336,12 @@ public class PackageParser {
public final Signature[] signatures;
public final boolean coreApp;
public final boolean multiArch;
+ public final String abiOverride;
public final boolean extractNativeLibs;
public ApkLite(String codePath, String packageName, String splitName, int versionCode,
int revisionCode, int installLocation, List<VerifierInfo> verifiers,
- Signature[] signatures, boolean coreApp, boolean multiArch,
+ Signature[] signatures, boolean coreApp, boolean multiArch, String abiOverride,
boolean extractNativeLibs) {
this.codePath = codePath;
this.packageName = packageName;
@@ -350,6 +353,7 @@ public class PackageParser {
this.signatures = signatures;
this.coreApp = coreApp;
this.multiArch = multiArch;
+ this.abiOverride = abiOverride;
this.extractNativeLibs = extractNativeLibs;
}
}
@@ -793,6 +797,7 @@ public class PackageParser {
}
pkg.codePath = packageDir.getAbsolutePath();
+ pkg.cpuAbiOverride = lite.abiOverride;
return pkg;
} finally {
IoUtils.closeQuietly(assets);
@@ -811,8 +816,8 @@ public class PackageParser {
*/
@Deprecated
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
+ final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
if (mOnlyCoreApps) {
- final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
if (!lite.coreApp) {
throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"Not a coreApp: " + apkFile);
@@ -823,6 +828,7 @@ public class PackageParser {
try {
final Package pkg = parseBaseApk(apkFile, assets, flags);
pkg.codePath = apkFile.getAbsolutePath();
+ pkg.cpuAbiOverride = lite.abiOverride;
return pkg;
} finally {
IoUtils.closeQuietly(assets);
@@ -1316,6 +1322,7 @@ public class PackageParser {
int revisionCode = 0;
boolean coreApp = false;
boolean multiArch = false;
+ String abiOverride = null;
boolean extractNativeLibs = true;
for (int i = 0; i < attrs.getAttributeCount(); i++) {
@@ -1356,6 +1363,9 @@ public class PackageParser {
if ("multiArch".equals(attr)) {
multiArch = attrs.getAttributeBooleanValue(i, false);
}
+ if ("abiOverride".equals(attr)) {
+ abiOverride = attrs.getAttributeValue(i);
+ }
if ("extractNativeLibs".equals(attr)) {
extractNativeLibs = attrs.getAttributeBooleanValue(i, true);
}
@@ -1365,7 +1375,7 @@ public class PackageParser {
return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode,
revisionCode, installLocation, verifiers, signatures, coreApp, multiArch,
- extractNativeLibs);
+ abiOverride, extractNativeLibs);
}
/**
@@ -3237,18 +3247,20 @@ public class PackageParser {
SCREEN_ORIENTATION_UNSPECIFIED);
a.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
- if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.N) {
- final boolean appDefault = (owner.applicationInfo.privateFlags
- & PRIVATE_FLAG_RESIZEABLE_ACTIVITIES) != 0;
- if (sa.getBoolean(
- R.styleable.AndroidManifestActivity_resizeableActivity, appDefault)) {
- if (sa.getBoolean(R.styleable.AndroidManifestActivity_supportsPictureInPicture,
- false)) {
- a.info.resizeMode = RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
- } else {
- a.info.resizeMode = RESIZE_MODE_RESIZEABLE;
- }
+ final boolean appDefault = (owner.applicationInfo.privateFlags
+ & PRIVATE_FLAG_RESIZEABLE_ACTIVITIES) != 0;
+ final boolean resizeable = sa.getBoolean(
+ R.styleable.AndroidManifestActivity_resizeableActivity, appDefault);
+
+ if (resizeable) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_supportsPictureInPicture,
+ false)) {
+ a.info.resizeMode = RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
+ } else {
+ a.info.resizeMode = RESIZE_MODE_RESIZEABLE;
}
+ } else if (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.N) {
+ a.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
} else if (a.info.screenOrientation == SCREEN_ORIENTATION_UNSPECIFIED
&& (a.info.flags & FLAG_IMMERSIVE) == 0) {
a.info.resizeMode = RESIZE_MODE_CROP_WINDOWS;
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index 8bdd42a30c78..bb0a04279904 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -175,7 +175,8 @@ public class LegacyMetadataMapper {
* colorCorrection.*
*/
m.set(COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES,
- new int[] { COLOR_CORRECTION_ABERRATION_MODE_FAST });
+ new int[] { COLOR_CORRECTION_ABERRATION_MODE_FAST,
+ COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY });
/*
* control.ae*
*/
@@ -210,7 +211,8 @@ public class LegacyMetadataMapper {
* noiseReduction.*
*/
m.set(NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES,
- new int[] { NOISE_REDUCTION_MODE_FAST });
+ new int[] { NOISE_REDUCTION_MODE_FAST,
+ NOISE_REDUCTION_MODE_HIGH_QUALITY});
/*
* scaler.*
@@ -1389,7 +1391,22 @@ public class LegacyMetadataMapper {
/*
* noiseReduction.*
*/
- m.set(CaptureRequest.NOISE_REDUCTION_MODE, NOISE_REDUCTION_MODE_FAST);
+ if (templateId == CameraDevice.TEMPLATE_STILL_CAPTURE) {
+ m.set(CaptureRequest.NOISE_REDUCTION_MODE, NOISE_REDUCTION_MODE_HIGH_QUALITY);
+ } else {
+ m.set(CaptureRequest.NOISE_REDUCTION_MODE, NOISE_REDUCTION_MODE_FAST);
+ }
+
+ /*
+ * colorCorrection.*
+ */
+ if (templateId == CameraDevice.TEMPLATE_STILL_CAPTURE) {
+ m.set(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE,
+ COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY);
+ } else {
+ m.set(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE,
+ COLOR_CORRECTION_ABERRATION_MODE_FAST);
+ }
/*
* lens.*
diff --git a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
index 6a44ac50de0a..2e06d5fb3b6f 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
@@ -89,7 +89,8 @@ public class LegacyRequestMapper {
COLOR_CORRECTION_ABERRATION_MODE,
/*defaultValue*/COLOR_CORRECTION_ABERRATION_MODE_FAST);
- if (aberrationMode != COLOR_CORRECTION_ABERRATION_MODE_FAST) {
+ if (aberrationMode != COLOR_CORRECTION_ABERRATION_MODE_FAST &&
+ aberrationMode != COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY) {
Log.w(TAG, "convertRequestToMetadata - Ignoring unsupported " +
"colorCorrection.aberrationMode = " + aberrationMode);
}
@@ -446,7 +447,8 @@ public class LegacyRequestMapper {
NOISE_REDUCTION_MODE,
/*defaultValue*/NOISE_REDUCTION_MODE_FAST);
- if (mode != NOISE_REDUCTION_MODE_FAST) {
+ if (mode != NOISE_REDUCTION_MODE_FAST &&
+ mode != NOISE_REDUCTION_MODE_HIGH_QUALITY) {
Log.w(TAG, "convertRequestToMetadata - Ignoring unsupported " +
"noiseReduction.mode = " + mode);
}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
index ce850052b19d..dc5823d80f21 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
@@ -67,7 +67,9 @@ public class LegacyResultMapper {
/*
* Attempt to look up the result from the cache if the parameters haven't changed
*/
- if (mCachedRequest != null && legacyRequest.parameters.same(mCachedRequest.parameters)) {
+ if (mCachedRequest != null &&
+ legacyRequest.parameters.same(mCachedRequest.parameters) &&
+ legacyRequest.captureRequest.equals(mCachedRequest.captureRequest)) {
result = new CameraMetadataNative(mCachedResult);
cached = true;
} else {
@@ -124,8 +126,8 @@ public class LegacyResultMapper {
*/
// colorCorrection.aberrationMode
{
- // Always hardcoded to FAST
- result.set(COLOR_CORRECTION_ABERRATION_MODE, COLOR_CORRECTION_ABERRATION_MODE_FAST);
+ result.set(COLOR_CORRECTION_ABERRATION_MODE,
+ request.get(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE));
}
/*
@@ -282,7 +284,7 @@ public class LegacyResultMapper {
* noiseReduction.*
*/
// noiseReduction.mode
- result.set(NOISE_REDUCTION_MODE, NOISE_REDUCTION_MODE_FAST);
+ result.set(NOISE_REDUCTION_MODE, request.get(CaptureRequest.NOISE_REDUCTION_MODE));
return result;
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 25b38c07aa66..7004e9725bd2 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -3215,11 +3215,24 @@ public class ConnectivityManager {
/**
* Device is restricting metered network activity while application is running on background.
+ * <p>
* In this state, application should not try to use the network while running on background,
* because it would be denied.
*/
public static final int RESTRICT_BACKGROUND_STATUS_ENABLED = 3;
+ /**
+ * A change in the background metered network activity restriction has occurred.
+ * <p>
+ * Applications should call {@link #getRestrictBackgroundStatus()} to check if the restriction
+ * applies to them.
+ * <p>
+ * This is only sent to registered receivers, not manifest receivers.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_RESTRICT_BACKGROUND_CHANGED =
+ "android.net.conn.RESTRICT_BACKGROUND_CHANGED";
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = false, value = {
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 1e4ee4bc2ef5..b2cabd860bfa 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -373,6 +373,12 @@ public class Process {
**/
public static final int THREAD_GROUP_AUDIO_SYS = 4;
+ /**
+ * Thread group for top foreground app.
+ * @hide
+ **/
+ public static final int THREAD_GROUP_TOP_APP = 5;
+
public static final int SIGNAL_QUIT = 3;
public static final int SIGNAL_KILL = 9;
public static final int SIGNAL_USR1 = 10;
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 407ff08ef2de..154c9bbab312 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -67,6 +67,7 @@ public class RecoverySystem {
/** Used to communicate with recovery. See bootable/recovery/recovery.cpp. */
private static File RECOVERY_DIR = new File("/cache/recovery");
+ private static File BLOCK_MAP_FILE = new File(RECOVERY_DIR, "block.map");
private static File COMMAND_FILE = new File(RECOVERY_DIR, "command");
private static File UNCRYPT_FILE = new File(RECOVERY_DIR, "uncrypt_file");
private static File LOG_FILE = new File(RECOVERY_DIR, "log");
@@ -473,7 +474,9 @@ public class RecoverySystem {
Log.e(TAG, "Error reading recovery log", e);
}
- if (UNCRYPT_FILE.exists()) {
+ // Only remove the OTA package if it's partially processed (uncrypt'd).
+ boolean reservePackage = BLOCK_MAP_FILE.exists();
+ if (!reservePackage && UNCRYPT_FILE.exists()) {
String filename = null;
try {
filename = FileUtils.readTextFile(UNCRYPT_FILE, 0, null);
@@ -492,11 +495,18 @@ public class RecoverySystem {
}
}
- // Delete everything in RECOVERY_DIR except those beginning
- // with LAST_PREFIX
+ // We keep the update logs (beginning with LAST_PREFIX), and optionally
+ // the block map file (BLOCK_MAP_FILE) for a package. BLOCK_MAP_FILE
+ // will be created at the end of a successful uncrypt. If seeing this
+ // file, we keep the block map file and the file that contains the
+ // package name (UNCRYPT_FILE). This is to reduce the work for GmsCore
+ // to avoid re-downloading everything again.
String[] names = RECOVERY_DIR.list();
for (int i = 0; names != null && i < names.length; i++) {
if (names[i].startsWith(LAST_PREFIX)) continue;
+ if (reservePackage && names[i].equals(BLOCK_MAP_FILE.getName())) continue;
+ if (reservePackage && names[i].equals(UNCRYPT_FILE.getName())) continue;
+
recursiveDelete(new File(RECOVERY_DIR, names[i]));
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index ad7b4e21d368..dc0e249d72ec 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -64,8 +64,8 @@ public class UserManager {
* use {@link android.accounts.AccountManager} APIs to add or remove accounts when account
* management is disallowed.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -75,10 +75,10 @@ public class UserManager {
/**
* Specifies if a user is disallowed from changing Wi-Fi
* access points. The default value is <code>false</code>.
- * <p/>This restriction has no effect in a managed profile.
+ * <p>This restriction has no effect in a managed profile.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -89,8 +89,8 @@ public class UserManager {
* Specifies if a user is disallowed from installing applications.
* The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -101,8 +101,8 @@ public class UserManager {
* Specifies if a user is disallowed from uninstalling applications.
* The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -112,11 +112,11 @@ public class UserManager {
/**
* Specifies if a user is disallowed from turning on location sharing.
* The default value is <code>false</code>.
- * <p/>In a managed profile, location sharing always reflects the primary user's setting, but
+ * <p>In a managed profile, location sharing always reflects the primary user's setting, but
* can be overridden and forced off by setting this restriction to true in the managed profile.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -128,8 +128,8 @@ public class UserManager {
* "Unknown Sources" setting, that allows installation of apps from unknown sources.
* The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -140,10 +140,10 @@ public class UserManager {
* Specifies if a user is disallowed from configuring bluetooth.
* This does <em>not</em> restrict the user from turning bluetooth on or off.
* The default value is <code>false</code>.
- * <p/>This restriction has no effect in a managed profile.
+ * <p>This restriction has no effect in a managed profile.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -155,8 +155,8 @@ public class UserManager {
* USB. This can only be set by device owners and profile owners on the primary user.
* The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -167,8 +167,8 @@ public class UserManager {
* Specifies if a user is disallowed from configuring user
* credentials. The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -181,8 +181,8 @@ public class UserManager {
* This restriction has no effect on managed profiles.
* The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -193,8 +193,8 @@ public class UserManager {
* Specifies if a user is disallowed from enabling or
* accessing debugging features. The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -207,8 +207,8 @@ public class UserManager {
* This restriction has an effect in a managed profile only from
* {@link android.os.Build.VERSION_CODES#M}
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -220,8 +220,8 @@ public class UserManager {
* & portable hotspots. This can only be set by device owners and profile owners on the
* primary user. The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -232,11 +232,11 @@ public class UserManager {
* Specifies if a user is disallowed from resetting network settings
* from Settings. This can only be set by device owners and profile owners on the primary user.
* The default value is <code>false</code>.
- * <p/>This restriction has no effect on secondary users and managed profiles since only the
+ * <p>This restriction has no effect on secondary users and managed profiles since only the
* primary user can reset the network settings of the device.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -247,11 +247,11 @@ public class UserManager {
* Specifies if a user is disallowed from factory resetting
* from Settings. This can only be set by device owners and profile owners on the primary user.
* The default value is <code>false</code>.
- * <p/>This restriction has no effect on secondary users and managed profiles since only the
+ * <p>This restriction has no effect on secondary users and managed profiles since only the
* primary user can factory reset the device.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -262,11 +262,11 @@ public class UserManager {
* Specifies if a user is disallowed from adding new users and
* profiles. This can only be set by device owners and profile owners on the primary user.
* The default value is <code>false</code>.
- * <p/>This restriction has no effect on secondary users and managed profiles since only the
+ * <p>This restriction has no effect on secondary users and managed profiles since only the
* primary user can add other users.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -277,8 +277,8 @@ public class UserManager {
* Specifies if a user is disallowed from disabling application
* verification. The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -289,11 +289,11 @@ public class UserManager {
* Specifies if a user is disallowed from configuring cell
* broadcasts. This can only be set by device owners and profile owners on the primary user.
* The default value is <code>false</code>.
- * <p/>This restriction has no effect on secondary users and managed profiles since only the
+ * <p>This restriction has no effect on secondary users and managed profiles since only the
* primary user can configure cell broadcasts.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -304,11 +304,11 @@ public class UserManager {
* Specifies if a user is disallowed from configuring mobile
* networks. This can only be set by device owners and profile owners on the primary user.
* The default value is <code>false</code>.
- * <p/>This restriction has no effect on secondary users and managed profiles since only the
+ * <p>This restriction has no effect on secondary users and managed profiles since only the
* primary user can configure mobile networks.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -328,8 +328,8 @@ public class UserManager {
* <p>
* The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -341,8 +341,8 @@ public class UserManager {
* physical external media. This can only be set by device owners and profile owners on the
* primary user. The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -354,8 +354,8 @@ public class UserManager {
* volume. If set, the microphone will be muted. This can only be set by device owners
* and profile owners on the primary user. The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -367,8 +367,8 @@ public class UserManager {
* volume. If set, the master volume will be muted. This can only be set by device owners
* and profile owners on the primary user. The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -379,11 +379,11 @@ public class UserManager {
* Specifies that the user is not allowed to make outgoing
* phone calls. Emergency calls are still permitted.
* The default value is <code>false</code>.
- * <p/>This restriction has no effect on managed profiles since call intents are normally
+ * <p>This restriction has no effect on managed profiles since call intents are normally
* forwarded to the primary user.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -394,8 +394,8 @@ public class UserManager {
* Specifies that the user is not allowed to send or receive
* SMS messages. The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -407,8 +407,8 @@ public class UserManager {
* device owner may wish to prevent the user from experiencing amusement or
* joy while using the device. The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -428,8 +428,8 @@ public class UserManager {
* <p>This can only be set by device owners and profile owners on the primary user.
* The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -442,8 +442,8 @@ public class UserManager {
* pasted in this profile.
* The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -454,8 +454,8 @@ public class UserManager {
* Specifies if the user is not allowed to use NFC to beam out data from apps.
* The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -492,8 +492,8 @@ public class UserManager {
* This can only be set by device owners and profile owners on the primary user.
* The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -568,8 +568,8 @@ public class UserManager {
* define a host can handle intents from the managed profile.
* The default value is <code>false</code>.
*
- * <p/>Key for user restrictions.
- * <p/>Type: Boolean
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
* @see #getUserRestrictions()
@@ -588,8 +588,8 @@ public class UserManager {
* management application that sets this flag to update it when the final
* restrictions are enforced.
*
- * <p/>Key for application restrictions.
- * <p/>Type: Boolean
+ * <p>Key for application restrictions.
+ * <p>Type: Boolean
* @see android.app.admin.DevicePolicyManager#setApplicationRestrictions(
* android.content.ComponentName, String, Bundle)
* @see android.app.admin.DevicePolicyManager#getApplicationRestrictions(
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 5997515e58d2..4b706496db20 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -438,8 +438,11 @@ public class VolumeInfo implements Parcelable {
final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(uri);
+
+ // note that docsui treats this as *force* show advanced. So sending
+ // false permits advanced to be shown based on user preferences.
+ intent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, isPrimary());
intent.putExtra(DocumentsContract.EXTRA_SHOW_FILESIZE, true);
- intent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, true);
return intent;
}
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index 8892e3430fe4..e9c196d0cdad 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -27,6 +27,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.R;
@@ -872,6 +873,23 @@ public final class PrintAttributes implements Parcelable {
mPackageName = null;
}
+ /**
+ * Get the Id of all predefined media sizes beside the {@link #UNKNOWN_PORTRAIT} and
+ * {@link #UNKNOWN_LANDSCAPE}.
+ *
+ * @return List of all predefined media sizes
+ *
+ * @hide
+ */
+ public static @NonNull ArraySet<MediaSize> getAllPredefinedSizes() {
+ ArraySet<MediaSize> definedMediaSizes = new ArraySet<>(sIdToMediaSizeMap.values());
+
+ definedMediaSizes.remove(UNKNOWN_PORTRAIT);
+ definedMediaSizes.remove(UNKNOWN_LANDSCAPE);
+
+ return definedMediaSizes;
+ }
+
/** @hide */
public MediaSize(String id, String label, String packageName,
int widthMils, int heightMils, int labelResId) {
diff --git a/core/java/android/provider/BlockedNumberContract.java b/core/java/android/provider/BlockedNumberContract.java
index 2a9d4ae762e9..7a9d0625c199 100644
--- a/core/java/android/provider/BlockedNumberContract.java
+++ b/core/java/android/provider/BlockedNumberContract.java
@@ -91,14 +91,6 @@ public class BlockedNumberContract {
/**
* Phone number to block. The system generates it from {@link #COLUMN_ORIGINAL_NUMBER}
* by removing all formatting characters.
- * <p>Must NOT be specified in {@code insert}.
- * <p>TYPE: String</p>
- */
- public static final String COLUMN_STRIPPED_NUMBER = "stripped_number";
-
- /**
- * Phone number to block. The system generates it from {@link #COLUMN_ORIGINAL_NUMBER}
- * by removing all formatting characters.
* <p>Optional in {@code insert}. When not specified, the system tries to generate it
* assuming the current country. (Which will still be null if the number is not valid.)
* <p>TYPE: String</p>
@@ -112,17 +104,32 @@ public class BlockedNumberContract {
/** @hide */
public static final String RES_NUMBER_IS_BLOCKED = "blocked";
+ /** @hide */
+ public static final String METHOD_CAN_CURRENT_USER_BLOCK_NUMBERS =
+ "can_current_user_block_numbers";
+
+ /** @hide */
+ public static final String RES_CAN_BLOCK_NUMBERS = "can_block";
+
/**
* Returns whether a given number is in the blocked list.
- *
- * TODO This should probably catch IllegalArgumentException to guard against the case where
- * the provider is encrypted or the user is not running.
- * (See addEntryAndRemoveExpiredEntries() in
- * http://ag/#/c/844426/3/core/java/android/provider/CallLog.java)
+ * <p> Note that if the {@link #canCurrentUserBlockNumbers} is {@code false} for the user
+ * context {@code context}, this method will throw an {@link UnsupportedOperationException}.
*/
public static boolean isBlocked(Context context, String phoneNumber) {
final Bundle res = context.getContentResolver().call(AUTHORITY_URI,
METHOD_IS_BLOCKED, phoneNumber, null);
return res != null && res.getBoolean(RES_NUMBER_IS_BLOCKED, false);
}
+
+ /**
+ * Returns {@code true} if blocking numbers is supported for the current user.
+ * <p> Typically, blocking numbers is only supported for the primary user.
+ */
+ public static boolean canCurrentUserBlockNumbers(Context context) {
+ final Bundle res = context.getContentResolver().call(
+ AUTHORITY_URI, METHOD_CAN_CURRENT_USER_BLOCK_NUMBERS, null, null);
+ return res != null && res.getBoolean(RES_CAN_BLOCK_NUMBERS, false);
+ }
+
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1561d46447a9..5535eaa0726d 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6895,6 +6895,15 @@ public final class Settings {
public static final String TETHER_DUN_APN = "tether_dun_apn";
/**
+ * List of carrier apps which are whitelisted to prompt the user for install when
+ * a sim card with matching uicc carrier privilege rules is inserted.
+ *
+ * The value is "package1;package2;..."
+ * @hide
+ */
+ public static final String CARRIER_APP_WHITELIST = "carrier_app_whitelist";
+
+ /**
* USB Mass Storage Enabled
*/
public static final String USB_MASS_STORAGE_ENABLED = "usb_mass_storage_enabled";
diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java
index 78d5bcd90624..29a72fdf2288 100644
--- a/core/java/android/util/PathParser.java
+++ b/core/java/android/util/PathParser.java
@@ -104,6 +104,7 @@ public class PathParser {
}
super.finalize();
}
+
}
/**
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 34713ad487fe..501c6e9e25e5 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -33,7 +33,7 @@ import java.util.ArrayList;
* @hide
*/
@RemoteViews.RemoteView
-public class NotificationHeaderView extends LinearLayout {
+public class NotificationHeaderView extends ViewGroup {
public static final int NO_COLOR = -1;
private final int mHeaderMinWidth;
private final int mExpandTopPadding;
@@ -134,27 +134,46 @@ public class NotificationHeaderView extends LinearLayout {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
- if (mProfileBadge.getVisibility() != View.GONE) {
- int paddingEnd = getPaddingEnd();
- if (mShowWorkBadgeAtEnd) {
- paddingEnd = mContentEndMargin;
+ int left = getPaddingStart();
+ int childCount = getChildCount();
+ int ownHeight = getHeight() - getPaddingTop() - getPaddingBottom();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (child.getVisibility() == GONE) {
+ continue;
+ }
+ int childHeight = child.getMeasuredHeight();
+ MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
+ left += params.getMarginStart();
+ int right = left + child.getMeasuredWidth();
+ int top = (int) (getPaddingTop() + (ownHeight - childHeight) / 2.0f);
+ int bottom = top + childHeight;
+ int layoutLeft = left;
+ int layoutRight = right;
+ if (child == mProfileBadge) {
+ int paddingEnd = getPaddingEnd();
+ if (mShowWorkBadgeAtEnd) {
+ paddingEnd = mContentEndMargin;
+ }
+ layoutRight = getWidth() - paddingEnd;
+ layoutLeft = layoutRight - child.getMeasuredWidth();
}
if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
- mProfileBadge.layout(paddingEnd,
- mProfileBadge.getTop(),
- paddingEnd + mProfileBadge.getMeasuredWidth(),
- mProfileBadge.getBottom());
- } else {
- mProfileBadge.layout(getWidth() - paddingEnd - mProfileBadge.getMeasuredWidth(),
- mProfileBadge.getTop(),
- getWidth() - paddingEnd,
- mProfileBadge.getBottom());
+ int ltrLeft = layoutLeft;
+ layoutLeft = getWidth() - layoutRight;
+ layoutRight = getWidth() - ltrLeft;
}
+ child.layout(layoutLeft, top, layoutRight, bottom);
+ left = right + params.getMarginEnd();
}
updateTouchListener();
}
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new ViewGroup.MarginLayoutParams(getContext(), attrs);
+ }
+
private void updateTouchListener() {
if (mExpandClickListener != null) {
mTouchListener.bindTouchRects();
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 88bffb5827aa..2aace0f30139 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -22,6 +22,7 @@ import android.graphics.Matrix;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Rect;
+import android.graphics.drawable.AnimatedVectorDrawable;
/**
* <p>A display list records a series of graphics related operations and can replay
@@ -771,6 +772,14 @@ public class RenderNode {
mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this);
}
+ public void addAnimator(AnimatedVectorDrawable.VectorDrawableAnimator animatorSet) {
+ if (mOwningView == null || mOwningView.mAttachInfo == null) {
+ throw new IllegalStateException("Cannot start this animator on a detached view!");
+ }
+ nAddAnimator(mNativeRenderNode, animatorSet.getAnimatorNativePtr());
+ mOwningView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(this);
+ }
+
public void endAllAnimators() {
nEndAllAnimators(mNativeRenderNode);
}
diff --git a/core/java/android/view/RenderNodeAnimatorSetHelper.java b/core/java/android/view/RenderNodeAnimatorSetHelper.java
new file mode 100644
index 000000000000..ba592d29fa3d
--- /dev/null
+++ b/core/java/android/view/RenderNodeAnimatorSetHelper.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 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.view;
+
+import android.animation.TimeInterpolator;
+import com.android.internal.view.animation.FallbackLUTInterpolator;
+import com.android.internal.view.animation.NativeInterpolatorFactory;
+import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
+
+/**
+ * This is a helper class to get access to methods and fields needed for RenderNodeAnimatorSet
+ * that are internal or package private to android.view package.
+ *
+ * @hide
+ */
+public class RenderNodeAnimatorSetHelper {
+
+ public static RenderNode getTarget(DisplayListCanvas recordingCanvas) {
+ return recordingCanvas.mNode;
+ }
+
+ public static long createNativeInterpolator(TimeInterpolator interpolator, long
+ duration) {
+ if (interpolator == null) {
+ // create LinearInterpolator
+ return NativeInterpolatorFactoryHelper.createLinearInterpolator();
+ } else if (RenderNodeAnimator.isNativeInterpolator(interpolator)) {
+ return ((NativeInterpolatorFactory)interpolator).createNativeInterpolator();
+ } else {
+ return FallbackLUTInterpolator.createNativeInterpolator(interpolator, duration);
+ }
+ }
+
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index dd0887f8f8c5..4a0a0b0dbeb7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6770,6 +6770,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
info.setVisibleToUser(isVisibleToUser());
+ if ((mAttachInfo != null) && ((mAttachInfo.mAccessibilityFetchFlags
+ & AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0)) {
+ info.setImportantForAccessibility(isImportantForAccessibility());
+ } else {
+ info.setImportantForAccessibility(true);
+ }
+
info.setPackageName(mContext.getPackageName());
info.setClassName(getAccessibilityClassName());
info.setContentDescription(getContentDescription());
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index b67338655057..bdaf291a2d3c 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -16,6 +16,7 @@
package android.view.accessibility;
+import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.Nullable;
import android.graphics.Rect;
@@ -568,6 +569,8 @@ public class AccessibilityNodeInfo implements Parcelable {
private static final int BOOLEAN_PROPERTY_CONTEXT_CLICKABLE = 0x00020000;
+ private static final int BOOLEAN_PROPERTY_IMPORTANCE = 0x0040000;
+
/**
* Bits that provide the id of a virtual descendant of a view.
*/
@@ -2156,6 +2159,33 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
+ * Returns whether the node originates from a view considered important for accessibility.
+ *
+ * @return {@code true} if the node originates from a view considered important for
+ * accessibility, {@code false} otherwise
+ *
+ * @see View#isImportantForAccessibility()
+ */
+ public boolean isImportantForAccessibility() {
+ return getBooleanProperty(BOOLEAN_PROPERTY_IMPORTANCE);
+ }
+
+ /**
+ * Sets whether the node is considered important for accessibility.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param important {@code true} if the node is considered important for accessibility,
+ * {@code false} otherwise
+ */
+ public void setImportantForAccessibility(boolean important) {
+ setBooleanProperty(BOOLEAN_PROPERTY_IMPORTANCE, important);
+ }
+
+ /**
* Gets the package this node comes from.
*
* @return The package name.
@@ -3311,9 +3341,21 @@ public class AccessibilityNodeInfo implements Parcelable {
* </li>
* <li><strong>Overriden standard actions</strong> - These are actions that override
* standard actions to customize them. For example, an app may add a label to the
- * standard click action to announce that this action clears browsing history.
+ * standard {@link #ACTION_CLICK} action to announce that this action clears browsing history.
* </ul>
* </p>
+ * <p>
+ * Actions are typically added to an {@link AccessibilityNodeInfo} by using
+ * {@link AccessibilityNodeInfo#addAction(AccessibilityAction)} within
+ * {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} and are performed
+ * within {@link View#performAccessibilityAction(int, Bundle)}.
+ * </p>
+ * <p class="note">
+ * <strong>Note:</strong> Views which support these actions should invoke
+ * {@link View#setImportantForAccessibility(int)} with
+ * {@link View#IMPORTANT_FOR_ACCESSIBILITY_YES} to ensure an {@link AccessibilityService}
+ * can discover the set of supported actions.
+ * </p>
*/
public static final class AccessibilityAction {
diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java
index fd857fd10c82..aac7bc3810e9 100644
--- a/core/java/com/android/internal/app/AlertController.java
+++ b/core/java/com/android/internal/app/AlertController.java
@@ -234,7 +234,6 @@ public class AlertController {
int contentView = selectContentView();
mWindow.setContentView(contentView);
setupView();
- setupDecor();
}
private int selectContentView() {
@@ -431,27 +430,6 @@ public class AlertController {
return mScrollView != null && mScrollView.executeKeyEvent(event);
}
- private void setupDecor() {
- final View decor = mWindow.getDecorView();
- final View parent = mWindow.findViewById(R.id.parentPanel);
- if (parent != null && decor != null) {
- decor.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
- @Override
- public WindowInsets onApplyWindowInsets(View view, WindowInsets insets) {
- if (insets.isRound()) {
- // TODO: Get the padding as a function of the window size.
- int roundOffset = mContext.getResources().getDimensionPixelOffset(
- R.dimen.alert_dialog_round_padding);
- parent.setPadding(roundOffset, roundOffset, roundOffset, roundOffset);
- }
- return insets.consumeSystemWindowInsets();
- }
- });
- decor.setFitsSystemWindows(true);
- decor.requestApplyInsets();
- }
- }
-
/**
* Resolves whether a custom or default panel should be used. Removes the
* default panel if a custom panel should be used. If the resolved panel is
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 27333915b2b6..cf13a13cafb7 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -25,6 +25,7 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.content.ServiceConnection;
+import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.LabeledIntent;
import android.content.pm.PackageManager;
@@ -35,6 +36,7 @@ import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
+import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -68,6 +70,7 @@ import com.android.internal.R;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
+import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -91,6 +94,11 @@ public class ChooserActivity extends ResolverActivity {
private ChooserListAdapter mChooserListAdapter;
private ChooserRowAdapter mChooserRowAdapter;
+ private SharedPreferences mPinnedSharedPrefs;
+ private static final float PINNED_TARGET_SCORE_BOOST = 1000.f;
+ private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
+ private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
+
private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
@@ -207,12 +215,30 @@ public class ChooserActivity extends ResolverActivity {
mRefinementIntentSender = intent.getParcelableExtra(
Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
setSafeForwardingMode(true);
+
+ mPinnedSharedPrefs = getPinnedSharedPrefs(this);
super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
null, false);
MetricsLogger.action(this, MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN);
}
+ static SharedPreferences getPinnedSharedPrefs(Context context) {
+ // The code below is because in the android:ui process, no one can hear you scream.
+ // The package info in the context isn't initialized in the way it is for normal apps,
+ // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
+ // build the path manually below using the same policy that appears in ContextImpl.
+ // This fails silently under the hood if there's a problem, so if we find ourselves in
+ // the case where we don't have access to credential encrypted storage we just won't
+ // have our pinned target info.
+ final File prefsFile = new File(new File(
+ Environment.getDataUserCredentialEncryptedPackageDirectory(null,
+ context.getUserId(), context.getPackageName()),
+ "shared_prefs"),
+ PINNED_SHARED_PREFS_NAME + ".xml");
+ return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
+ }
+
@Override
protected void onDestroy() {
super.onDestroy();
@@ -243,7 +269,7 @@ public class ChooserActivity extends ResolverActivity {
}
@Override
- void onActivityStarted(TargetInfo cti) {
+ public void onActivityStarted(TargetInfo cti) {
if (mChosenComponentSender != null) {
final ComponentName target = cti.getResolvedComponentName();
if (target != null) {
@@ -259,7 +285,7 @@ public class ChooserActivity extends ResolverActivity {
}
@Override
- void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
+ public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
boolean alwaysUseOption) {
final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
mChooserListAdapter = (ChooserListAdapter) adapter;
@@ -272,17 +298,17 @@ public class ChooserActivity extends ResolverActivity {
}
@Override
- int getLayoutResource() {
+ public int getLayoutResource() {
return R.layout.chooser_grid;
}
@Override
- boolean shouldGetActivityMetadata() {
+ public boolean shouldGetActivityMetadata() {
return true;
}
@Override
- boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
+ public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
final Intent intent = target.getResolvedIntent();
final ResolveInfo resolve = target.getResolveInfo();
@@ -299,6 +325,16 @@ public class ChooserActivity extends ResolverActivity {
return false;
}
+ @Override
+ public void showTargetDetails(ResolveInfo ri) {
+ ComponentName name = ri.activityInfo.getComponentName();
+ boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
+ ResolverTargetActionsDialogFragment f =
+ new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
+ name, pinned);
+ f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
+ }
+
private void modifyTargetIntent(Intent in) {
final String action = in.getAction();
if (Intent.ACTION_SEND.equals(action) ||
@@ -340,7 +376,7 @@ public class ChooserActivity extends ResolverActivity {
}
@Override
- void startSelected(int which, boolean always, boolean filtered) {
+ public void startSelected(int which, boolean always, boolean filtered) {
super.startSelected(which, always, filtered);
if (mChooserListAdapter != null) {
@@ -471,7 +507,7 @@ public class ChooserActivity extends ResolverActivity {
mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
}
- void onSetupVoiceInteraction() {
+ public void onSetupVoiceInteraction() {
// Do nothing. We'll send the voice stuff ourselves.
}
@@ -543,7 +579,7 @@ public class ChooserActivity extends ResolverActivity {
}
@Override
- ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
+ public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
boolean filterLastUsed) {
final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
@@ -711,6 +747,11 @@ public class ChooserActivity extends ResolverActivity {
}
return results;
}
+
+ @Override
+ public boolean isPinned() {
+ return mSourceInfo != null ? mSourceInfo.isPinned() : false;
+ }
}
public class ChooserListAdapter extends ResolveListAdapter {
@@ -777,6 +818,20 @@ public class ChooserActivity extends ResolverActivity {
}
@Override
+ public boolean isComponentPinned(ComponentName name) {
+ return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
+ }
+
+ @Override
+ public float getScore(DisplayResolveInfo target) {
+ float score = super.getScore(target);
+ if (target.isPinned()) {
+ score += PINNED_TARGET_SCORE_BOOST;
+ }
+ return score;
+ }
+
+ @Override
public View onCreateView(ViewGroup parent) {
return mInflater.inflate(
com.android.internal.R.layout.resolve_grid_item, parent, false);
@@ -1121,7 +1176,7 @@ public class ChooserActivity extends ResolverActivity {
v.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
- showAppDetails(
+ showTargetDetails(
mChooserListAdapter.resolveInfoForPosition(
holder.itemIndices[column], true));
return true;
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index ec148c554bb7..53e329d090cf 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -22,6 +22,7 @@ import android.app.ActivityThread;
import android.app.VoiceInteractor.PickOptionRequest;
import android.app.VoiceInteractor.PickOptionRequest.Option;
import android.app.VoiceInteractor.Prompt;
+import android.content.pm.ComponentInfo;
import android.os.AsyncTask;
import android.provider.Settings;
import android.text.TextUtils;
@@ -336,12 +337,12 @@ public class ResolverActivity extends Activity {
/**
* Perform any initialization needed for voice interaction.
*/
- void onSetupVoiceInteraction() {
+ public void onSetupVoiceInteraction() {
// Do it right now. Subclasses may delay this and send it later.
sendVoiceChoicesIfNeeded();
}
- void sendVoiceChoicesIfNeeded() {
+ public void sendVoiceChoicesIfNeeded() {
if (!isVoiceInteraction()) {
// Clearly not needed.
return;
@@ -382,7 +383,7 @@ public class ResolverActivity extends Activity {
return null;
}
- int getLayoutResource() {
+ public int getLayoutResource() {
return R.layout.resolver_list;
}
@@ -591,7 +592,7 @@ public class ResolverActivity extends Activity {
mAlwaysUseOption);
}
- void startSelected(int which, boolean always, boolean filtered) {
+ public void startSelected(int which, boolean always, boolean filtered) {
if (isFinishing()) {
return;
}
@@ -761,7 +762,7 @@ public class ResolverActivity extends Activity {
return true;
}
- void safelyStartActivity(TargetInfo cti) {
+ public void safelyStartActivity(TargetInfo cti) {
// If needed, show that intent is forwarded
// from managed profile to owner or other way around.
if (mProfileSwitchMessageId != -1) {
@@ -791,26 +792,26 @@ public class ResolverActivity extends Activity {
}
}
- void onActivityStarted(TargetInfo cti) {
+ public void onActivityStarted(TargetInfo cti) {
// Do nothing
}
- boolean shouldGetActivityMetadata() {
+ public boolean shouldGetActivityMetadata() {
return false;
}
- boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
+ public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
return true;
}
- void showAppDetails(ResolveInfo ri) {
+ public void showTargetDetails(ResolveInfo ri) {
Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
startActivity(in);
}
- ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
+ public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
boolean filterLastUsed) {
return new ResolveListAdapter(context, payloadIntents, initialIntents, rList,
@@ -820,7 +821,7 @@ public class ResolverActivity extends Activity {
/**
* Returns true if the activity is finishing and creation should halt
*/
- boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents,
+ public boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents,
List<ResolveInfo> rList, boolean alwaysUseOption) {
// The last argument of createAdapter is whether to do special handling
// of the last used choice to highlight it in the list. We need to always
@@ -867,7 +868,7 @@ public class ResolverActivity extends Activity {
return false;
}
- void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
+ public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
boolean alwaysUseOption) {
final boolean useHeader = adapter.hasFilteredItem();
final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
@@ -898,7 +899,7 @@ public class ResolverActivity extends Activity {
&& Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName);
}
- final class DisplayResolveInfo implements TargetInfo {
+ public final class DisplayResolveInfo implements TargetInfo {
private final ResolveInfo mResolveInfo;
private final CharSequence mDisplayLabel;
private Drawable mDisplayIcon;
@@ -906,8 +907,9 @@ public class ResolverActivity extends Activity {
private final CharSequence mExtendedInfo;
private final Intent mResolvedIntent;
private final List<Intent> mSourceIntents = new ArrayList<>();
+ private boolean mPinned;
- DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
+ public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
CharSequence pInfo, Intent pOrigIntent) {
mSourceIntents.add(originalIntent);
mResolveInfo = pri;
@@ -932,6 +934,7 @@ public class ResolverActivity extends Activity {
mExtendedInfo = other.mExtendedInfo;
mResolvedIntent = new Intent(other.mResolvedIntent);
mResolvedIntent.fillIn(fillInIntent, flags);
+ mPinned = other.mPinned;
}
public ResolveInfo getResolveInfo() {
@@ -1026,6 +1029,15 @@ public class ResolverActivity extends Activity {
activity.startActivityAsUser(mResolvedIntent, options, user);
return false;
}
+
+ @Override
+ public boolean isPinned() {
+ return mPinned;
+ }
+
+ public void setPinned(boolean pinned) {
+ mPinned = pinned;
+ }
}
/**
@@ -1039,7 +1051,7 @@ public class ResolverActivity extends Activity {
*
* @return the resolved intent for this target
*/
- public Intent getResolvedIntent();
+ Intent getResolvedIntent();
/**
* Get the resolved component name that represents this target. Note that this may not
@@ -1048,7 +1060,7 @@ public class ResolverActivity extends Activity {
*
* @return the resolved ComponentName for this target
*/
- public ComponentName getResolvedComponentName();
+ ComponentName getResolvedComponentName();
/**
* Start the activity referenced by this target.
@@ -1057,7 +1069,7 @@ public class ResolverActivity extends Activity {
* @param options ActivityOptions bundle
* @return true if the start completed successfully
*/
- public boolean start(Activity activity, Bundle options);
+ boolean start(Activity activity, Bundle options);
/**
* Start the activity referenced by this target as if the ResolverActivity's caller
@@ -1068,7 +1080,7 @@ public class ResolverActivity extends Activity {
* @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller
* @return true if the start completed successfully
*/
- public boolean startAsCaller(Activity activity, Bundle options, int userId);
+ boolean startAsCaller(Activity activity, Bundle options, int userId);
/**
* Start the activity referenced by this target as a given user.
@@ -1078,7 +1090,7 @@ public class ResolverActivity extends Activity {
* @param user handle for the user to start the activity as
* @return true if the start completed successfully
*/
- public boolean startAsUser(Activity activity, Bundle options, UserHandle user);
+ boolean startAsUser(Activity activity, Bundle options, UserHandle user);
/**
* Return the ResolveInfo about how and why this target matched the original query
@@ -1086,14 +1098,14 @@ public class ResolverActivity extends Activity {
*
* @return ResolveInfo representing this target's match
*/
- public ResolveInfo getResolveInfo();
+ ResolveInfo getResolveInfo();
/**
* Return the human-readable text label for this target.
*
* @return user-visible target label
*/
- public CharSequence getDisplayLabel();
+ CharSequence getDisplayLabel();
/**
* Return any extended info for this target. This may be used to disambiguate
@@ -1101,35 +1113,40 @@ public class ResolverActivity extends Activity {
*
* @return human-readable disambig string or null if none present
*/
- public CharSequence getExtendedInfo();
+ CharSequence getExtendedInfo();
/**
* @return The drawable that should be used to represent this target
*/
- public Drawable getDisplayIcon();
+ Drawable getDisplayIcon();
/**
* @return The (small) icon to badge the target with
*/
- public Drawable getBadgeIcon();
+ Drawable getBadgeIcon();
/**
* @return The content description for the badge icon
*/
- public CharSequence getBadgeContentDescription();
+ CharSequence getBadgeContentDescription();
/**
* Clone this target with the given fill-in information.
*/
- public TargetInfo cloneFilledIn(Intent fillInIntent, int flags);
+ TargetInfo cloneFilledIn(Intent fillInIntent, int flags);
/**
* @return the list of supported source intents deduped against this single target
*/
- public List<Intent> getAllSourceIntents();
+ List<Intent> getAllSourceIntents();
+
+ /**
+ * @return true if this target should be pinned to the front by the request of the user
+ */
+ boolean isPinned();
}
- class ResolveListAdapter extends BaseAdapter {
+ public class ResolveListAdapter extends BaseAdapter {
private final List<Intent> mIntents;
private final Intent[] mInitialIntents;
private final List<ResolveInfo> mBaseResolveList;
@@ -1376,9 +1393,12 @@ public class ResolverActivity extends Activity {
}
}
if (!found) {
- into.add(new ResolvedComponentInfo(new ComponentName(
- newInfo.activityInfo.packageName, newInfo.activityInfo.name),
- intent, newInfo));
+ final ComponentName name = new ComponentName(
+ newInfo.activityInfo.packageName, newInfo.activityInfo.name);
+ final ResolvedComponentInfo rci = new ResolvedComponentInfo(name,
+ intent, newInfo);
+ rci.setPinned(isComponentPinned(name));
+ into.add(rci);
}
}
}
@@ -1454,6 +1474,7 @@ public class ResolverActivity extends Activity {
final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent);
final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel,
extraInfo, replaceIntent);
+ dri.setPinned(rci.isPinned());
addResolveInfo(dri);
if (replaceIntent == intent) {
// Only add alternates if we didn't get a specific replacement from
@@ -1537,11 +1558,11 @@ public class ResolverActivity extends Activity {
return false;
}
- protected int getDisplayResolveInfoCount() {
+ public int getDisplayResolveInfoCount() {
return mDisplayList.size();
}
- protected DisplayResolveInfo getDisplayResolveInfo(int index) {
+ public DisplayResolveInfo getDisplayResolveInfo(int index) {
// Used to query services. We only query services for primary targets, not alternates.
return mDisplayList.get(index);
}
@@ -1571,6 +1592,10 @@ public class ResolverActivity extends Activity {
return !TextUtils.isEmpty(info.getExtendedInfo());
}
+ public boolean isComponentPinned(ComponentName name) {
+ return false;
+ }
+
public final void bindView(int position, View view) {
onBindView(view, getItem(position));
}
@@ -1607,6 +1632,7 @@ public class ResolverActivity extends Activity {
static final class ResolvedComponentInfo {
public final ComponentName name;
+ private boolean mPinned;
private final List<Intent> mIntents = new ArrayList<>();
private final List<ResolveInfo> mResolveInfos = new ArrayList<>();
@@ -1649,6 +1675,14 @@ public class ResolverActivity extends Activity {
}
return -1;
}
+
+ public boolean isPinned() {
+ return mPinned;
+ }
+
+ public void setPinned(boolean pinned) {
+ mPinned = pinned;
+ }
}
static class ViewHolder {
@@ -1702,7 +1736,7 @@ public class ResolverActivity extends Activity {
return false;
}
ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true);
- showAppDetails(ri);
+ showTargetDetails(ri);
return true;
}
diff --git a/core/java/com/android/internal/app/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java
index 31556e29bc32..0ae21c61112f 100644
--- a/core/java/com/android/internal/app/ResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverComparator.java
@@ -47,8 +47,8 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
private static final boolean DEBUG = false;
- // Two weeks
- private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
+ // One week
+ private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 7;
private static final long RECENCY_TIME_PERIOD = 1000 * 60 * 60 * 12;
@@ -171,15 +171,27 @@ class ResolverComparator implements Comparator<ResolvedComponentInfo> {
}
}
- if (mStats != null) {
- final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName(
- lhs.activityInfo.packageName, lhs.activityInfo.name));
- final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName(
- rhs.activityInfo.packageName, rhs.activityInfo.name));
- final float diff = rhsTarget.score - lhsTarget.score;
+ final boolean lPinned = lhsp.isPinned();
+ final boolean rPinned = rhsp.isPinned();
- if (diff != 0) {
- return diff > 0 ? 1 : -1;
+ if (lPinned && !rPinned) {
+ return -1;
+ } else if (!lPinned && rPinned) {
+ return 1;
+ }
+
+ // Pinned items stay stable within a normal lexical sort and ignore scoring.
+ if (!lPinned && !rPinned) {
+ if (mStats != null) {
+ final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName(
+ lhs.activityInfo.packageName, lhs.activityInfo.name));
+ final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName(
+ rhs.activityInfo.packageName, rhs.activityInfo.name));
+ final float diff = rhsTarget.score - lhsTarget.score;
+
+ if (diff != 0) {
+ return diff > 0 ? 1 : -1;
+ }
}
}
diff --git a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
new file mode 100644
index 000000000000..8156f79f3be3
--- /dev/null
+++ b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2016 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.AlertDialog.Builder;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+
+import com.android.internal.R;
+
+/**
+ * Shows a dialog with actions to take on a chooser target
+ */
+public class ResolverTargetActionsDialogFragment extends DialogFragment
+ implements DialogInterface.OnClickListener {
+ private static final String NAME_KEY = "componentName";
+ private static final String PINNED_KEY = "pinned";
+ private static final String TITLE_KEY = "title";
+
+ // Sync with R.array.resolver_target_actions_* resources
+ private static final int TOGGLE_PIN_INDEX = 0;
+ private static final int APP_INFO_INDEX = 1;
+
+ public ResolverTargetActionsDialogFragment() {
+ }
+
+ public ResolverTargetActionsDialogFragment(CharSequence title, ComponentName name,
+ boolean pinned) {
+ Bundle args = new Bundle();
+ args.putCharSequence(TITLE_KEY, title);
+ args.putParcelable(NAME_KEY, name);
+ args.putBoolean(PINNED_KEY, pinned);
+ setArguments(args);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Bundle args = getArguments();
+ final int itemRes = args.getBoolean(PINNED_KEY, false)
+ ? R.array.resolver_target_actions_unpin
+ : R.array.resolver_target_actions_pin;
+ return new Builder(getContext())
+ .setCancelable(true)
+ .setItems(itemRes, this)
+ .setTitle(args.getCharSequence(TITLE_KEY))
+ .create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final Bundle args = getArguments();
+ ComponentName name = args.getParcelable(NAME_KEY);
+ switch (which) {
+ case TOGGLE_PIN_INDEX:
+ SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext());
+ final String key = name.flattenToString();
+ boolean currentVal = sp.getBoolean(name.flattenToString(), false);
+ if (currentVal) {
+ sp.edit().remove(key).apply();
+ } else {
+ sp.edit().putBoolean(key, true).apply();
+ }
+
+ // Force the chooser to requery and resort things
+ getActivity().recreate();
+ break;
+ case APP_INFO_INDEX:
+ Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ .setData(Uri.fromParts("package", name.getPackageName(), null))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+ startActivity(in);
+ break;
+ }
+ dismiss();
+ }
+}
diff --git a/core/java/com/android/internal/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
index de54d96df78b..5047c4c11889 100644
--- a/core/java/com/android/internal/policy/BackdropFrameRenderer.java
+++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
@@ -289,12 +289,17 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame
DisplayListCanvas canvas = mFrameAndBackdropNode.start(width, height);
final Drawable drawable = mUserCaptionBackgroundDrawable != null
? mUserCaptionBackgroundDrawable : mCaptionBackgroundDrawable;
- drawable.setBounds(0, 0, left + width, top + mLastCaptionHeight);
- drawable.draw(canvas);
+
+ if (drawable != null) {
+ drawable.setBounds(0, 0, left + width, top + mLastCaptionHeight);
+ drawable.draw(canvas);
+ }
// The backdrop: clear everything with the background. Clipping is done elsewhere.
- mResizingBackgroundDrawable.setBounds(0, mLastCaptionHeight, left + width, top + height);
- mResizingBackgroundDrawable.draw(canvas);
+ if (mResizingBackgroundDrawable != null) {
+ mResizingBackgroundDrawable.setBounds(0, mLastCaptionHeight, left + width, top + height);
+ mResizingBackgroundDrawable.draw(canvas);
+ }
mFrameAndBackdropNode.end(canvas);
if (mSystemBarBackgroundNode != null && mStatusBarColor != null) {
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 36009dcf8847..1a20e5ce5793 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -205,6 +205,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
private float mAvailableWidth;
String mLogTag = TAG;
+ private final Rect mFloatingInsets = new Rect();
+ private boolean mApplyFloatingVerticalInsets = false;
+ private boolean mApplyFloatingHorizontalInsets = false;
DecorView(Context context, int featureId, PhoneWindow window,
WindowManager.LayoutParams params) {
@@ -567,6 +570,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
final int heightMode = getMode(heightMeasureSpec);
boolean fixedWidth = false;
+ mApplyFloatingHorizontalInsets = false;
if (widthMode == AT_MOST) {
final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor : mWindow.mFixedWidthMajor;
if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
@@ -584,10 +588,16 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
widthMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.min(w, widthSize), EXACTLY);
fixedWidth = true;
+ } else {
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+ widthMeasureSpec - mFloatingInsets.left - mFloatingInsets.right,
+ AT_MOST);
+ mApplyFloatingHorizontalInsets = true;
}
}
}
+ mApplyFloatingVerticalInsets = false;
if (heightMode == AT_MOST) {
final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor
: mWindow.mFixedHeightMinor;
@@ -601,10 +611,14 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
h = 0;
}
if (DEBUG_MEASURE) Log.d(mLogTag, "Fixed height: " + h);
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (h > 0) {
- final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.min(h, heightSize), EXACTLY);
+ } else if ((mWindow.getAttributes().flags & FLAG_FULLSCREEN) == 0) {
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ heightSize - mFloatingInsets.top - mFloatingInsets.bottom, AT_MOST);
+ mApplyFloatingVerticalInsets = true;
}
}
}
@@ -672,6 +686,12 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
if (mOutsets.top > 0) {
offsetTopAndBottom(-mOutsets.top);
}
+ if (mApplyFloatingVerticalInsets) {
+ offsetTopAndBottom(mFloatingInsets.top);
+ }
+ if (mApplyFloatingHorizontalInsets) {
+ offsetLeftAndRight(mFloatingInsets.left);
+ }
// If the application changed its SystemUI metrics, we might also have to adapt
// our shadow elevation.
@@ -868,6 +888,25 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ final WindowManager.LayoutParams attrs = mWindow.getAttributes();
+ mFloatingInsets.setEmpty();
+ if ((attrs.flags & FLAG_FULLSCREEN) == 0) {
+ // For dialog windows we want to make sure they don't go over the status bar or nav bar.
+ // We consume the system insets and we will reuse them later during the measure phase.
+ // We allow the app to ignore this and handle insets itself by using FLAG_FULLSCREEN.
+ if (attrs.height == WindowManager.LayoutParams.WRAP_CONTENT) {
+ mFloatingInsets.top = insets.getSystemWindowInsetTop();
+ mFloatingInsets.bottom = insets.getSystemWindowInsetBottom();
+ insets = insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0,
+ insets.getSystemWindowInsetRight(), 0);
+ }
+ if (mWindow.getAttributes().width == WindowManager.LayoutParams.WRAP_CONTENT) {
+ mFloatingInsets.left = insets.getSystemWindowInsetTop();
+ mFloatingInsets.right = insets.getSystemWindowInsetBottom();
+ insets = insets.replaceSystemWindowInsets(0, insets.getSystemWindowInsetTop(),
+ 0, insets.getSystemWindowInsetBottom());
+ }
+ }
mFrameOffsets.set(insets.getSystemWindowInsets());
insets = updateColorViews(insets, true /* animate */);
insets = updateStatusGuard(insets);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index ba456f9ce052..e239852673e8 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -883,6 +883,15 @@ public class LockPatternUtils {
}
/**
+ * Determine if the device is file encrypted
+ * @return true if device is file encrypted
+ */
+ public static boolean isFileEncryptionEnabled() {
+ final String status = SystemProperties.get("ro.crypto.type", "");
+ return "file".equalsIgnoreCase(status);
+ }
+
+ /**
* Clears the encryption password.
*/
public void clearEncryptionPassword() {
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index ffa3fa631b82..1b6b53ae49f4 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -51,6 +51,7 @@ LOCAL_SRC_FILES:= \
android_database_SQLiteConnection.cpp \
android_database_SQLiteGlobal.cpp \
android_database_SQLiteDebug.cpp \
+ android_graphics_drawable_AnimatedVectorDrawable.cpp \
android_graphics_drawable_VectorDrawable.cpp \
android_view_DisplayEventReceiver.cpp \
android_view_DisplayListCanvas.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 2e45f8c4d9c0..223fc1af46d5 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -131,6 +131,7 @@ extern int register_android_graphics_Rasterizer(JNIEnv* env);
extern int register_android_graphics_Region(JNIEnv* env);
extern int register_android_graphics_SurfaceTexture(JNIEnv* env);
extern int register_android_graphics_Xfermode(JNIEnv* env);
+extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env);
extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env);
extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env);
extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env);
@@ -1321,6 +1322,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_graphics_Typeface),
REG_JNI(register_android_graphics_Xfermode),
REG_JNI(register_android_graphics_YuvImage),
+ REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable),
REG_JNI(register_android_graphics_drawable_VectorDrawable),
REG_JNI(register_android_graphics_pdf_PdfDocument),
REG_JNI(register_android_graphics_pdf_PdfEditor),
diff --git a/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
new file mode 100644
index 000000000000..7a3c598e0aed
--- /dev/null
+++ b/core/jni/android_graphics_drawable_AnimatedVectorDrawable.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#define LOG_TAG "OpenGLRenderer"
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include "core_jni_helpers.h"
+#include "log/log.h"
+
+#include "Animator.h"
+#include "Interpolator.h"
+#include "PropertyValuesAnimatorSet.h"
+#include "PropertyValuesHolder.h"
+#include "VectorDrawable.h"
+
+namespace android {
+using namespace uirenderer;
+using namespace VectorDrawable;
+
+static struct {
+ jclass clazz;
+ jmethodID callOnFinished;
+} gVectorDrawableAnimatorClassInfo;
+
+static JNIEnv* getEnv(JavaVM* vm) {
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ return 0;
+ }
+ return env;
+}
+
+static AnimationListener* createAnimationListener(JNIEnv* env, jobject finishListener) {
+ class AnimationListenerBridge : public AnimationListener {
+ public:
+ AnimationListenerBridge(JNIEnv* env, jobject finishListener) {
+ mFinishListener = env->NewGlobalRef(finishListener);
+ env->GetJavaVM(&mJvm);
+ }
+
+ virtual ~AnimationListenerBridge() {
+ if (mFinishListener) {
+ onAnimationFinished(NULL);
+ }
+ }
+
+ virtual void onAnimationFinished(BaseRenderNodeAnimator*) {
+ LOG_ALWAYS_FATAL_IF(!mFinishListener, "Finished listener twice?");
+ JNIEnv* env = getEnv(mJvm);
+ env->CallStaticVoidMethod(
+ gVectorDrawableAnimatorClassInfo.clazz,
+ gVectorDrawableAnimatorClassInfo.callOnFinished,
+ mFinishListener);
+ releaseJavaObject();
+ }
+
+ private:
+ void releaseJavaObject() {
+ JNIEnv* env = getEnv(mJvm);
+ env->DeleteGlobalRef(mFinishListener);
+ mFinishListener = NULL;
+ }
+
+ JavaVM* mJvm;
+ jobject mFinishListener;
+ };
+ return new AnimationListenerBridge(env, finishListener);
+}
+
+static void addAnimator(JNIEnv*, jobject, jlong animatorSetPtr, jlong propertyHolderPtr,
+ jlong interpolatorPtr, jlong startDelay, jlong duration, jint repeatCount) {
+ PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
+ PropertyValuesHolder* holder = reinterpret_cast<PropertyValuesHolder*>(propertyHolderPtr);
+ Interpolator* interpolator = reinterpret_cast<Interpolator*>(interpolatorPtr);
+ set->addPropertyAnimator(holder, interpolator, startDelay, duration, repeatCount);
+}
+
+static jlong createAnimatorSet(JNIEnv*, jobject) {
+ PropertyValuesAnimatorSet* animatorSet = new PropertyValuesAnimatorSet();
+ return reinterpret_cast<jlong>(animatorSet);
+}
+
+static jlong createGroupPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jint propertyId,
+ jfloat startValue, jfloat endValue) {
+ VectorDrawable::Group* group = reinterpret_cast<VectorDrawable::Group*>(nativePtr);
+ GroupPropertyValuesHolder* newHolder = new GroupPropertyValuesHolder(group, propertyId,
+ startValue, endValue);
+ return reinterpret_cast<jlong>(newHolder);
+}
+
+static jlong createPathDataPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jlong startValuePtr,
+ jlong endValuePtr) {
+ VectorDrawable::Path* path = reinterpret_cast<VectorDrawable::Path*>(nativePtr);
+ PathData* startData = reinterpret_cast<PathData*>(startValuePtr);
+ PathData* endData = reinterpret_cast<PathData*>(endValuePtr);
+ PathDataPropertyValuesHolder* newHolder = new PathDataPropertyValuesHolder(path,
+ startData, endData);
+ return reinterpret_cast<jlong>(newHolder);
+}
+
+static jlong createPathColorPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jint propertyId,
+ int startValue, jint endValue) {
+ VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(nativePtr);
+ FullPathColorPropertyValuesHolder* newHolder = new FullPathColorPropertyValuesHolder(fullPath,
+ propertyId, startValue, endValue);
+ return reinterpret_cast<jlong>(newHolder);
+}
+
+static jlong createPathPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jint propertyId,
+ float startValue, jfloat endValue) {
+ VectorDrawable::FullPath* fullPath = reinterpret_cast<VectorDrawable::FullPath*>(nativePtr);
+ FullPathPropertyValuesHolder* newHolder = new FullPathPropertyValuesHolder(fullPath,
+ propertyId, startValue, endValue);
+ return reinterpret_cast<jlong>(newHolder);
+}
+
+static jlong createRootAlphaPropertyHolder(JNIEnv*, jobject, jlong nativePtr, jfloat startValue,
+ float endValue) {
+ VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(nativePtr);
+ RootAlphaPropertyValuesHolder* newHolder = new RootAlphaPropertyValuesHolder(tree,
+ startValue, endValue);
+ return reinterpret_cast<jlong>(newHolder);
+}
+static void setPropertyHolderData(JNIEnv* env, jobject, jlong propertyHolderPtr,
+ jfloatArray srcData, jint length) {
+
+ jfloat* propertyData = env->GetFloatArrayElements(srcData, nullptr);
+ PropertyValuesHolder* holder = reinterpret_cast<PropertyValuesHolder*>(propertyHolderPtr);
+ holder->setPropertyDataSource(propertyData, length);
+ env->ReleaseFloatArrayElements(srcData, propertyData, JNI_ABORT);
+}
+static void start(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) {
+ PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
+ // TODO: keep a ref count in finish listener
+ AnimationListener* listener = createAnimationListener(env, finishListener);
+ set->start(listener);
+}
+
+static void reverse(JNIEnv* env, jobject, jlong animatorSetPtr, jobject finishListener) {
+ // TODO: implement reverse
+}
+
+static void end(JNIEnv*, jobject, jlong animatorSetPtr) {
+ PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
+ set->end();
+}
+
+static void reset(JNIEnv*, jobject, jlong animatorSetPtr) {
+ PropertyValuesAnimatorSet* set = reinterpret_cast<PropertyValuesAnimatorSet*>(animatorSetPtr);
+ set->reset();
+}
+
+static const JNINativeMethod gMethods[] = {
+ {"nCreateAnimatorSet", "()J", (void*)createAnimatorSet},
+ {"nAddAnimator", "(JJJJJI)V", (void*)addAnimator},
+ {"nCreateGroupPropertyHolder", "!(JIFF)J", (void*)createGroupPropertyHolder},
+ {"nCreatePathDataPropertyHolder", "!(JJJ)J", (void*)createPathDataPropertyHolder},
+ {"nCreatePathColorPropertyHolder", "!(JIII)J", (void*)createPathColorPropertyHolder},
+ {"nCreatePathPropertyHolder", "!(JIFF)J", (void*)createPathPropertyHolder},
+ {"nCreateRootAlphaPropertyHolder", "!(JFF)J", (void*)createRootAlphaPropertyHolder},
+ {"nSetPropertyHolderData", "(J[FI)V", (void*)setPropertyHolderData},
+ {"nStart", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)start},
+ {"nReverse", "(JLandroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V", (void*)reverse},
+ {"nEnd", "!(J)V", (void*)end},
+ {"nReset", "!(J)V", (void*)reset},
+};
+
+const char* const kClassPathName = "android/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator";
+int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env) {
+ gVectorDrawableAnimatorClassInfo.clazz = FindClassOrDie(env, kClassPathName);
+ gVectorDrawableAnimatorClassInfo.clazz = MakeGlobalRefOrDie(env,
+ gVectorDrawableAnimatorClassInfo.clazz);
+
+ gVectorDrawableAnimatorClassInfo.callOnFinished = GetStaticMethodIDOrDie(
+ env, gVectorDrawableAnimatorClassInfo.clazz, "callOnFinished",
+ "(Landroid/graphics/drawable/AnimatedVectorDrawable$VectorDrawableAnimator;)V");
+ return RegisterMethodsOrDie(env, "android/graphics/drawable/AnimatedVectorDrawable",
+ gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/core/jni/android_graphics_drawable_VectorDrawable.cpp b/core/jni/android_graphics_drawable_VectorDrawable.cpp
index 563ec8bd9834..e88287644555 100644
--- a/core/jni/android_graphics_drawable_VectorDrawable.cpp
+++ b/core/jni/android_graphics_drawable_VectorDrawable.cpp
@@ -32,11 +32,6 @@ static jlong createTree(JNIEnv*, jobject, jlong groupPtr) {
return reinterpret_cast<jlong>(tree);
}
-static void deleteTree(JNIEnv*, jobject, jlong treePtr) {
- VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(treePtr);
- delete tree;
-}
-
static void setTreeViewportSize(JNIEnv*, jobject, jlong treePtr,
jfloat viewportWidth, jfloat viewportHeight) {
VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(treePtr);
@@ -333,7 +328,6 @@ static void setTrimPathOffset(JNIEnv*, jobject, jlong fullPathPtr, jfloat trimPa
static const JNINativeMethod gMethods[] = {
{"nCreateRenderer", "!(J)J", (void*)createTree},
- {"nDestroyRenderer", "!(J)V", (void*)deleteTree},
{"nSetRendererViewportSize", "!(JFF)V", (void*)setTreeViewportSize},
{"nSetRootAlpha", "!(JF)Z", (void*)setRootAlpha},
{"nGetRootAlpha", "!(J)F", (void*)getRootAlpha},
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
index 048b3c71c2c3..793d1325ab6f 100644
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ b/core/jni/android_hardware_SoundTrigger.cpp
@@ -68,6 +68,10 @@ static struct {
jfieldID data;
} gSoundModelFields;
+static const char* const kGenericSoundModelClassPathName =
+ "android/hardware/soundtrigger/SoundTrigger$GenericSoundModel";
+static jclass gGenericSoundModelClass;
+
static const char* const kKeyphraseClassPathName =
"android/hardware/soundtrigger/SoundTrigger$Keyphrase";
static jclass gKeyphraseClass;
@@ -105,6 +109,11 @@ static const char* const kKeyphraseRecognitionEventClassPathName =
static jclass gKeyphraseRecognitionEventClass;
static jmethodID gKeyphraseRecognitionEventCstor;
+static const char* const kGenericRecognitionEventClassPathName =
+ "android/hardware/soundtrigger/SoundTrigger$GenericRecognitionEvent";
+static jclass gGenericRecognitionEventClass;
+static jmethodID gGenericRecognitionEventCstor;
+
static const char* const kKeyphraseRecognitionExtraClassPathName =
"android/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra";
static jclass gKeyphraseRecognitionExtraClass;
@@ -266,6 +275,12 @@ void JNISoundTriggerCallback::onRecognitionEvent(struct sound_trigger_recognitio
event->capture_preamble_ms, event->trigger_in_data,
jAudioFormat, jData, jExtras);
env->DeleteLocalRef(jExtras);
+ } else if (event->type == SOUND_MODEL_TYPE_GENERIC) {
+ jEvent = env->NewObject(gGenericRecognitionEventClass, gGenericRecognitionEventCstor,
+ event->status, event->model, event->capture_available,
+ event->capture_session, event->capture_delay_ms,
+ event->capture_preamble_ms, event->trigger_in_data,
+ jAudioFormat, jData);
} else {
jEvent = env->NewObject(gRecognitionEventClass, gRecognitionEventCstor,
event->status, event->model, event->capture_available,
@@ -524,6 +539,9 @@ android_hardware_SoundTrigger_loadSoundModel(JNIEnv *env, jobject thiz,
if (env->IsInstanceOf(jSoundModel, gKeyphraseSoundModelClass)) {
offset = sizeof(struct sound_trigger_phrase_sound_model);
type = SOUND_MODEL_TYPE_KEYPHRASE;
+ } else if (env->IsInstanceOf(jSoundModel, gGenericSoundModelClass)) {
+ offset = sizeof(struct sound_trigger_generic_sound_model);
+ type = SOUND_MODEL_TYPE_GENERIC;
} else {
offset = sizeof(struct sound_trigger_sound_model);
type = SOUND_MODEL_TYPE_UNKNOWN;
@@ -631,6 +649,8 @@ android_hardware_SoundTrigger_loadSoundModel(JNIEnv *env, jobject thiz,
env->DeleteLocalRef(jPhrase);
}
env->DeleteLocalRef(jPhrases);
+ } else if (type == SOUND_MODEL_TYPE_GENERIC) {
+ /* No initialization needed */
}
status = module->loadSoundModel(memory, &handle);
ALOGV("loadSoundModel status %d handle %d", status, handle);
@@ -831,6 +851,9 @@ int register_android_hardware_SoundTrigger(JNIEnv *env)
"Ljava/util/UUID;");
gSoundModelFields.data = GetFieldIDOrDie(env, soundModelClass, "data", "[B");
+ jclass genericSoundModelClass = FindClassOrDie(env, kGenericSoundModelClassPathName);
+ gGenericSoundModelClass = MakeGlobalRefOrDie(env, genericSoundModelClass);
+
jclass keyphraseClass = FindClassOrDie(env, kKeyphraseClassPathName);
gKeyphraseClass = MakeGlobalRefOrDie(env, keyphraseClass);
gKeyphraseFields.id = GetFieldIDOrDie(env, keyphraseClass, "id", "I");
@@ -857,6 +880,11 @@ int register_android_hardware_SoundTrigger(JNIEnv *env)
gKeyphraseRecognitionEventCstor = GetMethodIDOrDie(env, keyphraseRecognitionEventClass, "<init>",
"(IIZIIIZLandroid/media/AudioFormat;[B[Landroid/hardware/soundtrigger/SoundTrigger$KeyphraseRecognitionExtra;)V");
+ jclass genericRecognitionEventClass = FindClassOrDie(env,
+ kGenericRecognitionEventClassPathName);
+ gGenericRecognitionEventClass = MakeGlobalRefOrDie(env, genericRecognitionEventClass);
+ gGenericRecognitionEventCstor = GetMethodIDOrDie(env, genericRecognitionEventClass, "<init>",
+ "(IIZIIIZLandroid/media/AudioFormat;[B)V");
jclass keyRecognitionConfigClass = FindClassOrDie(env, kRecognitionConfigClassPathName);
gRecognitionConfigClass = MakeGlobalRefOrDie(env, keyRecognitionConfigClass);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a518cc464885..7729d48c2452 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -231,6 +231,7 @@
<!-- @deprecated. Only {@link android.net.ConnectivityManager.CONNECTIVITY_ACTION} is sent. -->
<protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE" />
<protected-broadcast android:name="android.net.conn.DATA_ACTIVITY_CHANGE" />
+ <protected-broadcast android:name="android.net.conn.RESTRICT_BACKGROUND_CHANGED" />
<protected-broadcast android:name="android.net.conn.BACKGROUND_DATA_SETTING_CHANGED" />
<protected-broadcast android:name="android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED" />
@@ -1209,8 +1210,7 @@
<eat-comment />
<!-- Allows access to the list of accounts in the Accounts Service.
- <p>Protection level: dangerous
- @deprecated Not operative for apps apps with targetSdkVersion >= 24.
+ <p>Protection level: normal
-->
<permission android:name="android.permission.GET_ACCOUNTS"
android:permissionGroup="android.permission-group.CONTACTS"
diff --git a/core/res/res/drawable-hdpi/ic_user_secure.png b/core/res/res/drawable-hdpi/ic_user_secure.png
new file mode 100644
index 000000000000..60dcf2adc786
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_user_secure.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_user_secure.png b/core/res/res/drawable-mdpi/ic_user_secure.png
new file mode 100644
index 000000000000..0dea77a7b8b8
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_user_secure.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_user_secure.png b/core/res/res/drawable-xhdpi/ic_user_secure.png
new file mode 100644
index 000000000000..a6ef51af2ea8
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/ic_user_secure.png
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_user_secure.png b/core/res/res/drawable-xxhdpi/ic_user_secure.png
new file mode 100644
index 000000000000..e6154e59518d
--- /dev/null
+++ b/core/res/res/drawable-xxhdpi/ic_user_secure.png
Binary files differ
diff --git a/core/res/res/drawable-xxxhdpi/ic_user_secure.png b/core/res/res/drawable-xxxhdpi/ic_user_secure.png
new file mode 100644
index 000000000000..9a3959b292a3
--- /dev/null
+++ b/core/res/res/drawable-xxxhdpi/ic_user_secure.png
Binary files differ
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 163db30d6c28..b45f8bb92e18 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -22,8 +22,6 @@
android:layout_width="wrap_content"
android:layout_height="48dp"
android:clipChildren="false"
- android:layout_gravity="start|top"
- android:gravity="center_vertical"
android:paddingTop="5dp"
android:paddingBottom="16dp"
android:paddingStart="@dimen/notification_content_margin_start"
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index a6a456415689..c083a5eb9ae9 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -402,4 +402,15 @@
<item>@color/Red_700</item>
</array>
+ <!-- Used in ResolverTargetActionsDialogFragment -->
+ <string-array name="resolver_target_actions_pin">
+ <item>@string/pin_target</item>
+ <item>@string/app_info</item>
+ </string-array>
+
+ <string-array name="resolver_target_actions_unpin">
+ <item>@string/unpin_target</item>
+ <item>@string/app_info</item>
+ </string-array>
+
</resources>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c744bbdd7495..15a4ad853535 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -429,6 +429,12 @@
sets. -->
<attr name="multiArch" format ="boolean" />
+ <!-- Specify abiOverride for multiArch application.
+ @hide
+ @SystemApi
+ -->
+ <attr name="abiOverride" />
+
<!-- Specify whether a component is allowed to have multiple instances
of itself running in different processes. Use with the activity
and provider tags.
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index bbbb963f4dff..329e2e55b0f8 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2696,6 +2696,7 @@
<public type="attr" name="endX" />
<public type="attr" name="endY" />
<public type="attr" name="offset" />
+ <public type="attr" name="abiOverride" />
<public type="style" name="Theme.Material.Light.DialogWhenLarge.DarkActionBar" />
<public type="style" name="Widget.Material.SeekBar.Discrete" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 523826bf79ec..e5a6226fd937 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2869,6 +2869,14 @@
<string name="sim_added_message">Restart your device to access the cellular network.</string>
<!-- See SIM_ADDED_DIALOG. This is the button of that dialog. -->
<string name="sim_restart_button">Restart</string>
+ <!-- See Carrier_App_Dialog. This is the message of that dialog. -->
+ <string name="carrier_app_dialog_message">To get your new SIM working properly, you\'ll need to install and open an app from your carrier.</string>
+ <!-- See Carrier_App_Dialog. This is the button of that dialog. -->
+ <string name="carrier_app_dialog_button">GET THE APP</string>
+ <string name="carrier_app_dialog_not_now">NOT NOW</string>
+ <!-- See carrier_app_notification. This is the headline. -->
+ <string name="carrier_app_notification_title">New SIM inserted</string>
+ <string name="carrier_app_notification_text">Tap to set it up</string>
<!-- Date/Time picker dialogs strings -->
@@ -4206,4 +4214,15 @@
<string name="usb_mtp_launch_notification_title">Connected to <xliff:g id="product_name">%1$s</xliff:g></string>
<!-- Description of notification shown after a MTP device is connected to Android. -->
<string name="usb_mtp_launch_notification_description">Tap to view files</string>
+
+ <!-- Resolver target actions strings -->
+
+ <!-- Pin (as in to a bulletin board with a pushpin) a resolver
+ target to the front of the list. -->
+ <string name="pin_target">Pin</string>
+ <!-- Unpin a resolver target such that it sorts normally. -->
+ <string name="unpin_target">Unpin</string>
+ <!-- View application info for a target. -->
+ <string name="app_info">App info</string>
+
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1f0e21ecd7fe..f75f023b8c31 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2522,7 +2522,18 @@
<java-symbol type="string" name="user_encrypted_title" />
<java-symbol type="string" name="user_encrypted_message" />
<java-symbol type="string" name="user_encrypted_detail" />
+ <java-symbol type="drawable" name="ic_user_secure" />
<java-symbol type="string" name="usb_mtp_launch_notification_title" />
<java-symbol type="string" name="usb_mtp_launch_notification_description" />
+
+ <!-- Resolver target actions -->
+ <java-symbol type="array" name="resolver_target_actions_pin" />
+ <java-symbol type="array" name="resolver_target_actions_unpin" />
+
+ <java-symbol type="string" name="carrier_app_dialog_message" />
+ <java-symbol type="string" name="carrier_app_dialog_button" />
+ <java-symbol type="string" name="carrier_app_dialog_not_now" />
+ <java-symbol type="string" name="carrier_app_notification_title" />
+ <java-symbol type="string" name="carrier_app_notification_text" />
</resources>
diff --git a/docs/html/preview/support.jd b/docs/html/preview/support.jd
index 8c392aac2545..cfd94671604f 100644
--- a/docs/html/preview/support.jd
+++ b/docs/html/preview/support.jd
@@ -160,21 +160,20 @@ page.image=images/cards/card-support_16-9_2x.png
still perform BTLE and WiFi scans, but only when they are in the foreground. While in the background, those apps will get no results from BTLE and WiFi scans.</li>
</ul>
</li>
- <li>Accessing accounts
+ <li>Permission changes
<ul>
- <li>Updated the behavior of {@link android.accounts.AccountManager} account
- discovery methods.
+ <li>Updated the user interface for permissions and enhanced some of the permissions
+ behaviors.</li>
+ <li>The {@link android.Manifest.permission#GET_ACCOUNTS} permission is now a member of the
+ {@link android.Manifest.permission_group#CONTACTS} permission group and it has a
+ {@code android:protectionLevel} of {@code dangerous}. This change means that when
+ targeting Android 6.0 (API level 23), you must check for and request this permission if
+ your app requires it.
</li>
- <li>The GET_ACCOUNTS permission has been deprecated.
- </li>
- <li>Apps targeting API level 24 should start the intent returned by
- newChooseAccountIntent(...) and await the result to acquire a reference
- to the user's selected account. AccountManager methods like getAccounts and
- related methods will only return those accounts managed by
- authenticators that match the signatures of the calling app.
- </li>
- <li>Apps targeting API level 23 or earlier will continue to behave as
- before.
+
+ <li>The {@code android.permission.READ_PROFILE} and {@code android.permission.WRITE_PROFILE}
+ permissions have been removed from the {@link android.Manifest.permission_group#CONTACTS}
+ permission group.
</li>
</ul>
</li>
diff --git a/docs/html/training/id-auth/identify.jd b/docs/html/training/id-auth/identify.jd
index 4c399f9abf39..db9ab3a671e5 100644
--- a/docs/html/training/id-auth/identify.jd
+++ b/docs/html/training/id-auth/identify.jd
@@ -15,7 +15,8 @@ next.link=authenticate.html
<ol>
<li><a href="#ForYou">Determine if AccountManager is for You</a></li>
<li><a href="#TaskTwo">Decide What Type of Account to Use</a></li>
- <li><a href="#QueryAccounts">Query the user for an Account</a></li>
+ <li><a href="#GetPermission">Request GET_ACCOUNT permission</a></li>
+ <li><a href="#TaskFive">Query AccountManager for a List of Accounts</a></li>
<li><a href="#IdentifyUser">Use the Account Object to Personalize Your App</a></li>
<li><a href="#IdIsEnough">Decide Whether an Account Name is Enough</a></li>
</ol>
@@ -70,46 +71,48 @@ UI.</p>
<h2 id="TaskTwo">Decide What Type of Account to Use</h2>
<p>Android devices can store multiple accounts from many different providers.
-When you query {@link android.accounts.AccountManager} for account names, you
-can choose to filter by account type. The account type is a string that
-uniquely identifies the entity that issued the account. For instance, Google
-accounts have type "com.google," while Twitter uses
-"com.twitter.android.auth.login."</p>
+When you query {@link android.accounts.AccountManager} for account names, you can choose to filter
+by
+account type. The account type is a string that uniquely identifies the entity
+that issued the account. For instance, Google accounts have type "com.google,"
+while Twitter uses "com.twitter.android.auth.login."</p>
-<h2 id="QueryAccounts">Query the user for an Account</h2>
-<p>Once an account type has been determined, you can prompt the user with an
-account chooser as follows:
+<h2 id="GetPermission">Request GET_ACCOUNT permission</h2>
+
+<p>In order to get a list of accounts on the device, your app needs the {@link
+android.Manifest.permission#GET_ACCOUNTS}
+permission. Add a <a href="{@docRoot}guide/topics/manifest/uses-permission-element.html">{@code
+<uses-permission>}</a> tag in your manifest file to request
+this permission:</p>
<pre>
-AccountManager am = AccountManager.get(this); // "this" reference the current Context
-Intent chooserIntent = am.newChooseAccountIntent(
- null, // currently select account
- null, // list of accounts that are allowed to be shown
- new String[] { "com.google" }, // Only allow the user to select Google accounts
- false,
- null, // description text
- null, // add account auth token type
- null, // required features for added accounts
- null); // options for adding an account
-this.startActivityForResult(chooserIntent, MY_REQUEST_CODE);
+&lt;manifest ... >
+ &lt;uses-permission android:name="android.permission.GET_ACCOUNTS" /&gt;
+ ...
+&lt;/manifest>
</pre>
-<p>Once the chooser intent is started, the user will be presented with a list of
-appropriately typed accounts. From this list they will select one which will be
-returned to your app upon onActivityResult as follows:
+
+<h2 id="TaskFive">Query AccountManager for a List of Accounts</h2>
+
+<p>Once you decide what account type you're interested in, you need to query for accounts of that
+type. Get an instance of {@link android.accounts.AccountManager} by calling {@link
+android.accounts.AccountManager#get(android.content.Context) AccountManager.get()}. Then use that
+instance to call {@link android.accounts.AccountManager#getAccountsByType(java.lang.String)
+getAccountsByType()}.</p>
<pre>
-protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == MY_REQUEST_CODE && resultCode == RESULT_OK) {
- String name = data.getStringExtra(AccountManage.KEY_ACCOUNT_NAME);
- String type = data.getStringExtra(AccountManage.KEY_ACCOUNT_TYPE);
- Account selectedAccount = new Account(name, type);
- doSomethingWithSelectedAccount(selectedAccount);
- }
-}
+AccountManager am = AccountManager.get(this); // "this" references the current Context
+
+Account[] accounts = am.getAccountsByType("com.google");
</pre>
+<p>This returns an array of {@link android.accounts.Account} objects. If there's more than one
+{@link android.accounts.Account} in
+the array, you should present a dialog asking the user to select one.</p>
+
+
<h2 id="IdentifyUser">Use the Account Object to Personalize Your App</h2>
<p>The {@link android.accounts.Account} object contains an account name, which for Google accounts
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 1857345968fd..0a3e27e94c06 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -19,6 +19,10 @@ import android.animation.AnimatorInflater;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.Animator.AnimatorListener;
+import android.animation.PropertyValuesHolder;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.animation.ObjectAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.ColorStateList;
@@ -34,14 +38,23 @@ import android.graphics.Rect;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.LongArray;
+import android.util.PathParser;
+import android.util.TimeUtils;
+import android.view.Choreographer;
+import android.view.DisplayListCanvas;
+import android.view.RenderNode;
+import android.view.RenderNodeAnimatorSetHelper;
import android.view.View;
import com.android.internal.R;
+import com.android.internal.util.VirtualRefBasePtr;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
@@ -138,7 +151,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
private static final boolean DBG_ANIMATION_VECTOR_DRAWABLE = false;
/** Local, mutable animator set. */
- private final AnimatorSet mAnimatorSet = new AnimatorSet();
+ private final VectorDrawableAnimator mAnimatorSet = new VectorDrawableAnimator();
/**
* The resources against which this drawable was created. Used to attempt
@@ -200,6 +213,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
@Override
public void draw(Canvas canvas) {
+ if (canvas.isHardwareAccelerated()) {
+ mAnimatorSet.recordLastSeenTarget((DisplayListCanvas) canvas);
+ }
mAnimatedVectorState.mVectorDrawable.draw(canvas);
if (isStarted()) {
invalidateSelf();
@@ -582,9 +598,8 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
* Resets the AnimatedVectorDrawable to the start state as specified in the animators.
*/
public void reset() {
- // TODO: Use reverse or seek to implement reset, when AnimatorSet supports them.
- start();
- mAnimatorSet.cancel();
+ mAnimatorSet.reset();
+ invalidateSelf();
}
@Override
@@ -603,8 +618,12 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
@NonNull
private void ensureAnimatorSet() {
if (!mHasAnimatorSet) {
- mAnimatedVectorState.prepareLocalAnimators(mAnimatorSet, mRes);
+ // TODO: Skip the AnimatorSet creation and init the VectorDrawableAnimator directly
+ // with a list of LocalAnimators.
+ AnimatorSet set = new AnimatorSet();
+ mAnimatedVectorState.prepareLocalAnimators(set, mRes);
mHasAnimatorSet = true;
+ mAnimatorSet.initWithAnimatorSet(set);
mRes = null;
}
}
@@ -694,13 +713,13 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
}
};
}
- mAnimatorSet.addListener(mAnimatorListener);
+ mAnimatorSet.setListener(mAnimatorListener);
}
// A helper function to clean up the animator listener in the mAnimatorSet.
private void removeAnimatorSetListener() {
if (mAnimatorListener != null) {
- mAnimatorSet.removeListener(mAnimatorListener);
+ mAnimatorSet.removeListener();
mAnimatorListener = null;
}
}
@@ -730,4 +749,406 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 {
mAnimationCallbacks.clear();
}
-} \ No newline at end of file
+ /**
+ * @hide
+ */
+ public static class VectorDrawableAnimator {
+ private AnimatorListener mListener = null;
+ private final LongArray mStartDelays = new LongArray();
+ private PropertyValuesHolder.PropertyValues mTmpValues =
+ new PropertyValuesHolder.PropertyValues();
+ private long mSetPtr = 0;
+ private boolean mContainsSequentialAnimators = false;
+ private boolean mStarted = false;
+ private boolean mInitialized = false;
+ private boolean mAnimationPending = false;
+ private boolean mIsReversible = false;
+ // TODO: Consider using NativeAllocationRegistery to track native allocation
+ private final VirtualRefBasePtr mSetRefBasePtr;
+ private WeakReference<RenderNode> mTarget = null;
+ private WeakReference<RenderNode> mLastSeenTarget = null;
+
+
+ VectorDrawableAnimator() {
+ mSetPtr = nCreateAnimatorSet();
+ // Increment ref count on native AnimatorSet, so it doesn't get released before Java
+ // side is done using it.
+ mSetRefBasePtr = new VirtualRefBasePtr(mSetPtr);
+ }
+
+ private void initWithAnimatorSet(AnimatorSet set) {
+ if (mInitialized) {
+ // Already initialized
+ throw new UnsupportedOperationException("VectorDrawableAnimator cannot be " +
+ "re-initialized");
+ }
+ parseAnimatorSet(set, 0);
+ mInitialized = true;
+
+ // Check reversible.
+ if (mContainsSequentialAnimators) {
+ mIsReversible = false;
+ } else {
+ // Check if there's any start delay set on child
+ for (int i = 0; i < mStartDelays.size(); i++) {
+ if (mStartDelays.get(i) > 0) {
+ mIsReversible = false;
+ return;
+ }
+ }
+ }
+ mIsReversible = true;
+ }
+
+ private void parseAnimatorSet(AnimatorSet set, long startTime) {
+ ArrayList<Animator> animators = set.getChildAnimations();
+
+ boolean playTogether = set.shouldPlayTogether();
+ // Convert AnimatorSet to VectorDrawableAnimator
+ for (int i = 0; i < animators.size(); i++) {
+ Animator animator = animators.get(i);
+ // Here we only support ObjectAnimator
+ if (animator instanceof AnimatorSet) {
+ parseAnimatorSet((AnimatorSet) animator, startTime);
+ } else if (animator instanceof ObjectAnimator) {
+ createRTAnimator((ObjectAnimator) animator, startTime);
+ } // ignore ValueAnimators and others because they don't directly modify VD
+ // therefore will be useless to AVD.
+
+ if (!playTogether) {
+ // Assume not play together means play sequentially
+ startTime += animator.getTotalDuration();
+ mContainsSequentialAnimators = true;
+ }
+ }
+ }
+
+ // TODO: This method reads animation data from already parsed Animators. We need to move
+ // this step further up the chain in the parser to avoid the detour.
+ private void createRTAnimator(ObjectAnimator animator, long startTime) {
+ PropertyValuesHolder[] values = animator.getValues();
+ Object target = animator.getTarget();
+ if (target instanceof VectorDrawable.VGroup) {
+ createRTAnimatorForGroup(values, animator, (VectorDrawable.VGroup) target,
+ startTime);
+ } else if (target instanceof VectorDrawable.VPath) {
+ for (int i = 0; i < values.length; i++) {
+ values[i].getPropertyValues(mTmpValues);
+ if (mTmpValues.endValue instanceof PathParser.PathData &&
+ mTmpValues.propertyName.equals("pathData")) {
+ createRTAnimatorForPath(animator, (VectorDrawable.VPath) target,
+ startTime);
+ } else if (target instanceof VectorDrawable.VFullPath) {
+ createRTAnimatorForFullPath(animator, (VectorDrawable.VFullPath) target,
+ startTime);
+ } else {
+ throw new IllegalArgumentException("ClipPath only supports PathData " +
+ "property");
+ }
+
+ }
+ } else if (target instanceof VectorDrawable.VectorDrawableState) {
+ createRTAnimatorForRootGroup(values, animator,
+ (VectorDrawable.VectorDrawableState) target, startTime);
+ } else {
+ // Should never get here
+ throw new UnsupportedOperationException("Target should be either VGroup, VPath, " +
+ "or ConstantState, " + target.getClass() + " is not supported");
+ }
+ }
+
+ private void createRTAnimatorForGroup(PropertyValuesHolder[] values,
+ ObjectAnimator animator, VectorDrawable.VGroup target,
+ long startTime) {
+
+ long nativePtr = target.getNativePtr();
+ int propertyId;
+ for (int i = 0; i < values.length; i++) {
+ // TODO: We need to support the rare case in AVD where no start value is provided
+ values[i].getPropertyValues(mTmpValues);
+ propertyId = VectorDrawable.VGroup.getPropertyIndex(mTmpValues.propertyName);
+ if (mTmpValues.type != Float.class && mTmpValues.type != float.class) {
+ if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+ Log.e(LOGTAG, "Unsupported type: " +
+ mTmpValues.type + ". Only float value is supported for Groups.");
+ }
+ continue;
+ }
+ if (propertyId < 0) {
+ if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+ Log.e(LOGTAG, "Unsupported property: " +
+ mTmpValues.propertyName + " for Vector Drawable Group");
+ }
+ continue;
+ }
+ long propertyPtr = nCreateGroupPropertyHolder(nativePtr, propertyId,
+ (Float) mTmpValues.startValue, (Float) mTmpValues.endValue);
+ if (mTmpValues.dataSource != null) {
+ float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator
+ .getDuration());
+ nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
+ }
+ createNativeChildAnimator(propertyPtr, startTime, animator);
+ }
+ }
+ private void createRTAnimatorForPath( ObjectAnimator animator, VectorDrawable.VPath target,
+ long startTime) {
+
+ long nativePtr = target.getNativePtr();
+ long startPathDataPtr = ((PathParser.PathData) mTmpValues.startValue)
+ .getNativePtr();
+ long endPathDataPtr = ((PathParser.PathData) mTmpValues.endValue)
+ .getNativePtr();
+ long propertyPtr = nCreatePathDataPropertyHolder(nativePtr, startPathDataPtr,
+ endPathDataPtr);
+ createNativeChildAnimator(propertyPtr, startTime, animator);
+ }
+
+ private void createRTAnimatorForFullPath(ObjectAnimator animator,
+ VectorDrawable.VFullPath target, long startTime) {
+
+ int propertyId = target.getPropertyIndex(mTmpValues.propertyName);
+ long propertyPtr;
+ long nativePtr = target.getNativePtr();
+ if (mTmpValues.type == Float.class || mTmpValues.type == float.class) {
+ if (propertyId < 0) {
+ throw new IllegalArgumentException("Property: " + mTmpValues
+ .propertyName + " is not supported for FullPath");
+ }
+ propertyPtr = nCreatePathPropertyHolder(nativePtr, propertyId,
+ (Float) mTmpValues.startValue, (Float) mTmpValues.endValue);
+
+ } else if (mTmpValues.type == Integer.class || mTmpValues.type == int.class) {
+ propertyPtr = nCreatePathColorPropertyHolder(nativePtr, propertyId,
+ (Integer) mTmpValues.startValue, (Integer) mTmpValues.endValue);
+ } else {
+ throw new UnsupportedOperationException("Unsupported type: " +
+ mTmpValues.type + ". Only float, int or PathData value is " +
+ "supported for Paths.");
+ }
+ if (mTmpValues.dataSource != null) {
+ float[] dataPoints = createDataPoints(mTmpValues.dataSource, animator
+ .getDuration());
+ nSetPropertyHolderData(propertyPtr, dataPoints, dataPoints.length);
+ }
+ createNativeChildAnimator(propertyPtr, startTime, animator);
+ }
+
+ private void createRTAnimatorForRootGroup(PropertyValuesHolder[] values,
+ ObjectAnimator animator, VectorDrawable.VectorDrawableState target,
+ long startTime) {
+ long nativePtr = target.getNativeRenderer();
+ if (!animator.getPropertyName().equals("alpha")) {
+ throw new UnsupportedOperationException("Only alpha is supported for root " +
+ "group");
+ }
+ Float startValue = null;
+ Float endValue = null;
+ for (int i = 0; i < values.length; i++) {
+ values[i].getPropertyValues(mTmpValues);
+ if (mTmpValues.propertyName.equals("alpha")) {
+ startValue = (Float) mTmpValues.startValue;
+ endValue = (Float) mTmpValues.endValue;
+ break;
+ }
+ }
+ if (startValue == null && endValue == null) {
+ throw new UnsupportedOperationException("No alpha values are specified");
+ }
+ long propertyPtr = nCreateRootAlphaPropertyHolder(nativePtr, startValue, endValue);
+ createNativeChildAnimator(propertyPtr, startTime, animator);
+ }
+
+ // These are the data points that define the value of the animating properties.
+ // e.g. translateX and translateY can animate along a Path, at any fraction in [0, 1]
+ // a point on the path corresponds to the values of translateX and translateY.
+ // TODO: (Optimization) We should pass the path down in native and chop it into segments
+ // in native.
+ private static float[] createDataPoints(
+ PropertyValuesHolder.PropertyValues.DataSource dataSource, long duration) {
+ long frameIntervalNanos = Choreographer.getInstance().getFrameIntervalNanos();
+ int animIntervalMs = (int) (frameIntervalNanos / TimeUtils.NANOS_PER_MS);
+ int numAnimFrames = (int) Math.ceil(((double) duration) / animIntervalMs);
+ float values[] = new float[numAnimFrames];
+ float lastFrame = numAnimFrames - 1;
+ for (int i = 0; i < numAnimFrames; i++) {
+ float fraction = i / lastFrame;
+ values[i] = (Float) dataSource.getValueAtFraction(fraction);
+ }
+ return values;
+ }
+
+ private void createNativeChildAnimator(long propertyPtr, long extraDelay,
+ ObjectAnimator animator) {
+ long duration = animator.getDuration();
+ int repeatCount = animator.getRepeatCount();
+ long startDelay = extraDelay + animator.getStartDelay();
+ TimeInterpolator interpolator = animator.getInterpolator();
+ long nativeInterpolator =
+ RenderNodeAnimatorSetHelper.createNativeInterpolator(interpolator, duration);
+
+ startDelay *= ValueAnimator.getDurationScale();
+ duration *= ValueAnimator.getDurationScale();
+
+ mStartDelays.add(startDelay);
+ nAddAnimator(mSetPtr, propertyPtr, nativeInterpolator, startDelay, duration,
+ repeatCount);
+ }
+
+ /**
+ * Holds a weak reference to the target that was last seen (through the DisplayListCanvas
+ * in the last draw call), so that when animator set needs to start, we can add the animator
+ * to the last seen RenderNode target and start right away.
+ */
+ protected void recordLastSeenTarget(DisplayListCanvas canvas) {
+ if (mAnimationPending) {
+ mLastSeenTarget = new WeakReference<RenderNode>(
+ RenderNodeAnimatorSetHelper.getTarget(canvas));
+ if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+ Log.d(LOGTAG, "Target is set in the next frame");
+ }
+ mAnimationPending = false;
+ start();
+ } else {
+ mLastSeenTarget = new WeakReference<RenderNode>(
+ RenderNodeAnimatorSetHelper.getTarget(canvas));
+ }
+
+ }
+
+ private boolean setTarget(RenderNode node) {
+ if (mTarget != null && mTarget.get() != null) {
+ // TODO: Maybe we want to support target change.
+ throw new IllegalStateException("Target already set!");
+ }
+
+ node.addAnimator(this);
+ mTarget = new WeakReference<RenderNode>(node);
+ return true;
+ }
+
+ private boolean useLastSeenTarget() {
+ if (mLastSeenTarget != null && mLastSeenTarget.get() != null) {
+ setTarget(mLastSeenTarget.get());
+ return true;
+ }
+ return false;
+ }
+
+ public void start() {
+ if (!mInitialized) {
+ return;
+ }
+
+ if (mStarted) {
+ return;
+ }
+
+ if (!useLastSeenTarget()) {
+ mAnimationPending = true;
+ return;
+ }
+
+ if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+ Log.d(LOGTAG, "Target is set. Starting VDAnimatorSet from java");
+ }
+
+ nStart(mSetPtr, this);
+ if (mListener != null) {
+ mListener.onAnimationStart(null);
+ }
+ mStarted = true;
+ }
+
+ public void end() {
+ if (mInitialized && mStarted) {
+ nEnd(mSetPtr);
+ onAnimationEnd();
+ }
+ }
+
+ void reset() {
+ if (!mInitialized) {
+ return;
+ }
+ // TODO: Need to implement reset.
+ Log.w(LOGTAG, "Reset is yet to be implemented");
+ nReset(mSetPtr);
+ }
+
+ // Current (imperfect) Java AnimatorSet cannot be reversed when the set contains sequential
+ // animators or when the animator set has a start delay
+ void reverse() {
+ if (!mIsReversible) {
+ return;
+ }
+ // TODO: Need to support reverse (non-public API)
+ Log.w(LOGTAG, "Reverse is yet to be implemented");
+ nReverse(mSetPtr, this);
+ }
+
+ public long getAnimatorNativePtr() {
+ return mSetPtr;
+ }
+
+ boolean canReverse() {
+ return mIsReversible;
+ }
+
+ boolean isStarted() {
+ return mStarted;
+ }
+
+ boolean isRunning() {
+ if (!mInitialized) {
+ return false;
+ }
+ return mStarted;
+ }
+
+ void setListener(AnimatorListener listener) {
+ mListener = listener;
+ }
+
+ void removeListener() {
+ mListener = null;
+ }
+
+ private void onAnimationEnd() {
+ mStarted = false;
+ if (mListener != null) {
+ mListener.onAnimationEnd(null);
+ }
+ mTarget = null;
+ }
+
+ // onFinished: should be called from native
+ private static void callOnFinished(VectorDrawableAnimator set) {
+ if (DBG_ANIMATION_VECTOR_DRAWABLE) {
+ Log.d(LOGTAG, "on finished called from native");
+ }
+ set.onAnimationEnd();
+ }
+ }
+
+ private static native long nCreateAnimatorSet();
+ private static native void nAddAnimator(long setPtr, long propertyValuesHolder,
+ long nativeInterpolator, long startDelay, long duration, int repeatCount);
+
+ private static native long nCreateGroupPropertyHolder(long nativePtr, int propertyId,
+ float startValue, float endValue);
+
+ private static native long nCreatePathDataPropertyHolder(long nativePtr, long startValuePtr,
+ long endValuePtr);
+ private static native long nCreatePathColorPropertyHolder(long nativePtr, int propertyId,
+ int startValue, int endValue);
+ private static native long nCreatePathPropertyHolder(long nativePtr, int propertyId,
+ float startValue, float endValue);
+ private static native long nCreateRootAlphaPropertyHolder(long nativePtr, float startValue,
+ float endValue);
+ private static native void nSetPropertyHolderData(long nativePtr, float[] data, int length);
+ private static native void nStart(long animatorSetPtr, VectorDrawableAnimator set);
+ private static native void nReverse(long animatorSetPtr, VectorDrawableAnimator set);
+ private static native void nEnd(long animatorSetPtr);
+ private static native void nReset(long animatorSetPtr);
+}
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 1fc1b83f7a6c..f4bbc8c43d08 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -39,6 +39,7 @@ import android.util.PathParser;
import android.util.Xml;
import com.android.internal.R;
+import com.android.internal.util.VirtualRefBasePtr;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -47,6 +48,7 @@ import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.Stack;
/**
@@ -522,13 +524,13 @@ public class VectorDrawable extends Drawable {
public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
- if (mVectorState.mRootGroup != null || mVectorState.mNativeRendererPtr != 0) {
+ if (mVectorState.mRootGroup != null || mVectorState.mNativeRendererRefBase != null) {
// This VD has been used to display other VD resource content, clean up.
mVectorState.mRootGroup = new VGroup();
- if (mVectorState.mNativeRendererPtr != 0) {
- nDestroyRenderer(mVectorState.mNativeRendererPtr);
+ if (mVectorState.mNativeRendererRefBase != null) {
+ mVectorState.mNativeRendererRefBase.release();
}
- mVectorState.mNativeRendererPtr = nCreateRenderer(mVectorState.mRootGroup.mNativePtr);
+ mVectorState.createNativeRenderer(mVectorState.mRootGroup.mNativePtr);
}
final VectorDrawableState state = mVectorState;
state.setDensity(Drawable.resolveDensity(r, 0));
@@ -707,7 +709,7 @@ public class VectorDrawable extends Drawable {
return mVectorState.mAutoMirrored;
}
- private static class VectorDrawableState extends ConstantState {
+ static class VectorDrawableState extends ConstantState {
// Variables below need to be copied (deep copy if applicable) for mutation.
int[] mThemeAttrs;
int mChangingConfigurations;
@@ -722,7 +724,7 @@ public class VectorDrawable extends Drawable {
Insets mOpticalInsets = Insets.NONE;
String mRootName = null;
VGroup mRootGroup;
- long mNativeRendererPtr;
+ VirtualRefBasePtr mNativeRendererRefBase = null;
int mDensity = DisplayMetrics.DENSITY_DEFAULT;
final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>();
@@ -743,7 +745,7 @@ public class VectorDrawable extends Drawable {
mTintMode = copy.mTintMode;
mAutoMirrored = copy.mAutoMirrored;
mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap);
- mNativeRendererPtr = nCreateRenderer(mRootGroup.mNativePtr);
+ createNativeRenderer(mRootGroup.mNativePtr);
mBaseWidth = copy.mBaseWidth;
mBaseHeight = copy.mBaseHeight;
@@ -758,18 +760,15 @@ public class VectorDrawable extends Drawable {
}
}
- @Override
- public void finalize() throws Throwable {
- if (mNativeRendererPtr != 0) {
- nDestroyRenderer(mNativeRendererPtr);
- mNativeRendererPtr = 0;
- }
- super.finalize();
+ private void createNativeRenderer(long rootGroupPtr) {
+ mNativeRendererRefBase = new VirtualRefBasePtr(nCreateRenderer(rootGroupPtr));
}
-
long getNativeRenderer() {
- return mNativeRendererPtr;
+ if (mNativeRendererRefBase == null) {
+ return 0;
+ }
+ return mNativeRendererRefBase.get();
}
public boolean canReuseCache() {
@@ -808,7 +807,7 @@ public class VectorDrawable extends Drawable {
public VectorDrawableState() {
mRootGroup = new VGroup();
- mNativeRendererPtr = nCreateRenderer(mRootGroup.mNativePtr);
+ createNativeRenderer(mRootGroup.mNativePtr);
}
@Override
@@ -872,16 +871,16 @@ public class VectorDrawable extends Drawable {
* has changed.
*/
public boolean setAlpha(float alpha) {
- return nSetRootAlpha(mNativeRendererPtr, alpha);
+ return nSetRootAlpha(mNativeRendererRefBase.get(), alpha);
}
@SuppressWarnings("unused")
public float getAlpha() {
- return nGetRootAlpha(mNativeRendererPtr);
+ return nGetRootAlpha(mNativeRendererRefBase.get());
}
}
- private static class VGroup implements VObject {
+ static class VGroup implements VObject {
private static final int ROTATE_INDEX = 0;
private static final int PIVOT_X_INDEX = 1;
private static final int PIVOT_Y_INDEX = 2;
@@ -891,6 +890,28 @@ public class VectorDrawable extends Drawable {
private static final int TRANSLATE_Y_INDEX = 6;
private static final int TRANSFORM_PROPERTY_COUNT = 7;
+ private static final HashMap<String, Integer> sPropertyMap =
+ new HashMap<String, Integer>() {
+ {
+ put("translateX", TRANSLATE_X_INDEX);
+ put("translateY", TRANSLATE_Y_INDEX);
+ put("scaleX", SCALE_X_INDEX);
+ put("scaleY", SCALE_Y_INDEX);
+ put("pivotX", PIVOT_X_INDEX);
+ put("pivotY", PIVOT_Y_INDEX);
+ put("rotation", ROTATE_INDEX);
+ }
+ };
+
+ static int getPropertyIndex(String propertyName) {
+ if (sPropertyMap.containsKey(propertyName)) {
+ return sPropertyMap.get(propertyName);
+ } else {
+ // property not found
+ return -1;
+ }
+ }
+
// Temp array to store transform values obtained from native.
private float[] mTransform;
/////////////////////////////////////////////////////
@@ -1149,7 +1170,7 @@ public class VectorDrawable extends Drawable {
/**
* Common Path information for clip path and normal path.
*/
- private static abstract class VPath implements VObject {
+ static abstract class VPath implements VObject {
protected PathParser.PathData mPathData = null;
String mPathName;
@@ -1260,7 +1281,7 @@ public class VectorDrawable extends Drawable {
/**
* Normal path, which contains all the fill / paint information.
*/
- private static class VFullPath extends VPath {
+ static class VFullPath extends VPath {
private static final int STROKE_WIDTH_INDEX = 0;
private static final int STROKE_COLOR_INDEX = 1;
private static final int STROKE_ALPHA_INDEX = 2;
@@ -1274,6 +1295,20 @@ public class VectorDrawable extends Drawable {
private static final int STROKE_MITER_LIMIT_INDEX = 10;
private static final int TOTAL_PROPERTY_COUNT = 11;
+ private final static HashMap<String, Integer> sPropertyMap
+ = new HashMap<String, Integer> () {
+ {
+ put("strokeWidth", STROKE_WIDTH_INDEX);
+ put("strokeColor", STROKE_COLOR_INDEX);
+ put("strokeAlpha", STROKE_ALPHA_INDEX);
+ put("fillColor", FILL_COLOR_INDEX);
+ put("fillAlpha", FILL_ALPHA_INDEX);
+ put("trimPathStart", TRIM_PATH_START_INDEX);
+ put("trimPathEnd", TRIM_PATH_END_INDEX);
+ put("trimPathOffset", TRIM_PATH_OFFSET_INDEX);
+ }
+ };
+
// Temp array to store property data obtained from native getter.
private byte[] mPropertyData;
/////////////////////////////////////////////////////
@@ -1297,6 +1332,14 @@ public class VectorDrawable extends Drawable {
mFillColors = copy.mFillColors;
}
+ int getPropertyIndex(String propertyName) {
+ if (!sPropertyMap.containsKey(propertyName)) {
+ return -1;
+ } else {
+ return sPropertyMap.get(propertyName);
+ }
+ }
+
@Override
public boolean onStateChange(int[] stateSet) {
boolean changed = false;
@@ -1595,7 +1638,6 @@ public class VectorDrawable extends Drawable {
}
private static native long nCreateRenderer(long rootGroupPtr);
- private static native void nDestroyRenderer(long rendererPtr);
private static native void nSetRendererViewportSize(long rendererPtr, float viewportWidth,
float viewportHeight);
private static native boolean nSetRootAlpha(long rendererPtr, float alpha);
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 587a366730c2..6988b0294539 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -78,6 +78,8 @@ hwui_src_files := \
Program.cpp \
ProgramCache.cpp \
Properties.cpp \
+ PropertyValuesHolder.cpp \
+ PropertyValuesAnimatorSet.cpp \
RenderBufferCache.cpp \
RenderNode.cpp \
RenderProperties.cpp \
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index 5ca2a2fa37ab..7bd2b24bf56b 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -90,6 +90,9 @@ void BaseRenderNodeAnimator::pushStaging(AnimationContext& context) {
doSetStartValue(getValue(mTarget));
}
if (mStagingPlayState > mPlayState) {
+ if (mStagingPlayState == PlayState::Restarted) {
+ mStagingPlayState = PlayState::Running;
+ }
mPlayState = mStagingPlayState;
// Oh boy, we're starting! Man the battle stations!
if (mPlayState == PlayState::Running) {
@@ -131,6 +134,11 @@ bool BaseRenderNodeAnimator::animate(AnimationContext& context) {
return true;
}
+ // This should be set before setValue() so animators can query this time when setValue
+ // is called.
+ nsecs_t currentFrameTime = context.frameTimeMs();
+ onPlayTimeChanged(currentFrameTime - mStartTime);
+
// If BaseRenderNodeAnimator is handling the delay (not typical), then
// because the staging properties reflect the final value, we always need
// to call setValue even if the animation isn't yet running or is still
@@ -141,8 +149,9 @@ bool BaseRenderNodeAnimator::animate(AnimationContext& context) {
}
float fraction = 1.0f;
+
if (mPlayState == PlayState::Running && mDuration > 0) {
- fraction = (float)(context.frameTimeMs() - mStartTime) / mDuration;
+ fraction = (float)(currentFrameTime - mStartTime) / mDuration;
}
if (fraction >= 1.0f) {
fraction = 1.0f;
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index aea95bfc1c0e..2c9c9c3fe0f9 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -59,7 +59,13 @@ public:
mMayRunAsync = mayRunAsync;
}
bool mayRunAsync() { return mMayRunAsync; }
- ANDROID_API void start() { mStagingPlayState = PlayState::Running; onStagingPlayStateChanged(); }
+ ANDROID_API void start() {
+ if (mStagingPlayState == PlayState::NotStarted) {
+ mStagingPlayState = PlayState::Running;
+ } else {
+ mStagingPlayState = PlayState::Restarted;
+ }
+ onStagingPlayStateChanged(); }
ANDROID_API void end() { mStagingPlayState = PlayState::Finished; onStagingPlayStateChanged(); }
void attach(RenderNode* target);
@@ -77,10 +83,27 @@ public:
void forceEndNow(AnimationContext& context);
protected:
+ // PlayState is used by mStagingPlayState and mPlayState to track the state initiated from UI
+ // thread and Render Thread animation state, respectively.
+ // From the UI thread, mStagingPlayState transition looks like
+ // NotStarted -> Running -> Finished
+ // ^ |
+ // | |
+ // Restarted <------
+ // Note: For mStagingState, the Finished state (optional) is only set when the animation is
+ // terminated by user.
+ //
+ // On Render Thread, mPlayState transition:
+ // NotStart -> Running -> Finished
+ // ^ |
+ // | |
+ // -------------
+
enum class PlayState {
NotStarted,
Running,
Finished,
+ Restarted,
};
BaseRenderNodeAnimator(float finalValue);
@@ -93,6 +116,7 @@ protected:
void callOnFinishedListener(AnimationContext& context);
virtual void onStagingPlayStateChanged() {}
+ virtual void onPlayTimeChanged(nsecs_t playTime) {}
RenderNode* mTarget;
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index ca1f8f94f382..2184755d218a 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -728,23 +728,34 @@ void BakedOpDispatcher::onTextureLayerOp(BakedOpRenderer& renderer, const Textur
}
void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) {
+ // Note that we don't use op->paint in this function - it's never set on a LayerOp
OffscreenBuffer* buffer = *op.layerHandle;
- // Note that we don't use op->paint here - it's never set on a LayerOp
- float layerAlpha = op.alpha * state.alpha;
- Glop glop;
- GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
- .setRoundRectClipState(state.roundRectClipState)
- .setMeshTexturedIndexedVbo(buffer->vbo, buffer->elementCount)
- .setFillLayer(buffer->texture, op.colorFilter, layerAlpha, op.mode, Blend::ModeOrderSwap::NoSwap)
- .setTransform(state.computedState.transform, TransformFlags::None)
- .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top,
- Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight()))
- .build();
- renderer.renderGlop(state, glop);
+ if (CC_UNLIKELY(!buffer)) {
+ // Layer was not allocated, which can occur if there were no draw ops inside. We draw the
+ // equivalent by drawing a rect with the same layer properties (alpha/xfer/filter).
+ SkPaint paint;
+ paint.setAlpha(op.alpha * 255);
+ paint.setXfermodeMode(op.mode);
+ paint.setColorFilter(op.colorFilter);
+ RectOp rectOp(op.unmappedBounds, op.localMatrix, op.localClip, &paint);
+ BakedOpDispatcher::onRectOp(renderer, rectOp, state);
+ } else {
+ float layerAlpha = op.alpha * state.alpha;
+ Glop glop;
+ GlopBuilder(renderer.renderState(), renderer.caches(), &glop)
+ .setRoundRectClipState(state.roundRectClipState)
+ .setMeshTexturedIndexedVbo(buffer->vbo, buffer->elementCount)
+ .setFillLayer(buffer->texture, op.colorFilter, layerAlpha, op.mode, Blend::ModeOrderSwap::NoSwap)
+ .setTransform(state.computedState.transform, TransformFlags::None)
+ .setModelViewOffsetRectSnap(op.unmappedBounds.left, op.unmappedBounds.top,
+ Rect(op.unmappedBounds.getWidth(), op.unmappedBounds.getHeight()))
+ .build();
+ renderer.renderGlop(state, glop);
- if (op.destroy) {
- renderer.renderState().layerPool().putOrDelete(buffer);
+ if (op.destroy) {
+ renderer.renderState().layerPool().putOrDelete(buffer);
+ }
}
}
diff --git a/libs/hwui/Canvas.h b/libs/hwui/Canvas.h
index 9dfe454c7bc1..d7e2f09d4ba1 100644
--- a/libs/hwui/Canvas.h
+++ b/libs/hwui/Canvas.h
@@ -43,6 +43,13 @@ typedef uint32_t Flags;
} // namespace SaveFlags
+namespace uirenderer {
+namespace VectorDrawable {
+class Tree;
+};
+};
+typedef uirenderer::VectorDrawable::Tree VectorDrawableRoot;
+
class ANDROID_API Canvas {
public:
virtual ~Canvas() {};
@@ -185,6 +192,11 @@ public:
*/
virtual bool drawTextAbsolutePos() const = 0;
+ /**
+ * Draws a VectorDrawable onto the canvas.
+ */
+ virtual void drawVectorDrawable(VectorDrawableRoot* tree);
+
protected:
void drawTextDecorations(float x, float y, float length, const SkPaint& paint);
};
diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp
index 384e64d7700f..5366127092d4 100644
--- a/libs/hwui/DisplayListCanvas.cpp
+++ b/libs/hwui/DisplayListCanvas.cpp
@@ -16,11 +16,12 @@
#include "DisplayListCanvas.h"
-#include "ResourceCache.h"
#include "DeferredDisplayList.h"
#include "DeferredLayerUpdater.h"
#include "DisplayListOp.h"
+#include "ResourceCache.h"
#include "RenderNode.h"
+#include "VectorDrawable.h"
#include "utils/PaintUtils.h"
#include <SkCamera.h>
@@ -412,6 +413,16 @@ void DisplayListCanvas::drawPoints(const float* points, int count, const SkPaint
addDrawOp(new (alloc()) DrawPointsOp(points, count, refPaint(&paint)));
}
+void DisplayListCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
+ mDisplayList->ref(tree);
+ const SkBitmap& bitmap = tree->getBitmapUpdateIfDirty();
+ SkPaint* paint = tree->getPaint();
+ const SkRect bounds = tree->getBounds();
+ addDrawOp(new (alloc()) DrawBitmapRectOp(refBitmap(bitmap),
+ 0, 0, bitmap.width(), bitmap.height(),
+ bounds.left(), bounds.top(), bounds.right(), bounds.bottom(), refPaint(paint)));
+}
+
void DisplayListCanvas::drawTextOnPath(const uint16_t* glyphs, int count,
const SkPath& path, float hOffset, float vOffset, const SkPaint& paint) {
if (!glyphs || count <= 0) return;
diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h
index f1cfa08b3ac2..d41fff425e81 100644
--- a/libs/hwui/DisplayListCanvas.h
+++ b/libs/hwui/DisplayListCanvas.h
@@ -209,6 +209,8 @@ public:
float dstLeft, float dstTop, float dstRight, float dstBottom,
const SkPaint* paint) override;
+ virtual void drawVectorDrawable(VectorDrawableRoot* tree) override;
+
// Text
virtual void drawText(const uint16_t* glyphs, const float* positions, int count,
const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop,
@@ -217,7 +219,6 @@ public:
float hOffset, float vOffset, const SkPaint& paint) override;
virtual bool drawTextAbsolutePos() const override { return false; }
-
private:
CanvasState mState;
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index 9528587ed860..57e5b9d8735a 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -19,6 +19,7 @@
#include "Canvas.h"
#include "LayerUpdateQueue.h"
#include "RenderNode.h"
+#include "VectorDrawable.h"
#include "renderstate/OffscreenBufferPool.h"
#include "utils/FatVector.h"
#include "utils/PaintUtils.h"
@@ -543,6 +544,18 @@ void FrameBuilder::deferBitmapRectOp(const BitmapRectOp& op) {
currentLayer().deferUnmergeableOp(mAllocator, bakedState, OpBatchType::Bitmap);
}
+void FrameBuilder::deferVectorDrawableOp(const VectorDrawableOp& op) {
+ const SkBitmap& bitmap = op.vectorDrawable->getBitmapUpdateIfDirty();
+ SkPaint* paint = op.vectorDrawable->getPaint();
+ const BitmapRectOp* resolvedOp = new (mAllocator) BitmapRectOp(op.unmappedBounds,
+ op.localMatrix,
+ op.localClip,
+ paint,
+ &bitmap,
+ Rect(bitmap.width(), bitmap.height()));
+ deferBitmapRectOp(*resolvedOp);
+}
+
void FrameBuilder::deferCirclePropsOp(const CirclePropsOp& op) {
// allocate a temporary oval op (with mAllocator, so it persists until render), so the
// renderer doesn't have to handle the RoundRectPropsOp type, and so state baking is simple.
diff --git a/libs/hwui/FrameBuilder.h b/libs/hwui/FrameBuilder.h
index 5df2e46078b8..f44306a931bf 100644
--- a/libs/hwui/FrameBuilder.h
+++ b/libs/hwui/FrameBuilder.h
@@ -124,7 +124,8 @@ public:
layer.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers);
GL_CHECKPOINT(MODERATE);
renderer.endLayer();
- } else if (!layer.empty()) { // save layer - skip entire layer if empty
+ } else if (!layer.empty()) {
+ // save layer - skip entire layer if empty (in which case, LayerOp has null layer).
layer.offscreenBuffer = renderer.startTemporaryLayer(layer.width, layer.height);
GL_CHECKPOINT(MODERATE);
layer.replayBakedOpsImpl((void*)&renderer, unmergedReceivers, mergedReceivers);
diff --git a/libs/hwui/PropertyValuesAnimatorSet.cpp b/libs/hwui/PropertyValuesAnimatorSet.cpp
new file mode 100644
index 000000000000..eca1afcc54dc
--- /dev/null
+++ b/libs/hwui/PropertyValuesAnimatorSet.cpp
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2016 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 "PropertyValuesAnimatorSet.h"
+#include "RenderNode.h"
+
+namespace android {
+namespace uirenderer {
+
+void PropertyValuesAnimatorSet::addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder,
+ Interpolator* interpolator, nsecs_t startDelay,
+ nsecs_t duration, int repeatCount) {
+
+ PropertyAnimator* animator = new PropertyAnimator(propertyValuesHolder,
+ interpolator, startDelay, duration, repeatCount);
+ mAnimators.emplace_back(animator);
+ setListener(new PropertyAnimatorSetListener(this));
+}
+
+PropertyValuesAnimatorSet::PropertyValuesAnimatorSet()
+ : BaseRenderNodeAnimator(1.0f) {
+ setStartValue(0);
+ mLastFraction = 0.0f;
+ setInterpolator(new LinearInterpolator());
+}
+
+void PropertyValuesAnimatorSet::onFinished(BaseRenderNodeAnimator* animator) {
+ if (mOneShotListener.get()) {
+ mOneShotListener->onAnimationFinished(animator);
+ mOneShotListener = nullptr;
+ }
+}
+
+float PropertyValuesAnimatorSet::getValue(RenderNode* target) const {
+ return mLastFraction;
+}
+
+void PropertyValuesAnimatorSet::setValue(RenderNode* target, float value) {
+ mLastFraction = value;
+}
+
+void PropertyValuesAnimatorSet::onPlayTimeChanged(nsecs_t playTime) {
+ for (size_t i = 0; i < mAnimators.size(); i++) {
+ mAnimators[i]->setCurrentPlayTime(playTime);
+ }
+}
+
+void PropertyValuesAnimatorSet::reset() {
+ // TODO: implement reset through adding a play state because we need to support reset() even
+ // during an animation run.
+}
+
+void PropertyValuesAnimatorSet::start(AnimationListener* listener) {
+ init();
+ mOneShotListener = listener;
+ BaseRenderNodeAnimator::start();
+}
+
+void PropertyValuesAnimatorSet::reverse(AnimationListener* listener) {
+// TODO: implement reverse
+}
+
+void PropertyValuesAnimatorSet::init() {
+ if (mInitialized) {
+ return;
+ }
+ nsecs_t maxDuration = 0;
+ for (size_t i = 0; i < mAnimators.size(); i++) {
+ if (maxDuration < mAnimators[i]->getTotalDuration()) {
+ maxDuration = mAnimators[i]->getTotalDuration();
+ }
+ }
+ mDuration = maxDuration;
+ mInitialized = true;
+}
+
+uint32_t PropertyValuesAnimatorSet::dirtyMask() {
+ return RenderNode::DISPLAY_LIST;
+}
+
+PropertyAnimator::PropertyAnimator(PropertyValuesHolder* holder, Interpolator* interpolator,
+ nsecs_t startDelay, nsecs_t duration, int repeatCount)
+ : mPropertyValuesHolder(holder), mInterpolator(interpolator), mStartDelay(startDelay),
+ mDuration(duration) {
+ if (repeatCount < 0) {
+ mRepeatCount = UINT32_MAX;
+ } else {
+ mRepeatCount = repeatCount;
+ }
+ mTotalDuration = ((nsecs_t) mRepeatCount + 1) * mDuration + mStartDelay;
+}
+
+void PropertyAnimator::setCurrentPlayTime(nsecs_t playTime) {
+ if (playTime >= mStartDelay && playTime < mTotalDuration) {
+ nsecs_t currentIterationPlayTime = (playTime - mStartDelay) % mDuration;
+ mLatestFraction = currentIterationPlayTime / (float) mDuration;
+ } else if (mLatestFraction < 1.0f && playTime >= mTotalDuration) {
+ mLatestFraction = 1.0f;
+ } else {
+ return;
+ }
+
+ setFraction(mLatestFraction);
+}
+
+void PropertyAnimator::setFraction(float fraction) {
+ float interpolatedFraction = mInterpolator->interpolate(mLatestFraction);
+ mPropertyValuesHolder->setFraction(interpolatedFraction);
+}
+
+void PropertyAnimatorSetListener::onAnimationFinished(BaseRenderNodeAnimator* animator) {
+ mSet->onFinished(animator);
+}
+
+}
+}
diff --git a/libs/hwui/PropertyValuesAnimatorSet.h b/libs/hwui/PropertyValuesAnimatorSet.h
new file mode 100644
index 000000000000..4c7ce528bb20
--- /dev/null
+++ b/libs/hwui/PropertyValuesAnimatorSet.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include "Animator.h"
+#include "PropertyValuesHolder.h"
+#include "Interpolator.h"
+
+namespace android {
+namespace uirenderer {
+
+class PropertyAnimator {
+public:
+ PropertyAnimator(PropertyValuesHolder* holder, Interpolator* interpolator, nsecs_t startDelay,
+ nsecs_t duration, int repeatCount);
+ void setCurrentPlayTime(nsecs_t playTime);
+ nsecs_t getTotalDuration() {
+ return mTotalDuration;
+ }
+ void setFraction(float fraction);
+
+private:
+ std::unique_ptr<PropertyValuesHolder> mPropertyValuesHolder;
+ std::unique_ptr<Interpolator> mInterpolator;
+ nsecs_t mStartDelay;
+ nsecs_t mDuration;
+ uint32_t mRepeatCount;
+ nsecs_t mTotalDuration;
+ float mLatestFraction = 0.0f;
+};
+
+class ANDROID_API PropertyValuesAnimatorSet : public BaseRenderNodeAnimator {
+public:
+ friend class PropertyAnimatorSetListener;
+ PropertyValuesAnimatorSet();
+
+ void start(AnimationListener* listener);
+ void reverse(AnimationListener* listener);
+ void reset();
+
+ void addPropertyAnimator(PropertyValuesHolder* propertyValuesHolder,
+ Interpolator* interpolators, int64_t startDelays,
+ nsecs_t durations, int repeatCount);
+ virtual uint32_t dirtyMask();
+
+protected:
+ virtual float getValue(RenderNode* target) const override;
+ virtual void setValue(RenderNode* target, float value) override;
+ virtual void onPlayTimeChanged(nsecs_t playTime) override;
+
+private:
+ void init();
+ void onFinished(BaseRenderNodeAnimator* animator);
+ // Listener set from outside
+ sp<AnimationListener> mOneShotListener;
+ std::vector< std::unique_ptr<PropertyAnimator> > mAnimators;
+ float mLastFraction = 0.0f;
+ bool mInitialized = false;
+};
+
+class PropertyAnimatorSetListener : public AnimationListener {
+public:
+ PropertyAnimatorSetListener(PropertyValuesAnimatorSet* set) : mSet(set) {}
+ virtual void onAnimationFinished(BaseRenderNodeAnimator* animator) override;
+
+private:
+ PropertyValuesAnimatorSet* mSet;
+};
+
+} // namespace uirenderer
+} // namespace android
diff --git a/libs/hwui/PropertyValuesHolder.cpp b/libs/hwui/PropertyValuesHolder.cpp
new file mode 100644
index 000000000000..8f837f6048d6
--- /dev/null
+++ b/libs/hwui/PropertyValuesHolder.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 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 "PropertyValuesHolder.h"
+
+#include "utils/VectorDrawableUtils.h"
+
+#include <utils/Log.h>
+
+namespace android {
+namespace uirenderer {
+
+using namespace VectorDrawable;
+
+float PropertyValuesHolder::getValueFromData(float fraction) {
+ if (mDataSource.size() == 0) {
+ LOG_ALWAYS_FATAL("No data source is defined");
+ return 0;
+ }
+ if (fraction <= 0.0f) {
+ return mDataSource.front();
+ }
+ if (fraction >= 1.0f) {
+ return mDataSource.back();
+ }
+
+ fraction *= mDataSource.size() - 1;
+ int lowIndex = floor(fraction);
+ fraction -= lowIndex;
+
+ float value = mDataSource[lowIndex] * (1.0f - fraction)
+ + mDataSource[lowIndex + 1] * fraction;
+ return value;
+}
+
+void GroupPropertyValuesHolder::setFraction(float fraction) {
+ float animatedValue;
+ if (mDataSource.size() > 0) {
+ animatedValue = getValueFromData(fraction);
+ } else {
+ animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction;
+ }
+ mGroup->setPropertyValue(mPropertyId, animatedValue);
+}
+
+inline U8CPU lerp(U8CPU fromValue, U8CPU toValue, float fraction) {
+ return (U8CPU) (fromValue * (1 - fraction) + toValue * fraction);
+}
+
+// TODO: Add a test for this
+SkColor FullPathColorPropertyValuesHolder::interpolateColors(SkColor fromColor, SkColor toColor,
+ float fraction) {
+ U8CPU alpha = lerp(SkColorGetA(fromColor), SkColorGetA(toColor), fraction);
+ U8CPU red = lerp(SkColorGetR(fromColor), SkColorGetR(toColor), fraction);
+ U8CPU green = lerp(SkColorGetG(fromColor), SkColorGetG(toColor), fraction);
+ U8CPU blue = lerp(SkColorGetB(fromColor), SkColorGetB(toColor), fraction);
+ return SkColorSetARGB(alpha, red, green, blue);
+}
+
+void FullPathColorPropertyValuesHolder::setFraction(float fraction) {
+ SkColor animatedValue = interpolateColors(mStartValue, mEndValue, fraction);
+ mFullPath->setColorPropertyValue(mPropertyId, animatedValue);
+}
+
+void FullPathPropertyValuesHolder::setFraction(float fraction) {
+ float animatedValue;
+ if (mDataSource.size() > 0) {
+ animatedValue = getValueFromData(fraction);
+ } else {
+ animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction;
+ }
+ mFullPath->setPropertyValue(mPropertyId, animatedValue);
+}
+
+void PathDataPropertyValuesHolder::setFraction(float fraction) {
+ VectorDrawableUtils::interpolatePaths(&mPathData, mStartValue, mEndValue, fraction);
+ mPath->setPathData(mPathData);
+}
+
+void RootAlphaPropertyValuesHolder::setFraction(float fraction) {
+ float animatedValue = mStartValue * (1 - fraction) + mEndValue * fraction;
+ mTree->setRootAlpha(animatedValue);
+}
+
+} // namepace uirenderer
+} // namespace android
diff --git a/libs/hwui/PropertyValuesHolder.h b/libs/hwui/PropertyValuesHolder.h
new file mode 100644
index 000000000000..b905faef104c
--- /dev/null
+++ b/libs/hwui/PropertyValuesHolder.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include "VectorDrawable.h"
+
+#include <SkColor.h>
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * PropertyValues holder contains data needed to change a property of a Vector Drawable object.
+ * When a fraction in [0f, 1f] is provided, the holder will calculate an interpolated value based
+ * on its start and end value, and set the new value on the VectorDrawble's corresponding property.
+ */
+class ANDROID_API PropertyValuesHolder {
+public:
+ virtual void setFraction(float fraction) = 0;
+ void setPropertyDataSource(float* dataSource, int length) {
+ mDataSource.insert(mDataSource.begin(), dataSource, dataSource + length);
+ }
+ float getValueFromData(float fraction);
+ virtual ~PropertyValuesHolder() {}
+protected:
+ std::vector<float> mDataSource;
+};
+
+class ANDROID_API GroupPropertyValuesHolder : public PropertyValuesHolder {
+public:
+ GroupPropertyValuesHolder(VectorDrawable::Group* ptr, int propertyId, float startValue,
+ float endValue)
+ : mGroup(ptr)
+ , mPropertyId(propertyId)
+ , mStartValue(startValue)
+ , mEndValue(endValue){
+ }
+ void setFraction(float fraction) override;
+private:
+ VectorDrawable::Group* mGroup;
+ int mPropertyId;
+ float mStartValue;
+ float mEndValue;
+};
+
+class ANDROID_API FullPathColorPropertyValuesHolder : public PropertyValuesHolder {
+public:
+ FullPathColorPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId, int32_t startValue,
+ int32_t endValue)
+ : mFullPath(ptr)
+ , mPropertyId(propertyId)
+ , mStartValue(startValue)
+ , mEndValue(endValue) {};
+ void setFraction(float fraction) override;
+ static SkColor interpolateColors(SkColor fromColor, SkColor toColor, float fraction);
+private:
+ VectorDrawable::FullPath* mFullPath;
+ int mPropertyId;
+ int32_t mStartValue;
+ int32_t mEndValue;
+};
+
+class ANDROID_API FullPathPropertyValuesHolder : public PropertyValuesHolder {
+public:
+ FullPathPropertyValuesHolder(VectorDrawable::FullPath* ptr, int propertyId, float startValue,
+ float endValue)
+ : mFullPath(ptr)
+ , mPropertyId(propertyId)
+ , mStartValue(startValue)
+ , mEndValue(endValue) {};
+ void setFraction(float fraction) override;
+private:
+ VectorDrawable::FullPath* mFullPath;
+ int mPropertyId;
+ float mStartValue;
+ float mEndValue;
+};
+
+class ANDROID_API PathDataPropertyValuesHolder : public PropertyValuesHolder {
+public:
+ PathDataPropertyValuesHolder(VectorDrawable::Path* ptr, PathData* startValue,
+ PathData* endValue)
+ : mPath(ptr)
+ , mStartValue(*startValue)
+ , mEndValue(*endValue) {};
+ void setFraction(float fraction) override;
+private:
+ VectorDrawable::Path* mPath;
+ PathData mPathData;
+ PathData mStartValue;
+ PathData mEndValue;
+};
+
+class ANDROID_API RootAlphaPropertyValuesHolder : public PropertyValuesHolder {
+public:
+ RootAlphaPropertyValuesHolder(VectorDrawable::Tree* tree, float startValue, float endValue)
+ : mTree(tree)
+ , mStartValue(startValue)
+ , mEndValue(endValue) {}
+ void setFraction(float fraction) override;
+private:
+ VectorDrawable::Tree* mTree;
+ float mStartValue;
+ float mEndValue;
+};
+}
+}
diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h
index 593d690f2b43..bb26e2ec67a8 100644
--- a/libs/hwui/RecordedOp.h
+++ b/libs/hwui/RecordedOp.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_HWUI_RECORDED_OP_H
#define ANDROID_HWUI_RECORDED_OP_H
+#include "RecordedOp.h"
#include "font/FontUtil.h"
#include "Matrix.h"
#include "Rect.h"
@@ -39,6 +40,10 @@ class OffscreenBuffer;
class RenderNode;
struct Vertex;
+namespace VectorDrawable {
+class Tree;
+}
+
/**
* Authoritative op list, used for generating the op ID enum, ID based LUTS, and
* the functions to which they dispatch. Parameter macros are executed for each op,
@@ -75,6 +80,7 @@ struct Vertex;
PRE_RENDER_OP_FN(EndLayerOp) \
PRE_RENDER_OP_FN(BeginUnclippedLayerOp) \
PRE_RENDER_OP_FN(EndUnclippedLayerOp) \
+ PRE_RENDER_OP_FN(VectorDrawableOp) \
\
RENDER_ONLY_OP_FN(ShadowOp) \
RENDER_ONLY_OP_FN(LayerOp) \
@@ -325,6 +331,13 @@ struct RoundRectPropsOp : RecordedOp {
const float* ry;
};
+struct VectorDrawableOp : RecordedOp {
+ VectorDrawableOp(VectorDrawable::Tree* tree, BASE_PARAMS_PAINTLESS)
+ : SUPER_PAINTLESS(VectorDrawableOp)
+ , vectorDrawable(tree) {}
+ VectorDrawable::Tree* vectorDrawable;
+};
+
/**
* Real-time, dynamic-lit shadow.
*
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 328e291a60af..abbd9c38d0ad 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -19,6 +19,7 @@
#include "DeferredLayerUpdater.h"
#include "RecordedOp.h"
#include "RenderNode.h"
+#include "VectorDrawable.h"
namespace android {
namespace uirenderer {
@@ -395,7 +396,6 @@ void RecordingCanvas::drawCircle(
&x->value, &y->value, &radius->value));
}
-
void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
addOp(new (alloc()) OvalOp(
Rect(left, top, right, bottom),
@@ -422,6 +422,15 @@ void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
refPaint(&paint), refPath(&path)));
}
+void RecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
+ mDisplayList->ref(tree);
+ addOp(new (alloc()) VectorDrawableOp(
+ tree,
+ Rect(tree->getBounds()),
+ *(mState.currentSnapshot()->transform),
+ getRecordedClip()));
+}
+
// Bitmap-based
void RecordingCanvas::drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) {
save(SaveFlags::Matrix);
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 786f96e852ec..7c8ad8814d32 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -178,6 +178,8 @@ public:
const uint16_t* indices, int indexCount, const SkPaint& paint) override
{ /* RecordingCanvas does not support drawVertices(); ignore */ }
+ virtual void drawVectorDrawable(VectorDrawableRoot* tree) override;
+
// Bitmap-based
virtual void drawBitmap(const SkBitmap& bitmap, float left, float top, const SkPaint* paint) override;
virtual void drawBitmap(const SkBitmap& bitmap, const SkMatrix& matrix,
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 20e7c711a9ce..550995b2b9c6 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -27,6 +27,8 @@
#include <SkTLazy.h>
#include <SkTemplates.h>
+#include "VectorDrawable.h"
+
#include <memory>
namespace android {
@@ -136,6 +138,7 @@ public:
float hOffset, float vOffset, const SkPaint& paint) override;
virtual bool drawTextAbsolutePos() const override { return true; }
+ virtual void drawVectorDrawable(VectorDrawableRoot* vectorDrawable) override;
private:
struct SaveRec {
@@ -713,6 +716,14 @@ void SkiaCanvas::drawNinePatch(const SkBitmap& bitmap, const Res_png_9patch& chu
NinePatch::Draw(mCanvas, bounds, bitmap, chunk, paint, nullptr);
}
+void SkiaCanvas::drawVectorDrawable(VectorDrawableRoot* vectorDrawable) {
+ const SkBitmap& bitmap = vectorDrawable->getBitmapUpdateIfDirty();
+ SkRect bounds = vectorDrawable->getBounds();
+ drawBitmap(bitmap, 0, 0, bitmap.width(), bitmap.height(),
+ bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom,
+ vectorDrawable->getPaint());
+}
+
// ----------------------------------------------------------------------------
// Canvas draw operations: Text
// ----------------------------------------------------------------------------
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 1cf15ac01154..c72f87d7e481 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -138,18 +138,7 @@ void Path::setPath(const char* pathStr, size_t strLength) {
}
FullPath::FullPath(const FullPath& path) : Path(path) {
- mStrokeWidth = path.mStrokeWidth;
- mStrokeColor = path.mStrokeColor;
- mStrokeAlpha = path.mStrokeAlpha;
- mFillColor = path.mFillColor;
- mFillAlpha = path.mFillAlpha;
- mTrimPathStart = path.mTrimPathStart;
- mTrimPathEnd = path.mTrimPathEnd;
- mTrimPathOffset = path.mTrimPathOffset;
- mStrokeMiterLimit = path.mStrokeMiterLimit;
- mStrokeLineCap = path.mStrokeLineCap;
- mStrokeLineJoin = path.mStrokeLineJoin;
-
+ mProperties = path.mProperties;
SkRefCnt_SafeAssign(mStrokeGradient, path.mStrokeGradient);
SkRefCnt_SafeAssign(mFillGradient, path.mFillGradient);
}
@@ -159,7 +148,7 @@ const SkPath& FullPath::getUpdatedPath() {
return mTrimmedSkPath;
}
Path::getUpdatedPath();
- if (mTrimPathStart != 0.0f || mTrimPathEnd != 1.0f) {
+ if (mProperties.trimPathStart != 0.0f || mProperties.trimPathEnd != 1.0f) {
applyTrim();
return mTrimmedSkPath;
} else {
@@ -170,14 +159,14 @@ const SkPath& FullPath::getUpdatedPath() {
void FullPath::updateProperties(float strokeWidth, SkColor strokeColor, float strokeAlpha,
SkColor fillColor, float fillAlpha, float trimPathStart, float trimPathEnd,
float trimPathOffset, float strokeMiterLimit, int strokeLineCap, int strokeLineJoin) {
- mStrokeWidth = strokeWidth;
- mStrokeColor = strokeColor;
- mStrokeAlpha = strokeAlpha;
- mFillColor = fillColor;
- mFillAlpha = fillAlpha;
- mStrokeMiterLimit = strokeMiterLimit;
- mStrokeLineCap = SkPaint::Cap(strokeLineCap);
- mStrokeLineJoin = SkPaint::Join(strokeLineJoin);
+ mProperties.strokeWidth = strokeWidth;
+ mProperties.strokeColor = strokeColor;
+ mProperties.strokeAlpha = strokeAlpha;
+ mProperties.fillColor = fillColor;
+ mProperties.fillAlpha = fillAlpha;
+ mProperties.strokeMiterLimit = strokeMiterLimit;
+ mProperties.strokeLineCap = strokeLineCap;
+ mProperties.strokeLineJoin = strokeLineJoin;
// If any trim property changes, mark trim dirty and update the trim path
setTrimPathStart(trimPathStart);
@@ -195,12 +184,12 @@ void FullPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, float str
// Draw path's fill, if fill color or gradient is valid
bool needsFill = false;
if (mFillGradient != nullptr) {
- mPaint.setColor(applyAlpha(SK_ColorBLACK, mFillAlpha));
+ mPaint.setColor(applyAlpha(SK_ColorBLACK, mProperties.fillAlpha));
SkShader* newShader = mFillGradient->newWithLocalMatrix(matrix);
mPaint.setShader(newShader);
needsFill = true;
- } else if (mFillColor != SK_ColorTRANSPARENT) {
- mPaint.setColor(applyAlpha(mFillColor, mFillAlpha));
+ } else if (mProperties.fillColor != SK_ColorTRANSPARENT) {
+ mPaint.setColor(applyAlpha(mProperties.fillColor, mProperties.fillAlpha));
needsFill = true;
}
@@ -213,21 +202,21 @@ void FullPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, float str
// Draw path's stroke, if stroke color or gradient is valid
bool needsStroke = false;
if (mStrokeGradient != nullptr) {
- mPaint.setColor(applyAlpha(SK_ColorBLACK, mStrokeAlpha));
+ mPaint.setColor(applyAlpha(SK_ColorBLACK, mProperties.strokeAlpha));
SkShader* newShader = mStrokeGradient->newWithLocalMatrix(matrix);
mPaint.setShader(newShader);
needsStroke = true;
- } else if (mStrokeColor != SK_ColorTRANSPARENT) {
- mPaint.setColor(applyAlpha(mStrokeColor, mStrokeAlpha));
+ } else if (mProperties.strokeColor != SK_ColorTRANSPARENT) {
+ mPaint.setColor(applyAlpha(mProperties.strokeColor, mProperties.strokeAlpha));
needsStroke = true;
}
if (needsStroke) {
mPaint.setStyle(SkPaint::Style::kStroke_Style);
mPaint.setAntiAlias(true);
- mPaint.setStrokeJoin(mStrokeLineJoin);
- mPaint.setStrokeCap(mStrokeLineCap);
- mPaint.setStrokeMiter(mStrokeMiterLimit);
- mPaint.setStrokeWidth(mStrokeWidth * strokeScale);
+ mPaint.setStrokeJoin(SkPaint::Join(mProperties.strokeLineJoin));
+ mPaint.setStrokeCap(SkPaint::Cap(mProperties.strokeLineCap));
+ mPaint.setStrokeMiter(mProperties.strokeMiterLimit);
+ mPaint.setStrokeWidth(mProperties.strokeWidth * strokeScale);
outCanvas->drawPath(renderPath, mPaint);
}
}
@@ -236,14 +225,14 @@ void FullPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath, float str
* Applies trimming to the specified path.
*/
void FullPath::applyTrim() {
- if (mTrimPathStart == 0.0f && mTrimPathEnd == 1.0f) {
+ if (mProperties.trimPathStart == 0.0f && mProperties.trimPathEnd == 1.0f) {
// No trimming necessary.
return;
}
SkPathMeasure measure(mSkPath, false);
float len = SkScalarToFloat(measure.getLength());
- float start = len * fmod((mTrimPathStart + mTrimPathOffset), 1.0f);
- float end = len * fmod((mTrimPathEnd + mTrimPathOffset), 1.0f);
+ float start = len * fmod((mProperties.trimPathStart + mProperties.trimPathOffset), 1.0f);
+ float end = len * fmod((mProperties.trimPathEnd + mProperties.trimPathOffset), 1.0f);
mTrimmedSkPath.reset();
if (start > end) {
@@ -255,76 +244,69 @@ void FullPath::applyTrim() {
mTrimDirty = false;
}
-inline int putData(int8_t* outBytes, int startIndex, float value) {
- int size = sizeof(float);
- memcpy(&outBytes[startIndex], &value, size);
- return size;
-}
-
-inline int putData(int8_t* outBytes, int startIndex, int value) {
- int size = sizeof(int);
- memcpy(&outBytes[startIndex], &value, size);
- return size;
-}
-
-struct FullPathProperties {
- // TODO: Consider storing full path properties in this struct instead of the fields.
- float strokeWidth;
- SkColor strokeColor;
- float strokeAlpha;
- SkColor fillColor;
- float fillAlpha;
- float trimPathStart;
- float trimPathEnd;
- float trimPathOffset;
- int32_t strokeLineCap;
- int32_t strokeLineJoin;
- float strokeMiterLimit;
-};
-
-REQUIRE_COMPATIBLE_LAYOUT(FullPathProperties);
+REQUIRE_COMPATIBLE_LAYOUT(FullPath::Properties);
static_assert(sizeof(float) == sizeof(int32_t), "float is not the same size as int32_t");
static_assert(sizeof(SkColor) == sizeof(int32_t), "SkColor is not the same size as int32_t");
bool FullPath::getProperties(int8_t* outProperties, int length) {
- int propertyDataSize = sizeof(FullPathProperties);
+ int propertyDataSize = sizeof(Properties);
if (length != propertyDataSize) {
LOG_ALWAYS_FATAL("Properties needs exactly %d bytes, a byte array of size %d is provided",
propertyDataSize, length);
return false;
}
- // TODO: consider replacing the property fields with a FullPathProperties struct.
- FullPathProperties properties;
- properties.strokeWidth = mStrokeWidth;
- properties.strokeColor = mStrokeColor;
- properties.strokeAlpha = mStrokeAlpha;
- properties.fillColor = mFillColor;
- properties.fillAlpha = mFillAlpha;
- properties.trimPathStart = mTrimPathStart;
- properties.trimPathEnd = mTrimPathEnd;
- properties.trimPathOffset = mTrimPathOffset;
- properties.strokeLineCap = mStrokeLineCap;
- properties.strokeLineJoin = mStrokeLineJoin;
- properties.strokeMiterLimit = mStrokeMiterLimit;
-
- memcpy(outProperties, &properties, length);
+ Properties* out = reinterpret_cast<Properties*>(outProperties);
+ *out = mProperties;
return true;
}
+void FullPath::setColorPropertyValue(int propertyId, int32_t value) {
+ Property currentProperty = static_cast<Property>(propertyId);
+ if (currentProperty == Property::StrokeColor) {
+ mProperties.strokeColor = value;
+ } else if (currentProperty == Property::FillColor) {
+ mProperties.fillColor = value;
+ } else {
+ LOG_ALWAYS_FATAL("Error setting color property on FullPath: No valid property with id: %d",
+ propertyId);
+ }
+}
+
+void FullPath::setPropertyValue(int propertyId, float value) {
+ Property property = static_cast<Property>(propertyId);
+ switch (property) {
+ case Property::StrokeWidth:
+ setStrokeWidth(value);
+ break;
+ case Property::StrokeAlpha:
+ setStrokeAlpha(value);
+ break;
+ case Property::FillAlpha:
+ setFillAlpha(value);
+ break;
+ case Property::TrimPathStart:
+ setTrimPathStart(value);
+ break;
+ case Property::TrimPathEnd:
+ setTrimPathEnd(value);
+ break;
+ case Property::TrimPathOffset:
+ setTrimPathOffset(value);
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Invalid property id: %d for animation", propertyId);
+ break;
+ }
+}
+
void ClipPath::drawPath(SkCanvas* outCanvas, const SkPath& renderPath,
float strokeScale, const SkMatrix& matrix){
outCanvas->clipPath(renderPath, SkRegion::kIntersect_Op);
}
Group::Group(const Group& group) : Node(group) {
- mRotate = group.mRotate;
- mPivotX = group.mPivotX;
- mPivotY = group.mPivotY;
- mScaleX = group.mScaleX;
- mScaleY = group.mScaleY;
- mTranslateX = group.mTranslateX;
- mTranslateY = group.mTranslateY;
+ mProperties = group.mProperties;
}
void Group::draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix, float scaleX,
@@ -371,10 +353,11 @@ void Group::getLocalMatrix(SkMatrix* outMatrix) {
outMatrix->reset();
// TODO: use rotate(mRotate, mPivotX, mPivotY) and scale with pivot point, instead of
// translating to pivot for rotating and scaling, then translating back.
- outMatrix->postTranslate(-mPivotX, -mPivotY);
- outMatrix->postScale(mScaleX, mScaleY);
- outMatrix->postRotate(mRotate, 0, 0);
- outMatrix->postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
+ outMatrix->postTranslate(-mProperties.pivotX, -mProperties.pivotY);
+ outMatrix->postScale(mProperties.scaleX, mProperties.scaleY);
+ outMatrix->postRotate(mProperties.rotate, 0, 0);
+ outMatrix->postTranslate(mProperties.translateX + mProperties.pivotX,
+ mProperties.translateY + mProperties.pivotY);
}
void Group::addChild(Node* child) {
@@ -388,38 +371,68 @@ bool Group::getProperties(float* outProperties, int length) {
propertyCount, length);
return false;
}
- for (int i = 0; i < propertyCount; i++) {
- Property currentProperty = static_cast<Property>(i);
- switch (currentProperty) {
- case Property::Rotate_Property:
- outProperties[i] = mRotate;
- break;
- case Property::PivotX_Property:
- outProperties[i] = mPivotX;
- break;
- case Property::PivotY_Property:
- outProperties[i] = mPivotY;
- break;
- case Property::ScaleX_Property:
- outProperties[i] = mScaleX;
- break;
- case Property::ScaleY_Property:
- outProperties[i] = mScaleY;
- break;
- case Property::TranslateX_Property:
- outProperties[i] = mTranslateX;
- break;
- case Property::TranslateY_Property:
- outProperties[i] = mTranslateY;
- break;
- default:
- LOG_ALWAYS_FATAL("Invalid input index: %d", i);
- return false;
- }
- }
+ Properties* out = reinterpret_cast<Properties*>(outProperties);
+ *out = mProperties;
return true;
}
+// TODO: Consider animating the properties as float pointers
+float Group::getPropertyValue(int propertyId) const {
+ Property currentProperty = static_cast<Property>(propertyId);
+ switch (currentProperty) {
+ case Property::Rotate:
+ return mProperties.rotate;
+ case Property::PivotX:
+ return mProperties.pivotX;
+ case Property::PivotY:
+ return mProperties.pivotY;
+ case Property::ScaleX:
+ return mProperties.scaleX;
+ case Property::ScaleY:
+ return mProperties.scaleY;
+ case Property::TranslateX:
+ return mProperties.translateX;
+ case Property::TranslateY:
+ return mProperties.translateY;
+ default:
+ LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId);
+ return 0;
+ }
+}
+
+void Group::setPropertyValue(int propertyId, float value) {
+ Property currentProperty = static_cast<Property>(propertyId);
+ switch (currentProperty) {
+ case Property::Rotate:
+ mProperties.rotate = value;
+ break;
+ case Property::PivotX:
+ mProperties.pivotX = value;
+ break;
+ case Property::PivotY:
+ mProperties.pivotY = value;
+ break;
+ case Property::ScaleX:
+ mProperties.scaleX = value;
+ break;
+ case Property::ScaleY:
+ mProperties.scaleY = value;
+ break;
+ case Property::TranslateX:
+ mProperties.translateX = value;
+ break;
+ case Property::TranslateY:
+ mProperties.translateY = value;
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Invalid property index: %d", propertyId);
+ }
+}
+
+bool Group::isValidProperty(int propertyId) {
+ return propertyId >= 0 && propertyId < static_cast<int>(Property::Count);
+}
+
void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter,
const SkRect& bounds, bool needsMirroring, bool canReuseCache) {
// The imageView can scale the canvas in different ways, in order to
@@ -445,7 +458,9 @@ void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter,
return;
}
- int saveCount = outCanvas->save(SaveFlags::MatrixClip);
+ mPaint.setColorFilter(colorFilter);
+
+ int saveCount = outCanvas->save(SkCanvas::SaveFlags::kMatrixClip_SaveFlag);
outCanvas->translate(mBounds.fLeft, mBounds.fTop);
// Handle RTL mirroring.
@@ -458,43 +473,33 @@ void Tree::draw(Canvas* outCanvas, SkColorFilter* colorFilter,
// And we use this bound for the destination rect for the drawBitmap, so
// we offset to (0, 0);
mBounds.offsetTo(0, 0);
-
createCachedBitmapIfNeeded(scaledWidth, scaledHeight);
- if (!mAllowCaching) {
- updateCachedBitmap(scaledWidth, scaledHeight);
- } else {
- if (!canReuseCache || mCacheDirty) {
- updateCachedBitmap(scaledWidth, scaledHeight);
- }
- }
- drawCachedBitmapWithRootAlpha(outCanvas, colorFilter, mBounds);
+
+ outCanvas->drawVectorDrawable(this);
outCanvas->restoreToCount(saveCount);
}
-void Tree::drawCachedBitmapWithRootAlpha(Canvas* outCanvas, SkColorFilter* filter,
- const SkRect& originalBounds) {
+SkPaint* Tree::getPaint() {
SkPaint* paint;
- if (mRootAlpha == 1.0f && filter == NULL) {
+ if (mRootAlpha == 1.0f && mPaint.getColorFilter() == NULL) {
paint = NULL;
} else {
mPaint.setFilterQuality(kLow_SkFilterQuality);
mPaint.setAlpha(mRootAlpha * 255);
- mPaint.setColorFilter(filter);
paint = &mPaint;
}
- outCanvas->drawBitmap(mCachedBitmap, 0, 0, mCachedBitmap.width(), mCachedBitmap.height(),
- originalBounds.fLeft, originalBounds.fTop, originalBounds.fRight,
- originalBounds.fBottom, paint);
+ return paint;
}
-void Tree::updateCachedBitmap(int width, int height) {
+const SkBitmap& Tree::getBitmapUpdateIfDirty() {
mCachedBitmap.eraseColor(SK_ColorTRANSPARENT);
SkCanvas outCanvas(mCachedBitmap);
- float scaleX = width / mViewportWidth;
- float scaleY = height / mViewportHeight;
+ float scaleX = (float) mCachedBitmap.width() / mViewportWidth;
+ float scaleY = (float) mCachedBitmap.height() / mViewportHeight;
mRootNode->draw(&outCanvas, SkMatrix::I(), scaleX, scaleY);
mCacheDirty = false;
+ return mCachedBitmap;
}
void Tree::createCachedBitmapIfNeeded(int width, int height) {
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index 09bdce596a21..f8f1ea62a624 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -18,6 +18,7 @@
#define ANDROID_HWUI_VPATH_H
#include "Canvas.h"
+
#include <SkBitmap.h>
#include <SkColor.h>
#include <SkCanvas.h>
@@ -104,6 +105,21 @@ protected:
class ANDROID_API FullPath: public Path {
public:
+
+struct Properties {
+ float strokeWidth = 0;
+ SkColor strokeColor = SK_ColorTRANSPARENT;
+ float strokeAlpha = 1;
+ SkColor fillColor = SK_ColorTRANSPARENT;
+ float fillAlpha = 1;
+ float trimPathStart = 0;
+ float trimPathEnd = 1;
+ float trimPathOffset = 0;
+ int32_t strokeLineCap = SkPaint::Cap::kButt_Cap;
+ int32_t strokeLineJoin = SkPaint::Join::kMiter_Join;
+ float strokeMiterLimit = 4;
+};
+
FullPath(const FullPath& path); // for cloning
FullPath(const char* path, size_t strLength) : Path(path, strLength) {}
FullPath() : Path() {}
@@ -118,55 +134,58 @@ public:
float strokeAlpha, SkColor fillColor, float fillAlpha,
float trimPathStart, float trimPathEnd, float trimPathOffset,
float strokeMiterLimit, int strokeLineCap, int strokeLineJoin);
+ // TODO: Cleanup: Remove the setter and getters below, and their counterparts in java and JNI
float getStrokeWidth() {
- return mStrokeWidth;
+ return mProperties.strokeWidth;
}
void setStrokeWidth(float strokeWidth) {
- mStrokeWidth = strokeWidth;
+ mProperties.strokeWidth = strokeWidth;
}
SkColor getStrokeColor() {
- return mStrokeColor;
+ return mProperties.strokeColor;
}
void setStrokeColor(SkColor strokeColor) {
- mStrokeColor = strokeColor;
+ mProperties.strokeColor = strokeColor;
}
float getStrokeAlpha() {
- return mStrokeAlpha;
+ return mProperties.strokeAlpha;
}
void setStrokeAlpha(float strokeAlpha) {
- mStrokeAlpha = strokeAlpha;
+ mProperties.strokeAlpha = strokeAlpha;
}
SkColor getFillColor() {
- return mFillColor;
+ return mProperties.fillColor;
}
void setFillColor(SkColor fillColor) {
- mFillColor = fillColor;
+ mProperties.fillColor = fillColor;
}
float getFillAlpha() {
- return mFillAlpha;
+ return mProperties.fillAlpha;
}
void setFillAlpha(float fillAlpha) {
- mFillAlpha = fillAlpha;
+ mProperties.fillAlpha = fillAlpha;
}
float getTrimPathStart() {
- return mTrimPathStart;
+ return mProperties.trimPathStart;
}
void setTrimPathStart(float trimPathStart) {
- VD_SET_PROP_WITH_FLAG(mTrimPathStart, trimPathStart, mTrimDirty);
+ VD_SET_PROP_WITH_FLAG(mProperties.trimPathStart, trimPathStart, mTrimDirty);
}
float getTrimPathEnd() {
- return mTrimPathEnd;
+ return mProperties.trimPathEnd;
}
void setTrimPathEnd(float trimPathEnd) {
- VD_SET_PROP_WITH_FLAG(mTrimPathEnd, trimPathEnd, mTrimDirty);
+ VD_SET_PROP_WITH_FLAG(mProperties.trimPathEnd, trimPathEnd, mTrimDirty);
}
float getTrimPathOffset() {
- return mTrimPathOffset;
+ return mProperties.trimPathOffset;
}
void setTrimPathOffset(float trimPathOffset) {
- VD_SET_PROP_WITH_FLAG(mTrimPathOffset, trimPathOffset, mTrimDirty);
+ VD_SET_PROP_WITH_FLAG(mProperties.trimPathOffset, trimPathOffset, mTrimDirty);
}
bool getProperties(int8_t* outProperties, int length);
+ void setColorPropertyValue(int propertyId, int32_t value);
+ void setPropertyValue(int propertyId, float value);
void setFillGradient(SkShader* fillGradient) {
SkRefCnt_SafeAssign(mFillGradient, fillGradient);
@@ -182,24 +201,28 @@ protected:
float strokeScale, const SkMatrix& matrix) override;
private:
+ enum class Property {
+ StrokeWidth = 0,
+ StrokeColor,
+ StrokeAlpha,
+ FillColor,
+ FillAlpha,
+ TrimPathStart,
+ TrimPathEnd,
+ TrimPathOffset,
+ StrokeLineCap,
+ StrokeLineJoin,
+ StrokeMiterLimit,
+ Count,
+ };
// Applies trimming to the specified path.
void applyTrim();
- float mStrokeWidth = 0;
- SkColor mStrokeColor = SK_ColorTRANSPARENT;
- float mStrokeAlpha = 1;
- SkColor mFillColor = SK_ColorTRANSPARENT;
- SkShader* mStrokeGradient = nullptr;
- SkShader* mFillGradient = nullptr;
- float mFillAlpha = 1;
- float mTrimPathStart = 0;
- float mTrimPathEnd = 1;
- float mTrimPathOffset = 0;
+ Properties mProperties;
bool mTrimDirty = true;
- SkPaint::Cap mStrokeLineCap = SkPaint::Cap::kButt_Cap;
- SkPaint::Join mStrokeLineJoin = SkPaint::Join::kMiter_Join;
- float mStrokeMiterLimit = 4;
SkPath mTrimmedSkPath;
SkPaint mPaint;
+ SkShader* mStrokeGradient = nullptr;
+ SkShader* mFillGradient = nullptr;
};
class ANDROID_API ClipPath: public Path {
@@ -216,49 +239,58 @@ protected:
class ANDROID_API Group: public Node {
public:
+ struct Properties {
+ float rotate = 0;
+ float pivotX = 0;
+ float pivotY = 0;
+ float scaleX = 1;
+ float scaleY = 1;
+ float translateX = 0;
+ float translateY = 0;
+ };
Group(const Group& group);
Group() {}
float getRotation() {
- return mRotate;
+ return mProperties.rotate;
}
void setRotation(float rotation) {
- mRotate = rotation;
+ mProperties.rotate = rotation;
}
float getPivotX() {
- return mPivotX;
+ return mProperties.pivotX;
}
void setPivotX(float pivotX) {
- mPivotX = pivotX;
+ mProperties.pivotX = pivotX;
}
float getPivotY() {
- return mPivotY;
+ return mProperties.pivotY;
}
void setPivotY(float pivotY) {
- mPivotY = pivotY;
+ mProperties.pivotY = pivotY;
}
float getScaleX() {
- return mScaleX;
+ return mProperties.scaleX;
}
void setScaleX(float scaleX) {
- mScaleX = scaleX;
+ mProperties.scaleX = scaleX;
}
float getScaleY() {
- return mScaleY;
+ return mProperties.scaleY;
}
void setScaleY(float scaleY) {
- mScaleY = scaleY;
+ mProperties.scaleY = scaleY;
}
float getTranslateX() {
- return mTranslateX;
+ return mProperties.translateX;
}
void setTranslateX(float translateX) {
- mTranslateX = translateX;
+ mProperties.translateX = translateX;
}
float getTranslateY() {
- return mTranslateY;
+ return mProperties.translateY;
}
void setTranslateY(float translateY) {
- mTranslateY = translateY;
+ mProperties.translateY = translateY;
}
virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
float scaleX, float scaleY) override;
@@ -268,38 +300,33 @@ public:
void addChild(Node* child);
void dump() override;
bool getProperties(float* outProperties, int length);
+ float getPropertyValue(int propertyId) const;
+ void setPropertyValue(int propertyId, float value);
+ static bool isValidProperty(int propertyId);
private:
enum class Property {
- Rotate_Property = 0,
- PivotX_Property,
- PivotY_Property,
- ScaleX_Property,
- ScaleY_Property,
- TranslateX_Property,
- TranslateY_Property,
+ Rotate = 0,
+ PivotX,
+ PivotY,
+ ScaleX,
+ ScaleY,
+ TranslateX,
+ TranslateY,
// Count of the properties, must be at the end.
Count,
};
- float mRotate = 0;
- float mPivotX = 0;
- float mPivotY = 0;
- float mScaleX = 1;
- float mScaleY = 1;
- float mTranslateX = 0;
- float mTranslateY = 0;
std::vector<Node*> mChildren;
+ Properties mProperties;
};
-class ANDROID_API Tree {
+class ANDROID_API Tree : public VirtualLightRefBase {
public:
Tree(Group* rootNode) : mRootNode(rootNode) {}
void draw(Canvas* outCanvas, SkColorFilter* colorFilter,
const SkRect& bounds, bool needsMirroring, bool canReuseCache);
- void drawCachedBitmapWithRootAlpha(Canvas* outCanvas, SkColorFilter* filter,
- const SkRect& originalBounds);
- void updateCachedBitmap(int width, int height);
+ const SkBitmap& getBitmapUpdateIfDirty();
void createCachedBitmapIfNeeded(int width, int height);
bool canReuseBitmap(int width, int height);
void setAllowCaching(bool allowCaching) {
@@ -316,6 +343,10 @@ public:
mViewportWidth = viewportWidth;
mViewportHeight = viewportHeight;
}
+ SkPaint* getPaint();
+ const SkRect& getBounds() const {
+ return mBounds;
+ }
private:
// Cap the bitmap size, such that it won't hurt the performance too much
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index 93e6dacdd98c..80b3ffc23e31 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -115,7 +115,7 @@ public final class MediaBrowser {
* @param callback The connection callback.
* @param rootHints An optional bundle of service-specific arguments to send
* to the media browse service when connecting and retrieving the root id
- * for browsing, or null if none. The contents of this bundle may affect
+ * for browsing, or null if none. The contents of this bundle may affect
* the information returned when browsing.
*/
public MediaBrowser(Context context, ComponentName serviceComponent,
@@ -178,9 +178,9 @@ public final class MediaBrowser {
}
if (!bound) {
- // Tell them that it didn't work. We are already on the main thread,
- // but we don't want to do callbacks inside of connect(). So post it,
- // and then check that we are on the same ServiceConnection. We know
+ // Tell them that it didn't work. We are already on the main thread,
+ // but we don't want to do callbacks inside of connect(). So post it,
+ // and then check that we are on the same ServiceConnection. We know
// we won't also get an onServiceConnected or onServiceDisconnected,
// so we won't be doing double callbacks.
mHandler.post(new Runnable() {
@@ -207,13 +207,13 @@ public final class MediaBrowser {
*/
public void disconnect() {
// It's ok to call this any state, because allowing this lets apps not have
- // to check isConnected() unnecessarily. They won't appreciate the extra
- // assertions for this. We do everything we can here to go back to a sane state.
+ // to check isConnected() unnecessarily. They won't appreciate the extra
+ // assertions for this. We do everything we can here to go back to a sane state.
if (mServiceCallbacks != null) {
try {
mServiceBinder.disconnect(mServiceCallbacks);
} catch (RemoteException ex) {
- // We are disconnecting anyway. Log, just for posterity but it's not
+ // We are disconnecting anyway. Log, just for posterity but it's not
// a big problem.
Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
}
@@ -227,12 +227,12 @@ public final class MediaBrowser {
}
/**
- * Null out the variables and unbind from the service. This doesn't include
+ * Null out the variables and unbind from the service. This doesn't include
* calling disconnect on the service, because we only try to do that in the
* clean shutdown cases.
* <p>
* Everywhere that calls this EXCEPT for disconnect() should follow it with
- * a call to mCallback.onConnectionFailed(). Disconnect doesn't do that callback
+ * a call to mCallback.onConnectionFailed(). Disconnect doesn't do that callback
* for a clean shutdown, but everywhere else is a dirty shutdown and should
* notify the app.
*/
@@ -455,8 +455,8 @@ public final class MediaBrowser {
private void subscribeInternal(String parentId, Bundle options, SubscriptionCallback callback) {
// Check arguments.
- if (parentId == null) {
- throw new IllegalArgumentException("parentId is null");
+ if (TextUtils.isEmpty(parentId)) {
+ throw new IllegalArgumentException("parentId is empty.");
}
if (callback == null) {
throw new IllegalArgumentException("callback is null");
@@ -659,7 +659,7 @@ public final class MediaBrowser {
}
/**
- * Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not.
+ * Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not.
*/
private boolean isCurrent(IMediaBrowserServiceCallbacks callback, String funcName) {
if (mServiceCallbacks != callback) {
@@ -955,8 +955,8 @@ public final class MediaBrowser {
mServiceCallbacks);
} catch (RemoteException ex) {
// Connect failed, which isn't good. But the auto-reconnect on the service
- // will take over and we will come back. We will also get the
- // onServiceDisconnected, which has all the cleanup code. So let that do
+ // will take over and we will come back. We will also get the
+ // onServiceDisconnected, which has all the cleanup code. So let that do
// it.
Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
if (DBG) {
@@ -1005,7 +1005,7 @@ public final class MediaBrowser {
}
/**
- * Return true if this is the current ServiceConnection. Also logs if it's not.
+ * Return true if this is the current ServiceConnection. Also logs if it's not.
*/
private boolean isCurrent(String funcName) {
if (mServiceConnection != this) {
@@ -1031,7 +1031,7 @@ public final class MediaBrowser {
}
/**
- * The other side has acknowledged our connection. The parameters to this function
+ * The other side has acknowledged our connection. The parameters to this function
* are the initial data as requested.
*/
@Override
@@ -1044,7 +1044,7 @@ public final class MediaBrowser {
}
/**
- * The other side does not like us. Tell the app via onConnectionFailed.
+ * The other side does not like us. Tell the app via onConnectionFailed.
*/
@Override
public void onConnectFailed() {
diff --git a/media/java/android/media/browse/MediaBrowserUtils.java b/media/java/android/media/browse/MediaBrowserUtils.java
index 4f198aca96ab..b06e598a63d1 100644
--- a/media/java/android/media/browse/MediaBrowserUtils.java
+++ b/media/java/android/media/browse/MediaBrowserUtils.java
@@ -40,10 +40,10 @@ public class MediaBrowserUtils {
}
public static boolean hasDuplicatedItems(Bundle options1, Bundle options2) {
- int page1 = options1.getInt(MediaBrowser.EXTRA_PAGE, -1);
- int page2 = options2.getInt(MediaBrowser.EXTRA_PAGE, -1);
- int pageSize1 = options1.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1);
- int pageSize2 = options2.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1);
+ int page1 = options1 == null ? -1 : options1.getInt(MediaBrowser.EXTRA_PAGE, -1);
+ int page2 = options2 == null ? -1 : options2.getInt(MediaBrowser.EXTRA_PAGE, -1);
+ int pageSize1 = options1 == null ? -1 : options1.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1);
+ int pageSize2 = options2 == null ? -1 : options2.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1);
int startIndex1, startIndex2, endIndex1, endIndex2;
if (page1 == -1 || pageSize1 == -1) {
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index 299b77071c55..0393c943784d 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -16,6 +16,7 @@
package android.service.media;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
@@ -41,6 +42,8 @@ import android.util.Log;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -49,7 +52,7 @@ import java.util.List;
* Base class for media browse services.
* <p>
* Media browse services enable applications to browse media content provided by an application
- * and ask the application to start playing it. They may also be used to control content that
+ * and ask the application to start playing it. They may also be used to control content that
* is already playing by way of a {@link MediaSession}.
* </p>
*
@@ -86,6 +89,11 @@ public abstract class MediaBrowserService extends Service {
private static final int RESULT_FLAG_OPTION_NOT_HANDLED = 0x00000001;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag=true, value = { RESULT_FLAG_OPTION_NOT_HANDLED })
+ private @interface ResultFlags { }
+
private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap<>();
private final Handler mHandler = new Handler();
private ServiceBinder mBinder;
@@ -106,10 +114,10 @@ public abstract class MediaBrowserService extends Service {
* Completion handler for asynchronous callback methods in {@link MediaBrowserService}.
* <p>
* Each of the methods that takes one of these to send the result must call
- * {@link #sendResult} to respond to the caller with the given results. If those
+ * {@link #sendResult} to respond to the caller with the given results. If those
* functions return without calling {@link #sendResult}, they must instead call
* {@link #detach} before returning, and then may call {@link #sendResult} when
- * they are done. If more than one of those methods is called, an exception will
+ * they are done. If more than one of those methods is called, an exception will
* be thrown.
*
* @see MediaBrowserService#onLoadChildren
@@ -119,7 +127,7 @@ public abstract class MediaBrowserService extends Service {
private Object mDebug;
private boolean mDetachCalled;
private boolean mSendResultCalled;
- private int mFlag;
+ private int mFlags;
Result(Object debug) {
mDebug = debug;
@@ -133,7 +141,7 @@ public abstract class MediaBrowserService extends Service {
throw new IllegalStateException("sendResult() called twice for: " + mDebug);
}
mSendResultCalled = true;
- onResultSent(result, mFlag);
+ onResultSent(result, mFlags);
}
/**
@@ -156,15 +164,15 @@ public abstract class MediaBrowserService extends Service {
return mDetachCalled || mSendResultCalled;
}
- void setFlag(int flag) {
- mFlag = flag;
+ void setFlags(@ResultFlags int flags) {
+ mFlags = flags;
}
/**
* Called when the result is sent, after assertions about not being called twice
* have happened.
*/
- void onResultSent(T result, int flag) {
+ void onResultSent(T result, @ResultFlags int flags) {
}
}
@@ -184,7 +192,7 @@ public abstract class MediaBrowserService extends Service {
public void run() {
final IBinder b = callbacks.asBinder();
- // Clear out the old subscriptions. We are getting new ones.
+ // Clear out the old subscriptions. We are getting new ones.
mConnections.remove(b);
final ConnectionRecord connection = new ConnectionRecord();
@@ -228,7 +236,7 @@ public abstract class MediaBrowserService extends Service {
public void run() {
final IBinder b = callbacks.asBinder();
- // Clear out the old subscriptions. We are getting new ones.
+ // Clear out the old subscriptions. We are getting new ones.
final ConnectionRecord old = mConnections.remove(b);
if (old != null) {
// TODO
@@ -388,7 +396,7 @@ public abstract class MediaBrowserService extends Service {
// To support backward compatibility, when the implementation of MediaBrowserService doesn't
// override onLoadChildren() with options, onLoadChildren() without options will be used
// instead, and the options will be applied in the implementation of result.onResultSent().
- result.setFlag(RESULT_FLAG_OPTION_NOT_HANDLED);
+ result.setFlags(RESULT_FLAG_OPTION_NOT_HANDLED);
onLoadChildren(parentId, result);
}
@@ -574,7 +582,7 @@ public abstract class MediaBrowserService extends Service {
final Result<List<MediaBrowser.MediaItem>> result
= new Result<List<MediaBrowser.MediaItem>>(parentId) {
@Override
- void onResultSent(List<MediaBrowser.MediaItem> list, int flag) {
+ void onResultSent(List<MediaBrowser.MediaItem> list, @ResultFlags int flag) {
if (mConnections.get(connection.callbacks.asBinder()) != connection) {
if (DBG) {
Log.d(TAG, "Not sending onLoadChildren result for connection that has"
@@ -639,7 +647,7 @@ public abstract class MediaBrowserService extends Service {
final Result<MediaBrowser.MediaItem> result =
new Result<MediaBrowser.MediaItem>(itemId) {
@Override
- void onResultSent(MediaBrowser.MediaItem item, int flag) {
+ void onResultSent(MediaBrowser.MediaItem item, @ResultFlags int flag) {
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_MEDIA_ITEM, item);
receiver.send(0, bundle);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2SurfaceViewTestCase.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2SurfaceViewTestCase.java
index 74da2c9b1955..e718742d5a67 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2SurfaceViewTestCase.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/Camera2SurfaceViewTestCase.java
@@ -90,7 +90,7 @@ public class Camera2SurfaceViewTestCase extends
private static final int WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS = 1000;
// Instrumentation arguments
- protected static final String ARG_KEY_REPEAT = "repeat";
+ protected static final String ARG_KEY_ITERATIONS = "iterations";
protected static final String ARG_KEY_WAIT_INTERVAL_MS = "waitIntervalMs";
protected static final String ARG_KEY_RESULT_TO_FILE = "resultToFile";
@@ -126,8 +126,8 @@ public class Camera2SurfaceViewTestCase extends
protected WindowManager mWindowManager;
- // Repeat tests a given times. Default to 1.
- protected int mRepeat = 1;
+ // Set the number of iterations to run stress testing. Default to 1.
+ protected int mIterations = 1;
// The interval between test iterations used for stress test.
protected long mTestWaitIntervalMs = 1 * 1000; // 1 sec
protected boolean mWriteToFile = true;
@@ -165,10 +165,10 @@ public class Camera2SurfaceViewTestCase extends
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- mRepeat = getArgumentsAsNumber(ARG_KEY_REPEAT, 1).intValue();
+ mIterations = getArgumentsAsNumber(ARG_KEY_ITERATIONS, 1).intValue();
mTestWaitIntervalMs = getArgumentsAsNumber(ARG_KEY_WAIT_INTERVAL_MS, 1000).longValue();
mWriteToFile = getArgumentsAsBoolean(ARG_KEY_RESULT_TO_FILE, true);
- Log.i(TAG, "Argument: repeat count=" + mRepeat);
+ Log.i(TAG, "Argument: iteration count=" + mIterations);
Log.i(TAG, "Argument: interval (ms)=" + mTestWaitIntervalMs);
Log.i(TAG, "Argument: result to file=" + (mWriteToFile ? "true" : "false"));
mResultPrinter = new CameraTestResultPrinter(getInstrumentation(), mWriteToFile);
@@ -790,8 +790,8 @@ public class Camera2SurfaceViewTestCase extends
return defaultValue;
}
- protected int getRepeatCount() {
- return mRepeat;
+ protected int getIterationCount() {
+ return mIterations;
}
protected long getTestWaitIntervalMs() {
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java
index b1529756c8fe..ebfd92eec073 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2CaptureRequestTest.java
@@ -24,7 +24,6 @@ import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.util.Log;
-import android.util.Rational;
import android.util.Size;
import java.util.Arrays;
@@ -47,7 +46,7 @@ import static com.android.mediaframeworktest.helpers.CameraTestUtils.getValueNot
*
* adb shell am instrument \
* -e class com.android.mediaframeworktest.stress.Camera2CaptureRequestTest#testAeModeAndLock \
- * -e repeat 10 \
+ * -e iterations 10 \
* -e waitIntervalMs 1000 \
* -e resultToFile false \
* -r -w com.android.mediaframeworktest/.Camera2InstrumentationTestRunner
@@ -100,16 +99,16 @@ public class Camera2CaptureRequestTest extends Camera2SurfaceViewTestCase {
updatePreviewSurface(maxPreviewSz);
// Test iteration starts...
- for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
- Log.v(TAG, String.format("AE mode and lock: %d/%d", repeat + 1,
- getRepeatCount()));
+ for (int iteration = 0; iteration < getIterationCount(); ++iteration) {
+ Log.v(TAG, String.format("AE mode and lock: %d/%d", iteration + 1,
+ getIterationCount()));
// Test aeMode and lock
int[] aeModes = mStaticInfo.getAeAvailableModesChecked();
for (int mode : aeModes) {
aeModeAndLockTestByMode(mode);
}
- getResultPrinter().printStatus(getRepeatCount(), repeat + 1, mCameraIds[i]);
+ getResultPrinter().printStatus(getIterationCount(), iteration + 1, mCameraIds[i]);
Thread.sleep(getTestWaitIntervalMs());
}
} finally {
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java
index e7c91cf2bc98..a9b6bfd783f6 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java
@@ -67,7 +67,7 @@ import static com.android.mediaframeworktest.helpers.CameraTestUtils.getValueNot
*
* adb shell am instrument \
* -e class com.android.mediaframeworktest.stress.Camera2RecordingTest#testBasicRecording \
- * -e repeat 10 \
+ * -e iterations 10 \
* -e waitIntervalMs 1000 \
* -e resultToFile false \
* -r -w com.android.mediaframeworktest/.Camera2InstrumentationTestRunner
@@ -142,11 +142,12 @@ public class Camera2RecordingTest extends Camera2SurfaceViewTestCase {
initSupportedVideoSize(mCameraIds[i]);
// Test iteration starts...
- for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
- Log.v(TAG, String.format("Recording video: %d/%d", repeat + 1,
- getRepeatCount()));
+ for (int iteration = 0; iteration < getIterationCount(); ++iteration) {
+ Log.v(TAG, String.format("Recording video: %d/%d", iteration + 1,
+ getIterationCount()));
basicRecordingTestByCamera(mCamcorderProfileList, useVideoStab);
- getResultPrinter().printStatus(getRepeatCount(), repeat + 1, mCameraIds[i]);
+ getResultPrinter().printStatus(getIterationCount(), iteration + 1,
+ mCameraIds[i]);
Thread.sleep(getTestWaitIntervalMs());
}
} finally {
@@ -206,9 +207,9 @@ public class Camera2RecordingTest extends Camera2SurfaceViewTestCase {
}
// Test iteration starts...
- for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
- Log.v(TAG, String.format("Constrained high speed recording: %d/%d", repeat + 1,
- getRepeatCount()));
+ for (int iteration = 0; iteration < getIterationCount(); ++iteration) {
+ Log.v(TAG, String.format("Constrained high speed recording: %d/%d",
+ iteration + 1, getIterationCount()));
StreamConfigurationMap config =
mStaticInfo.getValueFromKeyNonNull(
@@ -257,7 +258,7 @@ public class Camera2RecordingTest extends Camera2SurfaceViewTestCase {
validateRecording(size, durationMs);
}
- getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id);
+ getResultPrinter().printStatus(getIterationCount(), iteration + 1, id);
Thread.sleep(getTestWaitIntervalMs());
}
}
@@ -481,11 +482,11 @@ public class Camera2RecordingTest extends Camera2SurfaceViewTestCase {
initSupportedVideoSize(id);
// Test iteration starts...
- for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
- Log.v(TAG, String.format("Video snapshot: %d/%d", repeat + 1,
- getRepeatCount()));
+ for (int iteration = 0; iteration < getIterationCount(); ++iteration) {
+ Log.v(TAG, String.format("Video snapshot: %d/%d", iteration + 1,
+ getIterationCount()));
videoSnapshotTestByCamera(burstTest);
- getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id);
+ getResultPrinter().printStatus(getIterationCount(), iteration + 1, id);
Thread.sleep(getTestWaitIntervalMs());
}
} finally {
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2ReprocessCaptureTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2ReprocessCaptureTest.java
index 2dac37128ae9..8f9489790bdb 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2ReprocessCaptureTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2ReprocessCaptureTest.java
@@ -58,7 +58,7 @@ import static com.android.mediaframeworktest.helpers.CameraTestUtils.verifyJpegK
* adb shell am instrument \
* -e class \
* com.android.mediaframeworktest.stress.Camera2StillCaptureTest#Camera2ReprocessCaptureTest \
- * -e repeat 1 \
+ * -e iterations 1 \
* -e waitIntervalMs 1000 \
* -e resultToFile false \
* -r -w com.android.mediaframeworktest/.Camera2InstrumentationTestRunner
@@ -110,12 +110,12 @@ public class Camera2ReprocessCaptureTest extends Camera2SurfaceViewTestCase {
}
// Test iteration starts...
- for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
- Log.v(TAG, String.format("Reprocessing YUV to JPEG: %d/%d", repeat + 1,
- getRepeatCount()));
+ for (int iteration = 0; iteration < getIterationCount(); ++iteration) {
+ Log.v(TAG, String.format("Reprocessing YUV to JPEG: %d/%d", iteration + 1,
+ getIterationCount()));
// YUV_420_888 -> JPEG must be supported.
testBasicReprocessing(id, ImageFormat.YUV_420_888, ImageFormat.JPEG);
- getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id);
+ getResultPrinter().printStatus(getIterationCount(), iteration + 1, id);
Thread.sleep(getTestWaitIntervalMs());
}
}
@@ -131,12 +131,12 @@ public class Camera2ReprocessCaptureTest extends Camera2SurfaceViewTestCase {
}
// Test iteration starts...
- for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
- Log.v(TAG, String.format("Reprocessing OPAQUE to JPEG: %d/%d", repeat + 1,
- getRepeatCount()));
+ for (int iteration = 0; iteration < getIterationCount(); ++iteration) {
+ Log.v(TAG, String.format("Reprocessing OPAQUE to JPEG: %d/%d", iteration + 1,
+ getIterationCount()));
// OPAQUE -> JPEG must be supported.
testBasicReprocessing(id, ImageFormat.PRIVATE, ImageFormat.JPEG);
- getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id);
+ getResultPrinter().printStatus(getIterationCount(), iteration + 1, id);
Thread.sleep(getTestWaitIntervalMs());
}
@@ -157,12 +157,12 @@ public class Camera2ReprocessCaptureTest extends Camera2SurfaceViewTestCase {
openDevice(id);
// Test iteration starts...
- for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
+ for (int iteration = 0; iteration < getIterationCount(); ++iteration) {
Log.v(TAG, String.format("Reprocessing size format with preview: %d/%d",
- repeat + 1, getRepeatCount()));
+ iteration + 1, getIterationCount()));
testReprocessingAllCombinations(id, mOrderedPreviewSizes.get(0),
CaptureTestCase.SINGLE_SHOT);
- getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id);
+ getResultPrinter().printStatus(getIterationCount(), iteration + 1, id);
Thread.sleep(getTestWaitIntervalMs());
}
} finally {
@@ -185,16 +185,16 @@ public class Camera2ReprocessCaptureTest extends Camera2SurfaceViewTestCase {
openDevice(id);
// Test iteration starts...
- for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
+ for (int iteration = 0; iteration < getIterationCount(); ++iteration) {
Log.v(TAG, String.format("Reprocessing mixed burst with or without preview: "
- + "%d/%d", repeat + 1, getRepeatCount()));
+ + "%d/%d", iteration + 1, getIterationCount()));
// no preview
testReprocessingAllCombinations(id, /*previewSize*/null,
CaptureTestCase.MIXED_BURST);
// with preview
testReprocessingAllCombinations(id, mOrderedPreviewSizes.get(0),
CaptureTestCase.MIXED_BURST);
- getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id);
+ getResultPrinter().printStatus(getIterationCount(), iteration + 1, id);
Thread.sleep(getTestWaitIntervalMs());
}
} finally {
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2StillCaptureTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2StillCaptureTest.java
index 16dfb2bbd3e7..812543b3fe41 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2StillCaptureTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2StillCaptureTest.java
@@ -59,7 +59,7 @@ import static com.android.mediaframeworktest.helpers.CameraTestUtils.makeImageRe
*
* adb shell am instrument \
* -e class com.android.mediaframeworktest.stress.Camera2StillCaptureTest#testTakePicture \
- * -e repeat 200 \
+ * -e iterations 200 \
* -e waitIntervalMs 1000 \
* -e resultToFile false \
* -r -w com.android.mediaframeworktest/.Camera2InstrumentationTestRunner
@@ -108,12 +108,12 @@ public class Camera2StillCaptureTest extends Camera2SurfaceViewTestCase {
}
// Test iteration starts...
- for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
- Log.v(TAG, String.format("Taking pictures: %d/%d", repeat + 1,
- getRepeatCount()));
+ for (int iteration = 0; iteration < getIterationCount(); ++iteration) {
+ Log.v(TAG, String.format("Taking pictures: %d/%d", iteration + 1,
+ getIterationCount()));
takePictureTestByCamera(/*aeRegions*/null, /*awbRegions*/null,
/*afRegions*/null);
- getResultPrinter().printStatus(getRepeatCount(), repeat + 1, id);
+ getResultPrinter().printStatus(getIterationCount(), iteration + 1, id);
Thread.sleep(getTestWaitIntervalMs());
}
} finally {
@@ -144,11 +144,12 @@ public class Camera2StillCaptureTest extends Camera2SurfaceViewTestCase {
}
// Test iteration starts...
- for (int repeat = 0; repeat < getRepeatCount(); ++repeat) {
- Log.v(TAG, String.format("Taking full RAW pictures: %d/%d", repeat + 1,
- getRepeatCount()));
+ for (int iteration = 0; iteration < getIterationCount(); ++iteration) {
+ Log.v(TAG, String.format("Taking full RAW pictures: %d/%d", iteration + 1,
+ getIterationCount()));
fullRawCaptureTestByCamera();
- getResultPrinter().printStatus(getRepeatCount(), repeat + 1, mCameraIds[i]);
+ getResultPrinter().printStatus(getIterationCount(), iteration + 1,
+ mCameraIds[i]);
Thread.sleep(getTestWaitIntervalMs());
}
} finally {
diff --git a/packages/DocumentsUI/lint.xml b/packages/DocumentsUI/lint.xml
new file mode 100644
index 000000000000..70d1ddff4074
--- /dev/null
+++ b/packages/DocumentsUI/lint.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lint>
+ <!-- min-sdk doesn't apply to platform apps -->
+ <issue id="UsesMinSdkAttributes" severity="ignore" />
+
+ <!-- Protected permissions don't apply to system apps -->
+ <issue id="ProtectedPermissions" severity="ignore" />
+</lint>
diff --git a/packages/DocumentsUI/res/drawable/ic_sd_storage.xml b/packages/DocumentsUI/res/drawable/ic_sd_storage.xml
new file mode 100644
index 000000000000..b0f3cc395bcd
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable/ic_sd_storage.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18 2h-8L4.02 8 4 20c0 1.1.9 2 2 2h12c1.1 0 2,-.9 2,-2V4c0,-1.1,-.9,-2,-2,-2zm-6 6h-2V4h2v4zm3 0h-2V4h2v4zm3 0h-2V4h2v4z"/>
+</vector>
diff --git a/packages/DocumentsUI/res/menu/activity.xml b/packages/DocumentsUI/res/menu/activity.xml
index a3cfde825f47..b791ef12b6f3 100644
--- a/packages/DocumentsUI/res/menu/activity.xml
+++ b/packages/DocumentsUI/res/menu/activity.xml
@@ -29,7 +29,8 @@
android:icon="@drawable/ic_menu_search"
android:showAsAction="always"
android:actionViewClass="android.widget.SearchView"
- android:imeOptions="actionSearch" />
+ android:imeOptions="actionSearch"
+ android:visible="false" />
<item
android:id="@+id/menu_sort"
android:title="@string/menu_sort"
@@ -51,12 +52,13 @@
android:id="@+id/menu_grid"
android:title="@string/menu_grid"
android:icon="@drawable/ic_menu_view_grid"
- android:showAsAction="never" />
+ android:showAsAction="always" />
<item
android:id="@+id/menu_list"
android:title="@string/menu_list"
android:icon="@drawable/ic_menu_view_list"
- android:showAsAction="never" />
+ android:showAsAction="always" />
+
<item
android:id="@+id/menu_new_window"
android:title="@string/menu_new_window"
@@ -88,5 +90,6 @@
<item
android:id="@+id/menu_settings"
android:title="@string/menu_settings"
- android:showAsAction="never" />
+ android:showAsAction="never"
+ android:visible="false" />
</menu>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index afe93369ae2b..3c49f167534e 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -39,8 +39,8 @@
<string name="menu_sort">Sort by</string>
<!-- Menu item that enters a mode to search for documents [CHAR LIMIT=24] -->
<string name="menu_search">Search</string>
- <!-- Menu item that enters activity to change settings [CHAR LIMIT=24] -->
- <string name="menu_settings">Settings</string>
+ <!-- Menu item that enters activity to change settings for current root [CHAR LIMIT=24] -->
+ <string name="menu_settings">Storage settings</string>
<!-- Menu item title that opens the selected documents [CHAR LIMIT=24] -->
<string name="menu_open">Open</string>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 1474aa6b7690..0fed6410b2cd 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -38,6 +38,7 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Root;
+import android.support.annotation.CallSuper;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.util.Log;
@@ -138,10 +139,12 @@ public abstract class BaseActivity extends Activity implements SearchManagerList
}
@Override
+ @CallSuper
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
- final RootInfo root = getCurrentRoot();
+ mSearchManager.showMenu(canSearchRoot());
+
final boolean inRecents = getCurrentDirectory() == null;
final MenuItem sort = menu.findItem(R.id.menu_sort);
@@ -150,26 +153,17 @@ public abstract class BaseActivity extends Activity implements SearchManagerList
final MenuItem list = menu.findItem(R.id.menu_list);
final MenuItem advanced = menu.findItem(R.id.menu_advanced);
final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
- final MenuItem settings = menu.findItem(R.id.menu_settings);
- final MenuItem search = menu.findItem(R.id.menu_search);
-
- // I'm thinkin' this isn't necesary here. If it is...'cuz of a bug....
- // then uncomment the linke and let's get a proper bug reference here.
- // mSearchManager.update(root);
- // Search uses backend ranking; no sorting
+ // Search uses backend ranking; no sorting, recents doesn't support sort.
sort.setVisible(!inRecents && !mSearchManager.isSearching());
+ sortSize.setVisible(mState.showSize); // Only sort by size when file sizes are visible
+ fileSize.setVisible(!mState.forceSize);
// grid/list is effectively a toggle.
grid.setVisible(mState.derivedMode != State.MODE_GRID);
list.setVisible(mState.derivedMode != State.MODE_LIST);
- sortSize.setVisible(mState.showSize); // Only sort by size when visible
- fileSize.setVisible(!mState.forceSize);
advanced.setVisible(!mState.forceAdvanced);
- settings.setVisible((root.flags & Root.FLAG_HAS_SETTINGS) != 0);
- search.setVisible(canSearchRoot());
-
advanced.setTitle(LocalPreferences.getDisplayAdvancedDevices(this)
? R.string.menu_advanced_hide : R.string.menu_advanced_show);
fileSize.setTitle(LocalPreferences.getDisplayFileSize(this)
@@ -273,8 +267,10 @@ public abstract class BaseActivity extends Activity implements SearchManagerList
return true;
case R.id.menu_paste_from_clipboard:
- DirectoryFragment.get(getFragmentManager())
- .pasteFromClipboard();
+ DirectoryFragment dir = getDirectoryFragment();
+ if (dir != null) {
+ dir.pasteFromClipboard();
+ }
return true;
case R.id.menu_advanced:
@@ -297,6 +293,10 @@ public abstract class BaseActivity extends Activity implements SearchManagerList
}
}
+ final @Nullable DirectoryFragment getDirectoryFragment() {
+ return DirectoryFragment.get(getFragmentManager());
+ }
+
void showCreateDirectoryDialog() {
CreateDirectoryFragment.show(getFragmentManager());
}
@@ -425,7 +425,10 @@ public abstract class BaseActivity extends Activity implements SearchManagerList
void setDisplayFileSize(boolean display) {
LocalPreferences.setDisplayFileSize(this, display);
mState.showSize = display;
- DirectoryFragment.get(getFragmentManager()).onDisplayStateChanged();
+ DirectoryFragment dir = getDirectoryFragment();
+ if (dir != null) {
+ dir.onDisplayStateChanged();
+ }
invalidateOptionsMenu();
}
@@ -434,7 +437,10 @@ public abstract class BaseActivity extends Activity implements SearchManagerList
*/
void setUserSortOrder(int sortOrder) {
mState.userSortOrder = sortOrder;
- DirectoryFragment.get(getFragmentManager()).onSortOrderChanged();
+ DirectoryFragment dir = getDirectoryFragment();
+ if (dir != null) {
+ dir.onSortOrderChanged();
+ };
}
/**
@@ -449,7 +455,10 @@ public abstract class BaseActivity extends Activity implements SearchManagerList
// in onOptionsItemSelected, and not do the full invalidation
// But! That's a larger refactoring we'll save for another day.
invalidateOptionsMenu();
- DirectoryFragment.get(getFragmentManager()).onViewModeChanged();
+ DirectoryFragment dir = getDirectoryFragment();
+ if (dir != null) {
+ dir.onViewModeChanged();
+ };
}
public void setPending(boolean pending) {
@@ -498,7 +507,8 @@ public abstract class BaseActivity extends Activity implements SearchManagerList
return;
}
- if (DirectoryFragment.get(getFragmentManager()).onBackPressed()) {
+ DirectoryFragment dir = getDirectoryFragment();
+ if (dir != null && dir.onBackPressed()) {
return;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index c3395aeb7ba2..b933d0a8744c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -293,19 +293,24 @@ public class DocumentsActivity extends BaseActivity {
final DocumentInfo cwd = getCurrentDirectory();
+ boolean picking = mState.action == ACTION_CREATE
+ || mState.action == ACTION_OPEN_TREE
+ || mState.action == ACTION_PICK_COPY_DESTINATION;
+
+ if (picking) {
+ // May already be hidden because the root
+ // doesn't support search.
+ mSearchManager.showMenu(false);
+ }
+
final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
final MenuItem grid = menu.findItem(R.id.menu_grid);
final MenuItem list = menu.findItem(R.id.menu_list);
final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
- final MenuItem settings = menu.findItem(R.id.menu_settings);
boolean recents = cwd == null;
- boolean picking = mState.action == ACTION_CREATE
- || mState.action == ACTION_OPEN_TREE
- || mState.action == ACTION_PICK_COPY_DESTINATION;
createDir.setVisible(picking && !recents && cwd.isCreateSupported());
- mSearchManager.showMenu(!picking);
// No display options in recent directories
if (picking && recents) {
@@ -314,7 +319,6 @@ public class DocumentsActivity extends BaseActivity {
}
fileSize.setVisible(fileSize.isVisible() && !picking);
- settings.setVisible(false);
if (mState.action == ACTION_CREATE) {
final FragmentManager fm = getFragmentManager();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
index 3302da97bcae..b6ded6c3fbd7 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DownloadsActivity.java
@@ -150,14 +150,12 @@ public class DownloadsActivity extends BaseActivity {
final MenuItem newWindow = menu.findItem(R.id.menu_new_window);
final MenuItem pasteFromCb = menu.findItem(R.id.menu_paste_from_clipboard);
final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
- final MenuItem search = menu.findItem(R.id.menu_search);
advanced.setVisible(false);
createDir.setVisible(false);
pasteFromCb.setEnabled(false);
newWindow.setEnabled(false);
fileSize.setVisible(false);
- search.setVisible(false);
Menus.disableHiddenItems(menu);
return true;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Events.java b/packages/DocumentsUI/src/com/android/documentsui/Events.java
index 10a78b9f8b51..99b425e9fac2 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Events.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Events.java
@@ -78,6 +78,27 @@ public final class Events {
}
/**
+ * Whether or not the given keyCode represents a navigation keystroke (e.g. up, down, home).
+ *
+ * @param keyCode
+ * @return
+ */
+ public static boolean isNavigationKeyCode(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_MOVE_HOME:
+ case KeyEvent.KEYCODE_MOVE_END:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+
+ /**
* Returns true if the "SHIFT" bit is set.
*/
public static boolean hasShiftBit(int metaState) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index b490c002dc6c..f0df3a2ce823 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -246,15 +246,20 @@ public class FilesActivity extends BaseActivity {
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
+ final RootInfo root = getCurrentRoot();
+
final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
- final MenuItem newWindow = menu.findItem(R.id.menu_new_window);
final MenuItem pasteFromCb = menu.findItem(R.id.menu_paste_from_clipboard);
+ final MenuItem settings = menu.findItem(R.id.menu_settings);
- createDir.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
createDir.setVisible(true);
createDir.setEnabled(canCreateDirectory());
-
pasteFromCb.setEnabled(mClipper.hasItemsToPaste());
+ settings.setVisible(root.hasSettings());
+
+ // TODO: For some reason settings menu item is not
+ // honoring the "showAsAction=never" setting in activity.xml.
+ settings.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
Menus.disableHiddenItems(menu, pasteFromCb);
return true;
@@ -271,9 +276,10 @@ public class FilesActivity extends BaseActivity {
createNewWindow();
return true;
case R.id.menu_paste_from_clipboard:
- DirectoryFragment dir = DirectoryFragment.get(getFragmentManager());
- dir = DirectoryFragment.get(getFragmentManager());
- dir.pasteFromClipboard();
+ DirectoryFragment dir = getDirectoryFragment();
+ if (dir != null) {
+ dir.pasteFromClipboard();
+ }
return true;
}
@@ -376,20 +382,26 @@ public class FilesActivity extends BaseActivity {
@Override
public boolean onKeyShortcut(int keyCode, KeyEvent event) {
DirectoryFragment dir;
+ // TODO: All key events should be statically bound using alphabeticShortcut.
+ // But not working.
switch (keyCode) {
case KeyEvent.KEYCODE_A:
- dir = DirectoryFragment.get(getFragmentManager());
- dir.selectAllFiles();
+ dir = getDirectoryFragment();
+ if (dir != null) {
+ dir.selectAllFiles();
+ }
return true;
case KeyEvent.KEYCODE_C:
- // TODO: Should be statically bound using alphabeticShortcut. See b/21330356.
- dir = DirectoryFragment.get(getFragmentManager());
- dir.copySelectedToClipboard();
+ dir = getDirectoryFragment();
+ if (dir != null) {
+ dir.copySelectedToClipboard();
+ }
return true;
case KeyEvent.KEYCODE_V:
- // TODO: Should be statically bound using alphabeticShortcut. See b/21330356.
- dir = DirectoryFragment.get(getFragmentManager());
- dir.pasteFromClipboard();
+ dir = getDirectoryFragment();
+ if (dir != null) {
+ dir.pasteFromClipboard();
+ }
return true;
default:
return super.onKeyShortcut(keyCode, event);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
index bae334b3759f..62d1c5a2de79 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Metrics.java
@@ -354,7 +354,7 @@ public final class Metrics {
* a single ROOT_OTHER bucket.
*/
private static @Root int sanitizeRoot(Uri uri) {
- if (LauncherActivity.isLaunchUri(uri)) {
+ if (uri == null || LauncherActivity.isLaunchUri(uri)) {
return ROOT_NONE;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index c2aeb86a1f07..d3a35d95fe32 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -302,8 +302,10 @@ public class RootsFragment extends Fragment {
for (final RootInfo root : roots) {
final RootItem item = new RootItem(root);
if (root.isLibrary()) {
+ if (DEBUG) Log.d(TAG, "Adding " + root + " as library.");
libraries.add(item);
} else {
+ if (DEBUG) Log.d(TAG, "Adding " + root + " as non-library.");
others.add(item);
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 2aabc991431c..eef77600d700 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -66,6 +66,7 @@ import android.util.TypedValue;
import android.view.ActionMode;
import android.view.DragEvent;
import android.view.GestureDetector;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
@@ -99,7 +100,6 @@ import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.RootInfo;
import com.android.documentsui.services.FileOperationService;
import com.android.documentsui.services.FileOperations;
-
import com.google.common.collect.Lists;
import java.util.ArrayList;
@@ -529,6 +529,9 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
final Cursor cursor = mModel.getItem(modelId);
checkNotNull(cursor, "Cursor cannot be null.");
+ // TODO: Should this be happening in onSelectionChanged? Technically this callback is
+ // triggered on "silent" selection updates (i.e. we might be reacting to unfinalized
+ // selection changes here)
final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
if ((docFlags & Document.FLAG_SUPPORTS_DELETE) == 0) {
mNoDeleteCount += selected ? 1 : -1;
@@ -827,7 +830,6 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
@Override
public void initDocumentHolder(DocumentHolder holder) {
holder.addEventListener(mItemEventListener);
- holder.addOnKeyListener(mSelectionManager);
}
@Override
@@ -1230,7 +1232,12 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
private class ItemEventListener implements DocumentHolder.EventListener {
@Override
public boolean onActivate(DocumentHolder doc) {
- handleViewItem(doc.modelId);
+ // Toggle selection if we're in selection mode, othewise, view item.
+ if (mSelectionManager.hasSelection()) {
+ mSelectionManager.toggleSelection(doc.modelId);
+ } else {
+ handleViewItem(doc.modelId);
+ }
return true;
}
@@ -1240,6 +1247,128 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
mSelectionManager.setSelectionRangeBegin(doc.getAdapterPosition());
return true;
}
+
+ @Override
+ public boolean onKey(DocumentHolder doc, int keyCode, KeyEvent event) {
+ // Only handle key-down events. This is simpler, consistent with most other UIs, and
+ // enables the handling of repeated key events from holding down a key.
+ if (event.getAction() != KeyEvent.ACTION_DOWN) {
+ return false;
+ }
+
+ boolean handled = false;
+ if (Events.isNavigationKeyCode(keyCode)) {
+ // Find the target item and focus it.
+ int endPos = findTargetPosition(doc.itemView, keyCode);
+
+ if (endPos != RecyclerView.NO_POSITION) {
+ focusItem(endPos);
+
+ // Handle any necessary adjustments to selection.
+ boolean extendSelection = event.isShiftPressed();
+ if (extendSelection) {
+ int startPos = doc.getAdapterPosition();
+ mSelectionManager.selectRange(startPos, endPos);
+ }
+ handled = true;
+ }
+ } else {
+ // Handle enter key events
+ if (keyCode == KeyEvent.KEYCODE_ENTER) {
+ handled = onActivate(doc);
+ }
+ }
+
+ return handled;
+ }
+
+ /**
+ * Finds the destination position where the focus should land for a given navigation event.
+ *
+ * @param view The view that received the event.
+ * @param keyCode The key code for the event.
+ * @return The adapter position of the destination item. Could be RecyclerView.NO_POSITION.
+ */
+ private int findTargetPosition(View view, int keyCode) {
+ if (keyCode == KeyEvent.KEYCODE_MOVE_HOME) {
+ return 0;
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_MOVE_END) {
+ return mAdapter.getItemCount() - 1;
+ }
+
+ // Find a navigation target based on the arrow key that the user pressed.
+ int searchDir = -1;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_UP:
+ searchDir = View.FOCUS_UP;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ searchDir = View.FOCUS_DOWN;
+ break;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ searchDir = View.FOCUS_LEFT;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ searchDir = View.FOCUS_RIGHT;
+ break;
+ }
+
+ if (searchDir != -1) {
+ View targetView = view.focusSearch(searchDir);
+ // TargetView can be null, for example, if the user pressed <down> at the bottom
+ // of the list.
+ if (targetView != null) {
+ // Ignore navigation targets that aren't items in the RecyclerView.
+ if (targetView.getParent() == mRecView) {
+ return mRecView.getChildAdapterPosition(targetView);
+ }
+ }
+ }
+
+ return RecyclerView.NO_POSITION;
+ }
+
+ /**
+ * Requests focus for the item in the given adapter position, scrolling the RecyclerView if
+ * necessary.
+ *
+ * @param pos
+ */
+ public void focusItem(final int pos) {
+ // If the item is already in view, focus it; otherwise, scroll to it and focus it.
+ RecyclerView.ViewHolder vh = mRecView.findViewHolderForAdapterPosition(pos);
+ if (vh != null) {
+ vh.itemView.requestFocus();
+ } else {
+ mRecView.smoothScrollToPosition(pos);
+ // Set a one-time listener to request focus when the scroll has completed.
+ mRecView.addOnScrollListener(
+ new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged (RecyclerView view, int newState) {
+ if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+ // When scrolling stops, find the item and focus it.
+ RecyclerView.ViewHolder vh =
+ view.findViewHolderForAdapterPosition(pos);
+ if (vh != null) {
+ vh.itemView.requestFocus();
+ } else {
+ // This might happen in weird corner cases, e.g. if the user is
+ // scrolling while a delete operation is in progress. In that
+ // case, just don't attempt to focus the missing item.
+ Log.w(
+ TAG, "Unable to focus position " + pos + " after a scroll");
+ }
+ view.removeOnScrollListener(this);
+ }
+ }
+ });
+ }
+ }
+
+
}
private final class ModelUpdateListener implements Model.UpdateListener {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java
index 8acf1af8713b..1bfc6e909c5a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java
@@ -84,13 +84,7 @@ public abstract class DocumentHolder
public boolean onKey(View v, int keyCode, KeyEvent event) {
// Event listener should always be set.
checkNotNull(mEventListener);
- // Intercept enter key-up events, and treat them as clicks. Forward other events.
- if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_ENTER) {
- return mEventListener.onActivate(this);
- } else if (mKeyListener != null) {
- return mKeyListener.onKey(v, keyCode, event);
- }
- return false;
+ return mEventListener.onKey(this, keyCode, event);
}
public void addEventListener(DocumentHolder.EventListener listener) {
@@ -159,15 +153,29 @@ public abstract class DocumentHolder
*/
interface EventListener {
/**
+ * Handles activation events on the document holder.
+ *
* @param doc The target DocumentHolder
* @return Whether the event was handled.
*/
public boolean onActivate(DocumentHolder doc);
/**
+ * Handles selection events on the document holder.
+ *
* @param doc The target DocumentHolder
* @return Whether the event was handled.
*/
public boolean onSelect(DocumentHolder doc);
+
+ /**
+ * Handles key events on the document holder.
+ *
+ * @param doc The target DocumentHolder.
+ * @param keyCode Key code for the event.
+ * @param event KeyEvent for the event.
+ * @return Whether the event was handled.
+ */
+ public boolean onKey(DocumentHolder doc, int keyCode, KeyEvent event);
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
index eea91a015efd..516b25e6f572 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -34,7 +34,6 @@ import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
-import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -57,7 +56,7 @@ import java.util.Set;
* Additionally it can be configured to restrict selection to a single element, @see
* #setSelectMode.
*/
-public final class MultiSelectManager implements View.OnKeyListener {
+public final class MultiSelectManager {
/** Selection mode for multiple select. **/
public static final int MODE_MULTIPLE = 0;
@@ -239,7 +238,8 @@ public final class MultiSelectManager implements View.OnKeyListener {
}
/**
- * Clears the selection, without notifying anyone.
+ * Clears the selection, without notifying selection listeners. UI elements still need to be
+ * notified about state changes so that they can update their appearance.
*/
private void clearSelectionQuietly() {
mRanger = null;
@@ -248,10 +248,10 @@ public final class MultiSelectManager implements View.OnKeyListener {
return;
}
- Selection intermediateSelection = getSelection(new Selection());
+ Selection oldSelection = getSelection(new Selection());
mSelection.clear();
- for (String id: intermediateSelection.getAll()) {
+ for (String id: oldSelection.getAll()) {
notifyItemStateChanged(id, false);
}
}
@@ -334,21 +334,7 @@ public final class MultiSelectManager implements View.OnKeyListener {
if (mSelection.contains(modelId)) {
changed = attemptDeselect(modelId);
} else {
- boolean canSelect = notifyBeforeItemStateChange(modelId, true);
- if (!canSelect) {
- return;
- }
- if (mSingleSelect && hasSelection()) {
- clearSelectionQuietly();
- }
-
- // Here we're already in selection mode. In that case
- // When a simple click/tap (without SHIFT) creates causes
- // an item to be selected.
- // By recreating Ranger at this point, we allow the user to create
- // multiple separate contiguous ranges with SHIFT+Click & Click.
- selectAndNotify(modelId);
- changed = true;
+ changed = attemptSelect(modelId);
}
if (changed) {
@@ -357,9 +343,46 @@ public final class MultiSelectManager implements View.OnKeyListener {
}
/**
- * Sets the magic location at which a selection range begins. This
- * value is consulted when determining how to extend, and modify
- * selection ranges.
+ * Handle a range selection event.
+ * <li> If the MSM is currently in single-select mode, only the last item in the range will
+ * actually be selected.
+ * <li>If a range selection is not already active, one will be started, and the given range of
+ * items will be selected. The given startPos becomes the anchor for the range selection.
+ * <li>If a range selection is already active, the anchor is not changed. The range is extended
+ * from its current anchor to endPos.
+ *
+ * @param startPos
+ * @param endPos
+ */
+ public void selectRange(int startPos, int endPos) {
+ // In single-select mode, just select the last item in the range.
+ if (mSingleSelect) {
+ attemptSelect(mAdapter.getModelId(endPos));
+ return;
+ }
+
+ // In regular (i.e. multi-select) mode
+ if (!isRangeSelectionActive()) {
+ // If a range selection isn't active, start one up
+ attemptSelect(mAdapter.getModelId(startPos));
+ setSelectionRangeBegin(startPos);
+ }
+ // Extend the range selection
+ mRanger.snapSelection(endPos);
+ notifySelectionChanged();
+ }
+
+ /**
+ * @return Whether or not there is a current range selection active.
+ */
+ private boolean isRangeSelectionActive() {
+ return mRanger != null;
+ }
+
+ /**
+ * Sets the magic location at which a selection range begins (the selection anchor). This value
+ * is consulted when determining how to extend, and modify selection ranges. Calling this when a
+ * range selection is active will reset the range selection.
*
* @throws IllegalStateException if {@code position} is not already be selected
* @param position
@@ -434,6 +457,24 @@ public final class MultiSelectManager implements View.OnKeyListener {
}
}
+ /**
+ * @param id
+ * @return True if the update was applied.
+ */
+ private boolean attemptSelect(String id) {
+ checkArgument(id != null);
+ boolean canSelect = notifyBeforeItemStateChange(id, true);
+ if (!canSelect) {
+ return false;
+ }
+ if (mSingleSelect && hasSelection()) {
+ clearSelectionQuietly();
+ }
+
+ selectAndNotify(id);
+ return true;
+ }
+
private boolean notifyBeforeItemStateChange(String id, boolean nextState) {
int lastListener = mCallbacks.size() - 1;
for (int i = lastListener; i > -1; i--) {
@@ -786,12 +827,10 @@ public final class MultiSelectManager implements View.OnKeyListener {
Point createAbsolutePoint(Point relativePoint);
Rect getAbsoluteRectForChildViewAt(int index);
int getAdapterPositionAt(int index);
- int getAdapterPositionForChildView(View view);
int getColumnCount();
int getRowCount();
int getChildCount();
int getVisibleChildCount();
- void focusItem(int position);
/**
* Layout items are excluded from the GridModel.
*/
@@ -812,17 +851,8 @@ public final class MultiSelectManager implements View.OnKeyListener {
}
@Override
- public int getAdapterPositionForChildView(View view) {
- if (view.getParent() == mView) {
- return mView.getChildAdapterPosition(view);
- } else {
- return RecyclerView.NO_POSITION;
- }
- }
-
- @Override
public int getAdapterPositionAt(int index) {
- return getAdapterPositionForChildView(mView.getChildAt(index));
+ return mView.getChildAdapterPosition(mView.getChildAt(index));
}
@Override
@@ -921,39 +951,6 @@ public final class MultiSelectManager implements View.OnKeyListener {
}
@Override
- public void focusItem(final int pos) {
- // If the item is already in view, focus it; otherwise, scroll to it and focus it.
- RecyclerView.ViewHolder vh = mView.findViewHolderForAdapterPosition(pos);
- if (vh != null) {
- vh.itemView.requestFocus();
- } else {
- mView.smoothScrollToPosition(pos);
- // Set a one-time listener to request focus when the scroll has completed.
- mView.addOnScrollListener(
- new RecyclerView.OnScrollListener() {
- @Override
- public void onScrollStateChanged (RecyclerView view, int newState) {
- if (newState == RecyclerView.SCROLL_STATE_IDLE) {
- // When scrolling stops, find the item and focus it.
- RecyclerView.ViewHolder vh =
- view.findViewHolderForAdapterPosition(pos);
- if (vh != null) {
- vh.itemView.requestFocus();
- } else {
- // This might happen in weird corner cases, e.g. if the user is
- // scrolling while a delete operation is in progress. In that
- // case, just don't attempt to focus the missing item.
- Log.w(
- TAG, "Unable to focus position " + pos + " after a scroll");
- }
- view.removeOnScrollListener(this);
- }
- }
- });
- }
- }
-
- @Override
public boolean isLayoutItem(int pos) {
// The band selection model only operates on documents and directories. Exclude other
// types of adapter items (e.g. whitespace items like dividers).
@@ -1907,99 +1904,4 @@ public final class MultiSelectManager implements View.OnKeyListener {
return true;
}
}
-
- // TODO: Might have to move this to a more global level. e.g. What should happen if the
- // user taps a file and then presses shift-down? Currently the RecyclerView never even sees
- // the key event. Perhaps install a global key handler to catch those events while in
- // selection mode?
- @Override
- public boolean onKey(View view, int keyCode, KeyEvent event) {
- // Listen for key-down events. This allows the handler to respond appropriately when
- // the user holds down the arrow keys for navigation.
- if (event.getAction() != KeyEvent.ACTION_DOWN) {
- return false;
- }
-
- // Here we unpack information from the event and pass it to an more
- // easily tested method....basically eliminating the need to synthesize
- // events and views and so on in our tests.
- int endPos = findTargetPosition(view, keyCode);
- if (endPos == RecyclerView.NO_POSITION) {
- // If there is no valid navigation target, don't handle the keypress.
- return false;
- }
-
- int startPos = mEnvironment.getAdapterPositionForChildView(view);
-
- return changeFocus(startPos, endPos, event.isShiftPressed());
- }
-
- /**
- * @param startPosition The current focus position.
- * @param targetPosition The adapter position to focus.
- * @param extendSelection
- */
- @VisibleForTesting
- boolean changeFocus(int startPosition, int targetPosition, boolean extendSelection) {
- // Focus the new file.
- mEnvironment.focusItem(targetPosition);
-
- if (extendSelection) {
- if (mSingleSelect) {
- // We're in single select and have an existing selection.
- // Our best guess as to what the user would expect is to advance the selection.
- clearSelection();
- toggleSelection(targetPosition);
- } else {
- if (!hasSelection()) {
- // No selection - start a selection when the user presses shift-arrow.
- toggleSelection(startPosition);
- setSelectionRangeBegin(startPosition);
- }
- mRanger.snapSelection(targetPosition);
- notifySelectionChanged();
- }
- }
-
- return true;
- }
-
- /**
- * Returns the adapter position that the key combo is targeted at.
- */
- private int findTargetPosition(View view, int keyCode) {
- int position = RecyclerView.NO_POSITION;
- if (keyCode == KeyEvent.KEYCODE_MOVE_HOME) {
- position = 0;
- } else if (keyCode == KeyEvent.KEYCODE_MOVE_END) {
- position = mAdapter.getItemCount() - 1;
- } else {
- // Find a navigation target based on the arrow key that the user pressed. Ignore
- // navigation targets that aren't items in the recycler view.
- int searchDir = -1;
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_UP:
- searchDir = View.FOCUS_UP;
- break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- searchDir = View.FOCUS_DOWN;
- break;
- case KeyEvent.KEYCODE_DPAD_LEFT:
- searchDir = View.FOCUS_LEFT;
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- searchDir = View.FOCUS_RIGHT;
- break;
- }
- if (searchDir != -1) {
- View targetView = view.focusSearch(searchDir);
- // TargetView can be null, for example, if the user pressed <down> at the bottom of
- // the list.
- if (targetView != null) {
- position = mEnvironment.getAdapterPositionForChildView(targetView);
- }
- }
- }
- return position;
- }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index 12c0b8fcd228..3f14a5506adf 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -176,6 +176,7 @@ public class RootInfo implements Durable, Parcelable {
} else if (isExternalStorage()) {
derivedIcon = R.drawable.ic_root_smartphone;
derivedType = TYPE_LOCAL;
+ // TODO: Apply SD card icon to SD devices.
} else if (isDownloads()) {
derivedIcon = R.drawable.ic_root_download;
derivedType = TYPE_DOWNLOADS;
@@ -244,6 +245,10 @@ public class RootInfo implements Durable, Parcelable {
|| derivedType == TYPE_RECENTS || derivedType == TYPE_DOWNLOADS;
}
+ public boolean hasSettings() {
+ return (flags & Root.FLAG_HAS_SETTINGS) != 0;
+ }
+
public Drawable loadIcon(Context context) {
if (derivedIcon != 0) {
return context.getDrawable(derivedIcon);
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
index ce5472580731..042ec85ad212 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
@@ -20,7 +20,6 @@ import static com.android.documentsui.StubProvider.ROOT_0_ID;
import static com.android.documentsui.StubProvider.ROOT_1_ID;
import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiObjectNotFoundException;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.LargeTest;
@@ -31,9 +30,6 @@ public class SearchViewUiTest extends InstrumentationTestCase {
private UiTestEnvironment mEnv;
- private UiObject mDocsList;
- private UiObject mMessageTextView;
-
@Override
public void setUp() throws Exception {
super.setUp();
@@ -151,4 +147,15 @@ public class SearchViewUiTest extends InstrumentationTestCase {
mEnv.bot().openRoot(ROOT_0_ID);
mEnv.assertDefaultContentOfTestDir0();
}
+
+ public void testSearchIconVisible_RootWithSearchSupport() throws Exception {
+ mEnv.bot().openRoot(ROOT_0_ID);
+ mEnv.bot().assertSearchTextFiledAndIcon(false, true);
+ }
+
+ public void testSearchIconHidden_RootNoSearchSupport() throws Exception {
+ mEnv.bot().openRoot(ROOT_1_ID);
+ mEnv.bot().assertSearchTextFiledAndIcon(false, false);
+ }
+
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index 980627b17e7c..98554276e71c 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -130,6 +130,11 @@ public class StubProvider extends DocumentsProvider {
Log.i(TAG, "Created new root directory @ " + file.getPath());
}
final RootInfo rootInfo = new RootInfo(file, getSize(rootId));
+
+ if(rootId.equals(ROOT_1_ID)) {
+ rootInfo.setSearchEnabled(false);
+ }
+
mStorage.put(rootInfo.document.documentId, rootInfo.document);
mRoots.put(rootId, rootInfo);
}
@@ -152,8 +157,7 @@ public class StubProvider extends DocumentsProvider {
final RootInfo info = entry.getValue();
final RowBuilder row = result.newRow();
row.add(Root.COLUMN_ROOT_ID, id);
- row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD
- | Root.FLAG_SUPPORTS_SEARCH);
+ row.add(Root.COLUMN_FLAGS, info.flags);
row.add(Root.COLUMN_TITLE, id);
row.add(Root.COLUMN_DOCUMENT_ID, info.document.documentId);
row.add(Root.COLUMN_AVAILABLE_BYTES, info.getRemainingCapacity());
@@ -705,22 +709,33 @@ public class StubProvider extends DocumentsProvider {
}
final static class RootInfo {
+ private static final int DEFAULT_ROOTS_FLAGS = Root.FLAG_SUPPORTS_SEARCH
+ | Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD;
+
public final String name;
public final StubDocument document;
public long capacity;
public long size;
+ public int flags;
RootInfo(File file, long capacity) {
this.name = file.getName();
this.capacity = 1024 * 1024;
- this.document = StubDocument.createRootDocument(file, this);
+ this.flags = DEFAULT_ROOTS_FLAGS;
this.capacity = capacity;
this.size = 0;
+ this.document = StubDocument.createRootDocument(file, this);
}
public long getRemainingCapacity() {
return capacity - size;
}
+
+ public void setSearchEnabled(boolean enabled) {
+ flags = enabled ? (flags | Root.FLAG_SUPPORTS_SEARCH)
+ : (flags & ~Root.FLAG_SUPPORTS_SEARCH);
+ }
+
}
final static class StubDocument {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
index f12ae106bb00..d609fa846591 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
@@ -150,7 +150,6 @@ class UiBot {
void assertSearchTextFiledAndIcon(boolean searchTextFieldExists, boolean searchIconExists) {
assertEquals(searchTextFieldExists, findSearchViewTextField().exists());
assertEquals(searchIconExists, findSearchViewIcon().exists());
-
}
void assertHasDocuments(String... labels) throws UiObjectNotFoundException {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DocumentHolderTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DocumentHolderTest.java
index 16efc6e98174..87cd42f08079 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DocumentHolderTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/DocumentHolderTest.java
@@ -22,6 +22,7 @@ import android.graphics.Rect;
import android.os.SystemClock;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
@@ -130,5 +131,10 @@ public class DocumentHolderTest extends AndroidTestCase {
return true;
}
+ @Override
+ public boolean onKey(DocumentHolder doc, int keyCode, KeyEvent event) {
+ return false;
+ }
+
}
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
index d3ef9aa64ffb..b1cb29e775b5 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
@@ -173,12 +173,6 @@ public class MultiSelectManagerTest extends AndroidTestCase {
assertRangeSelection(0, 7);
}
- public void testKeyboardSelection() {
- // This simulates shift-navigation.
- keyToPosition(5, 10, true);
- assertRangeSelection(5, 10);
- }
-
public void testSingleSelectMode() {
mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE);
mManager.addCallback(mCallback);
@@ -195,14 +189,6 @@ public class MultiSelectManagerTest extends AndroidTestCase {
assertSelection(items.get(20));
}
- public void testSingleSelectMode_ShiftDoesNotExtendSelection() {
- mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE);
- mManager.addCallback(mCallback);
- longPress(20);
- keyToPosition(20, 22, true);
- assertSelection(items.get(22));
- }
-
public void testProvisionalSelection() {
Selection s = mManager.getSelection();
assertSelection();
@@ -263,10 +249,6 @@ public class MultiSelectManagerTest extends AndroidTestCase {
mManager.onSingleTapUp(TestInputEvent.shiftClick(position));
}
- private void keyToPosition(int startPos, int endPos, boolean shift) {
- mManager.changeFocus(startPos, endPos, shift);
- }
-
private void assertSelected(String... expected) {
for (int i = 0; i < expected.length; i++) {
Selection selection = mManager.getSelection();
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
index 7920c50fb9a8..353d4bdfb0b1 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
@@ -23,7 +23,6 @@ import android.graphics.Rect;
import android.support.v7.widget.RecyclerView.OnScrollListener;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
-import android.view.View;
import com.android.documentsui.dirlist.MultiSelectManager.GridModel;
@@ -326,16 +325,6 @@ public class MultiSelectManager_GridModelTest extends AndroidTestCase {
}
@Override
- public int getAdapterPositionForChildView(View view) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void focusItem(int i) {
- throw new UnsupportedOperationException();
- }
-
- @Override
public boolean isLayoutItem(int adapterPosition) {
return false;
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
index 0e795615a137..8e624a036331 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
@@ -19,7 +19,6 @@ package com.android.documentsui.dirlist;
import android.graphics.Point;
import android.graphics.Rect;
import android.support.v7.widget.RecyclerView.OnScrollListener;
-import android.view.View;
import com.android.documentsui.dirlist.MultiSelectManager.SelectionEnvironment;
@@ -83,11 +82,6 @@ public class TestSelectionEnvironment implements SelectionEnvironment {
}
@Override
- public int getAdapterPositionForChildView(View view) {
- return 0;
- }
-
- @Override
public int getColumnCount() {
return 0;
}
@@ -108,10 +102,6 @@ public class TestSelectionEnvironment implements SelectionEnvironment {
}
@Override
- public void focusItem(int position) {
- }
-
- @Override
public boolean isLayoutItem(int adapterPosition) {
return false;
}
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 56e5a9b6c58d..f89934dc50e4 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -134,9 +134,6 @@ public class ExternalStorageProvider extends DocumentsProvider {
final String rootId;
final String title;
if (volume.getType() == VolumeInfo.TYPE_EMULATED) {
- // save off the primary volume for subsequent "Home" dir initialization.
- primaryVolume = volume;
-
// We currently only support a single emulated volume mounted at
// a time, and it's always considered the primary
rootId = ROOT_ID_PRIMARY_EMULATED;
@@ -167,9 +164,14 @@ public class ExternalStorageProvider extends DocumentsProvider {
mRoots.put(rootId, root);
root.rootId = rootId;
- root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
+ root.flags = Root.FLAG_LOCAL_ONLY
| Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
+ if (volume.isPrimary()) {
+ // save off the primary volume for subsequent "Home" dir initialization.
+ primaryVolume = volume;
+ root.flags |= Root.FLAG_ADVANCED;
+ }
// Dunno when this would NOT be the case, but never hurts to be correct.
if (volume.isMountedWritable()) {
root.flags |= Root.FLAG_SUPPORTS_CREATE;
diff --git a/packages/PrintSpooler/res/color/item_text_color.xml b/packages/PrintSpooler/res/color/item_text_color.xml
deleted file mode 100644
index f580fbd75577..000000000000
--- a/packages/PrintSpooler/res/color/item_text_color.xml
+++ /dev/null
@@ -1,20 +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_enabled="true" android:color="#333333" />
- <item android:color="#888888"/>
-</selector> \ No newline at end of file
diff --git a/packages/PrintSpooler/res/drawable/ic_info.xml b/packages/PrintSpooler/res/drawable/ic_info.xml
index 2ecd1c79d23e..d64435b83e74 100644
--- a/packages/PrintSpooler/res/drawable/ic_info.xml
+++ b/packages/PrintSpooler/res/drawable/ic_info.xml
@@ -16,9 +16,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportWidth="24"
+ android:viewportHeight="24">
<path
- android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"
- android:fillColor="#757575"/>
+ android:fillColor="@android:color/white"
+ android:pathData="M11,17l2,0l0,-6l-2,0l0,6.0zm1,-15.0C6.48,2 2,6.48 2,12.0s4.48,10 10,10 10,-4.48 10,-10.0S17.52,2 12,2.0zm0,18.0c-4.41,0 -8,-3.59 -8,-8.0s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8.0zM11,9l2,0L13,7l-2,0l0,2.0z"/>
</vector> \ No newline at end of file
diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
index e0efbc42a627..defbf8db0026 100644
--- a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
+++ b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
@@ -28,7 +28,6 @@
android:layout_width="32dip"
android:layout_height="32dip"
android:layout_gravity="center_vertical"
- android:layout_marginEnd="8dip"
android:duplicateParentState="true"
android:contentDescription="@null"
android:visibility="invisible">
@@ -38,6 +37,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
+ android:layout_marginStart="8dip"
android:duplicateParentState="true">
<TextView
diff --git a/packages/PrintSpooler/res/layout/printer_list_item.xml b/packages/PrintSpooler/res/layout/printer_list_item.xml
index 50f44c21656c..1209aa6f0fa2 100644
--- a/packages/PrintSpooler/res/layout/printer_list_item.xml
+++ b/packages/PrintSpooler/res/layout/printer_list_item.xml
@@ -25,20 +25,22 @@
<ImageView
android:id="@+id/icon"
- android:layout_width="32dip"
- android:layout_height="32dip"
+ android:layout_width="40dip"
+ android:layout_height="40dip"
android:layout_gravity="center_vertical"
- android:layout_marginEnd="8dip"
+ android:layout_marginTop="8dip"
+ android:layout_marginBottom="8dip"
android:duplicateParentState="true"
android:contentDescription="@null"
android:visibility="invisible">
</ImageView>
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
+ <RelativeLayout
+ android:layout_width="0dip"
android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="16dip"
android:duplicateParentState="true">
<TextView
@@ -49,7 +51,10 @@
android:singleLine="true"
android:ellipsize="end"
android:textIsSelectable="false"
- android:gravity="top|start"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentStart="true"
+ android:fadingEdge="horizontal"
+ android:textAlignment="viewStart"
android:textColor="?android:attr/textColorPrimary"
android:duplicateParentState="true">
</TextView>
@@ -58,24 +63,30 @@
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_below="@id/title"
+ android:layout_alignParentStart="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:singleLine="true"
android:ellipsize="end"
android:textIsSelectable="false"
android:visibility="gone"
android:textColor="?android:attr/textColorSecondary"
+ android:textAlignment="viewStart"
android:duplicateParentState="true">
</TextView>
- </LinearLayout>
+ </RelativeLayout>
<ImageView
android:id="@+id/more_info"
- android:layout_width="24dip"
- android:layout_height="24dip"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
+ android:paddingLeft="16dip"
android:contentDescription="@string/printer_info_desc"
android:src="@drawable/ic_info"
+ android:tint="?android:attr/colorControlNormal"
+ android:tintMode="src_in"
android:visibility="gone">
</ImageView>
diff --git a/packages/PrintSpooler/res/values-ca/arrays.xml b/packages/PrintSpooler/res/values-ca/arrays.xml
deleted file mode 100644
index c1b149c59960..000000000000
--- a/packages/PrintSpooler/res/values-ca/arrays.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<resources>
-
- <string-array name="pdf_printer_media_sizes" translatable="false">
- <item>NA_LETTER</item>
- <item>NA_GOVT_LETTER</item>
- <item>NA_LEGAL</item>
- <item>NA_JUNIOR_LEGAL</item>
- <item>NA_LEDGER</item>
- <item>NA_TABLOID</item>
- <item>NA_INDEX_3X5</item>
- <item>NA_INDEX_4X6</item>
- <item>NA_INDEX_5X8</item>
- <item>NA_MONARCH</item>
- <item>NA_QUARTO</item>
- <item>NA_FOOLSCAP</item>
- </string-array>
-
-</resources>
diff --git a/packages/PrintSpooler/res/values-en-rCA/arrays.xml b/packages/PrintSpooler/res/values-en-rCA/arrays.xml
deleted file mode 100644
index d40278c3973d..000000000000
--- a/packages/PrintSpooler/res/values-en-rCA/arrays.xml
+++ /dev/null
@@ -1,33 +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.
--->
-<resources>
-
- <string-array name="pdf_printer_media_sizes" translatable="false">
- <item>NA_LETTER</item>
- <item>NA_GOVT_LETTER</item>
- <item>NA_LEGAL</item>
- <item>NA_JUNIOR_LEGAL</item>
- <item>NA_LEDGER</item>
- <item>NA_TABLOID</item>
- <item>NA_INDEX_3X5</item>
- <item>NA_INDEX_4X6</item>
- <item>NA_INDEX_5X8</item>
- <item>NA_MONARCH</item>
- <item>NA_QUARTO</item>
- <item>NA_FOOLSCAP</item>
- </string-array>
-
-</resources>
diff --git a/packages/PrintSpooler/res/values-en-rUS/arrays.xml b/packages/PrintSpooler/res/values-en-rUS/arrays.xml
deleted file mode 100644
index d40278c3973d..000000000000
--- a/packages/PrintSpooler/res/values-en-rUS/arrays.xml
+++ /dev/null
@@ -1,33 +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.
--->
-<resources>
-
- <string-array name="pdf_printer_media_sizes" translatable="false">
- <item>NA_LETTER</item>
- <item>NA_GOVT_LETTER</item>
- <item>NA_LEGAL</item>
- <item>NA_JUNIOR_LEGAL</item>
- <item>NA_LEDGER</item>
- <item>NA_TABLOID</item>
- <item>NA_INDEX_3X5</item>
- <item>NA_INDEX_4X6</item>
- <item>NA_INDEX_5X8</item>
- <item>NA_MONARCH</item>
- <item>NA_QUARTO</item>
- <item>NA_FOOLSCAP</item>
- </string-array>
-
-</resources>
diff --git a/packages/PrintSpooler/res/values-es-rUS/arrays.xml b/packages/PrintSpooler/res/values-es-rUS/arrays.xml
deleted file mode 100644
index c1b149c59960..000000000000
--- a/packages/PrintSpooler/res/values-es-rUS/arrays.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<resources>
-
- <string-array name="pdf_printer_media_sizes" translatable="false">
- <item>NA_LETTER</item>
- <item>NA_GOVT_LETTER</item>
- <item>NA_LEGAL</item>
- <item>NA_JUNIOR_LEGAL</item>
- <item>NA_LEDGER</item>
- <item>NA_TABLOID</item>
- <item>NA_INDEX_3X5</item>
- <item>NA_INDEX_4X6</item>
- <item>NA_INDEX_5X8</item>
- <item>NA_MONARCH</item>
- <item>NA_QUARTO</item>
- <item>NA_FOOLSCAP</item>
- </string-array>
-
-</resources>
diff --git a/packages/PrintSpooler/res/values-ja/arrays.xml b/packages/PrintSpooler/res/values-ja/arrays.xml
deleted file mode 100644
index 3187cbefaa1e..000000000000
--- a/packages/PrintSpooler/res/values-ja/arrays.xml
+++ /dev/null
@@ -1,41 +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.
--->
-<resources>
-
- <string-array name="pdf_printer_media_sizes" translatable="false">
- <item>JIS_B10</item>
- <item>JIS_B9</item>
- <item>JIS_B8</item>
- <item>JIS_B7</item>
- <item>JIS_B6</item>
- <item>JIS_B5</item>
- <item>JIS_B4</item>
- <item>JIS_B3</item>
- <item>JIS_B2</item>
- <item>JIS_B1</item>
- <item>JIS_B0</item>
- <item>JIS_EXEC</item>
- <item>JPN_CHOU4</item>
- <item>JPN_CHOU3</item>
- <item>JPN_CHOU2</item>
- <item>JPN_HAGAKI</item>
- <item>JPN_OUFUKU</item>
- <item>JPN_KAHU</item>
- <item>JPN_KAKU2</item>
- <item>JPN_YOU4</item>
- </string-array>
-
-</resources>
diff --git a/packages/PrintSpooler/res/values/arrays.xml b/packages/PrintSpooler/res/values/arrays.xml
index afe3c7122dd7..8658be46c6eb 100644
--- a/packages/PrintSpooler/res/values/arrays.xml
+++ b/packages/PrintSpooler/res/values/arrays.xml
@@ -121,38 +121,4 @@
<!-- Everything else is ISO -->
</string-array>
- <string-array name="pdf_printer_media_sizes" translatable="false">
- <item>ISO_A0</item>
- <item>ISO_A1</item>
- <item>ISO_A2</item>
- <item>ISO_A3</item>
- <item>ISO_A4</item>
- <item>ISO_A5</item>
- <item>ISO_A6</item>
- <item>ISO_A7</item>
- <item>ISO_A8</item>
- <item>ISO_A9</item>
- <item>ISO_A10</item>
- <item>ISO_B1</item>
- <item>ISO_B2</item>
- <item>ISO_B3</item>
- <item>ISO_B4</item>
- <item>ISO_B5</item>
- <item>ISO_B6</item>
- <item>ISO_B7</item>
- <item>ISO_B8</item>
- <item>ISO_B9</item>
- <item>ISO_B10</item>
- <item>ISO_C1</item>
- <item>ISO_C2</item>
- <item>ISO_C3</item>
- <item>ISO_C4</item>
- <item>ISO_C5</item>
- <item>ISO_C6</item>
- <item>ISO_C7</item>
- <item>ISO_C8</item>
- <item>ISO_C9</item>
- <item>ISO_C10</item>
- </string-array>
-
</resources>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 892be2d06fce..a1ea658e5aff 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -61,7 +61,9 @@ import android.text.TextUtils;
import android.text.TextUtils.SimpleStringSplitter;
import android.text.TextWatcher;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
+import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -2252,10 +2254,17 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
if (icon != null) {
- iconView.setImageDrawable(icon);
iconView.setVisibility(View.VISIBLE);
+ if (!isEnabled(position)) {
+ icon.mutate();
+
+ TypedValue value = new TypedValue();
+ getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true);
+ icon.setAlpha((int)(value.getFloat() * 255));
+ }
+ iconView.setImageDrawable(icon);
} else {
- iconView.setVisibility(View.INVISIBLE);
+ iconView.setVisibility(View.GONE);
}
return convertView;
@@ -2353,6 +2362,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
private PrinterInfo createFakePdfPrinter() {
+ ArraySet<MediaSize> allMediaSizes = MediaSize.getAllPredefinedSizes();
MediaSize defaultMediaSize = MediaSizeUtils.getDefault(PrintActivity.this);
PrinterId printerId = new PrinterId(getComponentName(), "PDF printer");
@@ -2360,11 +2370,9 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
PrinterCapabilitiesInfo.Builder builder =
new PrinterCapabilitiesInfo.Builder(printerId);
- String[] mediaSizeIds = getResources().getStringArray(R.array.pdf_printer_media_sizes);
- final int mediaSizeIdCount = mediaSizeIds.length;
- for (int i = 0; i < mediaSizeIdCount; i++) {
- String id = mediaSizeIds[i];
- MediaSize mediaSize = MediaSize.getStandardMediaSizeById(id);
+ final int mediaSizeCount = allMediaSizes.size();
+ for (int i = 0; i < mediaSizeCount; i++) {
+ MediaSize mediaSize = allMediaSizes.valueAt(i);
builder.addMediaSize(mediaSize, mediaSize.equals(defaultMediaSize));
}
@@ -2442,6 +2450,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
}
updateOptionsUi();
+ updateSummary();
}
private boolean capabilitiesChanged(PrinterCapabilitiesInfo oldCapabilities,
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index 1aec2531e2ca..4f7624adc9ad 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -37,6 +37,7 @@ import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -47,6 +48,7 @@ import android.printservice.PrintServiceInfo;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import android.util.TypedValue;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
@@ -544,7 +546,10 @@ public final class SelectPrinterActivity extends Activity {
final int printerCount = mPrinters.size();
for (int i = 0; i < printerCount; i++) {
PrinterInfo printer = mPrinters.get(i);
- if (printer.getName().toLowerCase().contains(constraintLowerCase)) {
+ String description = printer.getDescription();
+ if (printer.getName().toLowerCase().contains(constraintLowerCase)
+ || description != null && description.toLowerCase()
+ .contains(constraintLowerCase)) {
filteredPrinters.add(printer);
}
}
@@ -663,18 +668,28 @@ public final class SelectPrinterActivity extends Activity {
@Override
public void onClick(View v) {
try {
- startIntentSender(printer.getInfoIntent().getIntentSender(), null, 0, 0, 0);
+ startIntentSender(printer.getInfoIntent().getIntentSender(), null, 0, 0,
+ 0);
} catch (SendIntentException e) {
Log.e(LOG_TAG, "Could not execute pending info intent: %s", e);
}
}
});
+ } else {
+ moreInfoView.setVisibility(View.GONE);
}
ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
if (icon != null) {
- iconView.setImageDrawable(icon);
iconView.setVisibility(View.VISIBLE);
+ if (!isActionable(position)) {
+ icon.mutate();
+
+ TypedValue value = new TypedValue();
+ getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true);
+ icon.setAlpha((int)(value.getFloat() * 255));
+ }
+ iconView.setImageDrawable(icon);
} else {
iconView.setVisibility(View.GONE);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index 56c4edb7f377..5ffa581c2de3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -141,6 +141,16 @@ public class SettingsDrawerActivity extends Activity {
mCategoryListeners.remove(listener);
}
+ public void setIsDrawerPresent(boolean isPresent) {
+ if (isPresent) {
+ mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+ updateDrawer();
+ } else {
+ mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
+ mDrawerLayout = null;
+ }
+ }
+
public void openDrawer() {
if (mDrawerLayout != null) {
mDrawerLayout.openDrawer(Gravity.START);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 2e96f18bedb2..b270dd86bb8e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -730,6 +730,11 @@ public class SettingsBackupAgent extends BackupAgentHelper {
try {
int stateVersion = dataInput.readInt();
+ if (stateVersion > STATE_VERSION) {
+ // Constrain the maximum state version this backup agent
+ // can handle in case a newer or corrupt backup set existed
+ stateVersion = STATE_VERSION;
+ }
for (int i = 0; i < STATE_SIZES[stateVersion]; i++) {
stateChecksums[i] = dataInput.readLong();
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2713fb51991c..c74e4112dada 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -349,6 +349,12 @@
android:resizeable="true"
android:supportsPictureInPicture="true"
android:excludeFromRecents="true" />
+ <activity
+ android:name="com.android.systemui.tv.pip.PipOnboardingActivity"
+ android:exported="true"
+ android:theme="@style/PipTheme"
+ android:launchMode="singleTop"
+ android:excludeFromRecents="true" />
<!-- platform logo easter egg activity -->
<activity
diff --git a/packages/SystemUI/res/layout/tv_pip_menu.xml b/packages/SystemUI/res/layout/tv_pip_menu.xml
index a638d175c341..3562c644e61e 100644
--- a/packages/SystemUI/res/layout/tv_pip_menu.xml
+++ b/packages/SystemUI/res/layout/tv_pip_menu.xml
@@ -17,45 +17,37 @@
*/
-->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <FrameLayout
- android:layout_alignParentEnd="true"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:paddingStart="10dp"
- android:paddingEnd="10dp"
- android:background="#88FFFFFF">
- <LinearLayout
- android:orientation="vertical"
- android:layout_gravity="center_vertical"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content" >
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:paddingStart="10dp"
+ android:paddingEnd="10dp"
+ android:background="#88FFFFFF"
+ android:gravity="center_vertical" >
- <Button android:id="@+id/full"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:text="@string/pip_fullscreen"
- android:textSize="10sp"
- android:focusable="true" />
+ <Button android:id="@+id/full"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:text="@string/pip_fullscreen"
+ android:textSize="10sp"
+ android:focusable="true" />
- <Button android:id="@+id/exit"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:text="@string/pip_exit"
- android:textSize="10sp"
- android:focusable="true" />
+ <Button android:id="@+id/exit"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:text="@string/pip_exit"
+ android:textSize="10sp"
+ android:focusable="true" />
- <Button android:id="@+id/cancel"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:text="@string/pip_cancel"
- android:textSize="10sp"
- android:focusable="true" />
- </LinearLayout>
- </FrameLayout>
-</RelativeLayout>
+ <Button android:id="@+id/cancel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:text="@string/pip_cancel"
+ android:textSize="10sp"
+ android:focusable="true" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/tv_pip_onboarding.xml b/packages/SystemUI/res/layout/tv_pip_onboarding.xml
new file mode 100644
index 000000000000..ef395558af64
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_pip_onboarding.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#C00288D1"
+ android:gravity="center"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="30sp"
+ android:textColor="@android:color/white"
+ android:text="@string/pip_onboarding_title" />
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_sysbar_home" />
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="30dp"
+ android:textSize="13sp"
+ android:textColor="@android:color/white"
+ android:text="@string/pip_onboarding_description" />
+ <Button
+ android:id="@+id/close"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="15sp"
+ android:textAllCaps="true"
+ android:text="@string/pip_onboarding_button" />
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/tv_pip_overlay.xml b/packages/SystemUI/res/layout/tv_pip_overlay.xml
index e8691b511706..6d9c48d44dc6 100644
--- a/packages/SystemUI/res/layout/tv_pip_overlay.xml
+++ b/packages/SystemUI/res/layout/tv_pip_overlay.xml
@@ -17,17 +17,13 @@
*/
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/guide_overlay"
android:layout_width="match_parent"
- android:layout_height="match_parent">
- <TextView
- android:id="@+id/guide_overlay"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:padding="3dp"
- android:textSize="13sp"
- android:textColor="#111111"
- android:background="#99EEEEEE"
- android:text="@string/pip_hold_home" />
-</FrameLayout>
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:padding="3dp"
+ android:textSize="13sp"
+ android:textColor="#111111"
+ android:background="#99EEEEEE"
+ android:text="@string/pip_hold_home" />
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index ebe0d973e752..e8df01b19415 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -159,18 +159,6 @@
in from the bottom of the screen. -->
<integer name="recents_enter_from_home_transition_duration">100</integer>
- <!-- The duration for animating the task from the bottom of the screen when transitioning
- from home. -->
- <integer name="recents_task_enter_from_home_duration">225</integer>
-
- <!-- The stagger for each task when animating the task from the bottom of the screen when
- transitioning from home. -->
- <integer name="recents_task_enter_from_home_stagger_delay">12</integer>
-
- <!-- The duration of the animation of the tasks to the bottom of the screen when leaving
- Recents to go back to the Launcher. -->
- <integer name="recents_task_exit_to_home_duration">225</integer>
-
<!-- The min animation duration for animating the nav bar scrim in. -->
<integer name="recents_nav_bar_scrim_enter_duration">400</integer>
diff --git a/packages/SystemUI/res/values/strings_tv.xml b/packages/SystemUI/res/values/strings_tv.xml
index c64327de65da..7c4768d29cfe 100644
--- a/packages/SystemUI/res/values/strings_tv.xml
+++ b/packages/SystemUI/res/values/strings_tv.xml
@@ -17,6 +17,8 @@
*/
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Picture-in-Picture menu -->
+ <eat-comment />
<!-- Button to close PIP on PIP UI -->
<string name="pip_exit" translatable="false">Close PIP</string>
<!-- Button to move PIP screen to the fullscreen on PIP UI -->
@@ -29,4 +31,13 @@
<string name="pip_cancel" translatable="false">Cancel</string>
<!-- Overlay text on PIP -->
<string name="pip_hold_home" translatable="false">Hold HOME to control PIP</string>
+
+ <!-- Picture-in-Picture onboarding screen -->
+ <eat-comment />
+ <!-- Title for onboarding screen. -->
+ <string name="pip_onboarding_title" translatable="false">Picture-in-picture</string>
+ <!-- Description for onboarding screen. -->
+ <string name="pip_onboarding_description" translatable="false">Press and hold the HOME\nbutton to close or control it</string>
+ <!-- Button to close onboarding screen. -->
+ <string name="pip_onboarding_button" translatable="false">Got it</string>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 7933cc692f02..e2b377737dbe 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -43,6 +43,7 @@ public final class Prefs {
Key.DND_FAVORITE_BUCKET_INDEX,
Key.DND_NONE_SELECTED,
Key.DND_FAVORITE_ZEN,
+ Key.TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN,
})
public @interface Key {
String OVERVIEW_SEARCH_APP_WIDGET_ID = "searchAppWidgetId";
@@ -58,6 +59,7 @@ public final class Prefs {
String DND_FAVORITE_BUCKET_INDEX = "DndCountdownMinuteIndex";
String DND_NONE_SELECTED = "DndNoneSelected";
String DND_FAVORITE_ZEN = "DndFavoriteZen";
+ String TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN = "TvPictureInPictureOnboardingShown";
}
public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
index e288878a15ff..ee3eb02c8a38 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
@@ -39,7 +39,7 @@ import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.TaskViewAnimation;
+import com.android.systemui.recents.views.AnimationProps;
import java.util.ArrayList;
import java.util.Calendar;
@@ -224,7 +224,7 @@ public class RecentsHistoryAdapter extends RecyclerView.Adapter<RecentsHistoryAd
if (row.getViewType() == TASK_ROW_VIEW_TYPE) {
TaskRow taskRow = (TaskRow) row;
Task task = taskRow.task;
- mStack.removeTask(task, TaskViewAnimation.IMMEDIATE);
+ mStack.removeTask(task, AnimationProps.IMMEDIATE);
EventBus.getDefault().send(new DeleteTaskDataEvent(task));
i = removeTaskRow(i);
}
@@ -304,7 +304,7 @@ public class RecentsHistoryAdapter extends RecyclerView.Adapter<RecentsHistoryAd
public void onTaskRemoved(Task task, int position) {
// Since this is removed from the history, we need to update the stack as well to ensure
// that the model is correct. Since the stack is hidden, we can update it immediately.
- mStack.removeTask(task, TaskViewAnimation.IMMEDIATE);
+ mStack.removeTask(task, AnimationProps.IMMEDIATE);
removeTaskRow(position);
if (mRows.isEmpty()) {
dismissHistory();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index aa8efa711550..1f91dceb3e99 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -43,7 +43,7 @@ import com.android.systemui.recents.misc.NamedCounter;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.views.DropTarget;
-import com.android.systemui.recents.views.TaskViewAnimation;
+import com.android.systemui.recents.views.AnimationProps;
import java.util.ArrayList;
import java.util.Collections;
@@ -228,12 +228,12 @@ public class TaskStack {
* Notifies when a task has been removed from the stack.
*/
void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
- Task newFrontMostTask, TaskViewAnimation animation);
+ Task newFrontMostTask, AnimationProps animation);
/**
* Notifies when a task has been removed from the history.
*/
- void onHistoryTaskRemoved(TaskStack stack, Task removedTask, TaskViewAnimation animation);
+ void onHistoryTaskRemoved(TaskStack stack, Task removedTask, AnimationProps animation);
}
/**
@@ -531,7 +531,7 @@ public class TaskStack {
* Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
* how they should update themselves.
*/
- public void removeTask(Task t, TaskViewAnimation animation) {
+ public void removeTask(Task t, AnimationProps animation) {
if (mStackTaskList.contains(t)) {
boolean wasFrontMostTask = (getStackFrontMostTask(false /* includeFreeform */) == t);
removeTaskImpl(mStackTaskList, t);
@@ -575,7 +575,7 @@ public class TaskStack {
if (!newTasksMap.containsKey(task.key)) {
if (notifyStackChanges) {
mCb.onStackTaskRemoved(this, task, i == (taskCount - 1), null,
- TaskViewAnimation.IMMEDIATE);
+ AnimationProps.IMMEDIATE);
}
}
task.setGroup(null);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
index 2d4174296e7b..58ec8521ec7b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
@@ -28,7 +28,7 @@ import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.model.TaskStack.TaskStackCallbacks;
-import com.android.systemui.recents.views.TaskViewAnimation;
+import com.android.systemui.recents.views.AnimationProps;
import java.util.ArrayList;
import java.util.List;
@@ -137,7 +137,7 @@ public class TaskStackHorizontalGridView extends HorizontalGridView implements T
@Override
public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
- Task newFrontMostTask, TaskViewAnimation animation) {
+ Task newFrontMostTask, AnimationProps animation) {
getAdapter().notifyItemRemoved(stack.getStackTasks().indexOf(removedTask));
if (mFocusedTask == removedTask) {
resetFocusedTask(removedTask);
@@ -152,7 +152,7 @@ public class TaskStackHorizontalGridView extends HorizontalGridView implements T
}
@Override
- public void onHistoryTaskRemoved(TaskStack stack, Task removedTask, TaskViewAnimation animation) {
+ public void onHistoryTaskRemoved(TaskStack stack, Task removedTask, AnimationProps animation) {
//No history task on tv
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java
new file mode 100644
index 000000000000..93878c52d6de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.annotation.IntDef;
+import android.util.SparseArray;
+import android.util.SparseLongArray;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.systemui.Interpolators;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * The generic set of animation properties to animate a {@link View}. The animation can have
+ * different interpolators, start delays and durations for each of the different properties.
+ */
+public class AnimationProps {
+
+ public static final AnimationProps IMMEDIATE = new AnimationProps(0, Interpolators.LINEAR);
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ALL, TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z, ALPHA, SCALE, BOUNDS})
+ public @interface PropType {}
+
+ public static final int ALL = 0;
+ public static final int TRANSLATION_X = 1;
+ public static final int TRANSLATION_Y = 2;
+ public static final int TRANSLATION_Z = 3;
+ public static final int ALPHA = 4;
+ public static final int SCALE = 5;
+ public static final int BOUNDS = 6;
+
+ private SparseLongArray mPropStartDelay;
+ private SparseLongArray mPropDuration;
+ private SparseArray<Interpolator> mPropInterpolators;
+ private Animator.AnimatorListener mListener;
+
+ /**
+ * The builder constructor.
+ */
+ public AnimationProps() {}
+
+ /**
+ * Creates an animation with a default {@param duration} and {@param interpolator} for all
+ * properties in this animation.
+ */
+ public AnimationProps(int duration, Interpolator interpolator) {
+ this(0, duration, interpolator, null);
+ }
+
+ /**
+ * Creates an animation with a default {@param duration} and {@param interpolator} for all
+ * properties in this animation.
+ */
+ public AnimationProps(int duration, Interpolator interpolator,
+ Animator.AnimatorListener listener) {
+ this(0, duration, interpolator, listener);
+ }
+
+ /**
+ * Creates an animation with a default {@param startDelay}, {@param duration} and
+ * {@param interpolator} for all properties in this animation.
+ */
+ public AnimationProps(int startDelay, int duration, Interpolator interpolator) {
+ this(startDelay, duration, interpolator, null);
+ }
+
+ /**
+ * Creates an animation with a default {@param startDelay}, {@param duration} and
+ * {@param interpolator} for all properties in this animation.
+ */
+ public AnimationProps(int startDelay, int duration, Interpolator interpolator,
+ Animator.AnimatorListener listener) {
+ setStartDelay(ALL, startDelay);
+ setDuration(ALL, duration);
+ setInterpolator(ALL, interpolator);
+ setListener(listener);
+ }
+
+ /**
+ * Creates a new {@link AnimatorSet} that will animate the given animators. Callers need to
+ * manually apply the individual animation properties for each of the animators respectively.
+ */
+ public AnimatorSet createAnimator(List<Animator> animators) {
+ AnimatorSet anim = new AnimatorSet();
+ if (mListener != null) {
+ anim.addListener(mListener);
+ }
+ anim.playTogether(animators);
+ return anim;
+ }
+
+ /**
+ * Applies the specific start delay, duration and interpolator to the given {@param animator}
+ * for the specified {@param propertyType}.
+ */
+ public <T extends Animator> T apply(@PropType int propertyType, T animator) {
+ animator.setStartDelay(getStartDelay(propertyType));
+ animator.setDuration(getDuration(propertyType));
+ animator.setInterpolator(getInterpolator(propertyType));
+ return animator;
+ }
+
+ /**
+ * Sets a start delay for a specific property.
+ */
+ public AnimationProps setStartDelay(@PropType int propertyType, int startDelay) {
+ if (mPropStartDelay == null) {
+ mPropStartDelay = new SparseLongArray();
+ }
+ mPropStartDelay.append(propertyType, startDelay);
+ return this;
+ }
+
+ /**
+ * Returns the start delay for a specific property.
+ */
+ public long getStartDelay(@PropType int propertyType) {
+ if (mPropStartDelay != null) {
+ long startDelay = mPropStartDelay.get(propertyType, -1);
+ if (startDelay != -1) {
+ return startDelay;
+ }
+ return mPropStartDelay.get(ALL, 0);
+ }
+ return 0;
+ }
+
+ /**
+ * Sets a duration for a specific property.
+ */
+ public AnimationProps setDuration(@PropType int propertyType, int duration) {
+ if (mPropDuration == null) {
+ mPropDuration = new SparseLongArray();
+ }
+ mPropDuration.append(propertyType, duration);
+ return this;
+ }
+
+ /**
+ * Returns the duration for a specific property.
+ */
+ public long getDuration(@PropType int propertyType) {
+ if (mPropDuration != null) {
+ long duration = mPropDuration.get(propertyType, -1);
+ if (duration != -1) {
+ return duration;
+ }
+ return mPropDuration.get(ALL, 0);
+ }
+ return 0;
+ }
+
+ /**
+ * Sets an interpolator for a specific property.
+ */
+ public AnimationProps setInterpolator(@PropType int propertyType, Interpolator interpolator) {
+ if (mPropInterpolators == null) {
+ mPropInterpolators = new SparseArray<>();
+ }
+ mPropInterpolators.append(propertyType, interpolator);
+ return this;
+ }
+
+ /**
+ * Returns the interpolator for a specific property, falling back to the general interpolator
+ * if there is no specific property interpolator.
+ */
+ public Interpolator getInterpolator(@PropType int propertyType) {
+ if (mPropInterpolators != null) {
+ Interpolator interp = mPropInterpolators.get(propertyType);
+ if (interp != null) {
+ return interp;
+ }
+ return mPropInterpolators.get(ALL, Interpolators.LINEAR);
+ }
+ return Interpolators.LINEAR;
+ }
+
+ /**
+ * Sets an animator listener for this animation.
+ */
+ public AnimationProps setListener(Animator.AnimatorListener listener) {
+ mListener = listener;
+ return this;
+ }
+
+ /**
+ * Returns the animator listener for this animation.
+ */
+ public Animator.AnimatorListener getListener() {
+ return mListener;
+ }
+
+ /**
+ * Returns whether this animation has any duration.
+ */
+ public boolean isImmediate() {
+ int count = mPropDuration.size();
+ for (int i = 0; i < count; i++) {
+ if (mPropDuration.valueAt(i) > 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index e2ff52cefd23..5e113b997bce 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -497,8 +497,9 @@ public class RecentsView extends FrameLayout {
}
@Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
+ public void onDrawForeground(Canvas canvas) {
+ super.onDrawForeground(canvas);
+
ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
for (int i = visDockStates.size() - 1; i >= 0; i--) {
Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
@@ -530,8 +531,7 @@ public class RecentsView extends FrameLayout {
public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
// Hide the history button
- int taskViewExitToHomeDuration = getResources().getInteger(
- R.integer.recents_task_exit_to_home_duration);
+ int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
hideHistoryButton(taskViewExitToHomeDuration, false /* translate */);
animateBackgroundScrim(0f, taskViewExitToHomeDuration);
}
@@ -588,7 +588,7 @@ public class RecentsView extends FrameLayout {
tmpTransform.scale = 1f;
tmpTransform.rect.set(taskViewRect);
mTaskStackView.updateTaskViewToTransform(event.taskView, tmpTransform,
- new TaskViewAnimation(125, Interpolators.ALPHA_OUT,
+ new AnimationProps(125, Interpolators.ALPHA_OUT,
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -598,7 +598,7 @@ public class RecentsView extends FrameLayout {
event.task.key.id, dockState.createMode);
// Animate the stack accordingly
- TaskViewAnimation stackAnim = new TaskViewAnimation(
+ AnimationProps stackAnim = new AnimationProps(
TaskStackView.DEFAULT_SYNC_STACK_DURATION,
Interpolators.FAST_OUT_SLOW_IN);
mTaskStackView.getStack().removeTask(event.task, stackAnim);
@@ -646,9 +646,8 @@ public class RecentsView extends FrameLayout {
public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
if (!launchState.launchedFromAppWithThumbnail && mStack.getTaskCount() > 0) {
- int taskViewEnterFromHomeDuration = getResources().getInteger(
- R.integer.recents_task_enter_from_home_duration);
- animateBackgroundScrim(DEFAULT_SCRIM_ALPHA, taskViewEnterFromHomeDuration);
+ animateBackgroundScrim(DEFAULT_SCRIM_ALPHA,
+ TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index 682c29820ce3..0eae183e58d6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -20,8 +20,10 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Path;
import android.graphics.RectF;
import android.view.View;
+import android.view.animation.PathInterpolator;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -63,6 +65,22 @@ public class TaskStackAnimationHelper {
ReferenceCountedTrigger postAnimationTrigger);
}
+ private static final int FRAME_OFFSET_MS = 16;
+
+ public static final int ENTER_FROM_HOME_ALPHA_DURATION = 100;
+ public static final int ENTER_FROM_HOME_TRANSLATION_DURATION = 333;
+ private static final PathInterpolator ENTER_FROM_HOME_TRANSLATION_INTERPOLATOR =
+ new PathInterpolator(0, 0, 0, 1f);
+ private static final PathInterpolator ENTER_FROM_HOME_ALPHA_INTERPOLATOR =
+ new PathInterpolator(0, 0, 0.2f, 1f);
+
+ public static final int EXIT_TO_HOME_ALPHA_DURATION = 100;
+ public static final int EXIT_TO_HOME_TRANSLATION_DURATION = 150;
+ private static final PathInterpolator EXIT_TO_HOME_TRANSLATION_INTERPOLATOR =
+ new PathInterpolator(0.8f, 0, 0.6f, 1f);
+ private static final PathInterpolator EXIT_TO_HOME_ALPHA_INTERPOLATOR =
+ new PathInterpolator(0.4f, 0, 1f, 1f);
+
private TaskStackView mStackView;
private TaskViewTransform mTmpTransform = new TaskViewTransform();
@@ -157,15 +175,12 @@ public class TaskStackAnimationHelper {
R.integer.recents_task_enter_from_app_duration);
int taskViewEnterFromAffiliatedAppDuration = res.getInteger(
R.integer.recents_task_enter_from_affiliated_app_duration);
- int taskViewEnterFromHomeDuration = res.getInteger(
- R.integer.recents_task_enter_from_home_duration);
- int taskViewEnterFromHomeStaggerDelay = res.getInteger(
- R.integer.recents_task_enter_from_home_stagger_delay);
// Create enter animations for each of the views from front to back
List<TaskView> taskViews = mStackView.getTaskViews();
int taskViewCount = taskViews.size();
for (int i = taskViewCount - 1; i >= 0; i--) {
+ int taskIndexFromFront = taskViewCount - i - 1;
final TaskView tv = taskViews.get(i);
Task task = tv.getTask();
boolean currentTaskOccludesLaunchTarget = false;
@@ -186,7 +201,7 @@ public class TaskStackAnimationHelper {
} else {
// Animate the task up if it was occluding the launch target
if (currentTaskOccludesLaunchTarget) {
- TaskViewAnimation taskAnimation = new TaskViewAnimation(
+ AnimationProps taskAnimation = new AnimationProps(
taskViewEnterFromAffiliatedAppDuration, Interpolators.ALPHA_IN,
new AnimatorListenerAdapter() {
@Override
@@ -202,14 +217,16 @@ public class TaskStackAnimationHelper {
} else if (launchState.launchedFromHome) {
// Animate the tasks up
- int frontIndex = (taskViewCount - i - 1);
- int delay = frontIndex * taskViewEnterFromHomeStaggerDelay;
- int duration = taskViewEnterFromHomeDuration +
- frontIndex * taskViewEnterFromHomeStaggerDelay;
-
- TaskViewAnimation taskAnimation = new TaskViewAnimation(delay,
- duration, Interpolators.DECELERATE_QUINT,
- postAnimationTrigger.decrementOnAnimationEnd());
+ AnimationProps taskAnimation = new AnimationProps()
+ .setStartDelay(AnimationProps.ALPHA, taskIndexFromFront * FRAME_OFFSET_MS)
+ .setDuration(AnimationProps.ALPHA, ENTER_FROM_HOME_ALPHA_DURATION)
+ .setDuration(AnimationProps.BOUNDS, ENTER_FROM_HOME_TRANSLATION_DURATION -
+ (taskIndexFromFront * FRAME_OFFSET_MS))
+ .setInterpolator(AnimationProps.BOUNDS,
+ ENTER_FROM_HOME_TRANSLATION_INTERPOLATOR)
+ .setInterpolator(AnimationProps.ALPHA,
+ ENTER_FROM_HOME_ALPHA_INTERPOLATOR)
+ .setListener(postAnimationTrigger.decrementOnAnimationEnd());
postAnimationTrigger.increment();
mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
}
@@ -221,7 +238,6 @@ public class TaskStackAnimationHelper {
*/
public void startExitToHomeAnimation(boolean animated,
ReferenceCountedTrigger postAnimationTrigger) {
- Resources res = mStackView.getResources();
TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
TaskStackViewScroller stackScroller = mStackView.getScroller();
TaskStack stack = mStackView.getStack();
@@ -232,19 +248,32 @@ public class TaskStackAnimationHelper {
}
int offscreenY = stackLayout.mStackRect.bottom;
- int taskViewExitToHomeDuration = res.getInteger(
- R.integer.recents_task_exit_to_home_duration);
// Create the animations for each of the tasks
List<TaskView> taskViews = mStackView.getTaskViews();
int taskViewCount = taskViews.size();
for (int i = 0; i < taskViewCount; i++) {
+ int taskIndexFromFront = taskViewCount - i - 1;
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
- TaskViewAnimation taskAnimation = new TaskViewAnimation(
- animated ? taskViewExitToHomeDuration : 0, Interpolators.FAST_OUT_LINEAR_IN,
- postAnimationTrigger.decrementOnAnimationEnd());
- postAnimationTrigger.increment();
+
+ // Animate the tasks down
+ AnimationProps taskAnimation;
+ if (animated) {
+ taskAnimation = new AnimationProps()
+ .setStartDelay(AnimationProps.ALPHA, i * FRAME_OFFSET_MS)
+ .setDuration(AnimationProps.ALPHA, EXIT_TO_HOME_ALPHA_DURATION)
+ .setDuration(AnimationProps.BOUNDS, EXIT_TO_HOME_TRANSLATION_DURATION +
+ (taskIndexFromFront * FRAME_OFFSET_MS))
+ .setInterpolator(AnimationProps.BOUNDS,
+ EXIT_TO_HOME_TRANSLATION_INTERPOLATOR)
+ .setInterpolator(AnimationProps.ALPHA,
+ EXIT_TO_HOME_ALPHA_INTERPOLATOR)
+ .setListener(postAnimationTrigger.decrementOnAnimationEnd());
+ postAnimationTrigger.increment();
+ } else {
+ taskAnimation = AnimationProps.IMMEDIATE;
+ }
stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
null);
@@ -283,7 +312,7 @@ public class TaskStackAnimationHelper {
screenPinningRequested, postAnimationTrigger);
} else if (currentTaskOccludesLaunchTarget) {
// Animate this task out of view
- TaskViewAnimation taskAnimation = new TaskViewAnimation(
+ AnimationProps taskAnimation = new AnimationProps(
taskViewExitToAppDuration, Interpolators.ALPHA_OUT,
postAnimationTrigger.decrementOnAnimationEnd());
postAnimationTrigger.increment();
@@ -315,7 +344,7 @@ public class TaskStackAnimationHelper {
deleteTaskView.setClipViewInStack(false);
// Compose the new animation and transform and star the animation
- TaskViewAnimation taskAnimation = new TaskViewAnimation(taskViewRemoveAnimDuration,
+ AnimationProps taskAnimation = new AnimationProps(taskViewRemoveAnimDuration,
Interpolators.ALPHA_OUT, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -352,7 +381,7 @@ public class TaskStackAnimationHelper {
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
- TaskViewAnimation taskAnimation = new TaskViewAnimation(startDelayIncr * i,
+ AnimationProps taskAnimation = new AnimationProps(startDelayIncr * i,
historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN,
postAnimationTrigger.decrementOnAnimationEnd());
postAnimationTrigger.increment();
@@ -381,7 +410,7 @@ public class TaskStackAnimationHelper {
int taskViewCount = taskViews.size();
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
- TaskViewAnimation taskAnimation = new TaskViewAnimation(startDelayIncr * i,
+ AnimationProps taskAnimation = new AnimationProps(startDelayIncr * i,
historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN);
stackLayout.getStackTransform(tv.getTask(), stackScroller.getStackScroll(),
mTmpTransform, null);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 232b41648748..1c97b5a22ec7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -16,6 +16,10 @@
package com.android.systemui.recents.views;
+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;
+
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.ComponentName;
@@ -34,9 +38,9 @@ import android.util.MutableBoolean;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import com.android.systemui.Interpolators;
@@ -85,10 +89,6 @@ import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
import java.util.List;
-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;
-
/* The visual representation of a task stack view */
public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
@@ -124,7 +124,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
ArrayList<TaskView> mTaskViews = new ArrayList<>();
ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
ArraySet<Task.TaskKey> mIgnoreTasks = new ArraySet<>();
- TaskViewAnimation mDeferredTaskViewLayoutAnimation = null;
+ AnimationProps mDeferredTaskViewLayoutAnimation = null;
DozeTrigger mUIDozeTrigger;
Task mFocusedTask;
@@ -135,6 +135,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
boolean mTaskViewsClipDirty = true;
boolean mAwaitingFirstLayout = true;
+ boolean mInMeasureLayout = false;
boolean mEnterAnimationComplete = false;
boolean mTouchExplorationEnabled;
boolean mScreenPinningEnabled;
@@ -537,17 +538,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
if (tv == null) {
tv = mViewPool.pickUpViewFromPool(task, task);
if (task.isFreeformTask()) {
- tv.updateViewPropertiesToTaskTransform(transform, TaskViewAnimation.IMMEDIATE,
+ tv.updateViewPropertiesToTaskTransform(transform, AnimationProps.IMMEDIATE,
mRequestUpdateClippingListener);
} else {
if (Float.compare(transform.p, 0f) <= 0) {
tv.updateViewPropertiesToTaskTransform(
mLayoutAlgorithm.getBackOfStackTransform(),
- TaskViewAnimation.IMMEDIATE, mRequestUpdateClippingListener);
+ AnimationProps.IMMEDIATE, mRequestUpdateClippingListener);
} else {
tv.updateViewPropertiesToTaskTransform(
mLayoutAlgorithm.getFrontOfStackTransform(),
- TaskViewAnimation.IMMEDIATE, mRequestUpdateClippingListener);
+ AnimationProps.IMMEDIATE, mRequestUpdateClippingListener);
}
}
} else {
@@ -580,9 +581,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
* animations that are current running on those task views, and will ensure that the children
* {@link TaskView}s will match the set of visible tasks in the stack.
*
- * @see #relayoutTaskViews(TaskViewAnimation, ArraySet<Task.TaskKey>)
+ * @see #relayoutTaskViews(AnimationProps, ArraySet<Task.TaskKey>)
*/
- void relayoutTaskViews(TaskViewAnimation animation) {
+ void relayoutTaskViews(AnimationProps animation) {
relayoutTaskViews(animation, mIgnoreTasks);
}
@@ -594,7 +595,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
*
* @param ignoreTasksSet the set of tasks to ignore in the relayout
*/
- void relayoutTaskViews(TaskViewAnimation animation, ArraySet<Task.TaskKey> ignoreTasksSet) {
+ void relayoutTaskViews(AnimationProps animation, ArraySet<Task.TaskKey> ignoreTasksSet) {
// If we had a deferred animation, cancel that
mDeferredTaskViewLayoutAnimation = null;
@@ -623,17 +624,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
/**
* Posts an update to synchronize the {@link TaskView}s with the stack on the next frame.
*/
- void relayoutTaskViewsOnNextFrame(TaskViewAnimation animation) {
+ void relayoutTaskViewsOnNextFrame(AnimationProps animation) {
mDeferredTaskViewLayoutAnimation = animation;
invalidate();
}
/**
* Called to update a specific {@link TaskView} to a given {@link TaskViewTransform} with a
- * given set of {@link TaskViewAnimation} properties.
+ * given set of {@link AnimationProps} properties.
*/
public void updateTaskViewToTransform(TaskView taskView, TaskViewTransform transform,
- TaskViewAnimation animation) {
+ AnimationProps animation) {
taskView.updateViewPropertiesToTaskTransform(transform, animation,
mRequestUpdateClippingListener);
}
@@ -1137,6 +1138,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ mInMeasureLayout = true;
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
@@ -1159,22 +1161,29 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mTmpTaskViews.addAll(mViewPool.getViews());
int taskViewCount = mTmpTaskViews.size();
for (int i = 0; i < taskViewCount; i++) {
- TaskView tv = mTmpTaskViews.get(i);
- if (tv.getBackground() != null) {
- tv.getBackground().getPadding(mTmpRect);
- } else {
- mTmpRect.setEmpty();
- }
- tv.measure(
- MeasureSpec.makeMeasureSpec(
- mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
- MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(
- mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,
- MeasureSpec.EXACTLY));
+ measureTaskView(mTmpTaskViews.get(i));
}
setMeasuredDimension(width, height);
+ mInMeasureLayout = false;
+ }
+
+ /**
+ * Measures a TaskView.
+ */
+ private void measureTaskView(TaskView tv) {
+ if (tv.getBackground() != null) {
+ tv.getBackground().getPadding(mTmpRect);
+ } else {
+ mTmpRect.setEmpty();
+ }
+ tv.measure(
+ MeasureSpec.makeMeasureSpec(
+ mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
+ MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(
+ mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,
+ MeasureSpec.EXACTLY));
}
/**
@@ -1190,15 +1199,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mTmpTaskViews.addAll(mViewPool.getViews());
int taskViewCount = mTmpTaskViews.size();
for (int i = 0; i < taskViewCount; i++) {
- TaskView tv = mTmpTaskViews.get(i);
- if (tv.getBackground() != null) {
- tv.getBackground().getPadding(mTmpRect);
- } else {
- mTmpRect.setEmpty();
- }
- Rect taskRect = mLayoutAlgorithm.mTaskRect;
- tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top,
- taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom);
+ layoutTaskView(mTmpTaskViews.get(i));
}
if (changed) {
@@ -1207,29 +1208,41 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
// Relayout all of the task views including the ignored ones
- relayoutTaskViews(TaskViewAnimation.IMMEDIATE, EMPTY_TASK_SET);
+ relayoutTaskViews(AnimationProps.IMMEDIATE, EMPTY_TASK_SET);
clipTaskViews();
if (mAwaitingFirstLayout || !mEnterAnimationComplete) {
mAwaitingFirstLayout = false;
onFirstLayout();
- return;
}
}
+ /**
+ * Lays out a TaskView.
+ */
+ private void layoutTaskView(TaskView tv) {
+ if (tv.getBackground() != null) {
+ tv.getBackground().getPadding(mTmpRect);
+ } else {
+ mTmpRect.setEmpty();
+ }
+ Rect taskRect = mLayoutAlgorithm.mTaskRect;
+ tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top,
+ taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom);
+ }
+
/** Handler for the first layout. */
void onFirstLayout() {
// Setup the view for the enter animation
mAnimationHelper.prepareForEnterAnimation();
// Animate in the freeform workspace
- animateFreeformWorkspaceBackgroundAlpha(
- mLayoutAlgorithm.getStackState().freeformBackgroundAlpha, 150,
- Interpolators.FAST_OUT_SLOW_IN);
+ int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha;
+ animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150,
+ Interpolators.FAST_OUT_SLOW_IN));
// Set the task focused state without requesting view focus, and leave the focus animations
// until after the enter-animation
- Task launchTask = mStack.getLaunchTarget();
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount());
@@ -1274,7 +1287,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
@Override
- protected void dispatchDraw(Canvas canvas) {
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
// Draw the freeform workspace background
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.hasFreeformWorkspaceSupport()) {
@@ -1282,8 +1297,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mFreeformWorkspaceBackground.draw(canvas);
}
}
-
- super.dispatchDraw(canvas);
}
@Override
@@ -1318,7 +1331,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
updateLayoutAlgorithm(true /* boundScroll */);
// Animate all the tasks into place
- relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+ relayoutTaskViews(new AnimationProps(DEFAULT_SYNC_STACK_DURATION,
Interpolators.FAST_OUT_SLOW_IN));
}
@@ -1327,7 +1340,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
*/
@Override
public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
- Task newFrontMostTask, TaskViewAnimation animation) {
+ Task newFrontMostTask, AnimationProps animation) {
if (mFocusedTask == removedTask) {
resetFocusedTask(removedTask);
}
@@ -1364,7 +1377,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
@Override
public void onHistoryTaskRemoved(TaskStack stack, Task removedTask,
- TaskViewAnimation animation) {
+ AnimationProps animation) {
// To be implemented
}
@@ -1404,7 +1417,21 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Add/attach the view to the hierarchy
if (isNewView) {
- addView(tv, insertIndex);
+ if (mInMeasureLayout) {
+ // If we are measuring the layout, then just add the view normally as it will be
+ // laid out during the layout pass
+ addView(tv, insertIndex);
+ } else {
+ // Otherwise, this is from a bindVisibleTaskViews() call outside the measure/layout
+ // pass, and we should layout the new child ourselves
+ ViewGroup.LayoutParams params = tv.getLayoutParams();
+ if (params == null) {
+ params = generateDefaultLayoutParams();
+ }
+ addViewInLayout(tv, insertIndex, params, true /* preventRequestLayout */);
+ measureTaskView(tv);
+ layoutTaskView(tv);
+ }
} else {
attachViewToParent(tv, insertIndex, tv.getLayoutParams());
}
@@ -1463,14 +1490,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
public void onFocusStateChanged(float prevFocusState, float curFocusState) {
if (mDeferredTaskViewLayoutAnimation == null) {
mUIDozeTrigger.poke();
- relayoutTaskViewsOnNextFrame(TaskViewAnimation.IMMEDIATE);
+ relayoutTaskViewsOnNextFrame(AnimationProps.IMMEDIATE);
}
}
/**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
@Override
- public void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation) {
+ public void onScrollChanged(float prevScroll, float curScroll, AnimationProps animation) {
mUIDozeTrigger.poke();
if (animation != null) {
relayoutTaskViewsOnNextFrame(animation);
@@ -1506,7 +1533,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
tv.dismissTask();
} else {
// Otherwise, remove the task from the stack immediately
- mStack.removeTask(t, TaskViewAnimation.IMMEDIATE);
+ mStack.removeTask(t, AnimationProps.IMMEDIATE);
}
}
}
@@ -1531,10 +1558,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger());
// Dismiss the freeform workspace background
- int taskViewExitToHomeDuration = getResources().getInteger(
- R.integer.recents_task_exit_to_home_duration);
- animateFreeformWorkspaceBackgroundAlpha(0, taskViewExitToHomeDuration,
- Interpolators.FAST_OUT_SLOW_IN);
+ int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
+ animateFreeformWorkspaceBackgroundAlpha(0, new AnimationProps(taskViewExitToHomeDuration,
+ Interpolators.FAST_OUT_SLOW_IN));
}
public final void onBusEvent(DismissFocusedTaskViewEvent event) {
@@ -1587,6 +1613,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
public final void onBusEvent(DragStartEvent event) {
+ // Ensure that the drag task is not animated
+ addIgnoreTask(event.task);
+
if (event.task.isFreeformTask()) {
// Animate to the front of the stack
mStackScroller.animateScroll(mLayoutAlgorithm.mInitialScrollP, null);
@@ -1599,7 +1628,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mTmpTransform.scale = finalScale;
mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1;
updateTaskViewToTransform(event.taskView, mTmpTransform,
- new TaskViewAnimation(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN));
+ new AnimationProps(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN));
}
public final void onBusEvent(DragStartInitializeDropTargetsEvent event) {
@@ -1611,7 +1640,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
public final void onBusEvent(DragDropTargetChangedEvent event) {
- TaskViewAnimation animation = new TaskViewAnimation(250, Interpolators.FAST_OUT_SLOW_IN);
+ AnimationProps animation = new AnimationProps(250, Interpolators.FAST_OUT_SLOW_IN);
if (event.dropTarget instanceof TaskStack.DockState) {
// Calculate the new task stack bounds that matches the window size that Recents will
// have after the drop
@@ -1683,10 +1712,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
mTmpTransform, null);
event.getAnimationTrigger().increment();
- relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+ relayoutTaskViews(new AnimationProps(DEFAULT_SYNC_STACK_DURATION,
Interpolators.FAST_OUT_SLOW_IN));
updateTaskViewToTransform(event.taskView, mTmpTransform,
- new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN,
+ new AnimationProps(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN,
event.getAnimationTrigger().decrementOnAnimationEnd()));
removeIgnoreTask(event.task);
}
@@ -1797,15 +1826,15 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
R.string.accessibility_recents_item_dismissed, task.title));
// Remove the task from the stack
- mStack.removeTask(task, new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+ mStack.removeTask(task, new AnimationProps(DEFAULT_SYNC_STACK_DURATION,
Interpolators.FAST_OUT_SLOW_IN));
}
/**
* Starts an alpha animation on the freeform workspace background.
*/
- private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha, int duration,
- Interpolator interpolator) {
+ private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha,
+ AnimationProps animation) {
if (mFreeformWorkspaceBackground.getAlpha() == targetAlpha) {
return;
}
@@ -1813,8 +1842,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
Utilities.cancelAnimationWithoutCallbacks(mFreeformWorkspaceBackgroundAnimator);
mFreeformWorkspaceBackgroundAnimator = ObjectAnimator.ofInt(mFreeformWorkspaceBackground,
Utilities.DRAWABLE_ALPHA, mFreeformWorkspaceBackground.getAlpha(), targetAlpha);
- mFreeformWorkspaceBackgroundAnimator.setDuration(duration);
- mFreeformWorkspaceBackgroundAnimator.setInterpolator(interpolator);
+ mFreeformWorkspaceBackgroundAnimator.setStartDelay(
+ animation.getDuration(AnimationProps.ALPHA));
+ mFreeformWorkspaceBackgroundAnimator.setDuration(
+ animation.getDuration(AnimationProps.ALPHA));
+ mFreeformWorkspaceBackgroundAnimator.setInterpolator(
+ animation.getInterpolator(AnimationProps.ALPHA));
mFreeformWorkspaceBackgroundAnimator.start();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index ced5d4b500f4..c641d75c7bc8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -38,7 +38,7 @@ public class TaskStackViewScroller {
private static final boolean DEBUG = false;
public interface TaskStackViewScrollerCallbacks {
- void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation);
+ void onScrollChanged(float prevScroll, float curScroll, AnimationProps animation);
}
/**
@@ -93,14 +93,14 @@ public class TaskStackViewScroller {
* Sets the current stack scroll immediately.
*/
public void setStackScroll(float s) {
- setStackScroll(s, TaskViewAnimation.IMMEDIATE);
+ setStackScroll(s, AnimationProps.IMMEDIATE);
}
/**
* Sets the current stack scroll, but indicates to the callback the preferred animation to
* update to this new scroll.
*/
- public void setStackScroll(float s, TaskViewAnimation animation) {
+ public void setStackScroll(float s, AnimationProps animation) {
float prevStackScroll = mStackScrollP;
mStackScrollP = s;
if (mCb != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index da99956d2a9c..b8b506800488 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -546,7 +546,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback {
mTmpTransform.translationZ = fromTransform.translationZ +
(toTransform.translationZ - fromTransform.translationZ) * dismissFraction;
- mSv.updateTaskViewToTransform(tv, mTmpTransform, TaskViewAnimation.IMMEDIATE);
+ mSv.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 853f8688767f..703005f00a49 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -29,7 +29,6 @@ import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
-import android.provider.Settings;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.IntProperty;
@@ -243,9 +242,9 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
}
void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform,
- TaskViewAnimation toAnimation, ValueAnimator.AnimatorUpdateListener updateCallback) {
+ AnimationProps toAnimation, ValueAnimator.AnimatorUpdateListener updateCallback) {
RecentsConfiguration config = Recents.getConfiguration();
- Utilities.cancelAnimationWithoutCallbacks(mTransformAnimation);
+ cancelTransformAnimation();
// Compose the animations for the transform
mTmpAnimators.clear();
@@ -255,8 +254,8 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
setTaskProgress(toTransform.p);
}
// Manually call back to the animator listener and update callback
- if (toAnimation.listener != null) {
- toAnimation.listener.onAnimationEnd(null);
+ if (toAnimation.getListener() != null) {
+ toAnimation.getListener().onAnimationEnd(null);
}
if (updateCallback != null) {
updateCallback.onAnimationUpdate(null);
@@ -280,7 +279,7 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
/** Resets this view's properties */
void resetViewProperties() {
- Utilities.cancelAnimationWithoutCallbacks(mTransformAnimation);
+ cancelTransformAnimation();
setDim(0);
setVisibility(View.VISIBLE);
getViewBounds().reset();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java
deleted file mode 100644
index 5455042241bc..000000000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.view.animation.Interpolator;
-
-import com.android.systemui.Interpolators;
-
-import java.util.List;
-
-/**
- * The animation properties to animate a {@link TaskView} to a given {@link TaskViewTransform}.
- */
-public class TaskViewAnimation {
-
- public static final TaskViewAnimation IMMEDIATE = new TaskViewAnimation(0,
- Interpolators.LINEAR);
-
- public final int startDelay;
- public final int duration;
- public final Interpolator interpolator;
- public final Animator.AnimatorListener listener;
-
- public TaskViewAnimation(int duration, Interpolator interpolator) {
- this(0 /* startDelay */, duration, interpolator, null);
- }
-
- public TaskViewAnimation(int duration, Interpolator interpolator,
- Animator.AnimatorListener listener) {
- this(0 /* startDelay */, duration, interpolator, listener);
- }
-
- public TaskViewAnimation(int startDelay, int duration, Interpolator interpolator) {
- this(startDelay, duration, interpolator, null);
- }
-
- public TaskViewAnimation(int startDelay, int duration, Interpolator interpolator,
- Animator.AnimatorListener listener) {
- this.startDelay = startDelay;
- this.duration = duration;
- this.interpolator = interpolator;
- this.listener = listener;
- }
-
- /**
- * Creates a new {@link AnimatorSet} that will animate the given animators with the current
- * animation properties.
- */
- public AnimatorSet createAnimator(List<Animator> animators) {
- AnimatorSet anim = new AnimatorSet();
- anim.setStartDelay(startDelay);
- anim.setDuration(duration);
- anim.setInterpolator(interpolator);
- if (listener != null) {
- anim.addListener(listener);
- }
- anim.playTogether(animators);
- return anim;
- }
-
- /**
- * Returns whether this animation has any duration.
- */
- public boolean isImmediate() {
- return duration <= 0;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 7cde46397a81..c91a833e943a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -274,8 +274,8 @@ public class TaskViewHeader extends FrameLayout
}
@Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
+ public void onDrawForeground(Canvas canvas) {
+ super.onDrawForeground(canvas);
// Draw the dim layer with the rounded corners
canvas.drawRoundRect(0, 0, mTaskViewRect.width(), getHeight() + mCornerRadius,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index 85b7c82112a4..32878b0afcb7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -154,13 +154,13 @@ public class TaskViewTransform {
* Applies this transform to a view.
*/
public void applyToTaskView(TaskView v, ArrayList<Animator> animators,
- TaskViewAnimation taskAnimation, boolean allowShadows) {
+ AnimationProps animation, boolean allowShadows) {
// Return early if not visible
if (!visible) {
return;
}
- if (taskAnimation.isImmediate()) {
+ if (animation.isImmediate()) {
if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) {
v.setTranslationZ(translationZ);
}
@@ -177,23 +177,27 @@ public class TaskViewTransform {
}
} else {
if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) {
- animators.add(ObjectAnimator.ofFloat(v, View.TRANSLATION_Z, v.getTranslationZ(),
- translationZ));
+ ObjectAnimator anim = ObjectAnimator.ofFloat(v, View.TRANSLATION_Z,
+ v.getTranslationZ(), translationZ);
+ animators.add(animation.apply(AnimationProps.TRANSLATION_Z, anim));
}
if (hasScaleChangedFrom(v.getScaleX())) {
- animators.add(ObjectAnimator.ofPropertyValuesHolder(v,
+ ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(v,
PropertyValuesHolder.ofFloat(View.SCALE_X, v.getScaleX(), scale),
- PropertyValuesHolder.ofFloat(View.SCALE_Y, v.getScaleX(), scale)));
+ PropertyValuesHolder.ofFloat(View.SCALE_Y, v.getScaleX(), scale));
+ animators.add(animation.apply(AnimationProps.SCALE, anim));
}
if (hasAlphaChangedFrom(v.getAlpha())) {
- animators.add(ObjectAnimator.ofFloat(v, View.ALPHA, v.getAlpha(), alpha));
+ ObjectAnimator anim = ObjectAnimator.ofFloat(v, View.ALPHA, v.getAlpha(), alpha);
+ animators.add(animation.apply(AnimationProps.ALPHA, anim));
}
if (hasRectChangedFrom(v)) {
- animators.add(ObjectAnimator.ofPropertyValuesHolder(v,
+ ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(v,
PropertyValuesHolder.ofInt(LEFT, v.getLeft(), (int) rect.left),
PropertyValuesHolder.ofInt(TOP, v.getTop(), (int) rect.top),
PropertyValuesHolder.ofInt(RIGHT, v.getRight(), (int) rect.right),
- PropertyValuesHolder.ofInt(BOTTOM, v.getBottom(), (int) rect.bottom)));
+ PropertyValuesHolder.ofInt(BOTTOM, v.getBottom(), (int) rect.bottom));
+ animators.add(animation.apply(AnimationProps.BOUNDS, anim));
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 00b9888d69b0..76e522e336ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -197,9 +197,13 @@ public class NotificationContentView extends FrameLayout {
if (expandedSize != collapsedSize) {
int paddingEnd = contractedHeader.getMeasuredWidth() - expandedSize;
contractedHeader.setPadding(
- isLayoutRtl() ? paddingEnd : contractedHeader.getPaddingLeft(),
+ contractedHeader.isLayoutRtl()
+ ? paddingEnd
+ : contractedHeader.getPaddingLeft(),
contractedHeader.getPaddingTop(),
- isLayoutRtl() ? contractedHeader.getPaddingLeft() : paddingEnd,
+ contractedHeader.isLayoutRtl()
+ ? contractedHeader.getPaddingLeft()
+ : paddingEnd,
contractedHeader.getPaddingBottom());
contractedHeader.setShowWorkBadgeAtEnd(true);
return true;
@@ -208,9 +212,13 @@ public class NotificationContentView extends FrameLayout {
int paddingEnd = mNotificationContentMarginEnd;
if (contractedHeader.getPaddingEnd() != paddingEnd) {
contractedHeader.setPadding(
- isLayoutRtl() ? paddingEnd : contractedHeader.getPaddingLeft(),
+ contractedHeader.isLayoutRtl()
+ ? paddingEnd
+ : contractedHeader.getPaddingLeft(),
contractedHeader.getPaddingTop(),
- isLayoutRtl() ? contractedHeader.getPaddingLeft() : paddingEnd,
+ contractedHeader.isLayoutRtl()
+ ? contractedHeader.getPaddingLeft()
+ : paddingEnd,
contractedHeader.getPaddingBottom());
contractedHeader.setShowWorkBadgeAtEnd(false);
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
index 6febe5fe8a4b..3e47d8571fad 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
@@ -32,11 +32,14 @@ import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
+import com.android.systemui.Prefs;
+
import java.util.ArrayList;
import java.util.List;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static com.android.systemui.Prefs.Key.TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN;
/**
* Manages the picture-in-picture (PIP) UI and states.
@@ -44,6 +47,7 @@ import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
public class PipManager {
private static final String TAG = "PipManager";
private static final boolean DEBUG = false;
+ private static final boolean DEBUG_FORCE_ONBOARDING = false;
private static PipManager sPipManager;
@@ -65,6 +69,7 @@ public class PipManager {
private Rect mMenuModePipBound;
private boolean mInitialized;
private int mPipTaskId = TASK_ID_NO_PIP;
+ private boolean mOnboardingShown;
private final Runnable mOnActivityPinnedRunnable = new Runnable() {
@Override
@@ -83,6 +88,7 @@ public class PipManager {
if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
showPipOverlay(false);
+ launchPipOnboardingActivityIfNeeded();
}
};
private final Runnable mOnTaskStackChanged = new Runnable() {
@@ -145,6 +151,8 @@ public class PipManager {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_MEDIA_RESOURCE_GRANTED);
mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+ mOnboardingShown = Prefs.getBoolean(
+ mContext, TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, false);
}
/**
@@ -169,7 +177,7 @@ public class PipManager {
}
/**
- * Closes PIP (PIPped activity and PIP system UI).
+ * Closes PIP (PIPed activity and PIP system UI).
*/
public void closePip() {
mState = STATE_NO_PIP;
@@ -194,7 +202,7 @@ public class PipManager {
}
/**
- * Moves the PIPped activity to the fullscreen and closes PIP system UI.
+ * Moves the PIPed activity to the fullscreen and closes PIP system UI.
*/
public void movePipToFullscreen() {
mState = STATE_NO_PIP;
@@ -260,6 +268,17 @@ public class PipManager {
mListeners.remove(listener);
}
+ private void launchPipOnboardingActivityIfNeeded() {
+ if (DEBUG_FORCE_ONBOARDING || !mOnboardingShown) {
+ mOnboardingShown = true;
+ Prefs.putBoolean(mContext, TV_PICTURE_IN_PICTURE_ONBOARDING_SHOWN, true);
+
+ Intent intent = new Intent(mContext, PipOnboardingActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ }
+ }
+
private boolean hasPipTasks() {
try {
StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
@@ -334,7 +353,7 @@ public class PipManager {
*/
public interface Listener {
/**
- * Invoked when a PIPped activity is closed.
+ * Invoked when a PIPed activity is closed.
*/
void onPipActivityClosed();
/**
@@ -342,7 +361,7 @@ public class PipManager {
*/
void onShowPipMenu();
/**
- * Invoked when the PIPped activity is returned back to the fullscreen.
+ * Invoked when the PIPed activity is returned back to the fullscreen.
*/
void onMoveToFullscreen();
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java
new file mode 100644
index 000000000000..a0b913ab9b2c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 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.tv.pip;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * Activity to show an overlay on top of PIP activity to show how to pop up PIP menu.
+ */
+public class PipOnboardingActivity extends Activity implements PipManager.Listener {
+ private final PipManager mPipManager = PipManager.getInstance();
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ setContentView(R.layout.tv_pip_onboarding);
+ mPipManager.addListener(this);
+ findViewById(R.id.close).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ });
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mPipManager.removeListener(this);
+ }
+
+ @Override
+ public void onPipActivityClosed() {
+ finish();
+ }
+
+ @Override
+ public void onShowPipMenu() {
+ finish();
+ }
+
+ @Override
+ public void onMoveToFullscreen() {
+ finish();
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
index e31080150280..912e8e2eb246 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
@@ -27,6 +27,8 @@ import android.gesture.Prediction;
import android.util.Slog;
import android.view.GestureDetector;
import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
import com.android.internal.R;
@@ -46,7 +48,9 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
public interface Listener {
public void onDoubleTapAndHold(MotionEvent event, int policyFlags);
public boolean onDoubleTap(MotionEvent event, int policyFlags);
- public boolean onGesture(int gestureId);
+ public boolean onGestureCompleted(int gestureId);
+ public void onGestureStarted();
+ public void onGestureCancelled(MotionEvent event, int policyFlags);
}
private final Listener mListener;
@@ -64,6 +68,10 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
// Indicates that motion events are being collected to match a gesture.
private boolean mRecognizingGesture;
+ // Indicates that we've collected enough data to be sure it could be a
+ // gesture.
+ private boolean mGestureConfirmed;
+
// Indicates that motion events from the second pointer are being checked
// for a double tap.
private boolean mSecondFingerDoubleTap;
@@ -81,18 +89,41 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
// The Y of the previous event.
private float mPreviousY;
+ // The X of the down event.
+ private float mBaseX;
+
+ // The Y of the down event.
+ private float mBaseY;
+
+ // Slop between the first and second tap to be a double tap.
+ private final int mDoubleTapSlop;
+
+ // The scaled velocity above which we detect gestures.
+ private final int mScaledGestureDetectionVelocity;
+
// Buffer for storing points for gesture detection.
private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
+ // Helper to track gesture velocity.
+ private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
+
// The minimal delta between moves to add a gesture point.
private static final int TOUCH_TOLERANCE = 3;
// The minimal score for accepting a predicted gesture.
private static final float MIN_PREDICTION_SCORE = 2.0f;
+ // The velocity above which we detect gestures. Expressed in DIPs/Second.
+ private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000;
+
+ // Constant used to calculate velocity in seconds.
+ private static final int VELOCITY_UNITS_SECONDS = 1000;
+
AccessibilityGestureDetector(Context context, Listener listener) {
mListener = listener;
+ mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+
mGestureDetector = new GestureDetector(context, this);
mGestureDetector.setOnDoubleTapListener(this);
@@ -100,9 +131,14 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
mGestureLibrary.setOrientationStyle(8 /* GestureStore.ORIENTATION_SENSITIVE_8 */);
mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE);
mGestureLibrary.load();
+
+ final float density = context.getResources().getDisplayMetrics().density;
+ mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
}
public boolean onMotionEvent(MotionEvent event, int policyFlags) {
+ mVelocityTracker.addMovement(event);
+
final float x = event.getX();
final float y = event.getY();
@@ -112,14 +148,48 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
mDoubleTapDetected = false;
mSecondFingerDoubleTap = false;
mRecognizingGesture = true;
+ mGestureConfirmed = false;
+ mBaseX = x;
+ mBaseY = y;
mPreviousX = x;
mPreviousY = y;
mStrokeBuffer.clear();
mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+ mVelocityTracker.clear();
+ mVelocityTracker.addMovement(event);
break;
case MotionEvent.ACTION_MOVE:
if (mRecognizingGesture) {
+ if (!mGestureConfirmed) {
+ mVelocityTracker.addMovement(event);
+ // It is *important* to use the distance traveled by the pointers
+ // on the screen which may or may not be magnified.
+ final float deltaX = mBaseX - event.getX(0);
+ final float deltaY = mBaseY - event.getY(0);
+ final double moveDelta = Math.hypot(deltaX, deltaY);
+ // The user has moved enough for us to decide.
+ if (moveDelta > mDoubleTapSlop) {
+ // Check whether the user is performing a gesture. We
+ // detect gestures if the pointer is moving above a
+ // given velocity.
+ mVelocityTracker.computeCurrentVelocity(VELOCITY_UNITS_SECONDS);
+ final float maxAbsVelocity = Math.max(
+ Math.abs(mVelocityTracker.getXVelocity(0)),
+ Math.abs(mVelocityTracker.getYVelocity(0)));
+ if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
+ // We have to perform gesture detection, so
+ // notify the listener.
+ mGestureConfirmed = true;
+ mListener.onGestureStarted();
+ } else {
+ // This won't match any gesture, so notify the
+ // listener.
+ cancelGesture();
+ mListener.onGestureCancelled(event, policyFlags);
+ }
+ }
+ }
final float dX = Math.abs(x - mPreviousX);
final float dY = Math.abs(y - mPreviousY);
if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
@@ -134,12 +204,13 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
if (maybeFinishDoubleTap(event, policyFlags)) {
return true;
}
- if (mRecognizingGesture) {
+ if (mGestureConfirmed) {
mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
- if (recognizeGesture()) {
- return true;
+ if (!recognizeGesture()) {
+ mListener.onGestureCancelled(event, policyFlags);
}
+ return true;
}
break;
@@ -167,6 +238,10 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
return true;
}
break;
+
+ case MotionEvent.ACTION_CANCEL:
+ clear();
+ break;
}
// If we're detecting taps on the second finger, map events from the
@@ -194,18 +269,13 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
mDoubleTapDetected = false;
mSecondFingerDoubleTap = false;
cancelGesture();
- mStrokeBuffer.clear();
+ mVelocityTracker.clear();
}
public boolean firstTapDetected() {
return mFirstTapDetected;
}
- public void cancelGesture() {
- mRecognizingGesture = false;
- mStrokeBuffer.clear();
- }
-
@Override
public void onLongPress(MotionEvent e) {
maybeSendLongPress(e, mPolicyFlags);
@@ -251,6 +321,12 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
return mListener.onDoubleTap(event, policyFlags);
}
+ private void cancelGesture() {
+ mRecognizingGesture = false;
+ mGestureConfirmed = false;
+ mStrokeBuffer.clear();
+ }
+
private boolean recognizeGesture() {
Gesture gesture = new Gesture();
gesture.addStroke(new GestureStroke(mStrokeBuffer));
@@ -265,7 +341,7 @@ class AccessibilityGestureDetector extends GestureDetector.SimpleOnGestureListen
}
try {
final int gestureId = Integer.parseInt(bestPrediction.name);
- if (mListener.onGesture(gestureId)) {
+ if (mListener.onGestureCompleted(gestureId)) {
return true;
}
} catch (NumberFormatException nfe) {
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index ca30349867de..9e6cd002e889 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -25,7 +25,6 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
-import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
@@ -87,9 +86,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
// Invalid pointer ID.
private static final int INVALID_POINTER_ID = -1;
- // The velocity above which we detect gestures.
- private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000;
-
// The minimal distance before we take the middle of the distance between
// the two dragging pointers as opposed to use the location of the primary one.
private static final int MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP = 200;
@@ -134,15 +130,9 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
// the two dragging pointers as opposed to use the location of the primary one.
private final int mScaledMinPointerDistanceToUseMiddleLocation;
- // The scaled velocity above which we detect gestures.
- private final int mScaledGestureDetectionVelocity;
-
// The handler to which to delegate events.
private EventStreamTransformation mNext;
- // Helper to track gesture velocity.
- private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
-
// Helper class to track received pointers.
private final ReceivedPointerTracker mReceivedPointerTracker;
@@ -200,7 +190,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
final float density = context.getResources().getDisplayMetrics().density;
mScaledMinPointerDistanceToUseMiddleLocation =
(int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density);
- mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
}
@Override
@@ -289,11 +278,19 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
mReceivedPointerTracker.onMotionEvent(rawEvent);
- if (mGestureDetector.onMotionEvent(event, policyFlags)) {
+ // The motion detector is interested in the movements in physical space,
+ // so it uses the rawEvent to ignore magnification and other
+ // transformations.
+ if (mGestureDetector.onMotionEvent(rawEvent, policyFlags)) {
// Event was handled by the gesture detector.
return;
}
+ if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
+ clear(event, policyFlags);
+ return;
+ }
+
switch(mCurrentState) {
case STATE_TOUCH_EXPLORING: {
handleMotionEventStateTouchExploring(event, rawEvent, policyFlags);
@@ -305,7 +302,7 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
handleMotionEventStateDelegating(event, policyFlags);
} break;
case STATE_GESTURE_DETECTING: {
- handleMotionEventGestureDetecting(rawEvent, policyFlags);
+ // Already handled.
} break;
default:
throw new IllegalStateException("Illegal state: " + mCurrentState);
@@ -440,26 +437,50 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
}
@Override
- public boolean onGesture(int gestureId) {
+ public boolean onGestureCompleted(int gestureId) {
if (mCurrentState != STATE_GESTURE_DETECTING) {
return false;
}
- mAms.onTouchInteractionEnd();
-
- // Announce the end of the gesture recognition.
- sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
- // Announce the end of a the touch interaction.
- sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+ endGestureDetection();
mAms.onGesture(gestureId);
- mExitGestureDetectionModeDelayed.cancel();
- mCurrentState = STATE_TOUCH_EXPLORING;
-
return true;
}
+ @Override
+ public void onGestureStarted() {
+ // We have to perform gesture detection, so
+ // clear the current state and try to detect.
+ mCurrentState = STATE_GESTURE_DETECTING;
+ mSendHoverEnterAndMoveDelayed.cancel();
+ mSendHoverExitDelayed.cancel();
+ mExitGestureDetectionModeDelayed.post();
+ // Send accessibility event to announce the start
+ // of gesture recognition.
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
+ }
+
+ @Override
+ public void onGestureCancelled(MotionEvent event, int policyFlags) {
+ if (mCurrentState == STATE_GESTURE_DETECTING) {
+ endGestureDetection();
+ } else if (mCurrentState == STATE_TOUCH_EXPLORING) {
+ final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
+ final int pointerIdBits = (1 << pointerId);
+
+ // Cache the event until we discern exploration from gesturing.
+ mSendHoverEnterAndMoveDelayed.addEvent(event);
+
+ // We have just decided that the user is touch,
+ // exploring so start sending events.
+ mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
+ mSendHoverExitDelayed.cancel();
+ sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
+ }
+ }
+
/**
* Handles a motion event in touch exploring state.
*
@@ -471,8 +492,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
int policyFlags) {
ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
- mVelocityTracker.addMovement(rawEvent);
-
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mAms.onTouchInteractionStart();
@@ -525,46 +544,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
if (mSendHoverEnterAndMoveDelayed.isPending()) {
// Cache the event until we discern exploration from gesturing.
mSendHoverEnterAndMoveDelayed.addEvent(event);
-
- // It is *important* to use the distance traveled by the pointers
- // on the screen which may or may not be magnified.
- final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
- - rawEvent.getX(pointerIndex);
- final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
- - rawEvent.getY(pointerIndex);
- final double moveDelta = Math.hypot(deltaX, deltaY);
- // The user has moved enough for us to decide.
- if (moveDelta > mDoubleTapSlop) {
- // Check whether the user is performing a gesture. We
- // detect gestures if the pointer is moving above a
- // given velocity.
- mVelocityTracker.computeCurrentVelocity(1000);
- final float maxAbsVelocity = Math.max(
- Math.abs(mVelocityTracker.getXVelocity(pointerId)),
- Math.abs(mVelocityTracker.getYVelocity(pointerId)));
- if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
- // We have to perform gesture detection, so
- // clear the current state and try to detect.
- mCurrentState = STATE_GESTURE_DETECTING;
- mVelocityTracker.clear();
- mSendHoverEnterAndMoveDelayed.cancel();
- mSendHoverExitDelayed.cancel();
- mExitGestureDetectionModeDelayed.post();
- // Send accessibility event to announce the start
- // of gesture recognition.
- sendAccessibilityEvent(
- AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
- } else {
- // We have just decided that the user is touch,
- // exploring so start sending events.
- mGestureDetector.cancelGesture();
- mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
- mSendHoverExitDelayed.cancel();
- sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE,
- pointerIdBits, policyFlags);
- }
- break;
- }
} else {
if (mTouchExplorationInProgress) {
sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
@@ -602,11 +581,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
}
}
- // We know that a new state transition is to happen and the
- // new state will not be gesture recognition, so cancel
- // the gesture.
- mGestureDetector.cancelGesture();
-
if (isDraggingGesture(event)) {
// Two pointers moving in the same direction within
// a given distance perform a drag.
@@ -620,7 +594,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
mCurrentState = STATE_DELEGATING;
sendDownForAllNotInjectedPointers(event, policyFlags);
}
- mVelocityTracker.clear();
} break;
default: {
// More than one pointer so the user is not touch exploring
@@ -639,7 +612,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
// More than two pointers are delegated to the view hierarchy.
mCurrentState = STATE_DELEGATING;
sendDownForAllNotInjectedPointers(event, policyFlags);
- mVelocityTracker.clear();
}
}
} break;
@@ -648,8 +620,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
final int pointerId = event.getPointerId(event.getActionIndex());
final int pointerIdBits = (1 << pointerId);
- mVelocityTracker.clear();
-
if (mSendHoverEnterAndMoveDelayed.isPending()) {
// If we have not delivered the enter schedule an exit.
mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags);
@@ -663,9 +633,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
}
} break;
- case MotionEvent.ACTION_CANCEL: {
- clear(event, policyFlags);
- } break;
}
}
@@ -756,9 +723,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
}
mCurrentState = STATE_TOUCH_EXPLORING;
} break;
- case MotionEvent.ACTION_CANCEL: {
- clear(event, policyFlags);
- } break;
}
}
@@ -795,9 +759,6 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
mCurrentState = STATE_TOUCH_EXPLORING;
} break;
- case MotionEvent.ACTION_CANCEL: {
- clear(event, policyFlags);
- } break;
default: {
// Deliver the event.
sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
@@ -805,22 +766,16 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
}
}
- private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) {
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_UP: {
- mAms.onTouchInteractionEnd();
- // Announce the end of the gesture recognition.
- sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
- // Announce the end of a the touch interaction.
- sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+ private void endGestureDetection() {
+ mAms.onTouchInteractionEnd();
- mExitGestureDetectionModeDelayed.cancel();
- mCurrentState = STATE_TOUCH_EXPLORING;
- } break;
- case MotionEvent.ACTION_CANCEL: {
- clear(event, policyFlags);
- } break;
- }
+ // Announce the end of the gesture recognition.
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
+ // Announce the end of a the touch interaction.
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+
+ mExitGestureDetectionModeDelayed.cancel();
+ mCurrentState = STATE_TOUCH_EXPLORING;
}
/**
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index d0cd5365db15..81607a98c3d0 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -200,7 +200,7 @@ public class LockSettingsService extends ILockSettings.Stub {
PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification.Builder(mContext)
- .setSmallIcon(com.android.internal.R.drawable.ic_secure)
+ .setSmallIcon(com.android.internal.R.drawable.ic_user_secure)
.setWhen(0)
.setOngoing(true)
.setTicker(title)
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 4a186a620cc1..3ce44526d17b 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -2817,8 +2817,9 @@ class MountService extends IMountService.Stub
public ParcelFileDescriptor mountAppFuse(final String name) throws RemoteException {
try {
final int uid = Binder.getCallingUid();
+ final int pid = Binder.getCallingPid();
final NativeDaemonEvent event =
- mConnector.execute("appfuse", "mount", uid, name);
+ mConnector.execute("appfuse", "mount", uid, pid, name);
if (event.getFileDescriptors() == null) {
throw new RemoteException("AppFuse FD from vold is null.");
}
@@ -2830,7 +2831,7 @@ class MountService extends IMountService.Stub
public void onClose(IOException e) {
try {
final NativeDaemonEvent event = mConnector.execute(
- "appfuse", "unmount", uid, name);
+ "appfuse", "unmount", uid, pid, name);
} catch (NativeDaemonConnectorException unmountException) {
Log.e(TAG, "Failed to unmount appfuse.");
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 143015f0f6ab..2683be66b347 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4266,6 +4266,21 @@ public class AccountManagerService
}
}
+ private boolean isPermitted(String opPackageName, int callingUid, String... permissions) {
+ for (String perm : permissions) {
+ if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, " caller uid " + callingUid + " has " + perm);
+ }
+ final int opCode = AppOpsManager.permissionToOpCode(perm);
+ if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOp(
+ opCode, callingUid, opPackageName) == AppOpsManager.MODE_ALLOWED) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
private int handleIncomingUser(int userId) {
try {
@@ -4340,53 +4355,12 @@ public class AccountManagerService
private List<String> getTypesVisibleToCaller(int callingUid, int userId,
String opPackageName) {
- List<String> permissionsToCheck = new ArrayList<String>(2);
- permissionsToCheck.add(Manifest.permission.GET_ACCOUNTS_PRIVILEGED);
- long id = Binder.clearCallingIdentity();
- try {
- ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
- opPackageName, 0 /* flags */);
- /*
- * At or before SDK 23, clients discover all the accounts in their
- * user profile (via AccountManager.getAccounts(...)) by declaring
- * the GET_ACCOUNTS permission.
- *
- * After SDK 23 the GET_ACCOUNTS permission is deprecated. Instead
- * apps will be able to retrieve those accounts managed by
- * authenticators sharing a package signature without any special
- * permissions. The only clients able to discover all the accounts
- * on the device will be those with the GET_ACCOUNTS_PRVILEGED
- * system permission.
- */
- if (23 >= appInfo.targetSdkVersion) {
- permissionsToCheck.add(Manifest.permission.GET_ACCOUNTS);
- }
- } catch (NameNotFoundException e) {
- // No application associated with the specified package.
- Log.w(TAG, "No application associated with package: " + opPackageName);
- } finally {
- Binder.restoreCallingIdentity(id);
- }
- boolean isPermitted = isPermitted(opPackageName, callingUid, permissionsToCheck);
+ boolean isPermitted =
+ isPermitted(opPackageName, callingUid, Manifest.permission.GET_ACCOUNTS,
+ Manifest.permission.GET_ACCOUNTS_PRIVILEGED);
return getTypesForCaller(callingUid, userId, isPermitted);
}
- private boolean isPermitted(String opPackageName, int callingUid, List<String> permissions) {
- for (String perm : permissions) {
- if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, " caller uid " + callingUid + " has " + perm);
- }
- final int opCode = AppOpsManager.permissionToOpCode(perm);
- if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOp(
- opCode, callingUid, opPackageName) == AppOpsManager.MODE_ALLOWED) {
- return true;
- }
- }
- }
- return false;
- }
-
private List<String> getTypesManagedByCaller(int callingUid, int userId) {
return getTypesForCaller(callingUid, userId, false);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ba5bb396c5ca..54c4ced18e47 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18080,7 +18080,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (app == TOP_APP) {
// The last app on the list is the foreground app.
adj = ProcessList.FOREGROUND_APP_ADJ;
- schedGroup = Process.THREAD_GROUP_DEFAULT;
+ schedGroup = Process.THREAD_GROUP_TOP_APP;
app.adjType = "top-activity";
foregroundActivities = true;
procState = PROCESS_STATE_CUR_TOP;
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 3ad6b8329e87..914776ea488a 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -584,13 +584,16 @@ final class ActivityRecord {
haveState = true;
if (aInfo != null) {
+ // If the class name in the intent doesn't match that of the target, this is
+ // probably an alias. We have to create a new ComponentName object to keep track
+ // of the real activity name, so that FLAG_ACTIVITY_CLEAR_TOP is handled properly.
if (aInfo.targetActivity == null
- || aInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE
- || aInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) {
+ || (aInfo.targetActivity.equals(_intent.getComponent().getClassName())
+ && (aInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE
+ || aInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP))) {
realActivity = _intent.getComponent();
} else {
- realActivity = new ComponentName(aInfo.packageName,
- aInfo.targetActivity);
+ realActivity = new ComponentName(aInfo.packageName, aInfo.targetActivity);
}
taskAffinity = aInfo.taskAffinity;
stateNotNeeded = (aInfo.flags&
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 98eebeaca953..b360b897d1ee 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1416,9 +1416,7 @@ class ActivityStarter {
if (mLaunchBounds != null) {
final int stackId = mTargetStack.mStackId;
if (StackId.resizeStackWithLaunchBounds(stackId)) {
- mSupervisor.resizeStackLocked(stackId, mLaunchBounds,
- null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
- !PRESERVE_WINDOWS, true /* allowResizeInDockedMode */);
+ mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE);
} else {
mStartActivity.task.updateOverrideConfiguration(mLaunchBounds);
}
@@ -1506,9 +1504,7 @@ class ActivityStarter {
stackId = stack.mStackId;
}
if (StackId.resizeStackWithLaunchBounds(stackId)) {
- mSupervisor.resizeStackLocked(stackId, mLaunchBounds,
- null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
- !PRESERVE_WINDOWS, true /* allowResizeInDockedMode */);
+ mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE);
}
}
mTargetStack = mInTask.stack;
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 91d4c9e36c1a..daf839a59e1b 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -600,9 +600,18 @@ public class SyncManager {
null, null);
}
- Intent startServiceIntent = new Intent(mContext, SyncJobService.class);
+ // Set up the communication channel between the scheduled job and the sync manager.
+ // This is posted to the *main* looper intentionally, to defer calling startService()
+ // until after the lengthy primary boot sequence completes on that thread, to avoid
+ // spurious ANR triggering.
+ final Intent startServiceIntent = new Intent(mContext, SyncJobService.class);
startServiceIntent.putExtra(SyncJobService.EXTRA_MESSENGER, new Messenger(mSyncHandler));
- mContext.startService(startServiceIntent);
+ new Handler(mContext.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ mContext.startService(startServiceIntent);
+ }
+ });
}
private boolean isDeviceProvisioned() {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 5c7707968a46..544f25522bf3 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -255,6 +255,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
private static final int MSG_RESTRICT_BACKGROUND_CHANGED = 6;
private static final int MSG_ADVISE_PERSIST_THRESHOLD = 7;
private static final int MSG_SCREEN_ON_CHANGED = 8;
+ private static final int MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED = 9;
private final Context mContext;
private final IActivityManager mActivityManager;
@@ -1864,6 +1865,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
writePolicyLocked();
// TODO: call other update methods like updateNetworkRulesLocked?
}
+ mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED, uid, 0).sendToTarget();
}
@Override
@@ -1873,6 +1875,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
synchronized (mRulesLock) {
removeRestrictBackgroundWhitelistedUidLocked(uid, true);
}
+ mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED, uid, 0).sendToTarget();
}
private void removeRestrictBackgroundWhitelistedUidLocked(int uid, boolean writePolicy) {
@@ -2571,6 +2574,25 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
}
mListeners.finishBroadcast();
+ final Intent intent =
+ new Intent(ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED);
+ intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ return true;
+ }
+ case MSG_RESTRICT_BACKGROUND_WHITELIST_CHANGED: {
+ final int uid = msg.arg1;
+ final PackageManager pm = mContext.getPackageManager();
+ final String[] packages = pm.getPackagesForUid(uid);
+ final int userId = UserHandle.getUserId(uid);
+ for (String packageName : packages) {
+ final Intent intent =
+ new Intent(ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED);
+ intent.setPackage(packageName);
+ intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
+ }
+
return true;
}
case MSG_ADVISE_PERSIST_THRESHOLD: {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
index 7b1accafc673..5830b0e8bfaa 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerShellCommand.java
@@ -20,6 +20,7 @@ import java.io.PrintWriter;
import android.content.Intent;
import android.net.INetworkPolicyManager;
+import android.os.Binder;
import android.os.RemoteException;
import android.os.ShellCommand;
@@ -86,7 +87,7 @@ public class NetworkPolicyManagerShellCommand extends ShellCommand {
}
switch(type) {
case "restrict-background":
- return getRestrictBackgroundWhitelist();
+ return getRestrictBackground();
}
pw.println("Error: unknown get type '" + type + "'");
return -1;
@@ -101,7 +102,7 @@ public class NetworkPolicyManagerShellCommand extends ShellCommand {
}
switch(type) {
case "restrict-background":
- return setRestrictBackgroundWhitelist();
+ return setRestrictBackground();
}
pw.println("Error: unknown set type '" + type + "'");
return -1;
@@ -169,19 +170,24 @@ public class NetworkPolicyManagerShellCommand extends ShellCommand {
return 0;
}
- private int getRestrictBackgroundWhitelist() throws RemoteException {
+ private int getRestrictBackground() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
pw.print("Restrict background status: ");
pw.println(mInterface.getRestrictBackground() ? "enabled" : "disabled");
return 0;
}
- private int setRestrictBackgroundWhitelist() throws RemoteException {
+ private int setRestrictBackground() throws RemoteException {
final int enabled = getNextBooleanArg();
if (enabled < 0) {
return enabled;
}
- mInterface.setRestrictBackground(enabled > 0);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mInterface.setRestrictBackground(enabled > 0);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
return 0;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ebdb1b242e66..078094cfef7a 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1265,6 +1265,12 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ public boolean hasBannedTopics(String pkg, int uid) {
+ checkCallerIsSystem();
+ return mRankingHelper.hasBannedTopics(pkg, uid);
+ }
+
+ @Override
public ParceledListSlice<Notification.Topic> getTopics(String pkg, int uid) {
checkCallerIsSystem();
return new ParceledListSlice<Notification.Topic>(mRankingHelper.getTopics(pkg, uid));
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 7f85e1f8123d..1a7e3550d8e3 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -37,4 +37,6 @@ public interface RankingConfig {
int getImportance(String packageName, int uid, Notification.Topic topic);
boolean doesAppUseTopics(String packageName, int uid);
+
+ boolean hasBannedTopics(String packageName, int uid);
}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 827482ff2a4b..aa36e298bb45 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -389,6 +389,17 @@ public class RankingHelper implements RankingConfig {
return topics;
}
+ @Override
+ public boolean hasBannedTopics(String packageName, int uid) {
+ final Record r = getOrCreateRecord(packageName, uid);
+ for (Topic t : r.topics.values()) {
+ if (t.importance == Ranking.IMPORTANCE_NONE) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Gets priority. If a topic is given, returns the priority of that topic. Otherwise, the
* priority of the app.
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 5d97afaf9793..dac89ec759f7 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -49,6 +49,8 @@ public final class Installer extends SystemService {
public static final int DEXOPT_BOOTCOMPLETE = 1 << 4;
/** Do not compile, only extract bytecode into an OAT file */
public static final int DEXOPT_EXTRACTONLY = 1 << 5;
+ /** This is an OTA update dexopt */
+ public static final int DEXOPT_OTA = 1 << 6;
/** @hide */
@IntDef(flag = true, value = {
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
new file mode 100644
index 000000000000..da62a2d6fc84
--- /dev/null
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2016 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.app.AppGlobals;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IOtaDexopt;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.Package;
+import android.content.pm.ResolveInfo;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.util.ArraySet;
+import android.util.Log;
+
+import dalvik.system.DexFile;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import static com.android.server.pm.Installer.DEXOPT_OTA;
+
+/**
+ * A service for A/B OTA dexopting.
+ *
+ * {@hide}
+ */
+public class OtaDexoptService extends IOtaDexopt.Stub {
+ private final static String TAG = "OTADexopt";
+ private final static boolean DEBUG_DEXOPT = true;
+ // Apps used in the last 7 days.
+ private final static long DEXOPT_LRU_THRESHOLD_IN_MINUTES = 7 * 24 * 60;
+
+ private final Context mContext;
+ private final PackageDexOptimizer mPackageDexOptimizer;
+ private final PackageManagerService mPackageManagerService;
+
+ // TODO: Evaluate the need for WeakReferences here.
+ private List<PackageParser.Package> mDexoptPackages;
+
+ public OtaDexoptService(Context context, PackageManagerService packageManagerService) {
+ this.mContext = context;
+ this.mPackageManagerService = packageManagerService;
+
+ // Use the package manager install and install lock here for the OTA dex optimizer.
+ mPackageDexOptimizer = new OTADexoptPackageDexOptimizer(packageManagerService.mInstaller,
+ packageManagerService.mInstallLock, context);
+ }
+
+ public static OtaDexoptService main(Context context,
+ PackageManagerService packageManagerService) {
+ OtaDexoptService ota = new OtaDexoptService(context, packageManagerService);
+ ServiceManager.addService("otadexopt", ota);
+
+ return ota;
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ResultReceiver resultReceiver) throws RemoteException {
+ (new OtaDexoptShellCommand(this)).exec(
+ this, in, out, err, args, resultReceiver);
+ }
+
+ @Override
+ public synchronized void prepare() throws RemoteException {
+ if (mDexoptPackages != null) {
+ throw new IllegalStateException("already called prepare()");
+ }
+
+ mDexoptPackages = new LinkedList<>();
+
+ ArrayList<PackageParser.Package> pkgs;
+ synchronized (mPackageManagerService.mPackages) {
+ pkgs = new ArrayList<PackageParser.Package>(mPackageManagerService.mPackages.values());
+ }
+
+ // Sort apps by importance for dexopt ordering. Important apps are given more priority
+ // in case the device runs out of space.
+
+ // Give priority to core apps.
+ for (PackageParser.Package pkg : pkgs) {
+ if (pkg.coreApp) {
+ if (DEBUG_DEXOPT) {
+ Log.i(TAG, "Adding core app " + mDexoptPackages.size() + ": " + pkg.packageName);
+ }
+ mDexoptPackages.add(pkg);
+ }
+ }
+ pkgs.removeAll(mDexoptPackages);
+
+ // Give priority to system apps that listen for pre boot complete.
+ Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
+ ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
+ for (PackageParser.Package pkg : pkgs) {
+ if (pkgNames.contains(pkg.packageName)) {
+ if (DEBUG_DEXOPT) {
+ Log.i(TAG, "Adding pre boot system app " + mDexoptPackages.size() + ": " +
+ pkg.packageName);
+ }
+ mDexoptPackages.add(pkg);
+ }
+ }
+ pkgs.removeAll(mDexoptPackages);
+
+ // Filter out packages that aren't recently used, add all remaining apps.
+ // TODO: add a property to control this?
+ if (mPackageManagerService.isHistoricalPackageUsageAvailable()) {
+ filterRecentlyUsedApps(pkgs, DEXOPT_LRU_THRESHOLD_IN_MINUTES * 60 * 1000);
+ }
+ mDexoptPackages.addAll(pkgs);
+
+ // Now go ahead and also add the libraries required for these packages.
+ // TODO: Think about interleaving things.
+ Set<PackageParser.Package> dependencies = new HashSet<>();
+ for (PackageParser.Package p : mDexoptPackages) {
+ dependencies.addAll(mPackageManagerService.findSharedNonSystemLibraries(p));
+ }
+ if (!dependencies.isEmpty()) {
+ dependencies.removeAll(mDexoptPackages);
+ }
+ mDexoptPackages.addAll(dependencies);
+
+ if (DEBUG_DEXOPT) {
+ StringBuilder sb = new StringBuilder();
+ for (PackageParser.Package pkg : mDexoptPackages) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(pkg.packageName);
+ }
+ Log.i(TAG, "Packages to be optimized: " + sb.toString());
+ }
+ }
+
+ @Override
+ public synchronized void cleanup() throws RemoteException {
+ if (DEBUG_DEXOPT) {
+ Log.i(TAG, "Cleaning up OTA Dexopt state.");
+ }
+ mDexoptPackages = null;
+ }
+
+ @Override
+ public synchronized boolean isDone() throws RemoteException {
+ if (mDexoptPackages == null) {
+ throw new IllegalStateException("done() called before prepare()");
+ }
+
+ return mDexoptPackages.isEmpty();
+ }
+
+ @Override
+ public synchronized void dexoptNextPackage() throws RemoteException {
+ if (mDexoptPackages == null) {
+ throw new IllegalStateException("dexoptNextPackage() called before prepare()");
+ }
+ if (mDexoptPackages.isEmpty()) {
+ // Tolerate repeated calls.
+ return;
+ }
+
+ PackageParser.Package nextPackage = mDexoptPackages.remove(0);
+
+ if (DEBUG_DEXOPT) {
+ Log.i(TAG, "Processing " + nextPackage.packageName + " for OTA dexopt.");
+ }
+
+ // Check for low space.
+ // TODO: If apps are not installed in the internal /data partition, we should compare
+ // against that storage's free capacity.
+ File dataDir = Environment.getDataDirectory();
+ long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
+ if (lowThreshold == 0) {
+ throw new IllegalStateException("Invalid low memory threshold");
+ }
+ long usableSpace = dataDir.getUsableSpace();
+ if (usableSpace < lowThreshold) {
+ Log.w(TAG, "Not running dexopt on " + nextPackage.packageName + " due to low memory: " +
+ usableSpace);
+ return;
+ }
+
+ mPackageDexOptimizer.performDexOpt(nextPackage, null /* ISAs */, false /* useProfiles */,
+ false /* extractOnly */);
+ }
+
+ private ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
+ List<ResolveInfo> ris = null;
+ try {
+ ris = AppGlobals.getPackageManager().queryIntentReceivers(
+ intent, null, 0, userId);
+ } catch (RemoteException e) {
+ }
+ ArraySet<String> pkgNames = new ArraySet<String>(ris == null ? 0 : ris.size());
+ if (ris != null) {
+ for (ResolveInfo ri : ris) {
+ pkgNames.add(ri.activityInfo.packageName);
+ }
+ }
+ return pkgNames;
+ }
+
+ private void filterRecentlyUsedApps(Collection<PackageParser.Package> pkgs,
+ long dexOptLRUThresholdInMills) {
+ // Filter out packages that aren't recently used.
+ int total = pkgs.size();
+ int skipped = 0;
+ long now = System.currentTimeMillis();
+ for (Iterator<PackageParser.Package> i = pkgs.iterator(); i.hasNext();) {
+ PackageParser.Package pkg = i.next();
+ long then = pkg.mLastPackageUsageTimeInMills;
+ if (then + dexOptLRUThresholdInMills < now) {
+ if (DEBUG_DEXOPT) {
+ Log.i(TAG, "Skipping dexopt of " + pkg.packageName + " last resumed: " +
+ ((then == 0) ? "never" : new Date(then)));
+ }
+ i.remove();
+ skipped++;
+ }
+ }
+ if (DEBUG_DEXOPT) {
+ Log.i(TAG, "Skipped optimizing " + skipped + " of " + total);
+ }
+ }
+
+ private static class OTADexoptPackageDexOptimizer extends
+ PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
+
+ public OTADexoptPackageDexOptimizer(Installer installer, Object installLock,
+ Context context) {
+ super(installer, installLock, context, "*otadexopt*");
+ }
+
+ @Override
+ protected int adjustDexoptFlags(int dexoptFlags) {
+ // Add the OTA flag.
+ return dexoptFlags | DEXOPT_OTA;
+ }
+
+ @Override
+ protected void recordSuccessfulDexopt(Package pkg, String instructionSet) {
+ // Never record the dexopt, as it's in the B partition.
+ }
+
+ }
+}
diff --git a/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java b/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java
new file mode 100644
index 000000000000..ea9cf1766232
--- /dev/null
+++ b/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java
@@ -0,0 +1,100 @@
+/*
+ * 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.pm.IOtaDexopt;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+class OtaDexoptShellCommand extends ShellCommand {
+ final IOtaDexopt mInterface;
+
+ OtaDexoptShellCommand(OtaDexoptService service) {
+ mInterface = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(null);
+ }
+
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ switch(cmd) {
+ case "prepare":
+ return runOtaPrepare();
+ case "cleanup":
+ return runOtaCleanup();
+ case "done":
+ return runOtaDone();
+ case "step":
+ return runOtaStep();
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (RemoteException e) {
+ pw.println("Remote exception: " + e);
+ }
+ return -1;
+ }
+
+ private int runOtaPrepare() throws RemoteException {
+ mInterface.prepare();
+ getOutPrintWriter().println("Success");
+ return 0;
+ }
+
+ private int runOtaCleanup() throws RemoteException {
+ mInterface.cleanup();
+ return 0;
+ }
+
+ private int runOtaDone() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ if (mInterface.isDone()) {
+ pw.println("OTA complete.");
+ } else {
+ pw.println("OTA incomplete.");
+ }
+ return 0;
+ }
+
+ private int runOtaStep() throws RemoteException {
+ mInterface.dexoptNextPackage();
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("OTA Dexopt (ota) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println("");
+ pw.println(" prepare");
+ pw.println(" Prepare an OTA dexopt pass, collecting all packages.");
+ pw.println(" done");
+ pw.println(" Replies whether the OTA is complete or not.");
+ pw.println(" step");
+ pw.println(" OTA dexopt the next package.");
+ pw.println(" cleanup");
+ pw.println(" Clean up internal states. Ends an OTA session.");
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index fe0f1416371d..64af21313a1d 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -20,6 +20,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.Package;
import android.os.PowerManager;
import android.os.UserHandle;
import android.os.WorkSource;
@@ -48,7 +49,7 @@ import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
/**
* Helper class for running dexopt command on packages.
*/
-final class PackageDexOptimizer {
+class PackageDexOptimizer {
private static final String TAG = "PackageManager.DexOptimizer";
static final String OAT_DIR_NAME = "oat";
// TODO b/19550105 Remove error codes and use exceptions
@@ -57,16 +58,28 @@ final class PackageDexOptimizer {
static final int DEX_OPT_DEFERRED = 2;
static final int DEX_OPT_FAILED = -1;
- private final PackageManagerService mPackageManagerService;
+ private static final boolean DEBUG_DEXOPT = PackageManagerService.DEBUG_DEXOPT;
+
+ private final Installer mInstaller;
+ private final Object mInstallLock;
private final PowerManager.WakeLock mDexoptWakeLock;
private volatile boolean mSystemReady;
- PackageDexOptimizer(PackageManagerService packageManagerService) {
- this.mPackageManagerService = packageManagerService;
- PowerManager powerManager = (PowerManager)packageManagerService.mContext.getSystemService(
- Context.POWER_SERVICE);
- mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*dexopt*");
+ PackageDexOptimizer(Installer installer, Object installLock, Context context,
+ String wakeLockTag) {
+ this.mInstaller = installer;
+ this.mInstallLock = installLock;
+
+ PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag);
+ }
+
+ protected PackageDexOptimizer(PackageDexOptimizer from) {
+ this.mInstaller = from.mInstaller;
+ this.mInstallLock = from.mInstallLock;
+ this.mDexoptWakeLock = from.mDexoptWakeLock;
+ this.mSystemReady = from.mSystemReady;
}
static boolean canOptimizePackage(PackageParser.Package pkg) {
@@ -77,27 +90,19 @@ final class PackageDexOptimizer {
* Performs dexopt on all code paths and libraries of the specified package for specified
* instruction sets.
*
- * <p>Calls to {@link com.android.server.pm.Installer#dexopt} are synchronized on
- * {@link PackageManagerService#mInstallLock}.
+ * <p>Calls to {@link com.android.server.pm.Installer#dexopt} on {@link #mInstaller} are
+ * synchronized on {@link #mInstallLock}.
*/
- int performDexOpt(PackageParser.Package pkg, String[] instructionSets,
- boolean inclDependencies, boolean useProfiles, boolean extractOnly, boolean force) {
- ArraySet<String> done;
- if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
- done = new ArraySet<String>();
- done.add(pkg.packageName);
- } else {
- done = null;
- }
- synchronized (mPackageManagerService.mInstallLock) {
+ int performDexOpt(PackageParser.Package pkg, String[] instructionSets, boolean useProfiles,
+ boolean extractOnly) {
+ synchronized (mInstallLock) {
final boolean useLock = mSystemReady;
if (useLock) {
mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
mDexoptWakeLock.acquire();
}
try {
- return performDexOptLI(pkg, instructionSets, done, useProfiles,
- extractOnly, force);
+ return performDexOptLI(pkg, instructionSets, useProfiles, extractOnly);
} finally {
if (useLock) {
mDexoptWakeLock.release();
@@ -106,21 +111,42 @@ final class PackageDexOptimizer {
}
}
+ /**
+ * Determine whether the package should be skipped for the given instruction set. A return
+ * value of true means the package will be skipped. A return value of false means that the
+ * package will be further investigated, and potentially compiled.
+ */
+ protected boolean shouldSkipBasedOnISA(PackageParser.Package pkg, String instructionSet) {
+ return pkg.mDexOptPerformed.contains(instructionSet);
+ }
+
+ /**
+ * Adjust the given dexopt-needed value. Can be overridden to influence the decision to
+ * optimize or not (and in what way).
+ */
+ protected int adjustDexoptNeeded(int dexoptNeeded) {
+ return dexoptNeeded;
+ }
+
+ /**
+ * Adjust the given dexopt flags that will be passed to the installer.
+ */
+ protected int adjustDexoptFlags(int dexoptFlags) {
+ return dexoptFlags;
+ }
+
+ /**
+ * Update the package status after a successful compilation.
+ */
+ protected void recordSuccessfulDexopt(PackageParser.Package pkg, String instructionSet) {
+ pkg.mDexOptPerformed.add(instructionSet);
+ }
+
private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets,
- ArraySet<String> done, boolean useProfiles, boolean extractOnly, boolean force) {
+ boolean useProfiles, boolean extractOnly) {
final String[] instructionSets = targetInstructionSets != null ?
targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
- if (done != null) {
- done.add(pkg.packageName);
- if (pkg.usesLibraries != null) {
- performDexOptLibsLI(pkg.usesLibraries, instructionSets, done);
- }
- if (pkg.usesOptionalLibraries != null) {
- performDexOptLibsLI(pkg.usesOptionalLibraries, instructionSets, done);
- }
- }
-
if (!canOptimizePackage(pkg)) {
return DEX_OPT_SKIPPED;
}
@@ -128,34 +154,26 @@ final class PackageDexOptimizer {
final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
- if (useProfiles) {
- // If we do a profile guided compilation then we might recompile
- // the same package if more profile information is available.
- force = true;
- }
-
final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
boolean performedDexOpt = false;
final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
for (String dexCodeInstructionSet : dexCodeInstructionSets) {
- if (!force && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) {
+ if (!useProfiles && shouldSkipBasedOnISA(pkg, dexCodeInstructionSet)) {
+ // Skip only if we do not use profiles since they might trigger a recompilation.
continue;
}
for (String path : paths) {
int dexoptNeeded;
- if (force) {
- dexoptNeeded = DexFile.DEX2OAT_NEEDED;
- } else {
- try {
- dexoptNeeded = DexFile.getDexOptNeeded(path, pkg.packageName,
- dexCodeInstructionSet, /* defer */false);
- } catch (IOException ioe) {
- Slog.w(TAG, "IOException reading apk: " + path, ioe);
- return DEX_OPT_FAILED;
- }
+ try {
+ dexoptNeeded = DexFile.getDexOptNeeded(path, pkg.packageName,
+ dexCodeInstructionSet, /* defer */false);
+ } catch (IOException ioe) {
+ Slog.w(TAG, "IOException reading apk: " + path, ioe);
+ return DEX_OPT_FAILED;
}
+ dexoptNeeded = adjustDexoptNeeded(dexoptNeeded);
if (dexoptNeeded == DexFile.NO_DEXOPT_NEEDED) {
// No dexopt needed and we don't use profiles. Nothing to do.
@@ -174,21 +192,22 @@ final class PackageDexOptimizer {
throw new IllegalStateException("Invalid dexopt needed: " + dexoptNeeded);
}
+
Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
+ pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
+ " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
+ " extractOnly=" + extractOnly + " oatDir = " + oatDir);
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
- final int dexFlags =
+ final int dexFlags = adjustDexoptFlags(
(!pkg.isForwardLocked() ? DEXOPT_PUBLIC : 0)
| (vmSafeMode ? DEXOPT_SAFEMODE : 0)
| (debuggable ? DEXOPT_DEBUGGABLE : 0)
| (extractOnly ? DEXOPT_EXTRACTONLY : 0)
- | DEXOPT_BOOTCOMPLETE;
+ | DEXOPT_BOOTCOMPLETE);
+
try {
- mPackageManagerService.mInstaller.dexopt(path, sharedGid,
- pkg.packageName, dexCodeInstructionSet, dexoptNeeded, oatDir,
- dexFlags, pkg.volumeUuid, useProfiles);
+ mInstaller.dexopt(path, sharedGid, pkg.packageName, dexCodeInstructionSet,
+ dexoptNeeded, oatDir, dexFlags, pkg.volumeUuid, useProfiles);
performedDexOpt = true;
} catch (InstallerException e) {
Slog.w(TAG, "Failed to dexopt", e);
@@ -201,7 +220,7 @@ final class PackageDexOptimizer {
// it isn't required. We therefore mark that this package doesn't need dexopt unless
// it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped
// it.
- pkg.mDexOptPerformed.add(dexCodeInstructionSet);
+ recordSuccessfulDexopt(pkg, dexCodeInstructionSet);
}
}
@@ -232,8 +251,7 @@ final class PackageDexOptimizer {
if (codePath.isDirectory()) {
File oatDir = getOatDir(codePath);
try {
- mPackageManagerService.mInstaller.createOatDir(oatDir.getAbsolutePath(),
- dexInstructionSet);
+ mInstaller.createOatDir(oatDir.getAbsolutePath(), dexInstructionSet);
} catch (InstallerException e) {
Slog.w(TAG, "Failed to create oat dir", e);
return null;
@@ -247,21 +265,36 @@ final class PackageDexOptimizer {
return new File(codePath, OAT_DIR_NAME);
}
- private void performDexOptLibsLI(ArrayList<String> libs, String[] instructionSets,
- ArraySet<String> done) {
- for (String libName : libs) {
- PackageParser.Package libPkg = mPackageManagerService.findSharedNonSystemLibrary(
- libName);
- if (libPkg != null && !done.contains(libName)) {
- // TODO: Analyze and investigate if we (should) profile libraries.
- // Currently this will do a full compilation of the library.
- performDexOptLI(libPkg, instructionSets, done, /*useProfiles*/ false,
- /* extractOnly */ false, /* force */ false);
- }
- }
- }
-
void systemReady() {
mSystemReady = true;
}
+
+ /**
+ * A specialized PackageDexOptimizer that overrides already-installed checks, forcing a
+ * dexopt path.
+ */
+ public static class ForcedUpdatePackageDexOptimizer extends PackageDexOptimizer {
+
+ public ForcedUpdatePackageDexOptimizer(Installer installer, Object installLock,
+ Context context, String wakeLockTag) {
+ super(installer, installLock, context, wakeLockTag);
+ }
+
+ public ForcedUpdatePackageDexOptimizer(PackageDexOptimizer from) {
+ super(from);
+ }
+
+ @Override
+ protected boolean shouldSkipBasedOnISA(Package pkg, String instructionSet) {
+ // Forced compilation, never skip.
+ return false;
+ }
+
+ @Override
+ protected int adjustDexoptNeeded(int dexoptNeeded) {
+ // Ensure compilation, no matter the current state.
+ // TODO: The return value is wrong when patchoat is needed.
+ return DexFile.DEX2OAT_NEEDED;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b6905b4325bd..553a9a26de16 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -139,6 +139,7 @@ import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ActivityIntentInfo;
+import android.content.pm.PackageParser.Package;
import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageStats;
@@ -263,6 +264,7 @@ import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
+import java.io.PrintStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
@@ -277,6 +279,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -313,7 +316,12 @@ public class PackageManagerService extends IPackageManager.Stub {
private static final boolean DEBUG_INTENT_MATCHING = false;
private static final boolean DEBUG_PACKAGE_SCANNING = false;
private static final boolean DEBUG_VERIFY = false;
- private static final boolean DEBUG_DEXOPT = false;
+
+ // Debug output for dexopting. This is shared between PackageManagerService, OtaDexoptService
+ // and PackageDexOptimizer. All these classes have their own flag to allow switching a single
+ // user, but by default initialize to this.
+ static final boolean DEBUG_DEXOPT = false;
+
private static final boolean DEBUG_ABI_SELECTION = false;
private static final boolean DEBUG_EPHEMERAL = false;
private static final boolean DEBUG_TRIAGED_MISSING = false;
@@ -1990,7 +1998,8 @@ public class PackageManagerService extends IPackageManager.Stub {
}
mInstaller = installer;
- mPackageDexOptimizer = new PackageDexOptimizer(this);
+ mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
+ "*dexopt*");
mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -3377,19 +3386,6 @@ public class PackageManagerService extends IPackageManager.Stub {
return null;
}
- /**
- * @hide
- */
- PackageParser.Package findSharedNonSystemLibrary(String libName) {
- synchronized (mPackages) {
- PackageManagerService.SharedLibraryEntry lib = mSharedLibraries.get(libName);
- if (lib != null && lib.apk != null) {
- return mPackages.get(lib.apk);
- }
- }
- return null;
- }
-
@Override
public FeatureInfo[] getSystemAvailableFeatures() {
Collection<FeatureInfo> featSet;
@@ -6718,8 +6714,8 @@ public class PackageManagerService extends IPackageManager.Stub {
try {
synchronized (mInstallLock) {
final String[] instructionSets = new String[] { targetInstructionSet };
- int result = mPackageDexOptimizer.performDexOpt(p, instructionSets,
- true /* inclDependencies */, useProfiles, extractOnly, force);
+ int result = performDexOptInternalWithDependenciesLI(p, instructionSets,
+ useProfiles, extractOnly, force);
return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
}
} finally {
@@ -6739,6 +6735,80 @@ public class PackageManagerService extends IPackageManager.Stub {
return pkgs;
}
+ private int performDexOptInternalWithDependenciesLI(PackageParser.Package p,
+ String instructionSets[], boolean useProfiles, boolean extractOnly, boolean force) {
+ // Select the dex optimizer based on the force parameter.
+ // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
+ // allocate an object here.
+ PackageDexOptimizer pdo = force
+ ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
+ : mPackageDexOptimizer;
+
+ // Optimize all dependencies first. Note: we ignore the return value and march on
+ // on errors.
+ Collection<PackageParser.Package> deps = findSharedNonSystemLibraries(p);
+ if (!deps.isEmpty()) {
+ for (PackageParser.Package depPackage : deps) {
+ // TODO: Analyze and investigate if we (should) profile libraries.
+ // Currently this will do a full compilation of the library.
+ pdo.performDexOpt(depPackage, instructionSets, false /* useProfiles */,
+ false /* extractOnly */);
+ }
+ }
+
+ return pdo.performDexOpt(p, instructionSets, useProfiles, extractOnly);
+ }
+
+ Collection<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
+ if (p.usesLibraries != null || p.usesOptionalLibraries != null) {
+ ArrayList<PackageParser.Package> retValue = new ArrayList<>();
+ Set<String> collectedNames = new HashSet<>();
+ findSharedNonSystemLibrariesRecursive(p, retValue, collectedNames);
+
+ retValue.remove(p);
+
+ return retValue;
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ private void findSharedNonSystemLibrariesRecursive(PackageParser.Package p,
+ Collection<PackageParser.Package> collected, Set<String> collectedNames) {
+ if (!collectedNames.contains(p.packageName)) {
+ collectedNames.add(p.packageName);
+ collected.add(p);
+
+ if (p.usesLibraries != null) {
+ findSharedNonSystemLibrariesRecursive(p.usesLibraries, collected, collectedNames);
+ }
+ if (p.usesOptionalLibraries != null) {
+ findSharedNonSystemLibrariesRecursive(p.usesOptionalLibraries, collected,
+ collectedNames);
+ }
+ }
+ }
+
+ private void findSharedNonSystemLibrariesRecursive(Collection<String> libs,
+ Collection<PackageParser.Package> collected, Set<String> collectedNames) {
+ for (String libName : libs) {
+ PackageParser.Package libPkg = findSharedNonSystemLibrary(libName);
+ if (libPkg != null) {
+ findSharedNonSystemLibrariesRecursive(libPkg, collected, collectedNames);
+ }
+ }
+ }
+
+ private PackageParser.Package findSharedNonSystemLibrary(String libName) {
+ synchronized (mPackages) {
+ PackageManagerService.SharedLibraryEntry lib = mSharedLibraries.get(libName);
+ if (lib != null && lib.apk != null) {
+ return mPackages.get(lib.apk);
+ }
+ }
+ return null;
+ }
+
public void shutdown() {
mPackageUsage.write(true);
}
@@ -6763,9 +6833,8 @@ public class PackageManagerService extends IPackageManager.Stub {
// Whoever is calling forceDexOpt wants a fully compiled package.
// Don't use profiles since that may cause compilation to be skipped.
- final int res = mPackageDexOptimizer.performDexOpt(pkg, instructionSets,
- true /* inclDependencies */, false /* useProfiles */,
- false /* extractOnly */, true /* force */);
+ final int res = performDexOptInternalWithDependenciesLI(pkg, instructionSets,
+ false /* useProfiles */, false /* extractOnly */, true /* force */);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -7980,6 +8049,11 @@ public class PackageManagerService extends IPackageManager.Stub {
pkg.applicationInfo.primaryCpuAbi = abi;
}
}
+ if (cpuAbiOverride != null &&
+ cpuAbiOverride.equals(pkg.applicationInfo.secondaryCpuAbi)) {
+ pkg.applicationInfo.secondaryCpuAbi = pkg.applicationInfo.primaryCpuAbi;
+ pkg.applicationInfo.primaryCpuAbi = cpuAbiOverride;
+ }
} else {
String[] abiList = (cpuAbiOverride != null) ?
new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS;
@@ -12842,8 +12916,11 @@ public class PackageManagerService extends IPackageManager.Stub {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- // Mark that we have an install time CPU ABI override.
- pkg.cpuAbiOverride = args.abiOverride;
+ // If package doesn't declare API override, mark that we have an install
+ // time CPU ABI override.
+ if (TextUtils.isEmpty(pkg.cpuAbiOverride)) {
+ pkg.cpuAbiOverride = args.abiOverride;
+ }
String pkgName = res.name = pkg.packageName;
if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY) != 0) {
@@ -13016,7 +13093,9 @@ public class PackageManagerService extends IPackageManager.Stub {
scanFlags |= SCAN_NO_DEX;
try {
- derivePackageAbi(pkg, new File(pkg.codePath), args.abiOverride,
+ String abiOverride = (TextUtils.isEmpty(pkg.cpuAbiOverride) ?
+ args.abiOverride : pkg.cpuAbiOverride);
+ derivePackageAbi(pkg, new File(pkg.codePath), abiOverride,
true /* extract libs */);
} catch (PackageManagerException pme) {
Slog.e(TAG, "Error deriving application ABI", pme);
@@ -13032,8 +13111,7 @@ public class PackageManagerService extends IPackageManager.Stub {
// Do not run PackageDexOptimizer through the local performDexOpt
// method because `pkg` is not in `mPackages` yet.
int result = mPackageDexOptimizer.performDexOpt(pkg, null /* instructionSets */,
- false /* inclDependencies */, false /* useProfiles */,
- true /* extractOnly */, false /* force */);
+ false /* useProfiles */, true /* extractOnly */);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
String msg = "Extracking package failed for " + pkgName;
@@ -18033,4 +18111,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
"Cannot call " + tag + " from UID " + callingUid);
}
}
+
+ boolean isHistoricalPackageUsageAvailable() {
+ return mPackageUsage.isHistoricalPackageUsageAvailable();
+ }
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 6452feeb6975..7ec945d74e52 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -302,7 +302,12 @@ class AppWindowToken extends WindowToken {
void setWindowsExiting(boolean exiting) {
for (int i = allAppWindows.size() - 1; i >= 0; i--) {
WindowState win = allAppWindows.get(i);
- win.mExiting = exiting;
+ // If the app already requested to remove its window, we don't modify
+ // its exiting state. Otherwise the stale window won't get removed on
+ // exit and could cause focus to be given to the wrong window.
+ if (!(win.mRemoveOnExit && win.mExiting)) {
+ win.mExiting = exiting;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 47bdb5cbad90..14e4fd59be29 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7260,8 +7260,18 @@ public class WindowManagerService extends IWindowManager.Stub
mTaskPositioner = new TaskPositioner(this);
mTaskPositioner.register(display);
mInputMonitor.updateInputWindowsLw(true /*force*/);
+
+ // We need to grab the touch focus so that the touch events during the
+ // resizing/scrolling are not sent to the app. 'win' is the main window
+ // of the app, it may not have focus since there might be other windows
+ // on top (eg. a dialog window).
+ WindowState transferFocusFromWin = win;
+ if (mCurrentFocus != null && mCurrentFocus != win
+ && mCurrentFocus.mAppToken == win.mAppToken) {
+ transferFocusFromWin = mCurrentFocus;
+ }
if (!mInputManager.transferTouchFocus(
- win.mInputChannel, mTaskPositioner.mServerChannel)) {
+ transferFocusFromWin.mInputChannel, mTaskPositioner.mServerChannel)) {
Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus");
mTaskPositioner.unregister();
mTaskPositioner = null;
diff --git a/services/core/jni/com_android_server_AlarmManagerService.cpp b/services/core/jni/com_android_server_AlarmManagerService.cpp
index 5cbb277a2f12..246ab0d259f4 100644
--- a/services/core/jni/com_android_server_AlarmManagerService.cpp
+++ b/services/core/jni/com_android_server_AlarmManagerService.cpp
@@ -40,6 +40,8 @@
#include <linux/android_alarm.h>
#include <linux/rtc.h>
+#include <memory>
+
namespace android {
static const size_t N_ANDROID_TIMERFDS = ANDROID_ALARM_TYPE_COUNT + 1;
@@ -323,14 +325,14 @@ static bool rtc_is_hctosys(unsigned int rtc_id)
static int wall_clock_rtc()
{
- DIR *dir = opendir(rtc_sysfs);
- if (!dir) {
+ std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(rtc_sysfs), closedir);
+ if (!dir.get()) {
ALOGE("failed to open %s: %s", rtc_sysfs, strerror(errno));
return -1;
}
struct dirent *dirent;
- while (errno = 0, dirent = readdir(dir)) {
+ while (errno = 0, dirent = readdir(dir.get())) {
unsigned int rtc_id;
int matched = sscanf(dirent->d_name, "rtc%u", &rtc_id);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 4022ac62bf96..cfe147e2cf13 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1463,6 +1463,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
void securityLogSetLoggingEnabledProperty(boolean enabled) {
SecurityLog.setLoggingEnabledProperty(enabled);
}
+
+ boolean securityLogGetLoggingEnabledProperty() {
+ return SecurityLog.getLoggingEnabledProperty();
+ }
+
+ boolean securityLogIsLoggingEnabled() {
+ return SecurityLog.isLoggingEnabled();
+ }
}
/**
@@ -1607,7 +1615,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (mOwners.hasDeviceOwner()) {
mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, "true");
disableDeviceLoggingIfNotCompliant();
- if (SecurityLog.getLoggingEnabledProperty()) {
+ if (mInjector.securityLogGetLoggingEnabledProperty()) {
mSecurityLogMonitor.start();
}
} else {
@@ -2005,9 +2013,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
void removeActiveAdminLocked(final ComponentName adminReceiver, final int userHandle) {
final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
if (admin != null) {
- synchronized (this) {
- getUserData(userHandle).mRemovingAdmins.add(adminReceiver);
- }
+ getUserData(userHandle).mRemovingAdmins.add(adminReceiver);
+
sendAdminCommandLocked(admin,
DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLED,
new BroadcastReceiver() {
@@ -2813,14 +2820,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (admin == null) {
return;
}
+ // Active device/profile owners must remain active admins.
+ if (isDeviceOwner(adminReceiver, userHandle)
+ || isProfileOwner(adminReceiver, userHandle)) {
+ Slog.e(LOG_TAG, "Device/profile owner cannot be removed: component=" +
+ adminReceiver);
+ return;
+ }
if (admin.getUid() != mInjector.binderGetCallingUid()) {
- // Active device/profile owners must remain active admins.
- if (isDeviceOwner(adminReceiver, userHandle)
- || isProfileOwner(adminReceiver, userHandle)) {
- Slog.e(LOG_TAG, "Device/profile owner cannot be removed: component=" +
- adminReceiver);
- return;
- }
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_DEVICE_ADMINS, null);
}
@@ -4472,7 +4479,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
mInjector.binderRestoreCallingIdentity(ident);
}
- if (SecurityLog.isLoggingEnabled()) {
+ if (mInjector.securityLogIsLoggingEnabled()) {
SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0);
}
}
@@ -4502,7 +4509,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
- if (SecurityLog.isLoggingEnabled()) {
+ if (mInjector.securityLogIsLoggingEnabled()) {
SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 1);
}
}
@@ -4511,7 +4518,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
public void reportKeyguardDismissed() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
- if (SecurityLog.isLoggingEnabled()) {
+ if (mInjector.securityLogIsLoggingEnabled()) {
SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISSED);
}
}
@@ -4520,7 +4527,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
public void reportKeyguardSecured() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
- if (SecurityLog.isLoggingEnabled()) {
+ if (mInjector.securityLogIsLoggingEnabled()) {
SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_SECURED);
}
}
@@ -5473,9 +5480,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
throw new SecurityException(e);
}
synchronized (this) {
+ final ComponentName deviceOwnerComponent = mOwners.getDeviceOwnerComponent();
+ final int deviceOwnerUserId = mOwners.getDeviceOwnerUserId();
if (!mOwners.hasDeviceOwner()
- || !mOwners.getDeviceOwnerComponent().getPackageName().equals(packageName)
- || (mOwners.getDeviceOwnerUserId() != UserHandle.getUserId(callingUid))) {
+ || !deviceOwnerComponent.getPackageName().equals(packageName)
+ || (deviceOwnerUserId != UserHandle.getUserId(callingUid))) {
throw new SecurityException(
"clearDeviceOwner can only be called by the device owner");
}
@@ -5487,8 +5496,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
admin.forceEphemeralUsers = false;
mUserManagerInternal.setForceEphemeralUsers(admin.forceEphemeralUsers);
}
-
- clearUserPoliciesLocked(new UserHandle(UserHandle.USER_SYSTEM));
+ clearUserPoliciesLocked(deviceOwnerUserId);
mOwners.clearDeviceOwner();
mOwners.writeDeviceOwner();
@@ -5498,6 +5506,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
long ident = mInjector.binderClearCallingIdentity();
try {
mInjector.getIBackupManager().setBackupServiceActive(UserHandle.USER_SYSTEM, true);
+
+ removeActiveAdminLocked(deviceOwnerComponent, deviceOwnerUserId);
} catch (RemoteException e) {
throw new IllegalStateException("Failed reactivating backup service.", e);
} finally {
@@ -5538,9 +5548,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
synchronized (this) {
admin.disableCamera = false;
admin.userRestrictions = null;
- clearUserPoliciesLocked(callingUser);
+ clearUserPoliciesLocked(userId);
mOwners.removeProfileOwner(userId);
mOwners.writeProfileOwner(userId);
+
+ final long ident = mInjector.binderClearCallingIdentity();
+ try {
+ removeActiveAdminLocked(who, userId);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
}
}
@@ -5568,8 +5585,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return mLockPatternUtils.getDeviceOwnerInfo();
}
- private void clearUserPoliciesLocked(UserHandle userHandle) {
- int userId = userHandle.getIdentifier();
+ private void clearUserPoliciesLocked(int userId) {
// Reset some of the user-specific policies
DevicePolicyData policy = getUserData(userId);
policy.mPermissionPolicy = DevicePolicyManager.PERMISSION_POLICY_PROMPT;
@@ -5583,8 +5599,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
try {
mIPackageManager.updatePermissionFlagsForAllApps(
PackageManager.FLAG_PERMISSION_POLICY_FIXED,
- 0 /* flagValues */, userHandle.getIdentifier());
- pushUserRestrictions(userHandle.getIdentifier());
+ 0 /* flagValues */, userId);
+ pushUserRestrictions(userId);
} catch (RemoteException re) {
} finally {
mInjector.binderRestoreCallingIdentity(ident);
@@ -7244,7 +7260,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (managedUserId < 0) {
return;
}
- if (getCrossProfileCallerIdDisabledForUser(managedUserId)) {
+ if (isCrossProfileQuickContactDisabled(managedUserId)) {
if (VERBOSE_LOG) {
Log.v(LOG_TAG,
"Cross-profile contacts access disabled for user " + managedUserId);
@@ -7260,6 +7276,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
/**
+ * @return true if cross-profile QuickContact is disabled
+ */
+ private boolean isCrossProfileQuickContactDisabled(int userId) {
+ return getCrossProfileCallerIdDisabledForUser(userId)
+ && getCrossProfileContactsSearchDisabledForUser(userId);
+ }
+
+ /**
* @return the user ID of the managed user that is linked to the current user, if any.
* Otherwise -1.
*/
@@ -8344,7 +8368,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Preconditions.checkNotNull(admin);
synchronized (this) {
getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
- return SecurityLog.getLoggingEnabledProperty();
+ return mInjector.securityLogGetLoggingEnabledProperty();
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 254a37a7adfa..b64db578decb 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -80,6 +80,7 @@ import com.android.server.os.SchedulingPolicyService;
import com.android.server.pm.BackgroundDexOptService;
import com.android.server.pm.Installer;
import com.android.server.pm.LauncherAppsService;
+import com.android.server.pm.OtaDexoptService;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerService;
import com.android.server.power.PowerManagerService;
@@ -1097,6 +1098,18 @@ public final class SystemServer {
}
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ // Manages A/B OTA dexopting.
+ boolean disableOtaDexopt = SystemProperties.getBoolean("config.disable_otadexopt",
+ false);
+ if (!disableOtaDexopt) {
+ traceBeginAndSlog("StartOtaDexOptService");
+ try {
+ OtaDexoptService.main(mSystemContext, mPackageManagerService);
+ } catch (Throwable e) {
+ reportWtf("starting BackgroundDexOptService", e);
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
}
mSystemServiceManager.startService(LauncherAppsService.class);
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 87ac846a2a43..62c635db8578 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -18,6 +18,7 @@ package android.net.ip;
import android.content.Context;
import android.net.DhcpResults;
+import android.net.InterfaceConfiguration;
import android.net.LinkProperties;
import android.net.LinkProperties.ProvisioningChange;
import android.net.RouteInfo;
@@ -86,8 +87,8 @@ public class IpManager extends StateMachine {
// TODO: Kill with fire once DHCP and static configuration are moved
// out of WifiStateMachine.
- public void onIPv4ProvisioningSuccess(DhcpResults dhcpResults, int reason) {}
- public void onIPv4ProvisioningFailure(int reason) {}
+ public void onIPv4ProvisioningSuccess(DhcpResults dhcpResults) {}
+ public void onIPv4ProvisioningFailure() {}
public void onProvisioningSuccess(LinkProperties newLp) {}
public void onProvisioningFailure(LinkProperties newLp) {}
@@ -216,8 +217,8 @@ public class IpManager extends StateMachine {
}
// TODO: Kill with fire once DHCPv4/static config is moved into IpManager.
- public void updateWithDhcpResults(DhcpResults dhcpResults, int reason) {
- sendMessage(CMD_UPDATE_DHCPV4_RESULTS, reason, 0, dhcpResults);
+ public void updateWithDhcpResults(DhcpResults dhcpResults) {
+ sendMessage(CMD_UPDATE_DHCPV4_RESULTS, dhcpResults);
}
@@ -382,7 +383,15 @@ public class IpManager extends StateMachine {
}
});
- // TODO: Check mStaticIpConfig and handle accordingly.
+ // If we have a StaticIpConfiguration attempt to apply it and
+ // handle the result accordingly.
+ if (mStaticIpConfig != null) {
+ if (applyStaticIpConfig()) {
+ sendMessage(CMD_UPDATE_DHCPV4_RESULTS, new DhcpResults(mStaticIpConfig));
+ } else {
+ sendMessage(CMD_UPDATE_DHCPV4_RESULTS);
+ }
+ }
}
@Override
@@ -415,15 +424,14 @@ public class IpManager extends StateMachine {
case CMD_UPDATE_DHCPV4_RESULTS:
final DhcpResults dhcpResults = (DhcpResults) msg.obj;
- final int reason = msg.arg1;
if (dhcpResults != null) {
mDhcpResults = new DhcpResults(dhcpResults);
setLinkProperties(assembleLinkProperties());
- mCallback.onIPv4ProvisioningSuccess(dhcpResults, reason);
+ mCallback.onIPv4ProvisioningSuccess(dhcpResults);
} else {
mDhcpResults = null;
setLinkProperties(assembleLinkProperties());
- mCallback.onIPv4ProvisioningFailure(reason);
+ mCallback.onIPv4ProvisioningFailure();
}
break;
@@ -457,5 +465,19 @@ public class IpManager extends StateMachine {
return HANDLED;
}
+ private boolean applyStaticIpConfig() {
+ final InterfaceConfiguration ifcg = new InterfaceConfiguration();
+ ifcg.setLinkAddress(mStaticIpConfig.ipAddress);
+ ifcg.setInterfaceUp();
+ try {
+ mNwService.setInterfaceConfig(mInterfaceName, ifcg);
+ if (DBG) Log.d(TAG, "Static IP configuration succeeded");
+ } catch (IllegalStateException | RemoteException e) {
+ Log.e(TAG, "Static IP configuration failed: ", e);
+ return false;
+ }
+
+ return true;
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index bd37f4a75e6f..db2a9ad3223c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -291,5 +291,15 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi
void securityLogSetLoggingEnabledProperty(boolean enabled) {
context.settings.securityLogSetLoggingEnabledProperty(enabled);
}
+
+ @Override
+ boolean securityLogGetLoggingEnabledProperty() {
+ return context.settings.securityLogGetLoggingEnabledProperty();
+ }
+
+ @Override
+ boolean securityLogIsLoggingEnabled() {
+ return context.settings.securityLogIsLoggingEnabled();
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 87569b7a6caa..64f60d93628b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -559,6 +559,10 @@ public class DevicePolicyManagerTest extends DpmTestBase {
expected.getMessage().contains("already has a device owner"));
}
+ // DO admin can't be deactivated.
+ dpm.removeActiveAdmin(admin1);
+ assertTrue(dpm.isAdminActive(admin1));
+
// TODO Test getDeviceOwnerName() too. To do so, we need to change
// DPMS.getApplicationLabel() because Context.createPackageContextAsUser() is not mockable.
}
@@ -763,6 +767,11 @@ public class DevicePolicyManagerTest extends DpmTestBase {
assertEquals(admin1, dpm.getDeviceOwnerComponentOnAnyUser());
+ dpm.addUserRestriction(admin1, UserManager.DISALLOW_ADD_USER);
+
+ assertTrue(dpm.isAdminActive(admin1));
+ assertFalse(dpm.isRemovingAdmin(admin1, UserHandle.USER_SYSTEM));
+
// Set up other mocks.
when(mContext.userManager.getUserRestrictions()).thenReturn(new Bundle());
@@ -770,11 +779,21 @@ public class DevicePolicyManagerTest extends DpmTestBase {
doReturn(DpmMockContext.CALLER_SYSTEM_USER_UID).when(mContext.packageManager).getPackageUidAsUser(
eq(admin1.getPackageName()),
anyInt());
+ reset(mContext.userManagerInternal);
dpm.clearDeviceOwnerApp(admin1.getPackageName());
// Now DO shouldn't be set.
assertNull(dpm.getDeviceOwnerComponentOnAnyUser());
+ verify(mContext.userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(UserHandle.USER_SYSTEM),
+ MockUtils.checkUserRestrictions(),
+ MockUtils.checkUserRestrictions()
+ );
+
+ assertTrue(dpm.isAdminActive(admin1));
+ assertTrue(dpm.isRemovingAdmin(admin1, UserHandle.USER_SYSTEM));
+
// TODO Check other calls.
}
@@ -823,6 +842,10 @@ public class DevicePolicyManagerTest extends DpmTestBase {
public void testSetProfileOwner() throws Exception {
setAsProfileOwner(admin1);
+ // PO admin can't be deactivated.
+ dpm.removeActiveAdmin(admin1);
+ assertTrue(dpm.isAdminActive(admin1));
+
// Try setting DO on the same user, which should fail.
setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID);
dpm.setActiveAdmin(admin2, /* refreshing= */ true, DpmMockContext.CALLER_USER_HANDLE);
@@ -835,6 +858,22 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
}
+ public void testClearProfileOwner() throws Exception {
+ setAsProfileOwner(admin1);
+
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+
+ assertTrue(dpm.isProfileOwnerApp(admin1.getPackageName()));
+ assertFalse(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
+
+ // Clear
+ dpm.clearProfileOwner(admin1);
+
+ // Check
+ assertFalse(dpm.isProfileOwnerApp(admin1.getPackageName()));
+ assertTrue(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE));
+ }
+
public void testSetProfileOwner_failures() throws Exception {
// TODO Test more failure cases. Basically test all chacks in enforceCanSetProfileOwner().
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 651873230082..7a000985a5ca 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -198,6 +198,14 @@ public class DpmMockContext extends MockContext {
void securityLogSetLoggingEnabledProperty(boolean enabled) {
}
+
+ public boolean securityLogGetLoggingEnabledProperty() {
+ return false;
+ }
+
+ public boolean securityLogIsLoggingEnabled() {
+ return false;
+ }
}
public final Context realTestContext;
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 56eb7ec5fb78..d45938cb9b85 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -348,11 +348,6 @@ public final class Phone {
}
private void checkCallTree(ParcelableCall parcelableCall) {
- if (parcelableCall.getParentCallId() != null &&
- !mCallByTelecomCallId.containsKey(parcelableCall.getParentCallId())) {
- Log.wtf(this, "ParcelableCall %s has nonexistent parent %s",
- parcelableCall.getId(), parcelableCall.getParentCallId());
- }
if (parcelableCall.getChildCallIds() != null) {
for (int i = 0; i < parcelableCall.getChildCallIds().size(); i++) {
if (!mCallByTelecomCallId.containsKey(parcelableCall.getChildCallIds().get(i))) {
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index af36b3e41eb2..95c8db54498c 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -243,4 +243,9 @@ interface ITelecomService {
* @see TelecomServiceImpl#setDefaultDialer
*/
boolean setDefaultDialer(in String packageName);
+
+ /**
+ * @see TelecomServiceImpl#launchManageBlockedNumbersActivity
+ **/
+ void launchManageBlockedNumbersActivity(in String callingPackageName);
}
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 8443490feb00..9eb1304c6ab6 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -187,6 +187,13 @@ public class DisconnectCause {
*/
public static final int CDMA_ALREADY_ACTIVATED = 49;
+ /**
+ * The call was terminated because it is not possible to place a video call while TTY is
+ * enabled.
+ * {@hide}
+ */
+ public static final int VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED = 50;
+
//*********************************************************************************************
// When adding a disconnect type:
// 1) Please assign the new type the next id value below.
@@ -202,7 +209,7 @@ public class DisconnectCause {
public static final int MINIMUM_VALID_VALUE = NOT_DISCONNECTED;
/** Largest valid value for call disconnect codes. */
- public static final int MAXIMUM_VALID_VALUE = CDMA_ALREADY_ACTIVATED;
+ public static final int MAXIMUM_VALID_VALUE = VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED;
/** Private constructor to avoid class instantiation. */
private DisconnectCause() {
@@ -310,6 +317,8 @@ public class DisconnectCause {
return "IMS_MERGED_SUCCESSFULLY";
case CDMA_ALREADY_ACTIVATED:
return "CDMA_ALREADY_ACTIVATED";
+ case VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED:
+ return "VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED";
default:
return "INVALID: " + cause;
}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 7d5645ed7337..429839f6f6f9 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -77,6 +77,21 @@ public interface RILConstants {
int SIM_SAP_MSG_SIZE_TOO_SMALL = 34;
int SIM_SAP_CONNECT_OK_CALL_ONGOING = 35;
int LCE_NOT_SUPPORTED = 36; /* Link Capacity Estimation (LCE) not supported */
+ int NO_MEMORY = 37; /* Not sufficient memory to process the request */
+ int INTERNAL_ERR = 38; /* Hit unexpected vendor internal error scenario */
+ int SYSTEM_ERR = 39; /* Hit platform or system error */
+ int MODEM_ERR = 40; /* Hit unexpected modem error */
+ int INVALID_STATE = 41; /* Can not process the request as vendor RIL is in
+ invalid state. */
+ int NO_RESOURCES = 42; /* Not sufficient resource to process the request */
+ int SIM_ERR = 43; /* Received error from SIM card */
+ int INVALID_ARGUMENTS = 44; /* Received invalid arguments in request */
+ int INVALID_SIM_STATE = 45; /* Can not process the request in current SIM state */
+ int INVALID_MODEM_STATE = 46; /* Can not process the request in current Modem state */
+ int INVALID_CALL_ID = 47; /* Received invalid call id in request */
+ int NO_SMS_TO_ACK = 48; /* ACK received when there is no SMS to ack */
+ int NETWORK_ERR = 49; /* Received error from network */
+ int REQUEST_RATE_LIMITED = 50; /* Operation denied due to overly-frequent requests */
/* NETWORK_MODE_* See ril.h RIL_REQUEST_SET_PREFERRED_NETWORK_TYPE */
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index a4f4ba928855..f74b93abd796 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -30,6 +30,7 @@ sources := \
compile/PseudolocaleGenerator.cpp \
compile/Pseudolocalizer.cpp \
compile/XmlIdCollector.cpp \
+ filter/ConfigFilter.cpp \
flatten/Archive.cpp \
flatten/TableFlattener.cpp \
flatten/XmlFlattener.cpp \
@@ -71,6 +72,7 @@ testSources := \
compile/PseudolocaleGenerator_test.cpp \
compile/Pseudolocalizer_test.cpp \
compile/XmlIdCollector_test.cpp \
+ filter/ConfigFilter_test.cpp \
flatten/FileExportWriter_test.cpp \
flatten/TableFlattener_test.cpp \
flatten/XmlFlattener_test.cpp \
diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp
index 03691568ed47..6acf3b09d301 100644
--- a/tools/aapt2/Locale.cpp
+++ b/tools/aapt2/Locale.cpp
@@ -70,7 +70,7 @@ static inline bool isNumber(const std::string& str) {
return std::all_of(std::begin(str), std::end(str), ::isdigit);
}
-bool LocaleValue::initFromFilterString(const std::string& str) {
+bool LocaleValue::initFromFilterString(const StringPiece& str) {
// A locale (as specified in the filter) is an underscore separated name such
// as "en_US", "en_Latn_US", or "en_US_POSIX".
std::vector<std::string> parts = util::splitAndLowercase(str, '_');
diff --git a/tools/aapt2/Locale.h b/tools/aapt2/Locale.h
index ceec764ba4fd..b1c80ab27641 100644
--- a/tools/aapt2/Locale.h
+++ b/tools/aapt2/Locale.h
@@ -17,6 +17,8 @@
#ifndef AAPT_LOCALE_VALUE_H
#define AAPT_LOCALE_VALUE_H
+#include "util/StringPiece.h"
+
#include <androidfw/ResourceTypes.h>
#include <string>
#include <vector>
@@ -37,7 +39,7 @@ struct LocaleValue {
/**
* Initialize this LocaleValue from a config string.
*/
- bool initFromFilterString(const std::string& config);
+ bool initFromFilterString(const StringPiece& config);
/**
* Initialize this LocaleValue from parts of a vector.
diff --git a/tools/aapt2/filter/ConfigFilter.cpp b/tools/aapt2/filter/ConfigFilter.cpp
new file mode 100644
index 000000000000..68a017d247f6
--- /dev/null
+++ b/tools/aapt2/filter/ConfigFilter.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 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 "ConfigDescription.h"
+#include "filter/ConfigFilter.h"
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+void AxisConfigFilter::addConfig(ConfigDescription config) {
+ uint32_t diffMask = ConfigDescription::defaultConfig().diff(config);
+
+ // Ignore the version
+ diffMask &= ~android::ResTable_config::CONFIG_VERSION;
+
+ // Ignore any densities. Those are best handled in --preferred-density
+ if ((diffMask & android::ResTable_config::CONFIG_DENSITY) != 0) {
+ config.density = 0;
+ diffMask &= ~android::ResTable_config::CONFIG_DENSITY;
+ }
+
+ mConfigs.insert(std::make_pair(config, diffMask));
+ mConfigMask |= diffMask;
+}
+
+bool AxisConfigFilter::match(const ConfigDescription& config) const {
+ const uint32_t mask = ConfigDescription::defaultConfig().diff(config);
+ if ((mConfigMask & mask) == 0) {
+ // The two configurations don't have any common axis.
+ return true;
+ }
+
+ uint32_t matchedAxis = 0;
+ for (const auto& entry : mConfigs) {
+ const ConfigDescription& target = entry.first;
+ const uint32_t diffMask = entry.second;
+ uint32_t diff = target.diff(config);
+ if ((diff & diffMask) == 0) {
+ // Mark the axis that was matched.
+ matchedAxis |= diffMask;
+ } else if ((diff & diffMask) == android::ResTable_config::CONFIG_LOCALE) {
+ // If the locales differ, but the languages are the same and
+ // the locale we are matching only has a language specified,
+ // we match.
+ if (config.language[0] &&
+ memcmp(config.language, target.language, sizeof(config.language)) == 0) {
+ if (config.country[0] == 0) {
+ matchedAxis |= android::ResTable_config::CONFIG_LOCALE;
+ }
+ }
+ } else if ((diff & diffMask) == android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE) {
+ // Special case if the smallest screen width doesn't match. We check that the
+ // config being matched has a smaller screen width than the filter specified.
+ if (config.smallestScreenWidthDp != 0 &&
+ config.smallestScreenWidthDp < target.smallestScreenWidthDp) {
+ matchedAxis |= android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE;
+ }
+ }
+ }
+ return matchedAxis == (mConfigMask & mask);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/filter/ConfigFilter.h b/tools/aapt2/filter/ConfigFilter.h
new file mode 100644
index 000000000000..36e9c44255e4
--- /dev/null
+++ b/tools/aapt2/filter/ConfigFilter.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 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 AAPT_FILTER_CONFIGFILTER_H
+#define AAPT_FILTER_CONFIGFILTER_H
+
+#include "ConfigDescription.h"
+
+#include <set>
+#include <utility>
+
+namespace aapt {
+
+/**
+ * Matches ConfigDescriptions based on some pattern.
+ */
+class IConfigFilter {
+public:
+ virtual ~IConfigFilter() = default;
+
+ /**
+ * Returns true if the filter matches the configuration, false otherwise.
+ */
+ virtual bool match(const ConfigDescription& config) const = 0;
+};
+
+/**
+ * Implements config axis matching. An axis is one component of a configuration, like screen
+ * density or locale. If an axis is specified in the filter, and the axis is specified in
+ * the configuration to match, they must be compatible. Otherwise the configuration to match is
+ * accepted.
+ *
+ * Used when handling "-c" options.
+ */
+class AxisConfigFilter : public IConfigFilter {
+public:
+ void addConfig(ConfigDescription config);
+
+ bool match(const ConfigDescription& config) const override;
+
+private:
+ std::set<std::pair<ConfigDescription, uint32_t>> mConfigs;
+ uint32_t mConfigMask = 0;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_FILTER_CONFIGFILTER_H */
diff --git a/tools/aapt2/filter/ConfigFilter_test.cpp b/tools/aapt2/filter/ConfigFilter_test.cpp
new file mode 100644
index 000000000000..f6b49557306d
--- /dev/null
+++ b/tools/aapt2/filter/ConfigFilter_test.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 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 "filter/ConfigFilter.h"
+#include "test/Common.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(ConfigFilterTest, EmptyFilterMatchesAnything) {
+ AxisConfigFilter filter;
+
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi")));
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithUnrelatedAxis) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr"));
+
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithSameValueAxis) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr"));
+
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithSameValueAxisAndOtherUnrelatedAxis) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr"));
+
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-320dpi")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithOneMatchingAxis) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr-rFR"));
+ filter.addConfig(test::parseConfigOrDie("sw360dp"));
+ filter.addConfig(test::parseConfigOrDie("normal"));
+ filter.addConfig(test::parseConfigOrDie("en-rUS"));
+
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("en")));
+}
+
+TEST(ConfigFilterTest, DoesNotMatchConfigWithDifferentValueAxis) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr"));
+
+ EXPECT_FALSE(filter.match(test::parseConfigOrDie("de")));
+}
+
+TEST(ConfigFilterTest, DoesNotMatchWhenOneQualifierIsExplicitlyNotMatched) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("fr-rFR"));
+ filter.addConfig(test::parseConfigOrDie("en-rUS"));
+ filter.addConfig(test::parseConfigOrDie("normal"));
+ filter.addConfig(test::parseConfigOrDie("large"));
+ filter.addConfig(test::parseConfigOrDie("xxhdpi"));
+ filter.addConfig(test::parseConfigOrDie("sw320dp"));
+
+ EXPECT_FALSE(filter.match(test::parseConfigOrDie("fr-sw600dp-v13")));
+}
+
+TEST(ConfigFilterTest, MatchesSmallestWidthWhenSmaller) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("sw600dp"));
+
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-sw320dp-v13")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithSameLanguageButNoRegionSpecified) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("de-rDE"));
+
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("de")));
+}
+
+TEST(ConfigFilterTest, IgnoresVersion) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("normal-v4"));
+
+ // The configs don't match on any axis besides version, which should be ignored.
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("sw600dp-v13")));
+}
+
+TEST(ConfigFilterTest, MatchesConfigWithRegion) {
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("kok"));
+ filter.addConfig(test::parseConfigOrDie("kok-rIN"));
+ filter.addConfig(test::parseConfigOrDie("kok-v419"));
+
+ EXPECT_TRUE(filter.match(test::parseConfigOrDie("kok-rIN")));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 3ecb2c4113d5..fd76e887ab2a 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -17,8 +17,10 @@
#include "AppInfo.h"
#include "Debug.h"
#include "Flags.h"
+#include "Locale.h"
#include "NameMangler.h"
#include "compile/IdAssigner.h"
+#include "filter/ConfigFilter.h"
#include "flatten/Archive.h"
#include "flatten/TableFlattener.h"
#include "flatten/XmlFlattener.h"
@@ -64,7 +66,7 @@ struct LinkOptions {
std::vector<std::string> extensionsToNotCompress;
Maybe<std::u16string> privateSymbols;
ManifestFixerOptions manifestFixerOptions;
-
+ IConfigFilter* configFilter = nullptr;
};
struct LinkContext : public IAaptContext {
@@ -97,8 +99,8 @@ struct LinkContext : public IAaptContext {
class LinkCommand {
public:
- LinkCommand(const LinkOptions& options) :
- mOptions(options), mContext(), mFinalTable(), mFileCollection(nullptr) {
+ LinkCommand(LinkContext* context, const LinkOptions& options) :
+ mOptions(options), mContext(context), mFinalTable(), mFileCollection(nullptr) {
std::unique_ptr<io::FileCollection> fileCollection =
util::make_unique<io::FileCollection>();
@@ -117,14 +119,14 @@ public:
AssetManagerSymbolTableBuilder builder;
for (const std::string& path : mOptions.includePaths) {
if (mOptions.verbose) {
- mContext.getDiagnostics()->note(DiagMessage(path) << "loading include path");
+ mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
}
std::unique_ptr<android::AssetManager> assetManager =
util::make_unique<android::AssetManager>();
int32_t cookie = 0;
if (!assetManager->addAssetPath(android::String8(path.data(), path.size()), &cookie)) {
- mContext.getDiagnostics()->error(
+ mContext->getDiagnostics()->error(
DiagMessage(path) << "failed to load include path");
return {};
}
@@ -135,7 +137,7 @@ public:
std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len) {
std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
- BinaryResourceParser parser(&mContext, table.get(), source, data, len);
+ BinaryResourceParser parser(mContext, table.get(), source, data, len);
if (!parser.parse()) {
return {};
}
@@ -207,7 +209,7 @@ public:
IArchiveWriter* writer) {
std::unique_ptr<io::IData> data = file->openAsData();
if (!data) {
- mContext.getDiagnostics()->error(DiagMessage(file->getSource())
+ mContext->getDiagnostics()->error(DiagMessage(file->getSource())
<< "failed to open file");
return false;
}
@@ -215,7 +217,7 @@ public:
std::string errorStr;
ssize_t offset = getWrappedDataOffset(data->data(), data->size(), &errorStr);
if (offset < 0) {
- mContext.getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr);
+ mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr);
return false;
}
@@ -228,7 +230,7 @@ public:
}
}
- mContext.getDiagnostics()->error(
+ mContext->getDiagnostics()->error(
DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
return false;
}
@@ -252,9 +254,9 @@ public:
*/
bool verifyNoExternalPackages() {
auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
- return mContext.getCompilationPackage() != pkg->name ||
+ return mContext->getCompilationPackage() != pkg->name ||
!pkg->id ||
- pkg->id.value() != mContext.getPackageId();
+ pkg->id.value() != mContext->getPackageId();
};
bool error = false;
@@ -270,13 +272,13 @@ public:
// 'android' package. This is due to legacy reasons.
if (valueCast<Id>(configValue.value.get()) &&
package->name == u"android") {
- mContext.getDiagnostics()->warn(
+ mContext->getDiagnostics()->warn(
DiagMessage(configValue.value->getSource())
<< "generated id '" << resName
<< "' for external package '" << package->name
<< "'");
} else {
- mContext.getDiagnostics()->error(
+ mContext->getDiagnostics()->error(
DiagMessage(configValue.value->getSource())
<< "defined resource '" << resName
<< "' for external package '" << package->name
@@ -297,9 +299,9 @@ public:
std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
if (mOptions.outputToDirectory) {
- return createDirectoryArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
+ return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
} else {
- return createZipFileArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
+ return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
}
}
@@ -308,7 +310,7 @@ public:
TableFlattenerOptions options = {};
options.useExtendedChunks = mOptions.staticLib;
TableFlattener flattener(&buffer, options);
- if (!flattener.consume(&mContext, table)) {
+ if (!flattener.consume(mContext, table)) {
return false;
}
@@ -320,7 +322,7 @@ public:
}
}
- mContext.getDiagnostics()->error(
+ mContext->getDiagnostics()->error(
DiagMessage() << "failed to write resources.arsc to archive");
return false;
}
@@ -332,7 +334,7 @@ public:
options.keepRawValues = mOptions.staticLib;
options.maxSdkLevel = maxSdkLevel;
XmlFlattener flattener(&buffer, options);
- if (!flattener.consume(&mContext, xmlRes)) {
+ if (!flattener.consume(mContext, xmlRes)) {
return false;
}
@@ -343,7 +345,7 @@ public:
}
}
}
- mContext.getDiagnostics()->error(
+ mContext->getDiagnostics()->error(
DiagMessage() << "failed to write " << path << " to archive");
return false;
}
@@ -361,13 +363,13 @@ public:
std::ofstream fout(outPath, std::ofstream::binary);
if (!fout) {
- mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+ mContext->getDiagnostics()->error(DiagMessage() << strerror(errno));
return false;
}
JavaClassGenerator generator(table, javaOptions);
if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
- mContext.getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
+ mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
return false;
}
return true;
@@ -380,24 +382,24 @@ public:
std::string outPath = mOptions.generateJavaClassPath.value();
file::appendPath(&outPath,
- file::packageToPath(util::utf16ToUtf8(mContext.getCompilationPackage())));
+ file::packageToPath(util::utf16ToUtf8(mContext->getCompilationPackage())));
file::mkdirs(outPath);
file::appendPath(&outPath, "Manifest.java");
std::ofstream fout(outPath, std::ofstream::binary);
if (!fout) {
- mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+ mContext->getDiagnostics()->error(DiagMessage() << strerror(errno));
return false;
}
ManifestClassGenerator generator;
- if (!generator.generate(mContext.getDiagnostics(), mContext.getCompilationPackage(),
+ if (!generator.generate(mContext->getDiagnostics(), mContext->getCompilationPackage(),
manifestXml, &fout)) {
return false;
}
if (!fout) {
- mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+ mContext->getDiagnostics()->error(DiagMessage() << strerror(errno));
return false;
}
return true;
@@ -410,13 +412,13 @@ public:
std::ofstream fout(mOptions.generateProguardRulesPath.value(), std::ofstream::binary);
if (!fout) {
- mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+ mContext->getDiagnostics()->error(DiagMessage() << strerror(errno));
return false;
}
proguard::writeKeepSet(&fout, keepSet);
if (!fout) {
- mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
+ mContext->getDiagnostics()->error(DiagMessage() << strerror(errno));
return false;
}
return true;
@@ -425,7 +427,7 @@ public:
bool mergeStaticLibrary(const std::string& input) {
// TODO(adamlesinski): Load resources from a static library APK and merge the table into
// TableMerger.
- mContext.getDiagnostics()->warn(DiagMessage()
+ mContext->getDiagnostics()->warn(DiagMessage()
<< "linking static libraries not supported yet: "
<< input);
return true;
@@ -433,12 +435,12 @@ public:
bool mergeResourceTable(io::IFile* file, bool override) {
if (mOptions.verbose) {
- mContext.getDiagnostics()->note(DiagMessage() << "linking " << file->getSource());
+ mContext->getDiagnostics()->note(DiagMessage() << "linking " << file->getSource());
}
std::unique_ptr<io::IData> data = file->openAsData();
if (!data) {
- mContext.getDiagnostics()->error(DiagMessage(file->getSource())
+ mContext->getDiagnostics()->error(DiagMessage(file->getSource())
<< "failed to open file");
return false;
}
@@ -460,7 +462,7 @@ public:
bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool overlay) {
if (mOptions.verbose) {
- mContext.getDiagnostics()->note(DiagMessage() << "adding " << file->getSource());
+ mContext->getDiagnostics()->note(DiagMessage() << "adding " << file->getSource());
}
bool result = false;
@@ -477,12 +479,12 @@ public:
// Add the exports of this file to the table.
for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
if (exportedSymbol.name.package.empty()) {
- exportedSymbol.name.package = mContext.getCompilationPackage().toString();
+ exportedSymbol.name.package = mContext->getCompilationPackage().toString();
}
ResourceNameRef resName = exportedSymbol.name;
- Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(
+ Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
exportedSymbol.name);
if (mangledName) {
resName = mangledName.value();
@@ -491,7 +493,7 @@ public:
std::unique_ptr<Id> id = util::make_unique<Id>();
id->setSource(fileDesc->source.withLine(exportedSymbol.line));
bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id),
- mContext.getDiagnostics());
+ mContext->getDiagnostics());
if (!result) {
return false;
}
@@ -507,7 +509,7 @@ public:
std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
input, &errorStr);
if (!collection) {
- mContext.getDiagnostics()->error(DiagMessage(input) << errorStr);
+ mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
return false;
}
@@ -543,12 +545,12 @@ public:
// Try opening the file and looking for an Export header.
std::unique_ptr<io::IData> data = file->openAsData();
if (!data) {
- mContext.getDiagnostics()->error(DiagMessage(src) << "failed to open");
+ mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
return false;
}
std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
- src, data->data(), data->size(), mContext.getDiagnostics());
+ src, data->data(), data->size(), mContext->getDiagnostics());
if (resourceFile) {
return mergeCompiledFile(file, std::move(resourceFile), override);
}
@@ -564,62 +566,63 @@ public:
int run(const std::vector<std::string>& inputFiles) {
// Load the AndroidManifest.xml
std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
- mContext.getDiagnostics());
+ mContext->getDiagnostics());
if (!manifestXml) {
return 1;
}
if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) {
- mContext.mCompilationPackage = maybeAppInfo.value().package;
+ mContext->mCompilationPackage = maybeAppInfo.value().package;
} else {
- mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
+ mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
<< "no package specified in <manifest> tag");
return 1;
}
- if (!util::isJavaPackageName(mContext.mCompilationPackage)) {
- mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
+ if (!util::isJavaPackageName(mContext->mCompilationPackage)) {
+ mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
<< "invalid package name '"
- << mContext.mCompilationPackage
+ << mContext->mCompilationPackage
<< "'");
return 1;
}
- mContext.mNameMangler = util::make_unique<NameMangler>(
- NameManglerPolicy{ mContext.mCompilationPackage });
+ mContext->mNameMangler = util::make_unique<NameMangler>(
+ NameManglerPolicy{ mContext->mCompilationPackage });
- if (mContext.mCompilationPackage == u"android") {
- mContext.mPackageId = 0x01;
+ if (mContext->mCompilationPackage == u"android") {
+ mContext->mPackageId = 0x01;
} else {
- mContext.mPackageId = 0x7f;
+ mContext->mPackageId = 0x7f;
}
- mContext.mSymbols = createSymbolTableFromIncludePaths();
- if (!mContext.mSymbols) {
+ mContext->mSymbols = createSymbolTableFromIncludePaths();
+ if (!mContext->mSymbols) {
return 1;
}
TableMergerOptions tableMergerOptions;
tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
- mTableMerger = util::make_unique<TableMerger>(&mContext, &mFinalTable, tableMergerOptions);
+ tableMergerOptions.filter = mOptions.configFilter;
+ mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
if (mOptions.verbose) {
- mContext.getDiagnostics()->note(
- DiagMessage() << "linking package '" << mContext.mCompilationPackage << "' "
- << "with package ID " << std::hex << (int) mContext.mPackageId);
+ mContext->getDiagnostics()->note(
+ DiagMessage() << "linking package '" << mContext->mCompilationPackage << "' "
+ << "with package ID " << std::hex << (int) mContext->mPackageId);
}
for (const std::string& input : inputFiles) {
if (!processFile(input, false)) {
- mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
+ mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input");
return 1;
}
}
for (const std::string& input : mOptions.overlayFiles) {
if (!processFile(input, true)) {
- mContext.getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
+ mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
return 1;
}
}
@@ -630,8 +633,8 @@ public:
if (!mOptions.staticLib) {
PrivateAttributeMover mover;
- if (!mover.consume(&mContext, &mFinalTable)) {
- mContext.getDiagnostics()->error(
+ if (!mover.consume(mContext, &mFinalTable)) {
+ mContext->getDiagnostics()->error(
DiagMessage() << "failed moving private attributes");
return 1;
}
@@ -639,23 +642,23 @@ public:
{
IdAssigner idAssigner;
- if (!idAssigner.consume(&mContext, &mFinalTable)) {
- mContext.getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
+ if (!idAssigner.consume(mContext, &mFinalTable)) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
return 1;
}
}
- mContext.mNameMangler = util::make_unique<NameMangler>(NameManglerPolicy{
- mContext.mCompilationPackage, mTableMerger->getMergedPackages() });
- mContext.mSymbols = JoinedSymbolTableBuilder()
+ mContext->mNameMangler = util::make_unique<NameMangler>(NameManglerPolicy{
+ mContext->mCompilationPackage, mTableMerger->getMergedPackages() });
+ mContext->mSymbols = JoinedSymbolTableBuilder()
.addSymbolTable(util::make_unique<SymbolTableWrapper>(&mFinalTable))
- .addSymbolTable(std::move(mContext.mSymbols))
+ .addSymbolTable(std::move(mContext->mSymbols))
.build();
{
ReferenceLinker linker;
- if (!linker.consume(&mContext, &mFinalTable)) {
- mContext.getDiagnostics()->error(DiagMessage() << "failed linking references");
+ if (!linker.consume(mContext, &mFinalTable)) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
return 1;
}
}
@@ -664,24 +667,24 @@ public:
std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
if (!archiveWriter) {
- mContext.getDiagnostics()->error(DiagMessage() << "failed to create archive");
+ mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
return 1;
}
bool error = false;
{
ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
- if (!manifestFixer.consume(&mContext, manifestXml.get())) {
+ if (!manifestFixer.consume(mContext, manifestXml.get())) {
error = true;
}
// AndroidManifest.xml has no resource name, but the CallSite is built from the name
// (aka, which package the AndroidManifest.xml is coming from).
// So we give it a package name so it can see local resources.
- manifestXml->file.name.package = mContext.getCompilationPackage().toString();
+ manifestXml->file.name.package = mContext->getCompilationPackage().toString();
XmlReferenceLinker manifestLinker;
- if (manifestLinker.consume(&mContext, manifestXml.get())) {
+ if (manifestLinker.consume(mContext, manifestXml.get())) {
if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
manifestXml.get(),
&proguardKeepSet)) {
@@ -704,7 +707,7 @@ public:
}
if (error) {
- mContext.getDiagnostics()->error(DiagMessage() << "failed processing manifest");
+ mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
return 1;
}
@@ -718,13 +721,13 @@ public:
(util::stringEndsWith<char>(path, ".xml.flat") ||
util::stringEndsWith<char>(path, ".xml"))) {
if (mOptions.verbose) {
- mContext.getDiagnostics()->note(DiagMessage() << "linking " << path);
+ mContext->getDiagnostics()->note(DiagMessage() << "linking " << path);
}
io::IFile* file = fileToMerge.file;
std::unique_ptr<io::IData> data = file->openAsData();
if (!data) {
- mContext.getDiagnostics()->error(DiagMessage(file->getSource())
+ mContext->getDiagnostics()->error(DiagMessage(file->getSource())
<< "failed to open file");
return 1;
}
@@ -733,9 +736,9 @@ public:
if (util::stringEndsWith<char>(path, ".flat")) {
xmlRes = loadBinaryXmlSkipFileExport(file->getSource(),
data->data(), data->size(),
- mContext.getDiagnostics());
+ mContext->getDiagnostics());
} else {
- xmlRes = xml::inflate(data->data(), data->size(), mContext.getDiagnostics(),
+ xmlRes = xml::inflate(data->data(), data->size(), mContext->getDiagnostics(),
file->getSource());
}
@@ -751,7 +754,7 @@ public:
};
XmlReferenceLinker xmlLinker;
- if (xmlLinker.consume(&mContext, xmlRes.get())) {
+ if (xmlLinker.consume(mContext, xmlRes.get())) {
if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(),
&proguardKeepSet)) {
error = true;
@@ -778,14 +781,14 @@ public:
xmlRes->file.config.sdkVersion = sdkLevel;
std::string genResourcePath = ResourceUtils::buildResourceFileName(
- xmlRes->file, mContext.getNameMangler());
+ xmlRes->file, mContext->getNameMangler());
bool added = mFinalTable.addFileReference(
xmlRes->file.name,
xmlRes->file.config,
xmlRes->file.source,
util::utf8ToUtf16(genResourcePath),
- mContext.getDiagnostics());
+ mContext->getDiagnostics());
if (!added) {
error = true;
continue;
@@ -804,7 +807,7 @@ public:
}
} else {
if (mOptions.verbose) {
- mContext.getDiagnostics()->note(DiagMessage() << "copying " << path);
+ mContext->getDiagnostics()->note(DiagMessage() << "copying " << path);
}
if (!copyFileToArchive(fileToMerge.file, fileToMerge.dstPath,
@@ -815,20 +818,20 @@ public:
}
if (error) {
- mContext.getDiagnostics()->error(DiagMessage() << "failed linking file resources");
+ mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
return 1;
}
if (!mOptions.noAutoVersion) {
AutoVersioner versioner;
- if (!versioner.consume(&mContext, &mFinalTable)) {
- mContext.getDiagnostics()->error(DiagMessage() << "failed versioning styles");
+ if (!versioner.consume(mContext, &mFinalTable)) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
return 1;
}
}
if (!flattenTable(&mFinalTable, archiveWriter.get())) {
- mContext.getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc");
+ mContext->getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc");
return 1;
}
@@ -840,8 +843,8 @@ public:
options.useFinal = false;
}
- const StringPiece16 actualPackage = mContext.getCompilationPackage();
- StringPiece16 outputPackage = mContext.getCompilationPackage();
+ const StringPiece16 actualPackage = mContext->getCompilationPackage();
+ StringPiece16 outputPackage = mContext->getCompilationPackage();
if (mOptions.customJavaPackage) {
// Override the output java package to the custom one.
outputPackage = mOptions.customJavaPackage.value();
@@ -852,7 +855,7 @@ public:
// to the original package, and private and public symbols to the private package.
options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
- if (!writeJavaFile(&mFinalTable, mContext.getCompilationPackage(),
+ if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
outputPackage, options)) {
return 1;
}
@@ -886,7 +889,7 @@ public:
private:
LinkOptions mOptions;
- LinkContext mContext;
+ LinkContext* mContext;
ResourceTable mFinalTable;
ResourceTable mLocalFileTable;
@@ -907,6 +910,7 @@ int link(const std::vector<StringPiece>& args) {
Maybe<std::string> versionCode, versionName;
Maybe<std::string> customJavaPackage;
std::vector<std::string> extraJavaPackages;
+ Maybe<std::string> configs;
bool legacyXFlag = false;
bool requireLocalization = false;
Flags flags = Flags()
@@ -928,6 +932,8 @@ int link(const std::vector<StringPiece>& args) {
&legacyXFlag)
.optionalSwitch("-z", "Require localization of strings marked 'suggested'",
&requireLocalization)
+ .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
+ "is all configurations", &configs)
.optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
"by -o",
&options.outputToDirectory)
@@ -967,6 +973,8 @@ int link(const std::vector<StringPiece>& args) {
return 1;
}
+ LinkContext context;
+
if (privateSymbolsPackage) {
options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
}
@@ -1011,7 +1019,31 @@ int link(const std::vector<StringPiece>& args) {
}
}
- LinkCommand cmd(options);
+ AxisConfigFilter filter;
+ if (configs) {
+ for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) {
+ ConfigDescription config;
+ LocaleValue lv;
+ if (lv.initFromFilterString(configStr)) {
+ lv.writeTo(&config);
+ } else if (!ConfigDescription::parse(configStr, &config)) {
+ context.getDiagnostics()->error(
+ DiagMessage() << "invalid config '" << configStr << "' for -c option");
+ return 1;
+ }
+
+ if (config.density != 0) {
+ context.getDiagnostics()->warn(
+ DiagMessage() << "ignoring density '" << config << "' for -c option");
+ } else {
+ filter.addConfig(config);
+ }
+ }
+
+ options.configFilter = &filter;
+ }
+
+ LinkCommand cmd(&context, options);
return cmd.run(flags.getArgs());
}
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 27a23bd65103..e01a00401133 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -100,7 +100,7 @@ bool TableMerger::mergeAndMangle(const Source& src, const StringPiece16& package
return false;
}
- mFilesToMerge[ResourceKeyRef{ name, config }] = FileToMerge{
+ mFilesToMerge[ResourceKeyRef(name, config)] = FileToMerge{
f, oldFile->getSource(), util::utf16ToUtf8(*newFile->path) };
return true;
};
@@ -201,6 +201,9 @@ bool TableMerger::doMerge(const Source& src,
auto iter = std::lower_bound(dstEntry->values.begin(), dstEntry->values.end(),
srcValue.config, cmp::lessThanConfig);
+ const bool stripConfig = mOptions.filter ?
+ !mOptions.filter->match(srcValue.config) : false;
+
if (iter != dstEntry->values.end() && iter->config == srcValue.config) {
const int collisionResult = ResourceTable::resolveValueCollision(
iter->value.get(), srcValue.value.get());
@@ -224,11 +227,15 @@ bool TableMerger::doMerge(const Source& src,
continue;
}
- } else {
+ } else if (!stripConfig){
// Insert a place holder value. We will fill it in below.
iter = dstEntry->values.insert(iter, ResourceConfigValue{ srcValue.config });
}
+ if (stripConfig) {
+ continue;
+ }
+
if (FileReference* f = valueCast<FileReference>(srcValue.value.get())) {
std::unique_ptr<FileReference> newFileRef;
if (manglePackage) {
@@ -287,7 +294,7 @@ bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, b
auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
FileReference* newFile, FileReference* oldFile) -> bool {
- mFilesToMerge[ResourceKeyRef{ name, config }] = FileToMerge{
+ mFilesToMerge[ResourceKeyRef(name, config)] = FileToMerge{
file, oldFile->getSource(), util::utf16ToUtf8(*newFile->path) };
return true;
};
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index e1be5d52e9cf..4539679fa769 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -20,6 +20,7 @@
#include "Resource.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
+#include "filter/ConfigFilter.h"
#include "io/File.h"
#include "process/IResourceTableConsumer.h"
#include "util/Util.h"
@@ -51,6 +52,11 @@ struct TableMergerOptions {
* If true, resources in overlays can be added without previously having existed.
*/
bool autoAddOverlay = false;
+
+ /**
+ * A filter that removes resources whose configurations don't match.
+ */
+ IConfigFilter* filter = nullptr;
};
/**
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index fa4afd3d852a..45c8c98780b8 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include "filter/ConfigFilter.h"
#include "io/FileSystem.h"
#include "link/TableMerger.h"
#include "test/Builders.h"
@@ -243,4 +244,36 @@ TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) {
ASSERT_FALSE(merger.mergeOverlay({}, tableB.get()));
}
+TEST_F(TableMergerTest, MergeAndStripResourcesNotMatchingFilter) {
+ ResourceTable finalTable;
+ TableMergerOptions options;
+ options.autoAddOverlay = false;
+
+ AxisConfigFilter filter;
+ filter.addConfig(test::parseConfigOrDie("en"));
+ options.filter = &filter;
+
+ test::TestFile fileA("res/layout-en/main.xml"), fileB("res/layout-fr-rFR/main.xml");
+ const ResourceName name = test::parseNameOrDie(u"@com.app.a:layout/main");
+ const ConfigDescription configEn = test::parseConfigOrDie("en");
+ const ConfigDescription configFr = test::parseConfigOrDie("fr-rFR");
+
+ TableMerger merger(mContext.get(), &finalTable, options);
+ ASSERT_TRUE(merger.mergeFile(ResourceFile{ name, configEn }, &fileA));
+ ASSERT_TRUE(merger.mergeFile(ResourceFile{ name, configFr }, &fileB));
+
+ EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(&finalTable,
+ u"@com.app.a:layout/main",
+ configEn));
+ EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(&finalTable,
+ u"@com.app.a:layout/main",
+ configFr));
+
+ EXPECT_NE(merger.getFilesToMerge().end(),
+ merger.getFilesToMerge().find(ResourceKeyRef(name, configEn)));
+
+ EXPECT_EQ(merger.getFilesToMerge().end(),
+ merger.getFilesToMerge().find(ResourceKeyRef(name, configFr)));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 93a11b9334a8..579a46ec230f 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -84,6 +84,11 @@ public:
util::make_unique<FileReference>(mTable->stringPool.makeRef(path)));
}
+ ResourceTableBuilder& addFileReference(const StringPiece16& name, const StringPiece16& path,
+ const ConfigDescription& config) {
+ return addValue(name, {}, config,
+ util::make_unique<FileReference>(mTable->stringPool.makeRef(path)));
+ }
ResourceTableBuilder& addValue(const StringPiece16& name,
std::unique_ptr<Value> value) {